> ## Documentation Index
> Fetch the complete documentation index at: https://docs.forecastable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP server

> Connect AI agents to Forecastable through the Integration MCP server.

The **Forecastable Integration MCP server** exposes the same partner Integration API as MCP tools. Use it from Cursor, Claude Desktop, or any client that supports the [Model Context Protocol](https://modelcontextprotocol.io) over Streamable HTTP.

Each tool maps to an Integration REST endpoint. Tool inputs mirror the REST request shape: path parameters, query `params`, request `data`, and optional `headers` for organization context.

## Prerequisites

Before connecting an MCP client, choose how the client authenticates:

* **OAuth (recommended for Cursor and Claude)** — the client prompts you to sign in and approve scopes
* **[API keys](/developers/api-keys)** — static bearer tokens for clients that do not support MCP OAuth, scripts, and server-to-server tools

Both credential types use [scopes](/developers/scopes) to limit what the client can access. You can optionally set a default organization with `X-Organization-Id` on the MCP connection.

## Connect manually

<Tabs>
  <Tab title="Claude Desktop">
    **Remote MCP URL**

    ```text theme={null}
    https://app.forecastable.com/mcp
    ```

    <Steps>
      <Step title="Open connectors in Claude">
        In Claude, open **Customize** in the sidebar, then go to **Connectors**.

        <Frame caption="Open Customize in Claude">
          <img src="https://mintcdn.com/forecastable/fU69crspsqRT-kfC/images/mcp/select-customize.png?fit=max&auto=format&n=fU69crspsqRT-kfC&q=85&s=534029922cab9970ef6b939c57d6050c" alt="Open Customize in Claude" className="max-w-[960px]" width="3350" height="2030" data-path="images/mcp/select-customize.png" />
        </Frame>

        Click the **+** button and choose **Add custom connector**.

        <Frame caption="Add a custom connector">
          <img src="https://mintcdn.com/forecastable/fU69crspsqRT-kfC/images/mcp/add-custom-connector-button.png?fit=max&auto=format&n=fU69crspsqRT-kfC&q=85&s=9365e74461247dc0989921835a2d819e" alt="Add custom connector in Claude" className="max-w-[960px]" width="2940" height="2010" data-path="images/mcp/add-custom-connector-button.png" />
        </Frame>
      </Step>

      <Step title="Add the MCP server">
        In the **Add custom connector** dialog, set:

        | Field | Value                                    |
        | ----- | ---------------------------------------- |
        | Name  | `Forecastable` (or your preferred label) |
        | URL   | `https://app.forecastable.com/mcp`       |

        Leave **Advanced settings** empty. Forecastable supports OAuth Dynamic Client Registration, so Claude registers an OAuth client automatically and receives a client ID and secret without showing them to you.

        <Frame caption="Configure the Forecastable connector">
          <img src="https://mintcdn.com/forecastable/fU69crspsqRT-kfC/images/mcp/add-custom-connector-dialog.png?fit=max&auto=format&n=fU69crspsqRT-kfC&q=85&s=cf96d0fdbc77f848190f074671f8ea4a" alt="Add Forecastable MCP server in Claude" className="max-w-[960px]" width="1120" height="455" data-path="images/mcp/add-custom-connector-dialog.png" />
        </Frame>
      </Step>

      <Step title="Authorize the connector">
        Open the Forecastable connector in Claude and click **Connect**.

        <Frame caption="Start the OAuth connection">
          <img src="https://mintcdn.com/forecastable/fU69crspsqRT-kfC/images/mcp/click-connect.png?fit=max&auto=format&n=fU69crspsqRT-kfC&q=85&s=5997e4b103ea907a6090c407ed6485d2" alt="Connect to Forecastable in Claude" width="712" height="182" data-path="images/mcp/click-connect.png" />
        </Frame>

        Forecastable prompts you to sign in (if needed) and review the requested scopes on a consent screen. Click **Allow** to grant access. Claude exchanges the authorization code for an access token and uses it on later MCP requests.
      </Step>

      <Step title="Verify the connection">
        After authorization succeeds, test the connector in Claude. A good first check is to list organizations, choose one, and call a read-only tool such as `listAccounts`.

        If organization-scoped tools fail with a missing-organization error, have Claude call `listOrganizations` and `setActiveOrganization` before account or contact tools.
      </Step>
    </Steps>

    ### Troubleshooting

    <AccordionGroup>
      <Accordion title="Claude asks for a client ID and secret">
        Confirm you are using `https://app.forecastable.com/mcp` and that the Forecastable OAuth discovery endpoint is reachable at `https://app.forecastable.com/.well-known/oauth-authorization-server`. Claude should only require manual credentials if it cannot discover or call the dynamic registration endpoint.
      </Accordion>

      {' '}

      <Accordion title="Redirect URI mismatch">
        Claude dynamically registers `https://claude.ai/api/mcp/auth_callback` as its redirect URL. If you
        created an OAuth app manually, confirm that callback URL is listed exactly in **Redirect URLs**.
      </Accordion>

      {' '}

      <Accordion title="Invalid client or unauthorized">
        Disconnect and reconnect the connector so Claude can register fresh OAuth credentials. If you
        configured a client manually in **Advanced settings**, verify the client ID and client secret
        match the **Credentials** section for the same OAuth app.
      </Accordion>

      {' '}

      <Accordion title="Insufficient scope">
        Disconnect and reconnect the connector so Claude can request the updated permissions and you can
        approve them on the Forecastable consent screen.
      </Accordion>

      <Accordion title="Missing organization context">
        Most MCP tools require an active organization. Ask Claude to call `listOrganizations`, pick an organization, then call `setActiveOrganization` before organization-scoped tools.
      </Accordion>
    </AccordionGroup>
  </Tab>

  <Tab title="Cursor">
    <div className="mcp-install-buttons">
      <a className="mcp-install-button mcp-install-button-primary" href="cursor://anysphere.cursor-deeplink/mcp/install?name=forecastable&config=eyJ1cmwiOiJodHRwczovL2FwcC5mb3JlY2FzdGFibGUuY29tL21jcCJ9">
        Add to Cursor
      </a>
    </div>

    Add a remote MCP server in your Cursor MCP configuration (`.cursor/mcp.json` or global MCP settings):

    ```json theme={null}
    {
      "mcpServers": {
        "forecastable": {
          "url": "https://app.forecastable.com/mcp"
        }
      }
    }
    ```

    Cursor discovers Forecastable's OAuth metadata, registers a client automatically, and prompts you to authenticate. Sign in to Forecastable (if needed), review the consent screen, and click **Allow**.
  </Tab>

  <Tab title="API key">
    Use an API key when your MCP client does not support OAuth. Add the server with an `Authorization` header:

    ```json theme={null}
    {
      "mcpServers": {
        "forecastable": {
          "url": "https://app.forecastable.com/mcp",
          "headers": {
            "Authorization": "Bearer YOUR_API_KEY"
          }
        }
      }
    }
    ```

    Replace the placeholder with your API key. To set an optional default organization, add `"X-Organization-Id": "YOUR_ORGANIZATION_ID"` to `headers`.
  </Tab>
</Tabs>

## Available tools

Tools are generated from the Integration OpenAPI specification. Each tool enforces the same scope and organization rules as the corresponding REST endpoint.

### Meta

| Tool      | Description                                                      | Scopes | Organization required |
| --------- | ---------------------------------------------------------------- | ------ | --------------------- |
| `getMeta` | Integration API metadata (auth, scopes, pagination, rate limits) | none   | no                    |

### Organizations

| Tool                    | Description                                        | Scopes               | Organization required |
| ----------------------- | -------------------------------------------------- | -------------------- | --------------------- |
| `listOrganizations`     | List organizations the API key can access          | `organizations:read` | no                    |
| `setActiveOrganization` | Select the active organization for the MCP session | `organizations:read` | yes                   |
| `getOrganization`       | Get one organization by ID                         | `organizations:read` | yes                   |
| `listOrganizationUsers` | List users in an organization                      | `organizations:read` | yes                   |

### Accounts

| Tool                 | Description               | Scopes            | Organization required |
| -------------------- | ------------------------- | ----------------- | --------------------- |
| `listAccounts`       | List accounts             | `accounts:read`   | yes                   |
| `getAccount`         | Get one account           | `accounts:read`   | yes                   |
| `createAccount`      | Create an account         | `accounts:write`  | yes                   |
| `updateAccount`      | Update an account         | `accounts:write`  | yes                   |
| `deleteAccount`      | Archive an account        | `accounts:delete` | yes                   |
| `bulkCreateAccounts` | Create up to 200 accounts | `accounts:write`  | yes                   |

### Contacts

| Tool                   | Description                    | Scopes            | Organization required |
| ---------------------- | ------------------------------ | ----------------- | --------------------- |
| `listAccountContacts`  | List contacts on an account    | `contacts:read`   | yes                   |
| `getContact`           | Get one contact                | `contacts:read`   | yes                   |
| `createAccountContact` | Create a contact on an account | `contacts:write`  | yes                   |
| `updateContact`        | Update a contact               | `contacts:write`  | yes                   |
| `deleteContact`        | Archive a contact              | `contacts:delete` | yes                   |

### Opportunities

| Tool                | Description                      | Scopes                 | Organization required |
| ------------------- | -------------------------------- | ---------------------- | --------------------- |
| `listOpportunities` | List opportunities               | `opportunities:read`   | yes                   |
| `getOpportunity`    | Get one opportunity              | `opportunities:read`   | yes                   |
| `createOpportunity` | Create an opportunity            | `opportunities:write`  | yes                   |
| `updateOpportunity` | Update an opportunity            | `opportunities:write`  | yes                   |
| `deleteOpportunity` | Delete or archive an opportunity | `opportunities:delete` | yes                   |

### Relationship maps

| Tool                           | Description                                               | Scopes            | Organization required |
| ------------------------------ | --------------------------------------------------------- | ----------------- | --------------------- |
| `getRelationshipMapByEntity`   | Get the relationship map for an account/opportunity/group | `accounts:read`   | yes                   |
| `getRelationshipMap`           | Get a relationship map by ID                              | `accounts:read`   | yes                   |
| `upsertRelationshipMap`        | Create or replace a relationship map                      | `accounts:write`  | yes                   |
| `updateRelationshipMap`        | Patch relationship map settings                           | `accounts:write`  | yes                   |
| `upsertRelationshipMapContact` | Create or update a contact on a relationship map          | `accounts:write`  | yes                   |
| `deleteRelationshipMapContact` | Remove a contact from a relationship map                  | `accounts:delete` | yes                   |

### Engage templates

| Tool                   | Description                | Scopes         | Organization required |
| ---------------------- | -------------------------- | -------------- | --------------------- |
| `listEngageTemplates`  | List Engage templates      | `engage:read`  | yes                   |
| `getEngageTemplate`    | Get one Engage template    | `engage:read`  | yes                   |
| `createEngageTemplate` | Create an Engage template  | `engage:write` | yes                   |
| `updateEngageTemplate` | Update an Engage template  | `engage:write` | yes                   |
| `deleteEngageTemplate` | Archive an Engage template | `engage:write` | yes                   |

### Engage drafts

| Tool                | Description                   | Scopes         | Organization required |
| ------------------- | ----------------------------- | -------------- | --------------------- |
| `listEngageDrafts`  | List Engage drafts for a user | `engage:read`  | yes                   |
| `getEngageDraft`    | Get one Engage draft          | `engage:read`  | yes                   |
| `createEngageDraft` | Create an Engage draft        | `engage:write` | yes                   |
| `updateEngageDraft` | Update an Engage draft        | `engage:write` | yes                   |

### Engage lists

| Tool                     | Description                                   | Scopes         | Organization required |
| ------------------------ | --------------------------------------------- | -------------- | --------------------- |
| `listEngageLists`        | List Engage lists                             | `engage:read`  | yes                   |
| `getEngageList`          | Get one Engage list                           | `engage:read`  | yes                   |
| `listAccountEngageLists` | List Engage lists targeting an account        | `engage:read`  | yes                   |
| `createEngageList`       | Create a manual or dynamic Engage list        | `engage:write` | yes                   |
| `updateEngageList`       | Update an Engage list                         | `engage:write` | yes                   |
| `deleteEngageList`       | Archive an Engage list                        | `engage:write` | yes                   |
| `listEngageListMembers`  | List list members (dynamic lists are derived) | `engage:read`  | yes                   |
| `addEngageListMembers`   | Add members to a manual list                  | `engage:write` | yes                   |
| `updateEngageListMember` | Update or link a manual list member           | `engage:write` | yes                   |
| `removeEngageListMember` | Remove a manual list member                   | `engage:write` | yes                   |

### Engage campaigns

| Tool                           | Description                                                                                                                                      | Scopes         | Organization required |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | --------------------- |
| `listEngageCampaigns`          | List Engage campaign definitions                                                                                                                 | `engage:read`  | yes                   |
| `getEngageCampaign`            | Get one campaign definition                                                                                                                      | `engage:read`  | yes                   |
| `createEngageCampaign`         | Create a campaign definition (does not send). Wait steps support relative `duration`/`unit` or absolute `scheduleMode=datetime` + `scheduledAt`. | `engage:write` | yes                   |
| `bulkCreateEngageCampaigns`    | Create up to 25 campaigns with per-index results                                                                                                 | `engage:write` | yes                   |
| `updateEngageCampaign`         | Update a campaign definition                                                                                                                     | `engage:write` | yes                   |
| `deleteEngageCampaign`         | Archive a campaign and deactivate live runs                                                                                                      | `engage:write` | yes                   |
| `getEngageCampaignStatus`      | Aggregate execution status for one campaign                                                                                                      | `engage:read`  | yes                   |
| `listEngageCampaignStatuses`   | Batch execution statuses (max 50 campaign IDs)                                                                                                   | `engage:read`  | yes                   |
| `getEngageEnrollment`          | Poll a campaign start (enrollment) job                                                                                                           | `engage:read`  | yes                   |
| `listEngageEnrollmentStatuses` | Batch enrollment job statuses (max 50 job IDs)                                                                                                   | `engage:read`  | yes                   |

### Engage queue (read-only)

| Tool                        | Description                                                               | Scopes        | Organization required |
| --------------------------- | ------------------------------------------------------------------------- | ------------- | --------------------- |
| `listEngageQueue`           | List outbound queue items across channels                                 | `engage:read` | yes                   |
| `getEngageQueueItem`        | Get one queue item                                                        | `engage:read` | yes                   |
| `listEngageQueueRecipients` | Per-recipient delivery and engagement for a send                          | `engage:read` | yes                   |
| `listEngageReplies`         | List inbound replies to Engage sends (email/Nylas; plain-text by default) | `engage:read` | yes                   |
| `getEngageReply`            | Get one inbound reply by provider message id                              | `engage:read` | yes                   |

### Feed

| Tool                            | Description                                                                                 | Scopes      | Organization required |
| ------------------------------- | ------------------------------------------------------------------------------------------- | ----------- | --------------------- |
| `getAccountInbox`               | Account inbox: email threads, calendar events, and Slack messages                           | `feed:read` | yes                   |
| `getAccountInboxThreadMessages` | Messages for an inbox email thread (plain-text by default; `includeBodyHtml=true` for HTML) | `feed:read` | yes                   |
| `getAccountInboxSlackThread`    | Messages in a Slack thread linked to the account                                            | `feed:read` | yes                   |
| `getCommunicationAnalytics`     | Dashboard communication analytics (timeline + account rows); user-backed token              | `feed:read` | yes                   |
| `listNeedsAttention`            | Accounts with unread email/Slack needing attention; user-backed token                       | `feed:read` | yes                   |

Account inbox tools accept account UUIDs (not Relay global IDs). Thread ids may be a plain Nylas thread id or `nylas:<grantId>:<threadId>`. Clients should merge `threads` / `events` / `slackMessages` by timestamp when a single chronological list is needed. Dashboard tools (`getCommunicationAnalytics`, `listNeedsAttention`) require a **user-backed** token.

### Calendar

| Tool                          | Description                                                                                                                                                                           | Scopes           | Organization required |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------- |
| `listCalendarEvents`          | List/search synced events (`start`/`end` required; filters: `search`, `participantEmail`, `participantDomain`, `ownerUserId`, `hasExternalParticipant`; `order=desc` for most recent) | `calendar:read`  | yes                   |
| `getCalendarEvent`            | Get one synced calendar event the authenticated user is authorized to view                                                                                                            | `calendar:read`  | yes                   |
| `getCalendarEventSummary`     | Get the stored meeting summary for a previous calendar event                                                                                                                          | `calendar:read`  | yes                   |
| `getCalendarEventActionItems` | Get extracted action items for a previous calendar event                                                                                                                              | `calendar:read`  | yes                   |
| `getCalendarEventTranscript`  | Get the normalized transcript for a previous calendar event                                                                                                                           | `calendar:read`  | yes                   |
| `updateCalendarEvent`         | Update a viewer-owned event (title, description, location, times, participants). For recurring events set `recurrenceScope` to `this` or `all`.                                       | `calendar:write` | yes                   |

Calendar tools require a **user-backed** token. Standard users see only their own events; admin and consulting users may also see authorized teammate events. Nested meeting tools return `404` when the event has no linked call recording. Updates apply only to viewer-owned events and push through the connected calendar provider.

For “most recent meeting with X”, prefer `listCalendarEvents` with `order=desc`, a recent window, and `search` / `participantDomain` / `participantEmail` — do not page a wide ascending range and pick the first title match.

<Warning>
  Tools marked as destructive (`deleteAccount`, `deleteContact`, `deleteOpportunity`,
  `deleteRelationshipMapContact`, `deleteEngageTemplate`, `deleteEngageList`,
  `deleteEngageCampaign`, `removeEngageListMember`) perform soft-delete or archive operations.
  Confirm with your agent workflow before calling them.
</Warning>

<Warning>
  **Sending is REST-only.** `POST /engage/messages/send` and
  `POST /engage/campaigns/{campaignId}/start` are deliberately not exposed as MCP tools, so an
  agent can prepare templates, drafts, lists, and campaigns but can never send messages or enroll
  recipients through MCP. Outbound operations require the REST API with the `engage:send` scope and
  an `Idempotency-Key` header.
</Warning>

## Authentication

Send your API key as a Bearer token on every authenticated MCP request:

```http theme={null}
Authorization: Bearer YOUR_API_KEY
```

Accepted credential types match the Integration REST API:

* integration API keys issued from developer settings
* OAuth2 authorization-code access tokens issued after user consent
* OAuth2 client-credentials access tokens for server-to-server clients, where enabled

Revoked or expired API keys return `401 unauthorized`.

## Organization context

Most tools require an active organization. Provide it in one of these ways:

1. **Request header (recommended):** `X-Organization-Id: <uuid>`
2. **Tool input headers:** `{ "headers": { "xOrganizationId": "<uuid>" } }`
3. **Query params in tool input:** `{ "params": { "organizationId": "<uuid>" } }`
4. **Session selection:** call `listOrganizations`, ask the user which organization to use, then call `setActiveOrganization` with the selected ID. Later tools in the same MCP session use that organization automatically.

If your API key is restricted to a single organization, Forecastable can infer the organization automatically. If you configure `X-Organization-Id` on the MCP connection, it acts as an optional default. Explicit tool headers or params override the session selection. If multiple explicit identifiers are supplied and they conflict, the server returns `ambiguous_subscriber_context`.

Legacy header `X-Subscriber-Id` and query param `subscriberId` are still accepted.

Optional header `X-Request-Id` is forwarded for tracing.

## Sessions

The server uses MCP **Streamable HTTP** transport:

| Method        | Purpose                                        |
| ------------- | ---------------------------------------------- |
| `POST /mcp`   | Initialize a session or send JSON-RPC messages |
| `GET /mcp`    | Open the SSE stream for an existing session    |
| `DELETE /mcp` | Close a session                                |

After the first `POST`, reuse the session by sending `mcp-session-id` on subsequent requests.

## Example tool call

After your MCP client connects, first let the model discover the available organizations:

```json theme={null}
{}
```

Call `listOrganizations`, ask the user which organization to use, then call `setActiveOrganization`:

```json theme={null}
{
  "organizationId": "YOUR_ORGANIZATION_ID"
}
```

After that, call organization-scoped tools without repeating the organization ID:

```json theme={null}
{
  "params": {
    "limit": 25
  }
}
```

You can still pass `xOrganizationId` in a tool call or configure `X-Organization-Id` on the MCP server connection when you want an explicit default.

## Scopes

MCP tools enforce the same [scopes](/developers/scopes) as the Integration REST API. Configure scopes on your API key or OAuth app before connecting.

## Rate limits

MCP tool calls share the Integration API [rate limits](/developers/rate-limits). When exceeded, the server returns `429 rate_limit_exceeded` with a `retryAfterSeconds` hint.

## Troubleshooting

<AccordionGroup>
  <Accordion title="401 unauthorized">
    Verify your API key is present, not expired, and not revoked. All tools except `getMeta` require authentication.
  </Accordion>

  {' '}

  <Accordion title="403 forbidden or insufficient scope">
    Check that your API key includes the scope required for the tool. For example, `listAccounts`
    needs `accounts:read`.
  </Accordion>

  {' '}

  <Accordion title="Missing organization context">
    Call `listOrganizations`, ask the user which organization to use, then call
    `setActiveOrganization`. You can also provide `X-Organization-Id` on the MCP connection or pass
    `xOrganizationId` in the tool `headers`.
  </Accordion>

  {' '}

  <Accordion title="404 Integration MCP is disabled">
    The server returned `not_found` because MCP is disabled in that environment. Contact Forecastable
    if you need MCP enabled.
  </Accordion>

  <Accordion title="Unknown MCP session">
    Ensure your client reuses `mcp-session-id` from the initialize response. Stale or missing session IDs return `400 validation_error`.
  </Accordion>
</AccordionGroup>

## Related resources

* [API reference](/api-reference/meta/get-integration-api-metadata) — full Integration REST API documentation
* [GET /meta](/api-reference/meta/get-integration-api-metadata) — live metadata for auth, scopes, and limits
