> ## 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.

# Developer platform

> The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.

FirstFlow integrates into your product from two sides and is operated from a third. A [browser SDK](/react/overview) renders experiences inside your chat UI over a realtime connection; a [server SDK](/server/overview) observes your LLM calls so FirstFlow can see the real conversation; and an [MCP server](/mcp/overview) lets a coding agent build and publish experiences for you. This page is the map of those pieces what each one is, the small contract it asks of your code, and where its deep reference lives.

The integration contract is deliberately small. You wrap your LLM client on the server, mount a provider plus a widget in the browser, and join the two with one id you own. Everything that decides *what to show* triggers, audience, schedule, frequency, the classifier, and widget composition runs server-side in FirstFlow Cloud (or your self-hosted backend), so adding a new experience never requires a client deploy.

## How the pieces fit

The mechanism behind every piece on this page is the same loop. Your server SDK reports each conversation turn; the backend evaluates eligibility and walks the experience's flow graph to a result; FirstFlow composes a `WidgetTree` with Anthropic (`claude-sonnet-4-6`) and pushes it over Socket.IO; the browser SDK renders it and reports interaction back. The browser is a passive consumer it never decides what to show, which is why you cannot trigger a widget from the client alone.

```mermaid theme={null}
flowchart TD
  A["Host app LLM chat turn"] -->|"observe(): firstflowAgentId + sessionId + userId"| B["@firstflow/sdk (server)"]
  B -->|"POST /v1/conversations/:id/messages (202)"| C["Backend: ConversationRouterService"]
  C -->|"triggers · audience · classifier · flow walk"| D["WidgetCompositionService claude-sonnet-4-6"]
  D -->|"widget.show { tree } over Socket.IO"| E["@firstflow/react (browser)"]
  E -->|"BlockRenderer → DOM"| F["User sees & interacts"]
  F -->|"widget.action → flow.next / flow.back (server-side)"| C
  G["Coding agent via MCP"] -.->|"create / edit / publish experiences"| C

  style A fill:#fde68a,stroke:#b45309,color:#1c1917
  style B fill:#bbf7d0,stroke:#15803d,color:#1c1917
  style C fill:#e9d5ff,stroke:#7e22ce,color:#1c1917
  style D fill:#e9d5ff,stroke:#7e22ce,color:#1c1917
  style E fill:#bae6fd,stroke:#0369a1,color:#1c1917
  style F fill:#fbcfe8,stroke:#be185d,color:#1c1917
  style G fill:#fed7aa,stroke:#c2410c,color:#1c1917
```

The packages map cleanly onto that loop: `@firstflow/sdk` is the server half (reporting turns, traces, and outcomes), `@firstflow/react` is the browser half (rendering and reporting activity), `@firstflow/nextjs` bundles both for App Router apps, `@firstflow/widget-kit` is the rendering core they share, and the MCP server is how an agent edits the experiences the loop runs.

## Browser SDK `@firstflow/react`

The [browser SDK](/react/overview) is the runtime that lives in your chat UI. It opens the realtime socket, identifies the end user, renders the widgets the backend pushes, and reports activity back. Mount [`FirstflowProvider`](/react/provider) once above your chat and drop a [`FirstflowWidget`](/react/widget) where experiences should appear.

```tsx theme={null}
import { FirstflowProvider, FirstflowWidget } from "@firstflow/react";

<FirstflowProvider
  agentId="agt_…"
  publishableKey="pk_live_…"
  user={{ id: user.id, email: user.email, traits: { plan: user.plan } }}
  conversationId={conversationId}
>
  <YourChat />
  <FirstflowWidget />
</FirstflowProvider>;
```

The provider takes `agentId` and a `publishableKey` (`pk_live_…`, browser-safe not a secret), plus an optional `user` and `conversationId`. It throws if `agentId` or `publishableKey` is empty, and identifies the user immediately from the `user` prop without waiting for the socket. Theme and slash commands arrive on the server's `agent.config` event and apply automatically. Around the widget, [`useFirstflow()`](/react/use-firstflow) gives you the instance: `analytics` (`track`, `identify`, `page`), `getUser`/`setUser`, `notifyActivity()`, the `commands` array, `triggerCommand()`, and `getConversationId()`. Identity, [analytics](/react/analytics), [slash commands](/react/commands), and [theming](/react/theming) each have their own page; [realtime](/react/realtime) covers the connection itself.

## Server SDK `@firstflow/sdk`

