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

# MCP Servers

> Track every tool call to your MCP server as a Raindrop event, grouped by the prompt that triggered it.

`@raindrop-ai/mcp` instruments any TypeScript [Model Context Protocol](https://modelcontextprotocol.io) server. Each tool call becomes a tool span; the calls a model makes for one user prompt are grouped into a single `mcp_activity` event, so you can see what agents actually do with your server and catch failing tools.

## Installation

```bash npm theme={null}
npm install @raindrop-ai/mcp
```

```bash pnpm theme={null}
pnpm add @raindrop-ai/mcp
```

Works with `@modelcontextprotocol/sdk` (v1) and `@modelcontextprotocol/server` (v2).

## Quick start

Wrap the server before registering tools, then register tools as usual.

```typescript theme={null}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createRaindropMcp } from "@raindrop-ai/mcp";
import { z } from "zod";

const raindrop = createRaindropMcp({ writeKey: process.env.RAINDROP_WRITE_KEY });

const server = raindrop.wrap(new McpServer({ name: "my-server", version: "1.0.0" }));

server.registerTool(
  "search",
  { description: "Search the docs", inputSchema: { query: z.string() } },
  async ({ query }) => ({ content: [{ type: "text", text: await search(query) }] })
);
```

Nothing else changes: tool handlers receive the same arguments and return the same results. Errors inside a handler are recorded on the tool span and returned to the client as a normal MCP error result.

### Wrapping a server you don't construct

For third-party servers that build their own `McpServer`, wrap the class once before the server loads:

```typescript theme={null}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createRaindropMcp } from "@raindrop-ai/mcp";

createRaindropMcp({ writeKey: process.env.RAINDROP_WRITE_KEY }).wrap(McpServer.prototype);

await import("some-mcp-server/dist/index.js");
```

## Turn tracking

MCP has no notion of a "turn", so by default the package adds an optional `raindrop_turn_id` parameter to every tool and appends the id to each result. The model copies it into its next call, and all calls carrying the same id land on one event. The parameter is stripped before your handler runs.

If a client sends `_meta["io.modelcontextprotocol/aiInvocation"].turnId`, that is used instead and nothing is appended.

Set `turnTracking: false` to leave tool schemas and results untouched; every call then becomes its own event.

```typescript theme={null}
const raindrop = createRaindropMcp({
  writeKey: process.env.RAINDROP_WRITE_KEY,
  turnTracking: false,
});
```

## Configuration

```typescript theme={null}
const raindrop = createRaindropMcp({
  writeKey: process.env.RAINDROP_WRITE_KEY,
  projectId: "my-project",
  userId: ({ extra }) => extra.authInfo?.clientId,
  redactArguments: ["api_token"],
  redactResults: ["get_credentials"],
  properties: ({ toolName }) => ({ "mcp.tool_group": toolName.split("_")[0] }),
});
```

| Option | Default | Description |
| - | - | - |
| `writeKey` | – | Raindrop write key. Without one, nothing ships and a warning is logged. |
| `projectId` | – | Raindrop project slug. |
| `eventName` | `mcp_activity` | Event name. |
| `turnTracking` | `true` | Advertise and echo `raindrop_turn_id` to group a prompt's calls into one event. |
| `userId` | OAuth subject → hashed bearer token → `session:<id>` → `anonymous` | Fixed id or `(call) => string \| undefined`. Returning `undefined` skips the call. |
| `convoId` | transport session, else the caller's UTC day | Fixed id or `(call) => string \| undefined`. |
| `properties` | – | Extra event properties, or `(call) => Record<string, unknown>`. |
| `redactArguments` | `[]` | Argument names never captured. Keys that look like secrets (`password`, `authorization`, `apiKey`, …) are always redacted. |
| `redactResults` | `[]` | Tools whose results are never captured, e.g. ones that return credentials. |
| `awaitDelivery` | `true` | Wait (at most 5s) for delivery before returning each tool result. Needed on serverless; set `false` on long-running servers to keep latency unchanged. |
| `maxTextFieldChars` | `1,000,000` | Per-field character cap for captured arguments and results. |
| `endpoint` | `https://api.raindrop.ai/v1/` | Ingest endpoint. |
| `appGit` | auto-detect | Application Git identity; `false` disables. |
| `debug` | `false` | Log shipping details. |

`call` passed to the resolvers is `{ toolName, args, sessionId, extra }`, where `extra` is the SDK's per-request object (v1 `RequestHandlerExtra`, v2 `ServerContext`).

## What gets captured

* One `mcp_activity` event per turn with properties `mcp.turn_id`, `mcp.tool` (latest tool in the turn), `mcp.is_error`, `mcp.session_id` when the transport has one, plus anything from `properties`.
* One tool span per call with the tool name, arguments and result (after redaction), and the error message when the handler fails.

Call `raindrop.shutdown()` on process exit to drain pending deliveries.

## Known limitations

* `wrap()` intercepts `registerTool`, so tools registered before wrapping are not instrumented.
* `raindrop_turn_id` is added by extending the tool's schema. Zod object schemas, raw Zod shapes and Standard Schemas are supported; a schema that cannot be extended (a Zod union, or a raw shape from a different Zod major) is registered unchanged and its calls each open their own event.
* Grouping depends on the model echoing `raindrop_turn_id`; clients that strip unknown result blocks start a new event per call.

Looking for Raindrop's own MCP server for your coding agent? See [MCP](/docs/mcp/overview).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.