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:

LanguageRequirementVerified against
TypeScriptNode ≥ 22, @modelcontextprotocol/sdk ≥ 1.11 < 2, ESMNode 22.0.0, 22.23.1, 24.21.0 × MCP SDK 1.11.0, 1.29.0, 1.30.0
PythonPython ≥ 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.xPython 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

TypeScript — install
npm install @agentdiscoverability/mcp-analytics
export MCP_ANALYTICS_SDK_KEY=adok_…
TypeScript — observe()
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

Python — install
pip install agentdiscoverability-mcp-analytics
export MCP_ANALYTICS_SDK_KEY=adok_…
Python — observe()
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.

Collector host
export MCP_ANALYTICS_COLLECTOR_URL=https://collector.agentdiscoverability.com

Verify it works

  1. 1

    Start your server and connect a client to it.

    initialize and tools/list are observed too, so a connection produces events before any tool is called.

  2. 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 landed
    Python — check the handle
    handle = observe(server, ...)
    handle.enabled            # True
    handle.client.stats()     # observed, enqueued, sent, dropped, …
    handle.health.status()    # lastOutcome: "delivered"

    observed rising with sent, and dropped at zero, means capture and delivery are both working.

  3. 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. 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.

  1. Find the MCP server in this repository — the file that constructs a Server, McpServer, FastMCP or MCPServer and registers tools.
  2. Add the dependency for that language to the project’s manifest.
  3. Add the observe(...) call from Install once, after the tools are registered and before the server starts serving. Read the key from MCP_ANALYTICS_SDK_KEY. If the operator gave you a collector URL, pass it as shown in Where events go.
  4. Put the key in the project’s ignored local env file and in the deploy configuration. Do not commit the key.
  5. Change nothing else. No tool name, description, schema, parameter or return value may differ before and after your change.
  6. Start the server, connect a client, call one tool, and confirm enabled is true and stats() shows observed and sent above zero.
  7. 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.