Skip to content

Add GenAI semconv attributes to native OTel spans#2854

Draft
etserend wants to merge 4 commits into
modelcontextprotocol:mainfrom
etserend:enrich-otel-spans-genai-semconv
Draft

Add GenAI semconv attributes to native OTel spans#2854
etserend wants to merge 4 commits into
modelcontextprotocol:mainfrom
etserend:enrich-otel-spans-genai-semconv

Conversation

@etserend

Copy link
Copy Markdown

The existing otel_middleware and client-side spans only emitted mcp.method.name and jsonrpc.request.id. This enriches them with attributes defined in the OTel GenAI MCP semantic conventions:

  • gen_ai.operation.name — operation type derived from the method (execute_tool for tools/call, list_tools for tools/list, read_resource for resources/read, get_prompt for prompts/get, etc.)
  • gen_ai.tool.name — tool or prompt name from params.name when present
  • mcp.resource.uri — resource URI from params.uri or params.ref.uri
  • rpc.system = "mcp" — standard RPC system attribute

Motivation and Context

The SDK already ships otel_middleware in src/mcp/server/runner.py producing SpanKind.SERVER spans with W3C trace context propagation. Aligning the native span attributes with the GenAI semconv spec means every MCP-based framework and every distribution gets conformant telemetry without independently re-implementing span plumbing.

How Has This Been Tested?

Adds three tests to tests/shared/test_otel.py:

  • test_client_and_server_spans — tools/call: asserts gen_ai.operation.name=execute_tool, gen_ai.tool.name, rpc.system, and trace context propagation (both spans share the same trace_id)
  • test_list_tools_spans — tools/list: asserts gen_ai.operation.name=list_tools and absence of gen_ai.tool.name (no specific tool targeted on a list call)
  • test_resource_read_spans — resources/read: asserts gen_ai.operation.name=read_resource and mcp.resource.uri

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@etserend

Copy link
Copy Markdown
Author

Here is example output

  tools/call
  ├── Span: 'MCP handle tools/call greet'  kind=SERVER
  │       trace_id: 0xbd62f33a2a8bca7fce981401e9e1fa37
  │       ├── gen_ai.operation.name: 'execute_tool'
  │       ├── gen_ai.tool.name: 'greet'
  │       ├── jsonrpc.request.id: '1'
  │       ├── mcp.method.name: 'tools/call'
  │       └── rpc.system: 'mcp'
  └── Span: 'MCP send tools/call greet'  kind=CLIENT
          trace_id: 0xbd62f33a2a8bca7fce981401e9e1fa37   ← same trace, context propagated
          ├── gen_ai.operation.name: 'execute_tool'
          ├── gen_ai.tool.name: 'greet'
          ├── jsonrpc.request.id: '1'
          ├── mcp.method.name: 'tools/call'
          └── rpc.system: 'mcp'

tools/list
  ├── Span: 'MCP handle tools/list'  kind=SERVER
  │       trace_id: 0xb34caa9e9dbe2748b4b3e37847bd4545
  │       ├── gen_ai.operation.name: 'list_tools'
  │       ├── jsonrpc.request.id: '1'
  │       ├── mcp.method.name: 'tools/list'
  │       └── rpc.system: 'mcp'
  └── Span: 'MCP send tools/list'  kind=CLIENT
          trace_id: 0xb34caa9e9dbe2748b4b3e37847bd4545
          ├── gen_ai.operation.name: 'list_tools'
          ├── jsonrpc.request.id: '1'
          ├── mcp.method.name: 'tools/list'
          └── rpc.system: 'mcp'
resources/read
  ├── Span: 'MCP handle resources/read'  kind=SERVER
  │       trace_id: 0x6f0d7ecaaea9cb4453f7a9e453f923b6
  │       ├── gen_ai.operation.name: 'read_resource'
  │       ├── jsonrpc.request.id: '1'
  │       ├── mcp.method.name: 'resources/read'
  │       ├── mcp.resource.uri: 'test://greeting'
  │       └── rpc.system: 'mcp'
  └── Span: 'MCP send resources/read'  kind=CLIENT
          trace_id: 0x6f0d7ecaaea9cb4453f7a9e453f923b6
          ├── gen_ai.operation.name: 'read_resource'
          ├── jsonrpc.request.id: '1'
          ├── mcp.method.name: 'resources/read'
          ├── mcp.resource.uri: 'test://greeting'
          └── rpc.system: 'mcp'

@etserend etserend force-pushed the enrich-otel-spans-genai-semconv branch from aa95d2b to 6930f92 Compare June 12, 2026 20:59
Comment thread src/mcp/shared/_otel.py Outdated

# Maps MCP JSON-RPC method names to GenAI semantic convention operation names.
# https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/mcp.md
_METHOD_TO_GEN_AI_OPERATION: dict[str, str] = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be mapping method name to operation name since it's redundant as we already have the mcp.method.name. We have have it for tool/call mcp methods but not sure about others.

But let me know if I missing the point.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants