| name | waniwani-sdk |
| description | Integrate the @waniwani/sdk package into MCP servers for event tracking, multi-step conversational flows, widget creation, knowledge base search, and chat components. Use when building or integrating WaniWani analytics, creating MCP tools with UI widgets, building multi-turn flows, or adding chat to a website. |
| license | AGPL-3.0-or-later |
| metadata | {"author":"WaniWani"} |
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
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:
WANIWANI_API_KEY=wwk_...
- Create a client singleton:
import { waniwani } from "@waniwani/sdk";
export const wani = waniwani();
- 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" });
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();
const client = waniwani({
apiKey: process.env.WANIWANI_API_KEY,
apiUrl: "https://app.waniwani.ai",
});
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({
});
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,
});
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?)
await client.flush();
const result = await client.shutdown({ timeoutMs: 2000 });
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) => {
const quote = await generateQuote(input.product);
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.
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
Common Mistakes
- Missing
WANIWANI_API_KEY env var -- Flow state and tracking will throw. Set it in all environments (dev, Vercel, production).
- Creating multiple clients -- Create one in
lib/waniwani.ts and import everywhere.
- Wrong import paths -- Hooks:
@waniwani/sdk/mcp/react. Chat: @waniwani/sdk/chat. Tools: @waniwani/sdk/mcp.
- Missing
WidgetProvider -- All React widget hooks require the WidgetProvider wrapper.
- Forgetting
START/END edges in flows -- Every flow needs addEdge(START, firstNode) and addEdge(lastNode, END).
- Importing
interrupt/showWidget directly -- These come from the handler context: ({ interrupt }) => interrupt(...).