The [server SDK](/server/overview) wraps your existing LLM client so every conversation turn, trace, token count, and cost flows to FirstFlow. Observing the conversation server-side is also what powers user-message triggers and classifier context the backend sees the real conversation and can decide what to show. The one rule is that every observed call carries three identifiers, all required: `firstflowAgentId`, `sessionId` (which **must equal** the browser's `conversationId`), and `userId`.

```ts theme={null}
import { OpenAI } from "@firstflow/sdk/openai";

const client = new OpenAI(); // reads FIRSTFLOW_API_KEY from env

const res = await client.chat.completions.create({
  firstflowAgentId: "agt_…",
  sessionId,   // == browser conversationId
  userId,
  model: "gpt-4o",
  messages,
});
```

<Warning>
  The wrapper strips those three fields before the request reaches OpenAI or
  Anthropic. If any one is missing, the call passes through **unobserved** (with a
  one-time warning) so partial tagging silently drops the turn from FirstFlow.
</Warning>

Import the wrapped client that matches your provider [`@firstflow/sdk/openai`](/server/wrap-llm-client), [`/anthropic`](/server/wrap-llm-client), or [`/aiclient`](/server/wrap-llm-client) for any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio) or use [`@firstflow/sdk/langchain`](/server/langchain) for LangChain. The instrumented methods are `chat.completions.create`, `messages.create`, `responses.create`, and `embeddings.create`. Alongside the wrappers, fire-and-forget helpers read `FIRSTFLOW_API_KEY` and never throw or block: [`observe()`](/server/observe) records a turn manually, [`trace()`](/server/traces) records nested spans, [`outcome()`](/server/outcomes) signals how a session ended, and [`track()` / `identify()`](/server/analytics) send server-side analytics. The only required env var is `FIRSTFLOW_API_KEY` (secret, server-only); set `FIRSTFLOW_API_BASE_URL` when [self-hosting](/server/configuration). Observed conversations are what the dashboard turns into [analytics and sessions](/features/analytics).

## Next.js wrapper `@firstflow/nextjs`

[`@firstflow/nextjs`](/react/nextjs) is a thin wrapper for App Router apps. Its default entry re-exports the browser SDK for client components, and its `/server` subpaths re-export the server helpers and pre-wrapped clients (`/server/openai`, `/server/anthropic`, `/server/aiclient`). Keeping server code on a separate subpath means a bundler never pulls server or OTel code into the browser. Use it when you want one dependency and Next-aware defaults; the underlying API and prop names are identical to the two SDKs above.

## Realtime widget

The widget is the visible surface, but it has no logic of its own it renders the `WidgetTree` the backend pushes. The connection is a Socket.IO link at path `/v1/socket`, authenticated with a short-lived client JWT the server SDK mints (carrying `workspace`, `sub`, and `traits`), and the browser joins a per-user room so only that user's widgets arrive. User interaction emits `widget.action` back; flow navigation (`flow.next` / `flow.back`) is resolved **server-side**, which then composes and pushes the next tree. See [`FirstflowWidget`](/react/widget) for props and event handlers, and [realtime](/react/realtime) for connecting without React.

## widget-kit `@firstflow/widget-kit`

[`@firstflow/widget-kit`](/widget-kit/overview) is the runtime-agnostic rendering core that both SDKs build on. It defines the [`WidgetTree` and `Block` schema](/concepts/widgets-and-blocks) and a `BlockRenderer` that turns a tree into React components, along with state utilities and streaming helpers that heal partial JSON trees as they arrive from the model. Most apps never import it directly `@firstflow/react` already renders widgets and re-exports the block types. Reach for widget-kit only to build a custom renderer or render trees outside the standard widget shell.

## AI Connect the MCP server

The [MCP server](/mcp/overview) is how a coding agent operates FirstFlow instead of integrating it. Mounted at `<BACKEND_URL>/mcp` over streamable HTTP and protected by OAuth 2.0 + PKCE, it lets Claude Code, Cursor, or Claude Desktop read your product context list [agents](/concepts/agents), inspect experiences and [audience](/concepts/audience) and then create tours, surveys, announcements, and guides, edit flow steps, and publish. What each call can do depends on its [scopes](/mcp/scopes). Note that static [API keys](/mcp/api-keys) are rejected on `/mcp` (clients must use OAuth) even though they still work on the REST API. To point a plain assistant at these docs as context rather than have it operate the platform, see [Use these docs with AI](/use-docs-with-ai).

## The linking id

The browser and server halves are joined by one value you own and persist: the browser's `conversationId` must equal the server SDK's `sessionId`. Mint it lazily omit it until the first message, then create and persist a stable id for the life of the thread and pass the same value to both sides so every observed turn and every pushed widget belong to the same conversation. The full model is in [Conversations & sessions](/concepts/conversations-and-sessions).

## Next

Pick the side you're integrating first.

<CardGroup cols={2}>
  <Card title="Browser SDK" icon="react" href="/react/overview" />

  <Card title="Server SDK" icon="server" href="/server/overview" />
</CardGroup>
