.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:
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 theregister shortcut for the common case:
server.registerTool directly if you prefer:
Registering widget display tools alongside flows
When a flow usesshowWidget({ 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.
Minimal end-to-end wiring
A completeserver/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:
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:
start(intent, known?): begins a run.intentis positional.knownstands for what the user already said. It goes out asstateUpdatesonstart, then each question or widget whose fieldknownholds is answered from it with acontinue, the way the model answers from earlier messages. That second pass covers fields markedneverPrefilled()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 questionknownhas 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 persistedFlowTokenContentwhen a store is passed viacreateFlowTestHarness(flow, { stateStore }).
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.