Skip to main content
The tracking module ships events from your MCP server to the WaniWani backend. You call client.track(), the SDK enqueues the event in memory, and a background transport batches and posts it to POST /api/mcp/events/v2/batch. Tracking never blocks your tool handler.

Mental model

Events

A typed record of something that happened (a tool call, a quote, a purchase). Has a name, properties, and correlation ids.

Sessions

A session groups events from one MCP conversation. The SDK reads the session id from MCP _meta, so you pass meta: extra._meta on every call.

Identify

Attach an externalUserId to the current session so events get linked to a real user.

Automatic tool tracking

withWaniwani(server) wraps every tool handler and emits tool.called for you with timing and status.

Track your first event

import { waniwani } from "@waniwani/sdk";

const wani = waniwani(); // reads WANIWANI_API_KEY from env

// Inside a tool handler
await wani.track({
  event: "quote.succeeded",
  properties: { amount: 1299, currency: "EUR" },
  meta: extra._meta, // associates the event with the current session
});

// In serverless runtimes, flush before the function returns
await wani.flush();
That is the whole happy path. Everything else on the following pages is detail.

How the transport behaves

When you call track(), the SDK:
  1. Normalizes the input into a canonical Events API V2 envelope (see events-api-v2-contract).
  2. Generates a deterministic event id (evt_<uuid>).
  3. Pushes the envelope on an in-memory buffer and returns { eventId } immediately.
  4. A background timer flushes the buffer every flushIntervalMs (default 1000), or sooner when the batch fills (maxBatchSize, default 20).
  5. Retries transient failures (408, 425, 429, 5xx) with exponential backoff, up to maxRetries (default 3).
  6. Stops retrying on 401 / 403 auth errors to avoid retry storms.

Long-running vs. serverless

RuntimeHow to make sure events ship
Long-running Node processNothing. The SDK attaches beforeExit, SIGINT, and SIGTERM hooks that call shutdown() automatically.
Serverless / short-livedCall await wani.flush() before the function returns, or pass flushAfterToolCall: true to withWaniwani().

Client API

interface TrackingClient {
  track(event: TrackInput): Promise<{ eventId: string }>;
  identify(
    userId: string,
    properties?: Record<string, unknown>,
    meta?: Record<string, unknown>,
  ): Promise<{ eventId: string }>;
  flush(): Promise<void>;
  shutdown(options?: { timeoutMs?: number }): Promise<{
    timedOut: boolean;
    pendingEvents: number;
  }>;
}

track(event)

Enqueue an event.

identify(userId, properties?, meta?)

Send a user.identified event and attach the user id to the session.

flush()

Wait for the current buffer to drain.

shutdown({ timeoutMs? })

Flush and stop the transport. Returns { timedOut, pendingEvents }.
All four methods throw synchronously if no apiKey was resolved (from config or WANIWANI_API_KEY). If you instantiate waniwani() without an api key, the client is inert and calling track() will throw.