waniwani-sdk
WaniWani SDK (@waniwani/sdk)
SDK for MCP event tracking, multi-step conversational flows, dual-platform widget creation, knowledge base search, and embeddable chat components. Works with @modelcontextprotocol/sdk, @vercel/mcp-handler, and Skybridge.
Docs: docs.waniwani.ai Dashboard: app.waniwani.ai
Install
bun add @waniwani/sdk # or: pnpm add / npm install
Peer dependencies vary by export path (see table below). The core tracking module has zero runtime dependencies.
Quick Start
- Get an API key from app.waniwani.ai (create an MCP project, copy
wwk_...key) - Set the env var:
# .env
WANIWANI_API_KEY=wwk_...
- Create a client singleton:
// lib/waniwani.ts
import { waniwani } from "@waniwani/sdk";
export const wani = waniwani();
// Reads WANIWANI_API_KEY from env — one instance, import everywhere
- Wrap your MCP server for automatic tracking:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { withWaniwani } from "@waniwani/sdk/mcp";
import { wani } from "../lib/waniwani";
const server = new McpServer({ name: "my-server", version: "1.0.0" });
// Every tool call now emits a tool.called event with timing and status
withWaniwani(server, { client: wani });
server.registerTool("get_pricing", /* ... */);
- Verify: trigger any tool call, then check your WaniWani dashboard.
Export Paths
| Export | Purpose | Reference | Peer Dependencies |
|---|---|---|---|
@waniwani/sdk |
Event tracking client | (this file) | None |
@waniwani/sdk/mcp |
Server-side tools, widgets, flows, tracking | tools-and-widgets, flows | @modelcontextprotocol/sdk, zod |
@waniwani/sdk/mcp/react |
Client-side widget React hooks | widget-react-hooks | react |
@waniwani/sdk/chat |
Chat React component + embed script | chat-widget | react, react-dom, @ai-sdk/react, ai |
@waniwani/sdk/chat/styles.css |
Chat widget stylesheet | chat-widget | -- |
@waniwani/sdk/next-js |
Next.js route handler adapter | chat-server | -- |
@waniwani/sdk/kb |
Knowledge base client | knowledge-base | None |
@waniwani/sdk/chat/server |
Chat API handler (server-side) | chat-server | ai |
@waniwani/sdk/evals |
Eval framework (chat, conversation, scenarios) | -- | ai |
Core: Event Tracking (@waniwani/sdk)
waniwani(config?)
Creates a client instance. Reads WANIWANI_API_KEY and WANIWANI_API_URL from env vars when called with no arguments.
import { waniwani } from "@waniwani/sdk";
const client = waniwani();
// Or with explicit config:
const client = waniwani({
apiKey: process.env.WANIWANI_API_KEY,
apiUrl: "https://app.waniwani.ai", // default
});
Create one client in lib/waniwani.ts and import it everywhere. Do not call waniwani() in multiple files.
defineConfig(config)
Used in waniwani.config.ts to define project-level configuration:
import { defineConfig } from "@waniwani/sdk";
export default defineConfig({
// WaniWaniProjectConfig fields
});
client.track(event)
Enqueues an event for batched delivery. Returns immediately after enqueue.
await client.track({
event: "tool.called",
properties: { name: "get_pricing", type: "pricing" },
meta: extra._meta, // MCP request metadata
});
client.identify(userId, properties?)
Sends a one-shot user.identified event.
await client.identify("user@example.com", { plan: "pro", company: "Acme" });
Event Types
| Event | Key Properties |
|---|---|
session.started |
-- |
tool.called |
name, type ("pricing", "product_info", "availability", "support", "other") |
quote.requested |
-- |
quote.succeeded |
amount, currency |
quote.failed |
-- |
link.clicked |
url |
purchase.completed |
amount, currency |
meta Field
Pass MCP request metadata to auto-extract session and user info:
@modelcontextprotocol/sdk:request.params._meta@vercel/mcp-handler:extra._meta
client.flush() / client.shutdown(options?)
// Flush buffered events
await client.flush();
// Flush and stop transport (for serverless/tests)
const result = await client.shutdown({ timeoutMs: 2000 });
// => { timedOut: boolean, pendingEvents: number }
In Node environments, the SDK auto-flushes on beforeExit, SIGINT, and SIGTERM. For serverless or edge runtimes, call shutdown() explicitly.
Auto-Tracking: withWaniwani (@waniwani/sdk/mcp)
Wraps an MCP server so all tool handlers automatically emit tool.called events after execution with durationMs, status ("ok" or "error"), and errorMessage (on failure).
import { withWaniwani } from "@waniwani/sdk/mcp";
import { wani } from "../lib/waniwani";
withWaniwani(server, { client: wani });
Options (all optional):
| Field | Type | Default | Description |
|---|---|---|---|
client |
WaniWaniClient |
auto from env | Pre-built client |
toolType |
string | (name) => string |
"other" |
Default tool type for events |
metadata |
Record<string, unknown> |
-- | Extra metadata on every event |
flushAfterToolCall |
boolean |
false |
Flush after each tool call |
onError |
(error) => void |
-- | Non-fatal tracking error callback |
injectWidgetToken |
boolean |
true |
Inject JWT into _meta.waniwani for browser widget tracking |
Combined example: auto-tracking + manual events
const client = waniwani();
withWaniwani(server, { client, flushAfterToolCall: true });
server.registerTool("get_quote", config, async (input, extra) => {
// tool.called is tracked automatically
const quote = await generateQuote(input.product);
// Additional event tracked manually
await client.track({
event: "quote.succeeded",
properties: { amount: quote.amount, currency: "USD" },
meta: extra._meta,
});
return { content: [{ type: "text", text: `Quote: $${quote.amount}` }] };
});
Building Flows
Multi-step conversational flows with server-side state. Define a state graph, compile it into an MCP tool, and let the AI drive the flow step by step.
import { createFlow, START, END, registerTools } from "@waniwani/sdk/mcp";
import { z } from "zod";
const flow = createFlow({
id: "demo_qualification",
title: "Demo Qualification",
description: "Qualify a lead for a demo.",
state: {
email: z.string().describe("Work email"),
role: z.string().describe("Role at company"),
},
})
.addNode("ask_email", ({ interrupt }) =>
interrupt({ email: { question: "What is your work email?" } })
)
.addNode("ask_role", ({ interrupt }) =>
interrupt({ role: { question: "What is your role?" } })
)
.addNode("done", ({ state }) => ({ summary: `${state.email}, ${state.role}` }))
.addEdge(START, "ask_email")
.addEdge("ask_email", "ask_role")
.addEdge("ask_role", "done")
.addEdge("done", END)
.compile();
await registerTools(server, [flow]);
Flows support interrupt validation, conditional edges, widget steps, nested state, and pre-filling. See references/flows.md for the full guide.
Guided Playbooks
Step-by-step scripts for common tasks. Follow these when the user wants to build something from scratch.
| User wants to... | Playbook |
|---|---|
| Initialize a new MCP distribution from the template | scripts/initialize.md |
| Create their first flow | scripts/create-flow.md |
| Tunnel the dev server for remote testing | scripts/tunnel.md |
When a playbook exists for the user's task, follow the playbook step by step instead of writing code directly. The playbooks include prerequisite checks, interactive design steps, and testing instructions.
Reading Guide
| You want to... | Read |
|---|---|
| Add analytics to an existing MCP server | setup + auto-tracking section above |
| Create tools with widget UIs | tools-and-widgets + widget-react-hooks |
| Build multi-step conversational flows | flows + flows API reference |
| Add a knowledge base with search | knowledge-base |
| Embed a chat widget on a website | chat-widget + chat-server |
Common Mistakes
- Missing
WANIWANI_API_KEYenv var -- Flow state and tracking will throw. Set it in all environments (dev, Vercel, production). - Creating multiple clients -- Create one in
lib/waniwani.tsand import everywhere. - Wrong import paths -- Hooks:
@waniwani/sdk/mcp/react. Chat:@waniwani/sdk/chat. Tools:@waniwani/sdk/mcp. - Missing
WidgetProviderin page component -- The page MUST wrap children in<WidgetProvider>. Never call hooks (useToolOutput,useTheme, etc.) directly in the page -- they throwuseWidgetClient must be used within a WidgetProvider. Put all hook usage in a child component rendered insideWidgetProvider. - Missing
InitializeNextJsInIframein layout -- Add<InitializeNextJsInIframe baseUrl={...} />in the root layout's<head>so fetch/navigation work inside cross-origin iframes. - Forgetting
START/ENDedges in flows -- Every flow needsaddEdge(START, firstNode)andaddEdge(lastNode, END). - Importing
interrupt/showWidgetdirectly -- These come from the handler context:({ interrupt }) => interrupt(...).
More from waniwani-ai/sdk
oai-submission
Generate OpenAI/ChatGPT App submission documents (Tool Justification + Test Cases) in Notion by analyzing the MCP server's tools, flows, and widgets
2copy
Copy text to the user's clipboard. Use when the user wants to copy generated content, summaries, or any text to their clipboard.
1translations
Add or update translations for pages and components in the WaniWani app. Use when the user wants to add translations, create translation files, internationalize a page, make text translatable, or update existing translations. Also use proactively when creating new pages or components with user-facing text.
1knowledge-base
Set up a knowledge base with search for an MCP project. Creates FAQ tool and ingestion script using the WaniWani KB API via @waniwani/sdk.
1frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
1