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

# AI Connect (MCP) overview

> How FirstFlow's MCP server lets a coding agent read your product context and build experiences the OAuth handshake, scope resolution, and per-request tool surface.

FirstFlow ships an **MCP (Model Context Protocol) server** so any MCP-compatible
coding agent Claude Code, Cursor, Claude Desktop can read your product
context and build experiences by calling tools. Instead of clicking through the
dashboard, you tell the agent what you want ("survey users who hit the pricing
page twice") and it lists [agents](/concepts/agents), inspects existing
[experiences](/concepts/experiences) and [audience](/concepts/audience), then
creates tours, surveys, announcements, and guides and publishes them.

The server runs on the backend at `<BACKEND_URL>/mcp` for the managed cloud
that is `https://api.firstflow.app/mcp`. Two things make it more than a remote
function list: the connection is authenticated with **OAuth 2.0 + PKCE** (no
copy-pasted secret), and the exact set of tools the agent sees is computed
**server-side from your role**, so an agent can never call something you couldn't
do yourself in the dashboard.

<Note>
  Static API keys from **Settings → API keys** are intentionally **rejected** on
  `/mcp` MCP clients must use the OAuth flow. Those same keys still work on the
  REST API. See [API keys](/mcp/api-keys) for why.
</Note>

## How it works

The endpoint is **stateless**: the controller builds a fresh `McpServer` for
every HTTP request, binds it to your resolved identity and scopes, answers the
call, and tears the transport down. Nothing about your session is cached between
calls. Transport is **streamable HTTP** (`@modelcontextprotocol/sdk`), so a
single agent turn can stream a long tool result back without holding a socket
open.

Three things happen, in order, on every request to `/mcp`:

```mermaid theme={null}
flowchart TD
    A["Agent → POST /mcp<br/>Authorization: Bearer …"] --> B{Token type?}
    B -->|Session JWT| C["Look up OrganizationMember role"]
    B -->|OAuth-issued key| D["Read scopes off the key<br/>+ expand legacy scopes"]
    B -->|No / bad token| E["401 + WWW-Authenticate<br/>(advertises OAuth metadata)"]
    C --> F["Role → scopes<br/>org:admin = all · member = read-only"]
    D --> G["Resolved context<br/>{ organizationId, scopes }"]
    F --> G
    G --> H["Build per-request McpServer"]
    H --> I["Apply org tool policy:<br/>disabled tools skip registerTool"]
    I --> J["Filter by required scopes per tool"]
    J --> K["tools/list & tools/call<br/>see only the allowed surface"]

    style A fill:#1f2937,color:#fff
    style E fill:#7f1d1d,color:#fff
    style G fill:#065f46,color:#fff
    style K fill:#1e3a8a,color:#fff
```

**1. Authenticate.** The controller reads the `Authorization: Bearer …` header
and tries a session JWT first (in-product callers like the dashboard), then an
API key. A key is only accepted if it was minted by the OAuth flow
(`metadata.issuedVia === 'mcp-oauth'`); a key you created in Settings is bounced
with a `401` whose `WWW-Authenticate` header tells the client to start OAuth.
With no token at all, the response is a plain `401` advertising the OAuth
resource metadata.

**2. Resolve scopes.** For a JWT, the server reads your `OrganizationMember` role
and maps it to a scope set. For an OAuth key, the scopes ride on the key itself
and any legacy coarse scopes are expanded to their granular equivalents. The
result is a small context organization id plus a list of granted scopes that
governs everything downstream. No organization context means a `403`.

**3. Build the tool surface.** The service constructs an `McpServer` and wraps
its `registerTool` method with your organization's tool policy. Tools your org
disabled, or that you lack the scope for, are **never registered** they don't
appear in `tools/list`, and a `tools/call` for them returns the SDK's native
"method not found". This is why the catalog is the source of truth: eligibility
is decided on the server, so a client cannot conjure a tool that your role
forbids.

### Why OAuth instead of a token

A pasted API key has to be created, scoped, and stored by hand, and it never
expires on its own. The OAuth + PKCE flow (RFC 9728, "OAuth protected resource")
removes all of that: the client discovers the endpoints, registers itself,
sends you to a consent screen in the dashboard, and receives a scoped token —
with no secret ever living in a config file. You approve exactly what the agent
may do, per organization, and can revoke it later. The end-to-end handshake is
walked step by step in the [quickstart](/mcp/quickstart).

## What an agent can do

The tool surface splits into two jobs. **Reading context** is always partly
available the Discovery group is forced on for every caller so an agent can
bootstrap (`list_agents`, `get_agent`, browse the integrations catalog) before it
knows anything about your workspace. **Building and managing** covers creating
tours, surveys, announcements, and guides, editing flow steps and logic nodes,
targeting audiences, and publishing all gated by scope.

| Job                | Representative tools                                                                                | Needs                               |
| ------------------ | --------------------------------------------------------------------------------------------------- | ----------------------------------- |
| Read context       | `list_agents`, `get_agent`, `list_experiences`, `get_experience`, `get_flow_graph`, `list_segments` | read/list scopes (members included) |
| Author experiences | `create_tour`, `create_survey`, `create_announcement`, `create_guide`                               | `experiences:create`                |
| Edit flows         | `add_step`, `update_step`, `reorder_steps`, `add_classify_node`, `add_flow_node`, `connect_nodes`   | `experiences:update`                |
| Target & configure | `configure_experience`, `list_audience_members`, `get_audience_membership`                          | `experiences:update`, `audience:*`  |
| Publish            | `publish_experience`                                                                                | `experiences:publish`               |

The full, grouped list with one-line descriptions lives in the
[tool catalog](/mcp/tools). The exact slice you get is decided by your
[scopes](/mcp/scopes), which come from your role.

### Roles map straight to scopes

There are two role outcomes. An **admin** (`org:admin`) gets every scope and the
complete catalog. A **member** (`basic_member`) gets read and list only —
`agents:list`, `agents:read`, `experiences:list`, `experiences:read`,
`audience:list`, `audience:read` so an agent acting as a member can survey your
workspace but cannot change it. Granular scopes (`experiences:create`,
`experiences:publish`, `agents:delete`, …) exist for fine-grained API keys; the
two roles above are just preset bundles of them.

## Connecting

Point your client at the `/mcp` URL and let it run the OAuth handshake; you only
intervene to approve the consent screen. For Claude Code:

```bash theme={null}
claude mcp add --transport http firstflow https://api.firstflow.app/mcp
```

For a generic `mcp.json` (Cursor / Claude Desktop style):

```json theme={null}
{
  "mcpServers": {
    "firstflow": {
      "type": "http",
      "url": "https://api.firstflow.app/mcp"
    }
  }
}
```

To confirm the connection, ask the agent to call `list_agents`:

```
Call the FirstFlow MCP's list_agents tool and tell me which agents exist.
```

A returned agent list means you are authenticated. A `401` means the OAuth flow
has not completed open the dashboard and approve the consent screen. The full
discovery → register → authorize → token sequence, including self-host URL
overrides, is in the [quickstart](/mcp/quickstart).

## Specific cases

<AccordionGroup>
  <Accordion title="Self-hosting the MCP server">
    The OAuth issuer and consent redirect are derived from your environment:
    set `BACKEND_PUBLIC_URL` to the backend's public origin and `FRONTEND_URL`
    to the dashboard's, so the `/oauth/consent` redirect resolves. Set
    `OAUTH_ISSUER` only if you need the issuer to differ from the backend
    origin. Then use your own `<BACKEND_URL>/mcp` in the client config above.
    See [self-hosting configuration](/self-hosting/configuration).
  </Accordion>

  <Accordion title="A tool is missing or returns 403">
    A tool that never appears in `tools/list` is either disabled for your
    organization or outside your scopes it was skipped at registration time.
    A `403` with `MCP_FORBIDDEN_SCOPE` means a call reached a tool your role
    can't use. Neither means the feature is broken; check your role and the
    org's enabled tools. Members are read-only by design.
  </Accordion>

  <Accordion title="Legacy and fine-grained API keys">
    Keys minted before the granular permission system carry coarse scopes
    (`experiences:write`, `agents:write`, `analytics:read`). The server expands
    these to their granular equivalents on every request, so old keys keep
    working `experiences:write`, for example, becomes
    `experiences:create` + `experiences:update` + `experiences:publish`. A key
    with no valid MCP scope is rejected with `MCP_KEY_NO_SCOPES`.
  </Accordion>

  <Accordion title="Rate limits and metering">
    `/mcp` is rate-limited per caller and metered per organization. In the
    self-hosted `community` edition, quotas are unlimited. There is no
    per-request session to keep alive each call is independent so retries
    are safe but still count toward the rate limit.
  </Accordion>
</AccordionGroup>

## Next

Walk the OAuth handshake and connect your first client in the
[quickstart](/mcp/quickstart), then browse the full [tool catalog](/mcp/tools)
and the [scopes and roles](/mcp/scopes) that gate it.
