mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-28 10:18:08 +02:00
OTEL: Fix attribute compliance with MCP semantic conventions (#3889)
* OTEL: Fix attribute compliance and improve telemetry helpers
Attribute compliance:
- Remove rpc.system/service/method (MCP is not traditional RPC)
- Add gen_ai.tool.name on tools/call spans
- Add gen_ai.prompt.name on prompts/get spans
- Fix session_id check (truthy -> is not None)
Telemetry helper improvements:
- Add is_recording() guards to skip work on non-recording spans
- Add error.type attribute with __qualname__ on error spans
- Use isinstance check for ToolError to set "tool_error" error type
- Include exception message in span status description
- Add tool_name/prompt_name params to server_span and client_span
Client call_tool enrichment:
- Reflect tool-level errors (result.isError) on client span status
so callers see ERROR even though the MCP protocol call succeeded
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove resource URI from span names to avoid high-cardinality
Per MCP semantic conventions, resource URIs SHOULD NOT be included in
span names by default since they can be unbounded (especially with
templates like users://{id}/profile). The URI remains available via
the mcp.resource.uri attribute.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing gen_ai/mcp attributes to proxy and delegate spans
- Proxy tool spans: add gen_ai.tool.name
- Proxy prompt spans: add gen_ai.prompt.name
- All delegate spans: add mcp.method.name
- Docs: remove rpc.* references, update span names and attributes table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Hoist ToolError imports to module level, add rpc.* migration note
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e4bb6666ab
commit
7184a4ca21
11 changed files with 216 additions and 96 deletions
|
|
@ -33,10 +33,12 @@ class TestToolTracing:
|
|||
assert span.attributes is not None
|
||||
# Standard MCP semantic conventions
|
||||
assert span.attributes["mcp.method.name"] == "tools/call"
|
||||
# Standard RPC semantic conventions
|
||||
assert span.attributes["rpc.system"] == "mcp"
|
||||
assert span.attributes["rpc.service"] == "test-server"
|
||||
assert span.attributes["rpc.method"] == "tools/call"
|
||||
# gen_ai semantic conventions
|
||||
assert span.attributes["gen_ai.tool.name"] == "greet"
|
||||
# RPC attributes must NOT be present
|
||||
assert "rpc.system" not in span.attributes
|
||||
assert "rpc.service" not in span.attributes
|
||||
assert "rpc.method" not in span.attributes
|
||||
# FastMCP-specific attributes
|
||||
assert span.attributes["fastmcp.server.name"] == "test-server"
|
||||
assert span.attributes["fastmcp.component.type"] == "tool"
|
||||
|
|
@ -60,6 +62,10 @@ class TestToolTracing:
|
|||
span = spans[0]
|
||||
assert span.name == "tools/call failing_tool"
|
||||
assert span.status.status_code == StatusCode.ERROR
|
||||
assert span.status.description is not None
|
||||
assert "Something went wrong" in span.status.description
|
||||
assert span.attributes is not None
|
||||
assert span.attributes["error.type"] == "tool_error"
|
||||
assert len(span.events) > 0 # Exception recorded
|
||||
|
||||
async def test_call_nonexistent_tool_sets_error(
|
||||
|
|
@ -76,6 +82,9 @@ class TestToolTracing:
|
|||
span = spans[0]
|
||||
assert span.name == "tools/call nonexistent"
|
||||
assert span.status.status_code == StatusCode.ERROR
|
||||
assert span.attributes is not None
|
||||
# NotFoundError is not a ToolError, so uses class name as fallback
|
||||
assert span.attributes["error.type"] == "NotFoundError"
|
||||
|
||||
|
||||
class TestResourceTracing:
|
||||
|
|
@ -95,16 +104,16 @@ class TestResourceTracing:
|
|||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
assert span.name == "resources/read config://app"
|
||||
assert span.name == "resources/read"
|
||||
assert span.kind == SpanKind.SERVER
|
||||
assert span.attributes is not None
|
||||
# Standard MCP semantic conventions
|
||||
assert span.attributes["mcp.method.name"] == "resources/read"
|
||||
assert span.attributes["mcp.resource.uri"] == "config://app"
|
||||
# Standard RPC semantic conventions
|
||||
assert span.attributes["rpc.system"] == "mcp"
|
||||
assert span.attributes["rpc.service"] == "test-server"
|
||||
assert span.attributes["rpc.method"] == "resources/read"
|
||||
# RPC attributes must NOT be present
|
||||
assert "rpc.system" not in span.attributes
|
||||
assert "rpc.service" not in span.attributes
|
||||
assert "rpc.method" not in span.attributes
|
||||
# FastMCP-specific attributes
|
||||
assert span.attributes["fastmcp.server.name"] == "test-server"
|
||||
assert span.attributes["fastmcp.component.type"] == "resource"
|
||||
|
|
@ -126,14 +135,15 @@ class TestResourceTracing:
|
|||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
assert span.name == "resources/read users://123/profile"
|
||||
assert span.name == "resources/read"
|
||||
assert span.kind == SpanKind.SERVER
|
||||
assert span.attributes is not None
|
||||
# Standard MCP semantic conventions
|
||||
assert span.attributes["mcp.method.name"] == "resources/read"
|
||||
assert span.attributes["mcp.resource.uri"] == "users://123/profile"
|
||||
# Standard RPC semantic conventions
|
||||
assert span.attributes["rpc.method"] == "resources/read"
|
||||
# RPC attributes must NOT be present
|
||||
assert "rpc.system" not in span.attributes
|
||||
assert "rpc.method" not in span.attributes
|
||||
# Template component type is set by get_span_attributes
|
||||
assert span.attributes["fastmcp.component.type"] == "resource_template"
|
||||
assert (
|
||||
|
|
@ -153,7 +163,7 @@ class TestResourceTracing:
|
|||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
assert span.name == "resources/read nonexistent://resource"
|
||||
assert span.name == "resources/read"
|
||||
assert span.status.status_code == StatusCode.ERROR
|
||||
|
||||
|
||||
|
|
@ -179,10 +189,12 @@ class TestPromptTracing:
|
|||
assert span.attributes is not None
|
||||
# Standard MCP semantic conventions
|
||||
assert span.attributes["mcp.method.name"] == "prompts/get"
|
||||
# Standard RPC semantic conventions
|
||||
assert span.attributes["rpc.system"] == "mcp"
|
||||
assert span.attributes["rpc.service"] == "test-server"
|
||||
assert span.attributes["rpc.method"] == "prompts/get"
|
||||
# gen_ai semantic conventions
|
||||
assert span.attributes["gen_ai.prompt.name"] == "greeting"
|
||||
# RPC attributes must NOT be present
|
||||
assert "rpc.system" not in span.attributes
|
||||
assert "rpc.service" not in span.attributes
|
||||
assert "rpc.method" not in span.attributes
|
||||
# FastMCP-specific attributes
|
||||
assert span.attributes["fastmcp.server.name"] == "test-server"
|
||||
assert span.attributes["fastmcp.component.type"] == "prompt"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue