Skip to content

Commit f3d5ca0

Browse files
committed
Set gen_ai.operation.name for every tools/call; correct error docstring
Gate gen_ai.operation.name on the method, not the tool-name target, so a malformed tools/call still records execute_tool (cubic review). Tighten the docstring to describe the per-branch error.type/rpc.response.status_code behavior accurately (claude review).
1 parent 2c05e4b commit f3d5ca0

2 files changed

Lines changed: 11 additions & 9 deletions

File tree

src/mcp/server/_otel.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ class OpenTelemetryMiddleware(ServerMiddleware[Any]):
1919
`jsonrpc.request.id` is set only when `ctx.request_id` is present (notifications have none).
2020
2121
Tool and prompt operations additionally carry the GenAI semantic-convention attributes `gen_ai.tool.name` /
22-
`gen_ai.prompt.name`, and `gen_ai.operation.name` is set to `execute_tool` for `tools/call`. Failures set
23-
`error.type` and `rpc.response.status_code` to the JSON-RPC error code as a string (e.g. `-32602`), or
24-
`error.type` to `tool_error` for a `tools/call` result carrying `is_error`; a non-`MCPError` handler exception
25-
sets `error.type` to its type name.
22+
`gen_ai.prompt.name`, and `gen_ai.operation.name` is set to `execute_tool` for `tools/call`. A protocol or
23+
validation failure sets `error.type` and `rpc.response.status_code` to the JSON-RPC error code as a string
24+
(e.g. `-32602`); any other handler exception sets `error.type` to its type name (the wire code is not yet known
25+
here). A `tools/call` result carrying `is_error` sets `error.type` to `tool_error`.
2626
"""
2727

2828
async def __call__(self, ctx: ServerRequestContext[Any, Any], call_next: CallNext) -> HandlerResult:
@@ -36,12 +36,12 @@ async def __call__(self, ctx: ServerRequestContext[Any, Any], call_next: CallNex
3636
if ctx.request_id is not None:
3737
attributes["jsonrpc.request.id"] = str(ctx.request_id)
3838

39-
if target is not None:
40-
if ctx.method == "tools/call":
41-
attributes["gen_ai.operation.name"] = "execute_tool"
39+
if ctx.method == "tools/call":
40+
attributes["gen_ai.operation.name"] = "execute_tool"
41+
if target is not None:
4242
attributes["gen_ai.tool.name"] = target
43-
elif ctx.method == "prompts/get":
44-
attributes["gen_ai.prompt.name"] = target
43+
elif ctx.method == "prompts/get" and target is not None:
44+
attributes["gen_ai.prompt.name"] = target
4545

4646
with otel_span(
4747
name=f"{ctx.method}{f' {target}' if target else ''}",

tests/server/test_otel.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ async def test_validation_failure_sets_sanitized_status(server: SrvT, spans: Spa
261261
assert span.attributes is not None
262262
assert span.attributes["error.type"] == str(INVALID_PARAMS)
263263
assert span.attributes["rpc.response.status_code"] == str(INVALID_PARAMS)
264+
assert span.attributes["gen_ai.operation.name"] == "execute_tool"
265+
assert "gen_ai.tool.name" not in span.attributes
264266
assert not span.events
265267

266268

0 commit comments

Comments
 (0)