You’ve built an MCP server. People are using it — maybe. Cursor is connecting, Claude Desktop is connecting, Windsurf is connecting. But you have no idea:
- How many connections happen per day
- Which tools get called most often
- Whether usage is growing or dying
- Where users drop off
This is the analytics blind spot for MCP server builders in 2026. And right now, almost nobody has solved it.
Here’s how to add meaningful analytics to your MCP server using Measure.events.
Why Standard Analytics Don’t Work for MCP Servers
MCP servers don’t serve web pages. They’re not HTTP servers in the traditional sense — they communicate via stdio or WebSocket with AI clients. You can’t drop a <script> tag into a browser because there’s no browser.
Standard web analytics (Google Analytics, Plausible, even Measure.events’ standard JS snippet) track page views and user sessions. That model doesn’t apply here.
What you need instead: server-side event tracking via the Measure.events API.
What to Track on an MCP Server
Before writing any code, decide what matters:
Connection events
- New client connected (track: client name, timestamp)
- Client disconnected
Tool calls
- Which tool was invoked (e.g.,
get_pageviews,query_referrers) - Response time
- Success vs. error
Usage patterns
- Daily active connections
- Most popular tools
- Peak usage hours
Growth signals
- New clients (first-time connections)
- Returning vs. one-off connections
Setting Up Measure.events API Tracking
Measure.events has a server-side event API that works for exactly this use case. No browser required.
First, grab your API key from lets.measure.events/api_keys. Then create a site in your dashboard for your MCP server.
Node.js / TypeScript MCP Server
If you’re using the TypeScript MCP SDK (@modelcontextprotocol/sdk), add tracking in your tool handlers:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const MEASURE_API_KEY = process.env.MEASURE_API_KEY;
const MEASURE_SITE_ID = process.env.MEASURE_SITE_ID;
async function trackEvent(event: string, properties?: Record<string, unknown>) {
if (!MEASURE_API_KEY || !MEASURE_SITE_ID) return;
try {
await fetch(`https://lets.measure.events/api/v1/sites/${MEASURE_SITE_ID}/events`, {
method: "POST",
headers: {
"Authorization": `Bearer ${MEASURE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
event,
properties: {
...properties,
timestamp: new Date().toISOString(),
},
}),
});
} catch {
// Never let analytics errors affect your server
}
}
const server = new Server(
{ name: "your-mcp-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Track when a client connects
server.onerror = (error) => {
trackEvent("server_error", { error: error.message });
};
// Wrap your tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
await trackEvent("tools_listed");
return { tools: YOUR_TOOLS };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const startTime = Date.now();
await trackEvent("tool_called", {
tool_name: request.params.name,
});
try {
const result = await handleTool(request.params.name, request.params.arguments);
await trackEvent("tool_success", {
tool_name: request.params.name,
duration_ms: Date.now() - startTime,
});
return result;
} catch (error) {
await trackEvent("tool_error", {
tool_name: request.params.name,
error: error instanceof Error ? error.message : "unknown",
duration_ms: Date.now() - startTime,
});
throw error;
}
});
Python MCP Server
If you’re using the Python MCP SDK:
import httpx
import asyncio
import os
from mcp.server import Server
from mcp.server.stdio import stdio_server
MEASURE_API_KEY = os.getenv("MEASURE_API_KEY")
MEASURE_SITE_ID = os.getenv("MEASURE_SITE_ID")
async def track_event(event: str, properties: dict = None):
if not MEASURE_API_KEY or not MEASURE_SITE_ID:
return
try:
async with httpx.AsyncClient() as client:
await client.post(
f"https://lets.measure.events/api/v1/sites/{MEASURE_SITE_ID}/events",
headers={"Authorization": f"Bearer {MEASURE_API_KEY}"},
json={"event": event, "properties": properties or {}},
)
except Exception:
pass # Never block on analytics
app = Server("your-mcp-server")
@app.call_tool()
async def call_tool(name: str, arguments: dict):
await track_event("tool_called", {"tool_name": name})
# ... your tool logic
Querying Your MCP Analytics with AI
Here’s where it gets interesting. Once your MCP server is tracking events via Measure.events, you can query your own server’s analytics using that same MCP server — if you expose Measure.events as a data source.
Or more practically: add the Measure.events MCP server to your Claude Desktop config and ask:
“Which tools are my users calling most often this week?”
“Did usage grow after I published on Product Hunt?”
“What’s the error rate on my get_pageviews tool?”
You get natural language answers backed by real data. No dashboard required.
What This Looks Like in Practice
After a week of tracking, you’ll see things like:
tools_listed→ 847 events (how often AI clients enumerate your tools)tool_called: get_pageviews→ 312 events (most popular tool)tool_called: query_referrers→ 89 eventstool_error→ 14 events (2.5% error rate — investigate this)
That’s a real product health signal. You can see if a new version introduced regressions. You can see which tools are driving adoption and which are dead weight. You can see if a mention in a newsletter drove a spike.
Environment Variables
Keep your Measure.events credentials in .env:
MEASURE_API_KEY=mk_your_key_here
MEASURE_SITE_ID=your_site_id
Add to your MCP server’s mcp.json or claude_desktop_config.json:
{
"mcpServers": {
"your-server": {
"command": "node",
"args": ["dist/index.js"],
"env": {
"MEASURE_API_KEY": "mk_your_key_here",
"MEASURE_SITE_ID": "your_site_id"
}
}
}
}
Fire and Forget, Always
One rule: analytics should never break your server. Always wrap tracking calls in try/catch. Use fire-and-forget patterns. Your MCP server’s primary job is to respond to tool calls — analytics is secondary.
The code examples above follow this rule. If Measure.events is down or your network is flaky, your MCP server keeps working.
Get Started
- Create a free account at lets.measure.events
- Add a new site for your MCP server
- Copy the event tracking snippet above
- Add your
MEASURE_API_KEYandMEASURE_SITE_IDenv vars - Deploy and watch the data come in
You built something people are using. You should know exactly how they’re using it.
Ready to see accurate analytics?
No cookies. No consent banners. No personal data. $29/mo with a 14-day free trial.
Start free trial →