> ## Documentation Index
> Fetch the complete documentation index at: https://docs.armature.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript

> Instrument a TypeScript MCP server with @armature-tech/mcp-analytics

The TypeScript SDK wraps servers built with the official [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) or [Mastra](https://mastra.ai). It captures sessions and tool calls without changing how your server behaves.

## Install

```bash theme={null}
npm install @armature-tech/mcp-analytics
```

The SDK declares `@modelcontextprotocol/sdk` as a peer dependency. If your tools use Zod 4 raw-shape schemas, use MCP SDK **1.29 or later** — 1.20 silently drops fields added to Zod 4 raw shapes.

## Set your API key

The SDK reads your key from the environment:

```bash theme={null}
ANALYTICS_INGEST_API_KEY=<your API key>
```

Create a key in the dashboard, then add it to your deployment secrets. If the key is missing, the SDK quietly no-ops — your server keeps working, and no data is sent.

## Wrap your server

If you build your server in a factory function, wrap the factory with `createMcpAnalyticsServer`. Every tool registered inside is instrumented automatically:

```typescript server.ts theme={null}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createMcpAnalyticsServer } from "@armature-tech/mcp-analytics";
import { z } from "zod";

const server = createMcpAnalyticsServer(() => {
  const mcp = new McpServer({ name: "customer-mcp", version: "1.0.0" });

  mcp.registerTool(
    "lookup_customer",
    {
      description: "Look up a customer by ID",
      inputSchema: { customer_id: z.string() },
    },
    async ({ customer_id }) => ({
      content: [{ type: "text", text: `customer ${customer_id} is active` }],
    }),
  );

  return mcp;
});

await server.connect(new StdioServerTransport());
```

That's the whole integration. Deploy, and sessions appear in the dashboard within minutes.

## Other server shapes

<AccordionGroup>
  <Accordion title="Existing server + tool registry" icon="list">
    If you already have an `McpServer` instance and a list of tool definitions, instrument them in place:

    ```typescript theme={null}
    import { instrumentMcpServerTools } from "@armature-tech/mcp-analytics";

    const { server: instrumented, recorder } = instrumentMcpServerTools({
      server,
      tools, // array or record of { ...toolRegistration, handler }
    });
    ```
  </Accordion>

  <Accordion title="Custom dispatcher" icon="route">
    If you handle `tools/list` and `tools/call` yourself, use the recorder directly:

    ```typescript theme={null}
    import { createAnalyticsRecorder } from "@armature-tech/mcp-analytics";

    const analytics = createAnalyticsRecorder();

    analytics.tool(toolDefinition, toolHandler);

    // tools/list
    const tools = analytics.toolDefinitions();

    // tools/call
    const result = await analytics.dispatch(name, args, context);
    ```
  </Accordion>

  <Accordion title="Mastra" icon="wand-sparkles">
    Wrap a Mastra tool map with the dedicated adapter:

    ```typescript theme={null}
    import { wrapMastraTools } from "@armature-tech/mcp-analytics/mastra";

    const instrumentedTools = wrapMastraTools(tools);
    ```

    The adapter reads MCP request context from `context.mcp.extra` or `context.requestContext.get("mcp.extra")` automatically.
  </Accordion>
</AccordionGroup>

<Note>
  Running on serverless or stateless HTTP? See [Stateless & serverless servers](/sdks/stateless-servers) for session handling, and set `delivery: "await"` so events are sent before the function freezes.
</Note>

## Configuration

Pass options under the `armature` key:

```typescript theme={null}
const server = createMcpAnalyticsServer(buildServer, {
  armature: {
    delivery: "background",
    timeoutMs: 500,
    onError: (error) => console.warn("analytics delivery failed", error),
  },
});
```

| Option        | Default                            | Description                                                                                               |
| ------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `apiKey`      | `ANALYTICS_INGEST_API_KEY` env var | API key used to authenticate ingestion.                                                                   |
| `endpointUrl` | Armature ingest endpoint           | Override with `ANALYTICS_INGEST_URL` or this option.                                                      |
| `enabled`     | `true`                             | Set `false` to disable capture entirely.                                                                  |
| `delivery`    | `"background"`                     | `"background"` posts events off the request path; `"await"` sends before returning (use in serverless).   |
| `timeoutMs`   | `500`                              | Delivery timeout in milliseconds.                                                                         |
| `actorId`     | Derived from request auth          | A string or resolver used to attribute calls to a user. See [Identifying users](/sdks/identifying-users). |
| `emit`        | Network emitter                    | Replace delivery with your own function — useful in tests.                                                |
| `onError`     | `console.warn`                     | Called when delivery fails. Failures never reach your tools.                                              |

## Flush on shutdown

With background delivery, drain pending events before the process exits:

```typescript theme={null}
import { withMcpAnalytics } from "@armature-tech/mcp-analytics";

const { result: server, recorder } = withMcpAnalytics({}, buildServer);

process.on("SIGTERM", async () => {
  await recorder.flush();
  process.exit(0);
});
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="`Mixed Zod versions detected in object shape`">
    Fixed in current releases — update the SDK. Older versions crashed when a tool declared a Zod 4 raw-shape schema while the SDK injected its telemetry field with Zod 3.
  </Accordion>

  <Accordion title="Sessions show up but telemetry fields are empty">
    Make sure `@modelcontextprotocol/sdk` is 1.29+. Version 1.20 silently drops fields the SDK adds to Zod 4 raw-shape schemas, so agents never see them.
  </Accordion>

  <Accordion title="No data arrives">
    Check that `ANALYTICS_INGEST_API_KEY` is set in the server's environment (missing keys no-op silently by design) and add an `onError` handler to surface delivery failures.
  </Accordion>
</AccordionGroup>
