mcplytics — Usage and Health for a Remote MCP Server
Open-source wrapper for a remote MCP server: which tools get called, which apps call them, how each call ended and how long it took. No SDK, no backend, and it never stores what people asked.
Overview
In plain terms: you built an MCP server and put it online. AI apps now call it: Claude, Cursor, ChatGPT, scripts. mcplytics answers the plain questions about that traffic:
- Which tools are used, and which never are?
- Which apps call, and how often?
- How many calls fail, and do they fail because a tool broke or because the request was wrong?
- How fast is it? Median and slowest 5%.
It wraps the one function that answers your server's HTTP requests. You don't change your tools, add an SDK or run a backend. It never stores what people asked: no arguments, no answers, no IP addresses.
What the report looks like
mcplytics: 21 calls, 2026-10-20 19:05 to 2026-10-20 19:05 UTC
failed 4.8% · p50 1 ms · p95 4 ms
Tools
name calls failed p50 p95
search 12 0% 2 ms 11 ms
get_item 6 0% 1 ms 2 ms
delete_everything 1 100% 0 ms 0 ms
list_categories 1 0% 2 ms 2 ms
Apps calling (client name)
name calls failed p50 p95
claude-code 9 11% 1 ms 11 ms
cursor 7 0% 1 ms 4 ms
openai-mcp 4 0% 1 ms 4 ms
python-httpx 1 0% 2 ms 2 msA test run. delete_everything doesn't exist; someone tried anyway.
Install
npm install mcplyticsOn Cloudflare Workers, wrap your handler and bind an Analytics Engine dataset named MCPLYTICS:
import { mcplytics, analyticsEngine } from 'mcplytics';
const handler = async (request, env, ctx) => { /* your MCP endpoint */ };
export default { fetch: mcplytics(handler, { sink: analyticsEngine() }) };On Deno, Bun or Node, write events to a local file instead:
import { mcplytics } from 'mcplytics';
import { jsonl } from 'mcplytics/node';
Bun.serve({ fetch: mcplytics(handler, { sink: jsonl('mcplytics.jsonl') }) });Then read the report:
npx mcplytics report mcplytics.jsonl --days 7How a call can end
A tool that fails still answers with HTTP 200, so status codes alone can't tell you much. mcplytics reads the answer:
| Outcome | Meaning |
|---|---|
ok |
a result |
tool_error |
the tool ran and failed (isError: true) |
protocol_error |
a JSON-RPC error: unknown tool, bad arguments |
http_error |
refused before any answer, e.g. 401 sign-in required |
no_response |
a stream that closed before answering |
crash |
your handler threw |
The full list is in the README.
What it can't tell you
- App names are what apps send. A client names itself in the request, and any name can be claimed. Good enough for counting traffic, never for deciding who gets in.
- Remote servers only. A local server talks to its app over stdio, with no HTTP request to wrap.
- No intent. It doesn't ask the model why it called a tool. That would mean adding a field to every tool.
Tech Stack
- Language: JavaScript, zero dependencies, web APIs only
- Runs on: Cloudflare Workers and Pages, Deno, Bun, Node
- Works with: any Streamable HTTP MCP server: the official SDK, another framework, or by hand
- Storage: Cloudflare Analytics Engine, a JSONL file, or your own callback
← All work