Skip to content
Maple Docs
Open app

MCP Servers

See the tools agents call on your MCP server, the clients calling them, why calls fail, and each session and user, from the OpenTelemetry spans your server already emits.

MCP servers lists every service that answers MCP requests. For each one it shows the tools agents call, the clients calling them, why calls fail, and the sessions and users behind the calls. Maple reads the spans your server emits under the OpenTelemetry MCP conventions. There is no separate SDK and no second call to make.

What Maple reads

Every request is a SERVER span carrying mcp.method.name.

AttributeWhat Maple does with it
mcp.method.nameMarks the span as an MCP request: tools/call, initialize, tools/list…
gen_ai.tool.nameThe tool a tools/call called.
error.typeSet when a call failed: tool_error for an isError result, else the JSON-RPC code.
error.messageWhy it failed, to group failures by cause. The span’s status message also works.
user_agent.originalThe client: Claude Code, Cursor, Codex, curl…
user.id (or enduser.id)The end user: a column, a filter, user counts, and sessions.
user.name, user.emailHow the user is shown; the id when neither is set.
mcp.session.idGroups an older, stateful server’s requests into its own sessions.

Identify the user

Set user.id on each MCP request’s span, in the handler that already knows who is calling: the one that checks the API key or the OAuth token. It is the standard OpenTelemetry attribute, so nothing Maple-specific goes into your server.

span.setAttributes({
	"user.id": user.id, // required to count and group by user
	"user.name": user.name, // optional, shown instead of the id
	"user.email": user.email, // optional, shown when there is no name
})

With the Effect SDK the same keys go through Effect.annotateCurrentSpan:

yield * Effect.annotateCurrentSpan({ "user.id": user.id, "user.name": user.name })

Send an id you already use for the user, never a secret: it is stored with the span and shown on the page.

Sessions

MCP is stateless from spec version 2026-07-28: there is no initialize handshake and no Mcp-Session-Id, and every request is authenticated on its own. Maple builds sessions from user.id instead. It joins one user’s calls from one client and starts a new session after 30 minutes without a request, so the attribute from Identify the user is all a session needs.

A server on an earlier spec version that issues an Mcp-Session-Id can record it as mcp.session.id on every server span. Those requests group into the session the server issued, and both kinds list side by side. Calls with neither a user nor a session id belong to no session.

Each session lists its requests in order, the tools it called, and the trace behind every request. When the client sends its trace context in params._meta, the session also links to the Agent Session that made each call.