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

# Compile & Register

> Compile a flow and wire it onto an MCP server.

Once nodes and edges are in place, you compile the graph and register it. `.compile()` returns a `RegisteredFlow` that maps onto the standard `server.registerTool(name, config, handler)` contract.

## Compile

```ts theme={null}
const onboardingFlow = createFlow({
  id: "onboarding",
  title: "User Onboarding",
  description: "Use when a new user wants to get started.",
  state: { /* ... */ },
})
  .addNode({ id: "...", run: () => ({}) })
  .addEdge(/* ... */)
  .compile();
```

`.compile()` validates the graph (see [Edges](/sdk/flows/edges)) and throws immediately if the graph is malformed. Accepts one optional argument for a custom state store:

```ts theme={null}
.compile({ store: myCustomStore });
```

The default is `WaniwaniFlowStore`, which uses the Waniwani API as a key-value store keyed by session id. It reads `WANIWANI_API_KEY` and `WANIWANI_API_URL` from env.

## The RegisteredFlow shape

```ts theme={null}
type RegisteredFlow = {
  name: string;            // MCP tool name (equals config.id)
  config: ToolConfig;      // title, description, inputSchema, annotations
  handler: FlowToolHandler;
  register: (server: McpServer) => Promise<void>;
  graph: () => string;     // Mermaid flowchart TD
};
```

## Registering

Use the `register` shortcut for the common case:

```ts theme={null}
await onboardingFlow.register(server);
```

Or call `server.registerTool` directly if you prefer:

```ts theme={null}
server.registerTool(
  onboardingFlow.name,
  onboardingFlow.config,
  onboardingFlow.handler,
);
```

Both are equivalent.

## Registering widget display tools alongside flows

When a flow uses `showWidget({ tool: "name", ... })`, the named display tool must be registered on the same server. Use [Skybridge's](https://docs.skybridge.tech) `server.registerWidget(name, meta, schema, handler)` — it binds the widget name to the matching React component under `web/src/widgets/<name>.tsx` and emits the widget-rendering metadata that ChatGPT, Claude, and other MCP clients understand. You don't need to set `_meta.openai/outputTemplate` by hand.

```ts theme={null}
server
  .registerWidget(
    "show_pricing",
    { description: "Display the pricing plan grid. Do NOT list plans in text." },
    { inputSchema: { /* zod fields */ }, annotations: { readOnlyHint: true } },
    async (input) => ({
      structuredContent: input,
      content: [{ type: "text", text: "Pricing shown" }],
      isError: false,
    }),
  )
  .registerTool(quoteFlow.name, quoteFlow.config, quoteFlow.handler);
```

See the [Pet insurance example](/sdk/guides/pet-insurance#register-the-widget-display-tools) for a complete server.

## Minimal end-to-end wiring

A complete `server/src/app.ts` looks like this:

```ts server/src/app.ts theme={null}
import "dotenv/config";
import { McpServer } from "skybridge/server";
import { withWaniwani } from "@waniwani/sdk/mcp";
import { onboardingFlow } from "./flows/onboarding.js";
import { quoteFlow } from "./flows/quote.js";

export const server = new McpServer(
  { name: "my-mcp-app", version: "0.0.1" },
  { capabilities: {} },
)
  .registerTool(onboardingFlow.name, onboardingFlow.config, onboardingFlow.handler)
  .registerTool(quoteFlow.name, quoteFlow.config, quoteFlow.handler);

// Wrap after registrations to auto-track every tool call.
withWaniwani(server);

export type AppType = typeof server;
```

`withWaniwani` is idempotent — calling it twice on the same server is a no-op. The `skybridge/server` import is what the [MCP distribution template](https://github.com/WaniWani-AI/mcp-distribution-template) uses as the dev/build runtime; it provides the `.registerWidget(...)` fluent API plus widget mounting for the React components under `web/src/widgets/`.

## Debugging a compiled flow

### Inspect the graph

`flow.graph()` returns a Mermaid diagram of the compiled flow. Log it at startup in development:

```ts theme={null}
if (process.env.NODE_ENV !== "production") {
  console.log(onboardingFlow.graph());
}
```

Paste the output into any Mermaid renderer.

### Unit-test with `createFlowTestHarness`

The SDK ships a test harness that drives a compiled flow without spinning up a real transport or store. Import it from `@waniwani/sdk/mcp`:

```ts theme={null}
import { createFlowTestHarness } from "@waniwani/sdk/mcp";
import { onboardingFlow } from "./flows/onboarding.js";

const harness = await createFlowTestHarness(onboardingFlow);

const first = await harness.start("Get me onboarded");
// first.status === "interrupt"
// first.field === "email"

const second = await harness.continueWith({ email: "alice@example.com" });
// second.status === "interrupt"
// second.field === "useCase"
```

The harness API is:

* `start(intent, stateUpdates?)`: begins a run. `intent` is positional.
* `continueWith(stateUpdates?)`: resumes the paused run.
* `lastState()`: returns the persisted `FlowTokenContent` when a store is passed via `createFlowTestHarness(flow, { stateStore })`.

Each call returns a `FlowTestResult` that is the parsed `FlowContent` (interrupt, widget, complete, or error) plus a `decodedState` field populated when a store is supplied. Use it to write deterministic tests for branching, validation, and state transitions.
