{
  "markdown": "# mcp-telemetry\n\n[![CI](https://github.com/arnavranjan005/mcp-telemetry/actions/workflows/ci.yml/badge.svg)](https://github.com/arnavranjan005/mcp-telemetry/actions/workflows/ci.yml)\n[![npm (sdk)](https://img.shields.io/npm/v/mcp-telemetry-sdk?label=mcp-telemetry-sdk)](https://www.npmjs.com/package/mcp-telemetry-sdk)\n[![npm (server)](https://img.shields.io/npm/v/mcp-telemetry-server?label=mcp-telemetry-server)](https://www.npmjs.com/package/mcp-telemetry-server)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n**Socket.IO for AI agents.** Instrument any MCP server's tool calls with a few lines, and any MCP client watching (Claude Code, Cursor, or your own tooling) gets live, structured progress — no polling, no context-flooding tool calls.\n\n![mcp-telemetry demo](assets/demo.gif)\n\n```\nYour MCP server                    mcp-telemetry server         Agent\n─────────────────                  ────────────────────         ─────\nmcp-telemetry-sdk\n  job.stepStart('build')\n       │\n       │ local socket (queued, persistent connection)\n       ▼\n  collector receives event\n  store updates job state\n       │\n       │ MCP notifications/progress\n       ▼\n                                    telemetry_subscribe tool     Claude Code\n                                    pushes event to agent   →   sees inline\n                                                                 live status\n```\n\n## Why this exists\n\nMCP tool calls are synchronous: an agent calls a tool, waits, gets a result. For anything long-running, that leaves two bad options — block the whole call with no visibility, or have the agent poll a status tool in a loop (which floods the conversation with repeated tool calls and burns context for no new information).\n\nMCP does have one legitimate way for a server to push updates mid-call: `notifications/progress`, keyed to a `progressToken` on the in-flight request. But every MCP server author ends up re-implementing the same plumbing — extracting the token, wiring a timer, tailing output, cleaning up on completion. `mcp-telemetry` is that plumbing, factored out once, plus a companion server so a job started in _one_ session can be watched from a completely different one.\n\n## How it fits together\n\nTwo packages, one job each:\n\n| Package                                   | Who uses it        | What it does                                                                                                                                                                              |\n| ----------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [`mcp-telemetry-sdk`](packages/sdk)       | MCP server authors | Import it, call `job.start()` / `.stepDone()` / `.log()` from your tool handlers. Zero runtime dependencies — it's a socket writer with a persistent, queued connection and nothing else. |\n| [`mcp-telemetry-server`](packages/server) | Agent users        | An MCP server you register once. Exposes `telemetry_subscribe` (blocks and streams live progress for a job), plus `telemetry_jobs`/`telemetry_job_status` for point-in-time queries.      |\n\nThese two packages are architecturally independent — the SDK never calls any MCP tool, and the server never imports your tool's code. They only ever meet at a local socket, so a producer with a broken connection can't take down anything, and a collector that's overwhelmed can't block your tool call.\n\n## Quickstart\n\n### 1. Instrument your MCP server (`mcp-telemetry-sdk`)\n\n```bash\nnpm install mcp-telemetry-sdk\n```\n\n```js\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { MCPTelemetry } from \"mcp-telemetry-sdk\";\n\nconst server = new McpServer({ name: \"my-deploy-server\", version: \"1.0.0\" });\nconst telemetry = new MCPTelemetry(); // zero config — derives a socket path from cwd\n\nserver.tool(\"deploy\", { env: z.string() }, async ({ env }) => {\n  const job = telemetry.createJob({ task: `deploy ${env}` });\n\n  job.start();\n  job.stepStart(\"build\");\n  await runBuild();\n  job.stepDone(\"build\", { duration: 2100 });\n\n  job.stepStart(\"test\");\n  const passed = await runTests();\n  if (!passed) {\n    job.stepFailed(\"test\", \"3 tests failed\");\n    await job.done(1);\n    return { content: [{ type: \"text\", text: \"Deploy failed at test stage\" }] };\n  }\n  job.stepDone(\"test\");\n\n  await job.done(0);\n  return { content: [{ type: \"text\", text: \"Deployed successfully\" }] };\n});\n```\n\nThat's the entire integration. If nothing is listening on the socket, every call is a fast no-op — your server behaves identically with or without a collector running.\n\n### 2. Watch it from an agent (`mcp-telemetry-server`)\n\n```bash\nnpm install -g mcp-telemetry-server\n```\n\nRegister it as an MCP server, alongside your instrumented one:\n\n```json\n{\n  \"mcpServers\": {\n    \"deploy\": { \"command\": \"npx\", \"args\": [\"-y\", \"deploy-mcp\"] },\n    \"telemetry\": { \"command\": \"npx\", \"args\": [\"-y\", \"mcp-telemetry-server\"] }\n  }\n}\n```\n\nThen, in your agent session:\n\n```\nYou:   deploy to staging\nAgent: calls the deploy tool, then telemetry_subscribe with the returned job id\n\n  ▶ deploy staging\n  ↻ build\n  ✓ build (duration=2100)\n  ↻ test\n  ✓ test\n  ✓ job done (exit 0)\n\nAgent: \"Deployed to staging successfully.\"\n```\n\nNo polling, no separate terminal, no context-flooding tool calls — one `deploy` call plus one `telemetry_subscribe` call, regardless of how long the job runs.\n\n## API reference\n\n### `mcp-telemetry-sdk`\n\n**`new MCPTelemetry(opts?)`**\nCreates a telemetry client. `opts.socketPath` overrides the default (derived from `process.cwd()` via `getSocketPath()`). Owns one persistent, queued connection shared by every job it creates.\n\n**`telemetry.createJob({ id?, task })` → `JobHandle`**\nStarts tracking a job. `id` defaults to an auto-incrementing `job-N`.\n\n**`telemetry.disconnect()`**\nCloses the underlying connection. Call on server shutdown if you want a clean teardown instead of letting it idle.\n\n| `JobHandle` method          | Emits         | Notes                                                                                                                                                                                                                                           |\n| --------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `start()`                   | `job_start`   | Call once, at the beginning of the tool handler.                                                                                                                                                                                                |\n| `stepStart(name, meta?)`    | `step_start`  | `name` is any string — `'build'`, `'implement'`, whatever fits your domain.                                                                                                                                                                     |\n| `stepDone(name, meta?)`     | `step_done`   | `meta` is arbitrary key/value data (shown in `telemetry_subscribe`'s live output).                                                                                                                                                              |\n| `stepFailed(name, reason?)` | `step_failed` |                                                                                                                                                                                                                                                 |\n| `log(line, stream?)`        | `log`         | `stream` is `'stdout' \\| 'stderr'`, optional. Rapid log lines are coalesced by the server before being pushed live — see below.                                                                                                                 |\n| `cost(amount, meta?)`       | `cost`        | `amount` in USD.                                                                                                                                                                                                                                |\n| `done(exitCode?)`           | `job_done`    | **Async.** This is the terminal event — nothing else may be sent after it, so it actively retries delivery for up to 1.5s instead of relying on a future `send()` to recover from a transient connection failure. Safe to call without `await`. |\n\n`getSocketPath(root?)` is also exported, for advanced cases where you need to compute the same path a producer and a server will independently derive.\n\n### `mcp-telemetry-server`\n\nExposes three MCP tools:\n\n| Tool                                          | Behavior                                                                                                                                                                                                                             |\n| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `telemetry_subscribe({ jobId?, timeoutMs? })` | **Blocks** and streams live `notifications/progress` for the given job (or the next job to start, if `jobId` is omitted) until it finishes or `timeoutMs` elapses (default 5 min). This is the tool your agent calls to watch a job. |\n| `telemetry_jobs()`                            | Lists all jobs the server currently knows about, with status and cost.                                                                                                                                                               |\n| `telemetry_job_status({ jobId })`             | Full state of one job — every step, cost, and any failure reason.                                                                                                                                                                    |\n\n## Comparison\n\nThree genuinely different categories of approach exist near this space — none of them solve the same problem:\n\n|                                | **mcp-telemetry**                 | Async job runners                                 | Completion notifiers                         | OpenTelemetry MCP instrumentation                |\n| ------------------------------ | --------------------------------- | ------------------------------------------------- | -------------------------------------------- | ------------------------------------------------ |\n| Mechanism                      | Push (`notifications/progress`)   | Poll (call a status/tail tool yourself)           | Push, but only at completion (webhook/sound) | Traces/metrics to an observability backend       |\n| Live step-by-step progress     | Yes                               | No — you ask, it answers                          | No — only \"it's done\"                        | No — post-hoc analysis                           |\n| Watch from a different session | Yes                               | No — tied to the session that started it          | Partial (a webhook can fire anywhere)        | N/A — not agent-facing                           |\n| Who it's for                   | Any MCP server author + any agent | Anyone needing async shell execution specifically | Anyone wanting a completion ping             | Server operators monitoring their own deployment |\n\n**Relationship to [SEP-1686 (MCP Tasks)](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686):** the MCP spec's own answer to this problem — **Accepted** into the spec (not just proposed), giving requests a durable task handle (`taskId`) with `tasks/get` polling and a `progressToken` valid for the task's whole lifetime. It's the eventual \"correct\" fix, backed by real production cases (Amazon cites healthcare data pipelines, CI/CD wrapping, and multi-agent systems in the SEP itself). The catch: it's labeled `awaiting-sdk-change` — the standard is settled, but client/server SDKs haven't implemented it yet, so it isn't something you can rely on today. `mcp-telemetry` solves the same problem now, on the current stable protocol — a working bridge you can adopt today and retire once Tasks lands in the SDKs you depend on, not a competing standard.\n\n## Design notes worth knowing before you rely on this\n\n- **The producer→server connection is a persistent, queued socket**, not one connection per event. Events are flushed in order once connected; a burst that arrives before the connection finishes establishing is queued and delivered in order once it does.\n- **Delivery is best-effort, not guaranteed**, with one exception: `done()`. Every other event silently drops if the collector isn't reachable and nothing else triggers a retry — this is deliberate (telemetry should never be able to block or crash your actual tool call). `done()` is the one event that actively retries for a bounded window, since it's usually the last thing a job ever sends.\n- **`telemetry_subscribe` only shows live-forward events** — it doesn't replay history. If a job already finished before you subscribed, use `telemetry_job_status` instead.\n- **This is not a distributed job queue.** There's no persistence across a collector restart, no cross-machine delivery, and no retry policy beyond what's described above. If you need that, you want a real message queue — this is deliberately just enough to solve \"watch a local MCP tool call live,\" nothing more.\n\n## Development\n\n```bash\ngit clone https://github.com/arnavranjan005/mcp-telemetry.git\ncd mcp-telemetry\nnpm install\nnpm run build\nnpm test\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the full setup, monorepo layout, and PR expectations.\n\n## License\n\n[MIT](LICENSE) © [Arnav Ranjan](https://github.com/arnavranjan005)\n",
  "bytes": 14170,
  "sha": "5030ed8202518fd96a8cd1c2a2aa42d5adf81e8a19028f9c26756df95dacff78",
  "repo_slug": "arnavranjan005/mcp-telemetry",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_arnavranjan005_mcp_telemetry_s_b664386a/readme"
}