How do I trace the tool calls my own MCP server handles?
Rius traces your own MCP server when you initialize the Rius SDK or any OpenTelemetry SDK in it and export to Rius with an API key from the same workspace as your agent. Your agent’s call appears as an execute_tool span, and the server’s spans arrive from the other side. With the mcp package at 2.x, the server’s spans nest under that span in one waterfall. On mcp 1.x, and with the TypeScript client, they arrive as separate traces.
How it works
Two instrumented processes each send spans to Rius, and trace context ties them together.
- Agent side. The MCP integration turns on by itself when the MCP package is importable. Each tool call becomes a span named
execute_tool <name>, withgen_ai.tool.name, the arguments ininput.valueand the result inoutput.value. The span also carriesmcp.method.nameset totools/call. - Server side. Your server must be instrumented, either with the Rius SDK or with any OpenTelemetry SDK. It must export its traces to Rius using an API key for the same workspace as the agent.
- Joining. With the
mcppackage at 2.x, trace context propagates through the tool call. The server’s spans appear under the agent’sexecute_toolspan, so you read both sides of one call in one waterfall. - Without propagation. On
mcp1.x there is no context propagation. The server’s spans still arrive, but as their own traces. The TypeScript integration does not propagate trace context either, so server spans arrive as separate traces there too. Both sides stay queryable and their timestamps line up, but there is no single waterfall.
Failures are recorded with ERROR status. That covers a call that raises and a result the server flags as an error, and a flagged result sets error.type to tool_error on the agent’s span.
Set it up
-
Instrument the agent. There is nothing to install, because the MCP instrumentation ships inside the SDK. Call
init()before you make tool calls, then use your MCP client as before. See Integrations for the other libraries that turn on the same way.import rius rius.init(api_key="ri_...", service_name="my-agent") # use mcp.ClientSession exactly as before -
Instrument the server. In the process that runs your MCP server, call
init()with an API key for the same workspace. Give the server its own service name so it is easy to tell apart from the agent. The service name below is an example, so use your own.import rius rius.init(api_key="ri_...", service_name="my-mcp-server") -
Trace your handlers.
init()alone records nothing for incoming tool calls, so wrap each tool handler with@rius.observe(kind=SpanKind.TOOL). Those spans nest under the agent’sexecute_toolspan. In TypeScript, wrap it withobserveand give the function a name, because an anonymous function traces as anonymous.from rius import SpanKind @rius.observe(kind=SpanKind.TOOL) def handle(query: str) -> str: ...Here
handleand its body stand for your own tool code. -
Check your versions. For one joined waterfall, run the
mcppackage at 2.x on the Python side. If you use an OpenTelemetry SDK on the server instead of the Rius SDK, point its exporter at Rius with the same key. -
Open the trace. Make a tool call from the agent, then open the trace in Rius. The agent’s
execute_toolspan should have the server’s spans beneath it. If you see two traces instead, check the key’s workspace and themcpversion.
In a CommonJS TypeScript agent, require the MCP SDK before init(). A copy of the Client class that is first required later stays untraced.
What you get
- One waterfall per tool call on
mcp2.x, covering the agent’s request and the work your server did to answer it. - Tool name, arguments and result on every call, plus the MCP protocol version when the session exposes it. In TypeScript, since 1.2.0, that covers stdio, SSE and Streamable HTTP for a client that connects after
init(). - Failures that stand out: a raised error and an error-flagged result both show as ERROR.
- The calling agent’s name on each tool span, so you can see which agent made the call.
- Argument and result values are content attributes, so your privacy controls apply to them like everything else.
A tool call that comes back asking for more input records no output, because an input request carries conversation content. Its span still carries mcp.method.name, so the round counts as an MCP tool call. The final round of the same call records its output as usual.
When something else fits
- If your server runs on
mcp1.x, or your client is TypeScript, you still get both sides in Rius, but as separate traces. Upgrade the Python side tomcp2.x for one waterfall. - If you want AI clients such as Claude Code to query your telemetry, that is the Rius MCP server, which runs in the opposite direction and is separate from this integration.
- If your server is not instrumented or exports to a different workspace, you see only the agent’s side of each call.