SDK
Install the MCP analytics SDK
Add AgentDiscoverability usage analytics to your own MCP server: one install, one observe() call, and a way to confirm events are arriving. It takes about five minutes.
What the SDK does
The SDK sits inside your own MCP server process and watches the MCP requests it already handles — initialize, tools/list and tools/call — then batches them to us, so you can see which tools agents call, how long they take, which errors they hit and which AI clients are calling.
It is observe-only. It adds no tool, changes no tool name, description or schema, adds no parameter to a call and adds no text to a result. It never blocks a tool handler, and a slow or dead collector cannot change what your tools return or how fast they return it.
Before you start
An SDK key. Mint one on the MCP page in the app (“Connect your MCP server”). It looks like adok_…, it is shown once at creation, and an Organization may hold three live keys at a time. One key per server is the intended shape.
A supported runtime. What has actually been run, not what the version ranges allow:
| Language | Requirement | Verified against |
|---|---|---|
| TypeScript | Node ≥ 22, @modelcontextprotocol/sdk ≥ 1.11 < 2, ESM | Node 22.0.0, 22.23.1, 24.21.0 × MCP SDK 1.11.0, 1.29.0, 1.30.0 |
| Python | Python ≥ 3.10, mcp ≥ 1.10 < 3 (FastMCP on 1.x, MCPServer on 2.x, or a low-level Server), optionally community fastmcp 3.x or 4.x | Python 3.10–3.14 × mcp 1.10.0–1.30.0 and 2.0.0–2.2.0; fastmcp 3.0.2–4.0.3 |
Not supported: @modelcontextprotocol/server (MCP SDK v2), CommonJS require() of the TypeScript package, Bun, Deno and Cloudflare Workers; community fastmcp 2.x.
Install
Read the key from an environment variable — MCP_ANALYTICS_SDK_KEY is the name the app’s own snippets use. Never commit it.
TypeScript
npm install @agentdiscoverability/mcp-analytics
export MCP_ANALYTICS_SDK_KEY=adok_…import { observe } from "@agentdiscoverability/mcp-analytics";
observe(server, { sdkKey: process.env.MCP_ANALYTICS_SDK_KEY! });server is your low-level Server or high-level McpServer. Call observe() once, after your tools are registered and before the server starts serving — tools registered later are picked up too. Calling it twice on the same server is a no-op that returns the first handle.
Python
pip install agentdiscoverability-mcp-analytics
export MCP_ANALYTICS_SDK_KEY=adok_…import os
from mcp_analytics import observe
observe(server, sdk_key=os.environ["MCP_ANALYTICS_SDK_KEY"])server is a FastMCP (mcp 1.x or community fastmcp 3.x/4.x), an MCPServer (mcp 2.x) or a low-level Server; observe() works out which at runtime. Call it once, after your tools are registered and before server.run(...).
That is the whole install. observe() never throws or raises: a missing key or an unrecognised server is logged and the server comes back uninstrumented (enabled: false / handle.enabled is False).
Where events go
Batches are POSTed to /v1/events on one host, with Authorization: Bearer <your key>. Redirects are refused and there is no other destination — no third-party analytics, no exporters, no files written.
export MCP_ANALYTICS_COLLECTOR_URL=https://collector.agentdiscoverability.comVerify it works
- 1
Start your server and connect a client to it.
initializeandtools/listare observed too, so a connection produces events before any tool is called. - 2
Check the SDK locally.
It prints nothing by default — on a stdio server stdout is the protocol channel — so ask it:
TypeScript — check the handle const { enabled, client, health } = observe(server, { /* … */ }); // enabled === true client.stats(); // { observed, enqueued, sent, dropped, … } health.status().lastOutcome; // "delivered" once a report has landedPython — check the handle handle = observe(server, ...) handle.enabled # True handle.client.stats() # observed, enqueued, sent, dropped, … handle.health.status() # lastOutcome: "delivered"observedrising withsent, anddroppedat zero, means capture and delivery are both working. - 3
Watch the MCP page in the app.
The live badge polls every 10 seconds and flips from “Waiting for your first event…” to “Receiving · N events”. The default flush interval is one second, so this normally happens within about a minute of the first request.
The checklist above it moves in two steps, from two different facts: Install the SDK goes green as soon as the collector authenticates your key — your server has reached us, before any tool call — and First event received goes green when events are actually stored.
- 4
Open MCP events from that page to see the individual calls.
What is collected
One record per initialize, tools/list and tools/call:
- The tool name, the shape of its arguments (key names and JSON types — never the values, unless you allowlist specific keys), how long it took, and whether it errored with the error detail.
- The result the tool returned, today, after sanitization, redaction and truncation. A switch to shape-only capture is decided in principle and implemented in no SDK yet.
- Which AI client family called — Claude, Claude Code, ChatGPT, Cursor or unknown — with name and version where the client supplies them, plus your server’s and the SDK’s own versions.
- An end user only if your server supplies one. There is no
identify()unless you write one; when you do, the id is hashed on our side before storage and the raw value is never written to a row, an archive or a log. The display name and attributes you attach are stored as sent, so treat them as personal data. - Operational health reports about the SDK process itself: versions, uptime and queue counters, with a random id regenerated at every restart.
Never collected: your users’ conversations or files, their account with the AI host (actively refused), IP addresses, request headers, credentials, or argument values outside your allowlist. Records are kept for 90 days.
Our Privacy Policy covers how we handle personal information generally.
Troubleshooting
Nothing arrives. In order: is enabled true (a missing key or an unsupported server returns the server uninstrumented)? Is the collector URL set — an unconfigured install points at the .invalid placeholder and sends nowhere? Did observe() run before the server started serving? Is anything actually calling the server? Then read stats(): observed at zero means capture never ran, dropped with a reason tells you where delivery lost them. A short-lived stdio process killed by a signal gets no drain, so only what the periodic flush already sent survives.
401. The key is unknown or revoked — they are one answer on purpose. Keys are shown once, so a truncated paste fails the same way. Mint a fresh one on the MCP page and redeploy; a revocation takes up to 60 seconds to take effect.
503. Ingest is switched off for everyone. Nothing to fix on your side.
429 or 413. Per-key and per-Organization rate budgets, a batch over 1 MiB or over 500 events. The SDK retries 429s with backoff; 413 means lower flushBatchSize / flush_batch_size.
Behind a proxy. Delivery goes straight to the collector host over HTTPS on 443 — the Python SDK deliberately ignores HTTP_PROXY/HTTPS_PROXY so no batch can be diverted, and both SDKs refuse redirects. Allow direct egress to the collector host, or nothing will be delivered; a TLS-intercepting or redirecting proxy in the path will break it.
Still stuck. Email [email protected] with your key prefix (adok_ plus six characters, shown on the MCP page) — never the key itself — and the output of stats().
If you are a coding agent
Do exactly this, then stop.
- Find the MCP server in this repository — the file that constructs a
Server,McpServer,FastMCPorMCPServerand registers tools. - Add the dependency for that language to the project’s manifest.
- Add the
observe(...)call from Install once, after the tools are registered and before the server starts serving. Read the key fromMCP_ANALYTICS_SDK_KEY. If the operator gave you a collector URL, pass it as shown in Where events go. - Put the key in the project’s ignored local env file and in the deploy configuration. Do not commit the key.
- Change nothing else. No tool name, description, schema, parameter or return value may differ before and after your change.
- Start the server, connect a client, call one tool, and confirm
enabledis true andstats()showsobservedandsentabove zero. - Report to the operator: the file and line you changed, where the key is read from, and the
stats()output. Tell them to check the MCP page for “Receiving”.
Done looks like: one dependency added, one observe() call added, no key in git, no change to any tool’s contract, and events visible in the app.