Metadata-Version: 2.5
Name: raindrop-mcp
Version: 0.0.1
Summary: Raindrop instrumentation for Python MCP servers: one event per turn, one tool span per call, for any FastMCP / MCPServer.
Project-URL: Homepage, https://raindrop.ai
Project-URL: Repository, https://github.com/invisible-tools/dawn
Author-email: Raindrop AI <sdk@raindrop.ai>
License-Expression: MIT
License-File: LICENSE
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: mcp>=1.10.0
Requires-Dist: raindrop-ai>=0.0.70
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# raindrop-mcp

Raindrop instrumentation for Python MCP servers. Works with the official SDK's `mcp` 1.x (`FastMCP`) and `mcp` 2.x (`MCPServer`), with the standalone [`fastmcp`](https://pypi.org/project/fastmcp/) package (2.x and 3.x+), and with any server built on them: wrap it once and every tool call lands in Raindrop as a tool span on an `mcp_activity` event. Python twin of [`@raindrop-ai/mcp`](https://github.com/raindrop-ai/raindrop-js/tree/main/packages/mcp).

## Install

```bash
pip install raindrop-mcp
```

## Quick Start

```python
import os
from mcp.server.fastmcp import FastMCP  # mcp 2.x: from mcp.server.mcpserver import MCPServer
from raindrop_mcp import RaindropMcp

raindrop = RaindropMcp(api_key=os.environ.get("RAINDROP_API_KEY"))
mcp = raindrop.wrap(FastMCP("my-server"))

@mcp.tool()
def search(query: str) -> str:
    ...

mcp.run()
```

Omitting `api_key` disables shipping (a warning is emitted) but never crashes the server.

Third-party servers usually build their own `FastMCP` at import time. Wrap the class before importing them, or wrap the instance afterwards (tools already registered are instrumented too):

```python
from mcp.server.fastmcp import FastMCP
from raindrop_mcp import RaindropMcp

RaindropMcp(api_key=...).wrap(FastMCP)   # every FastMCP built from now on
import some_mcp_server                   # noqa: E402
some_mcp_server.mcp.run()
```

The standalone `fastmcp` package works the same way:

```python
from fastmcp import FastMCP
from raindrop_mcp import RaindropMcp

mcp = RaindropMcp(api_key=...).wrap(FastMCP("Demo"))

@mcp.tool
def add(a: int, b: int) -> int:
    return a + b
```

## Turn tracking

MCP has no turn boundary, so by default every tool advertises an optional `_turn_id` argument and every result ends with a `_turn_id: <id>` text block. The model copies it on its next call and omits it on a new prompt, so the calls of one prompt share one event (one trace, one tool span per call). The argument is removed before your tool runs. A tool that raises still echoes the id, so the model's next call stays on the same event.

If the client sends `_meta["io.modelcontextprotocol/aiInvocation"].turnId` (SEP-2817), that id is used and nothing is echoed.

`turn_tracking=False` leaves schemas and results untouched (this also disables `intent`); every call is then its own event.

## Intent and client

Every tool also advertises an optional `_intent` argument: one sentence on what the user is trying to accomplish, which the model fills on the first call of a prompt. It is stripped before your tool runs, recorded as the `mcp.intent` property, and appended to the event input under a `Session intent:` line after `MCP session started`. Set `intent=False` to leave it out. It is free text the model wrote, so the key-based secret redaction does not apply to it; list `_intent` in `redact_arguments` to keep the field advertised but out of telemetry.

The MCP client that made the call (`clientInfo`, e.g. `claude-code 2.1.0`, `Cursor`, `codex-mcp-client`) is recorded as `mcp.client_name` / `mcp.client_version` on every span and event. It comes from the per-request `_meta` of 2026-era clients, or from the `initialize` handshake on older ones.

## Options

| Option                 | Default                                                            | Description                                                                                                                                  |
| ---------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_key`              | –                                                                  | Raindrop write key. Without one, nothing ships and a warning is emitted.                                                                     |
| `endpoint`             | `https://api.raindrop.ai/v1/`                                      | Ingest endpoint.                                                                                                                             |
| `project_id`           | –                                                                  | Raindrop project slug.                                                                                                                       |
| `event_name`           | `mcp_activity`                                                     | Event name.                                                                                                                                  |
| `turn_tracking`        | `True`                                                             | Advertise and echo `_turn_id` to group a prompt's calls into one event.                                                                      |
| `intent`               | `True`                                                             | Advertise `_intent` so the model states the user's goal on the first call of a prompt.                                                       |
| `user_id`              | OAuth subject → hashed bearer token → `session:<id>` → `anonymous` | Fixed id or `callable(call) -> str \| None`. Returning `None` skips the call.                                                                |
| `convo_id`             | transport session, else the caller's UTC day                       | Fixed id or `callable(call) -> str \| None`.                                                                                                 |
| `properties`           | –                                                                  | Extra event properties, a dict or `callable(call) -> dict`.                                                                                  |
| `redact_arguments`     | `[]`                                                               | Argument names never captured. Keys that look like secrets (`password`, `authorization`, `api_key`, …) are always redacted.                 |
| `redact_results`       | `[]`                                                               | Tools whose results are never captured, e.g. ones that return credentials.                                                                   |
| `await_delivery`       | `True`                                                             | Wait (at most 5s, off the event loop) for delivery before returning each tool result. Needed on serverless. Set `False` on long-running servers to keep tool latency unchanged. |
| `max_text_field_chars` | `1,000,000`                                                        | Per-field character cap for captured arguments and results.                                                                                  |
| `debug`                | `False`                                                            | Log shipping details.                                                                                                                        |
| `app_git`              | auto-detect                                                        | Application Git identity; `False` disables.                                                                                                  |
| `client`               | –                                                                  | An existing `raindrop.Raindrop` client to ship through (multi-project setups).                                                               |

`call` passed to the callables is a `McpCallContext(tool_name, args, session_id, context)`, where `context` is the SDK's per-request `Context`.

## Event properties

`mcp.turn_id`, `mcp.tool` (the latest tool in the turn), `mcp.is_error`, `mcp.client_name` / `mcp.client_version` when the client identified itself, `mcp.intent` when the model filled `_intent`, `mcp.session_id` when the transport has one, plus anything from `properties`. Tool spans carry the arguments, the result text (or the error), `mcp.turn_id`, `mcp.session_id`, the client and the intent.

## Known limitations

- Servers built on the official SDK's `FastMCP` / `MCPServer` or on standalone `fastmcp` are supported; the low-level `mcp.server.Server` is not.
- On standalone `fastmcp` 2.x a tool that raises cannot echo the id (its `ToolResult` has no error form), so the model's next call starts a new event; 3.x+ echoes like the official SDK.
- `_turn_id` and `_intent` are added to the tool's JSON schema. A tool that declares either name itself keeps it: the field is neither advertised by this package nor stripped from the arguments for that tool, and a warning is emitted at registration since that tool then has no intent (or no turn grouping).
- Grouping depends on the model echoing `_turn_id`; clients that strip unknown result blocks start a new event per call.
- Delivery runs on the Raindrop SDK's background thread; `await_delivery` flushes before returning but cannot wait for a flush that thread already started.

## Testing

```bash
pip install -e ".[dev]" && pytest
```

The suite runs unchanged against `mcp<2` and `mcp>=2`.
