Skip to main content

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 package (2.x and 3.x+), with a hand-wired low-level mcp.server.Server, and with any server built on them: wrap it once (or launch with raindrop-mcp) and every request the server answers lands in Raindrop as a tool span on an mcp_activity event: tool calls, prompts, resources, and the failures no handler sees (unknown tools, bad arguments, cancelled and dropped requests). Python twin of @raindrop-ai/mcp.

Install

pip install raindrop-mcp

Quick Start

No code changes: launch the server through raindrop-mcp with the write key in the environment.

RAINDROP_WRITE_KEY=... raindrop-mcp python server.py      # or: raindrop-mcp python -m my_server

RAINDROP_PROJECT_ID picks the project, RAINDROP_MCP_CAPTURE_CONTENT=false ships names, counts, errors and client identity but never arguments, results or intent, and RAINDROP_MCP_DISABLED=1 turns the launcher off without editing the command. Use either the launcher or wrap(), not both.

In code:

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):

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:

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

A hand-wired low-level server works too: raindrop.wrap(Server("my-server")) on an mcp.server.lowlevel.Server instance or the class.

What is recorded

Every JSON-RPC request the server answers is one call: tools/call (named by the tool), prompts/get <name>, resources/read <uri>, completion/complete, and so on, with mcp.method on the event and span. Failures carry mcp.error_type:

mcp.error_type When
tool_error The tool returned an isError result.
handler_throw The tool raised.
rpc_<code> The server answered a JSON-RPC error (unknown tool or prompt, invalid arguments).
cancelled The client cancelled the request before the response.
closed_before_response The connection closed while the request was still pending.

Lifecycle traffic never ships: initialize, server/discover, subscriptions/listen, ping and every */list, whether they succeed, fail or are still open when the connection closes.

An instrumented client that puts a W3C traceparent in params._meta has it recorded as mcp.traceparent. Credentials in URIs (user:pass@, query strings, fragments) are stripped from labels, arguments and error text.

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, and credentials inside URL values (user:pass@host, ?token=) are replaced in every captured string.
redact_results [] Tools, prompts or methods (resources/read) 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.
capture_content True False ships tool names, counts, errors and client identity but never arguments, results or intent.
protocol True Record every request the server answers, not only the tools it ran.
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 call in the turn), mcp.method, mcp.is_error, mcp.error_type on failures, mcp.traceparent when the client sent one, 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

  • Protocol-level calls (prompts, resources, handler-less failures) are recorded after the reply is sent and never wait for delivery; their caller identity comes from the request's HTTP headers (bearer token, session id), not the OAuth subject.
  • 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

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

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

Metadata

Release files for raindrop-mcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for raindrop-mcp 0.1.0
File Size Uploaded Provenance Actions
raindrop_mcp-0.1.0.tar.gz 45.7 kB Without provenance Details for raindrop_mcp-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for raindrop-mcp 0.1.0
File Interpreter ABI Platform Actions
raindrop_mcp-0.1.0-py3-none-any.whl Python 3 none any Details for raindrop_mcp-0.1.0-py3-none-any.whl

Total release size: 73.9 kB

Release files / raindrop_mcp-0.1.0.tar.gz

Download URL raindrop_mcp-0.1.0.tar.gz
Size 45.7 kB
Tags Source
SHA-256 checksum
How to use checksums
67a2e673fa8d9446d5f9dba0270147af65f0d368af227b203699cc6729710b76
BLAKE2b-256 checksum
How to use checksums
8d4e183f17a1a5b727d87bd86a4b307aabc138958bb238bc41d7e265a210bacd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / raindrop_mcp-0.1.0-py3-none-any.whl

Download URL raindrop_mcp-0.1.0-py3-none-any.whl
Size 28.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
84b280644cb6107caf33698ab6a3ae45511d4f1cbc01ccc04ac0339d1e473e8b
BLAKE2b-256 checksum
How to use checksums
7881f7db18c7b152acd7daadc83869cc8913c08c1eb76ba4feed7bceba02d98e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for raindrop-mcp 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page