Skip to main content
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

.compile() validates the graph (see Edges) and throws immediately if the graph is malformed. Accepts one optional argument for a custom state store:
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

Registering

Use the register shortcut for the common case:
Or call server.registerTool directly if you prefer:
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 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.
See the Pet insurance example for a complete server.

Minimal end-to-end wiring

A complete server/src/app.ts looks like this:
server/src/app.ts
withWaniwani is idempotent — calling it twice on the same server is a no-op. The skybridge/server import is what the 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:
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:
The harness API is:
  • start(intent, known?): begins a run. intent is positional. known stands for what the user already said. It goes out as stateUpdates on start, then each question or widget whose field known holds is answered from it with a continue, the way the model answers from earlier messages. That second pass covers fields marked neverPrefilled() and values that failed their schema. Each field is sent once, so a value a validator rejects comes back as that rejection. The call returns the response the flow stops on, usually a question known has no value for.
  • continueWith(stateUpdates?): resumes the paused run. It writes what any call writes (see What a call can write).
  • resetWith(stateUpdates): restarts the run with a corrected value, written by the same rule, and returns an error when it has nothing to write.
  • 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.