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

# Self-hosting

> Two pieces you can self-host independently: the MCP app and the KV store.

A Waniwani deployment has **two pieces you can self-host independently**:

1. **The MCP app** — the server that hosts your `createFlow` tools and serves MCP clients.
2. **The KV store** — where flow state lives between tool calls.

These are orthogonal. The most common setup is "self-host the MCP app, let Waniwani host the KV store" — your infra for the server, the [Waniwani Platform](/sdk/platform/overview) for state and dashboards. The next step is self-hosting the KV too, eliminating any outbound call to `app.waniwani.ai`.

|                                | KV on `WaniwaniKvStore`                                                      | KV self-hosted                        |
| ------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------- |
| **MCP app self-hosted**        | Hybrid: your infra for the server, hosted state. Recommended starting point. | Fully open source. No outbound calls. |
| **MCP app on Managed Hosting** | Fully managed.                                                               | Not supported.                        |

## 1. Self-host the MCP app

Deploy the server you wrote on infrastructure you control. By itself this gets you self-hosted MCP + hosted state (the Hybrid row above).

### Scaffold

Start from the [MCP Distribution Template](https://github.com/WaniWani-AI/mcp-distribution-template) or any MCP server of your own:

```bash theme={null}
bun add @waniwani/sdk
```

### Wire up the server

```ts server.ts theme={null}
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { onboardingFlow } from "./flow";

const server = new McpServer({ name: "my-mcp-app", version: "0.0.1" });
await onboardingFlow.register(server);

const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await server.connect(transport);

const app = express();
app.use(express.json());
app.post("/mcp", (req, res) => transport.handleRequest(req, res, req.body));
app.listen(3000);
```

When `.compile()` is called with no `store` and `WANIWANI_API_KEY` is set, the SDK uses `WaniwaniKvStore` automatically. That's the Hybrid setup.

### Deploy

Any platform that can run Node 18.17+ works: Vercel, Render, Railway, Fly.io, Cloudflare Workers, AWS Lambda, Google Cloud Run. See [Deployment overview](/sdk/deployment/overview) for the choice between this and Managed Hosting.

### Register with Waniwani

In the [Waniwani dashboard](https://app.waniwani.ai), create a self-hosted project and paste your deployed MCP URL. Waniwani routes chat requests, widget rendering, and analytics ingestion through that URL.

## 2. Self-host the KV store

Pass your own `KvStore` to `.compile()` and the engine never touches `app.waniwani.ai` for state. Useful when you have data-residency constraints, run in fully open-source mode, or want all state inside your existing infrastructure.

### Pick a backend

See [KV store adapters](/sdk/flows/kv-store) for recipes. Quick decision tree:

* **Vercel / Netlify / Cloudflare Workers** → Upstash Redis or Cloudflare KV
* **AWS Lambda** → DynamoDB
* **Long-running Node** → ioredis against a managed Redis (Render, Railway, Fly)
* **Local box** → SQLite via `better-sqlite3`

### Implement the interface

```ts kv-store.ts theme={null}
import type { KvStore } from "@waniwani/sdk/mcp";
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

export const flowStore: KvStore = {
  async get(key) { return (await redis.get(key)) as never; },
  async set(key, value) { await redis.set(key, value); },
  async delete(key) { await redis.del(key); },
};
```

### Pass it to `.compile()`

```ts flow.ts theme={null}
import { createFlow, END, START } from "@waniwani/sdk/mcp";
import { flowStore } from "./kv-store";

export const onboardingFlow = createFlow({ /* ... */ })
  .addNode({ /* ... */ })
  .addEdge(START, "...")
  .addEdge("...", END)
  .compile({ store: flowStore });
```

State now reads and writes against your backend. Nothing about flow state ever leaves your infrastructure.

## Fully open-source mode

Combine the two: self-host the MCP app, pass a custom `KvStore`, omit `WANIWANI_API_KEY`. Result: no outbound calls to `app.waniwani.ai`, no telemetry, no hosted dashboards. The engine is fully usable in this mode.

### Verifying it's truly offline

```bash theme={null}
# 1. No WANIWANI_API_KEY in env
echo $WANIWANI_API_KEY
# (empty)

# 2. Run with network observability
DEBUG=* bun run start 2>&1 | grep waniwani
# (no requests to app.waniwani.ai)
```

The only outbound calls the SDK makes are when the `waniwani()` client is instantiated with an API key, or when `WaniwaniKvStore` is used. Neither happens in fully open-source mode.

## Switching modes later

The same code moves between modes by editing one or two lines:

* **OSS → Hybrid:** drop the `store` argument from `.compile()`, set `WANIWANI_API_KEY`.
* **Hybrid → OSS:** pass a custom `store` to `.compile()`.
* **Self-hosted → Managed Hosting:** see [Deployment overview](/sdk/deployment/overview#managed-hosting).

Existing sessions stay where they started; there is no automatic state migration between stores.
