feat: first-class MCP tool-call instrumentation#24
Merged
Conversation
Wrap mcp.ClientSession.call_tool so every MCP tool invocation an agent makes becomes a TOOL-kind span: "execute_tool <name>" with gen_ai.tool.name, arguments/result as input.value/output.value (covered by the existing mask / capture_content controls), latency, and error status — both raised exceptions and isError tool results. Generic SDKs cover MCP unevenly (the OpenInference MCP package only propagates context, it creates no spans), so this instrumentor is ours. Registered in the instrumentation registry under "mcp": auto-enabled on global init when the mcp package is installed, explicit opt-in via instruments=["mcp"], re-binding on shutdown/re-init, and the extras smoke test all apply unchanged. The top-level `import mcp` makes an environment without the package look "not installed" to the registry. Tests run against a real FastMCP server over the in-memory transport: span shape, nesting under the current span, error results, content stripping, and uninstrument restoring the original method.
7019b30 to
361bf63
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Last Phase 4 ticket: first-class spans for MCP tool calls — the differentiator piece. Generic SDKs handle MCP unevenly (OpenInference's MCP package only propagates context across the transport; it creates no spans), so this instrumentor is ours, not bundled.
Every
ClientSession.call_tool()an agent makes becomes:Design
MCPInstrumentorduck-types the instrumentor interface and registers under"mcp", so it inherits everything from the auto-instrumentation machinery unchanged — auto-enable on global init whenmcpis installed,instruments=["mcp"]opt-in for scoped clients, re-binding after shutdown/re-init, never-steal, and the extras-smoke CI job (now 6 entries).import mcpmakes an env without the package look "not installed" to the registry — same semantics as a missing third-party instrumentor. A[mcp]extra exists so the registry's install hint works.input.value/output.value, which the masking exporter already treats as content —maskandcapture_content=Falsecover tool I/O with zero extra code (test included).Tests
6 tests against a real FastMCP server over MCP's in-memory transport (no fakes, no network): span shape + attributes, nesting under the caller's current span,
isErrorresults marking ERROR status,capture_content=Falsestripping tool I/O while keepinggen_ai.tool.name, uninstrument restoring the original method, and registry membership. The all-extras smoke job validates the entry like any other.117 tests pass; ruff/format/mypy clean.