What problem do you want to solve?
opentelemetry-util-genai provides reusable invocation types (ToolInvocation, AgentInvocation, WorkflowInvocation, etc.) and a TelemetryHandler with lifecycle methods and context managers for each. There is currently no equivalent for Model Context Protocol (MCP) operations.
The GenAI semconv MCP spec (status: Development, merged January 2026) defines two canonical span types — mcp.client (SpanKind: CLIENT) and mcp.server (SpanKind: SERVER) — along with four metrics histograms. Without MCP types in opentelemetry-util-genai, each MCP SDK instrumentation must independently re-implement the same span and metric plumbing, risking drift from the semconv.
This was observed in practice: issue #4197 reported MCP list-tools spans showing as "unknown" in opentelemetry-instrumentation-openai-agents-v2, and PR #4629 added a per-instrumentation workaround rather than building on a shared foundation.
Describe the solution you'd like
Add MCPClientInvocation and MCPServerInvocation types to opentelemetry-util-genai, with lifecycle methods and context managers on TelemetryHandler, and four MCP-specific metric histograms.
MCPClientInvocation — covers all MCP operations initiated by the client side (mcp.client span type, SpanKind.CLIENT):
invocation = handler.start_mcp_client(
mcp_method_name="tools/call",
tool_name="get_weather",
server_address="mcp.example.com",
server_port=443,
)
invocation.mcp_session_id = "..."
invocation.mcp_protocol_version = "2025-06-18"
invocation.jsonrpc_request_id = "1"
invocation.tool_call_arguments = {...} # opt-in
invocation.stop()
# context manager form
with handler.mcp_client(mcp_method_name="resources/read", mcp_resource_uri="file:///report.pdf") as inv:
inv.mcp_session_id = "..."
MCPServerInvocation — covers all MCP operations processed on the server side (mcp.server span type, SpanKind.SERVER):
with handler.mcp_server(mcp_method_name="tools/call", tool_name="get_weather") as inv:
inv.client_address = "192.0.2.1"
inv.tool_call_result = {...} # opt-in
Tool call operations (mcp.method.name = "tools/call") are handled by the same MCPClientInvocation / MCPServerInvocation types — the spec explicitly states that mcp.client/mcp.server spans with tools/call are compatible with GenAI execute_tool spans. The invocation type automatically sets gen_ai.operation.name = execute_tool when mcp_method_name == "tools/call", and SHOULD NOT set it for any other method.
Span name follows {mcp.method.name} {target} where target is gen_ai.tool.name or gen_ai.prompt.name when applicable; plain {mcp.method.name} otherwise.
Attributes (from spans.yaml and common.yaml):
| Attribute |
Requirement level |
Client |
Server |
mcp.method.name |
Required |
✅ |
✅ |
gen_ai.tool.name |
Cond. req. when tool op |
✅ |
✅ |
gen_ai.operation.name |
Recommended (execute_tool for tools/call only) |
✅ |
✅ |
gen_ai.prompt.name |
Cond. req. when prompt op |
✅ |
✅ |
error.type |
Cond. req. on error |
✅ |
✅ |
mcp.protocol.version |
Recommended |
✅ |
✅ |
rpc.response.status_code |
Cond. req. |
✅ |
✅ |
mcp.session.id |
Recommended |
✅ |
✅ |
mcp.resource.uri |
Cond. req. for resource ops |
✅ |
✅ |
jsonrpc.request.id |
Cond. req. |
✅ |
✅ |
network.transport / network.protocol.* |
Recommended |
✅ |
✅ |
jsonrpc.protocol.version |
Recommended (when ≠ 2.0) |
✅ |
✅ |
server.address / server.port |
Recommended |
✅ |
— |
client.address / client.port |
Recommended |
— |
✅ |
gen_ai.tool.call.arguments |
Opt-in |
✅ |
✅ |
gen_ai.tool.call.result |
Opt-in |
✅ |
✅ |
TelemetryHandler additions:
def start_mcp_client(self, *, mcp_method_name: str, tool_name=None, prompt_name=None,
server_address=None, server_port=None) -> MCPClientInvocation: ...
def mcp_client(self, *, mcp_method_name: str, ...) -> AbstractContextManager[MCPClientInvocation]: ...
def start_mcp_server(self, *, mcp_method_name: str, tool_name=None, prompt_name=None,
client_address=None, client_port=None) -> MCPServerInvocation: ...
def mcp_server(self, *, mcp_method_name: str, ...) -> AbstractContextManager[MCPServerInvocation]: ...
Metrics (from metrics.yaml):
| Metric |
Unit |
Notes |
mcp.client.operation.duration |
s |
All client-side MCP operations |
mcp.server.operation.duration |
s |
All server-side MCP operations |
mcp.client.session.duration |
s |
Recorded on initialize completion |
mcp.server.session.duration |
s |
Recorded on initialize completion |
InvocationMetricsRecorder extended with four new histograms in instruments.py. mcp.resource.uri included as opt-in metric dimension per spec.
Exports: MCPClientInvocation and MCPServerInvocation added to invocation.py __all__.
Questions for maintainers
-
Semconv constants: mcp.* and jsonrpc.* attributes are not yet in the opentelemetry-semantic-conventions Python package. The established pattern (see _GEN_AI_AGENT_VERSION in _agent_invocation.py) is module-level string literals with a # TODO: Migrate once in semconv package comment. Is that acceptable here?
-
Session duration: mcp.client/server.session.duration should be recorded when the session ends. Should the invocation type detect mcp_method_name == "initialize" and record session duration automatically on stop(), or should this be caller-driven via metric_attributes?
-
mcp.resource.uri on metrics: Spec marks it as opt-in for operation duration. Should this be a flag on the invocation (e.g. include_resource_uri_in_metrics: bool = False) or left to the caller via metric_attributes?
References
Would you like to implement this?
Yes — we plan to open a PR once we have alignment on the questions above.
What problem do you want to solve?
opentelemetry-util-genaiprovides reusable invocation types (ToolInvocation,AgentInvocation,WorkflowInvocation, etc.) and aTelemetryHandlerwith lifecycle methods and context managers for each. There is currently no equivalent for Model Context Protocol (MCP) operations.The GenAI semconv MCP spec (status: Development, merged January 2026) defines two canonical span types —
mcp.client(SpanKind: CLIENT) andmcp.server(SpanKind: SERVER) — along with four metrics histograms. Without MCP types inopentelemetry-util-genai, each MCP SDK instrumentation must independently re-implement the same span and metric plumbing, risking drift from the semconv.This was observed in practice: issue #4197 reported MCP list-tools spans showing as
"unknown"inopentelemetry-instrumentation-openai-agents-v2, and PR #4629 added a per-instrumentation workaround rather than building on a shared foundation.Describe the solution you'd like
Add
MCPClientInvocationandMCPServerInvocationtypes toopentelemetry-util-genai, with lifecycle methods and context managers onTelemetryHandler, and four MCP-specific metric histograms.MCPClientInvocation— covers all MCP operations initiated by the client side (mcp.clientspan type,SpanKind.CLIENT):MCPServerInvocation— covers all MCP operations processed on the server side (mcp.serverspan type,SpanKind.SERVER):Tool call operations (
mcp.method.name = "tools/call") are handled by the sameMCPClientInvocation/MCPServerInvocationtypes — the spec explicitly states thatmcp.client/mcp.serverspans withtools/callare compatible with GenAIexecute_toolspans. The invocation type automatically setsgen_ai.operation.name = execute_toolwhenmcp_method_name == "tools/call", and SHOULD NOT set it for any other method.Span name follows
{mcp.method.name} {target}where target isgen_ai.tool.nameorgen_ai.prompt.namewhen applicable; plain{mcp.method.name}otherwise.Attributes (from
spans.yamlandcommon.yaml):mcp.method.namegen_ai.tool.namegen_ai.operation.nameexecute_toolfortools/callonly)gen_ai.prompt.nameerror.typemcp.protocol.versionrpc.response.status_codemcp.session.idmcp.resource.urijsonrpc.request.idnetwork.transport/network.protocol.*jsonrpc.protocol.version2.0)server.address/server.portclient.address/client.portgen_ai.tool.call.argumentsgen_ai.tool.call.resultTelemetryHandleradditions:Metrics (from
metrics.yaml):mcp.client.operation.durationsmcp.server.operation.durationsmcp.client.session.durationsinitializecompletionmcp.server.session.durationsinitializecompletionInvocationMetricsRecorderextended with four new histograms ininstruments.py.mcp.resource.uriincluded as opt-in metric dimension per spec.Exports:
MCPClientInvocationandMCPServerInvocationadded toinvocation.py__all__.Questions for maintainers
Semconv constants:
mcp.*andjsonrpc.*attributes are not yet in theopentelemetry-semantic-conventionsPython package. The established pattern (see_GEN_AI_AGENT_VERSIONin_agent_invocation.py) is module-level string literals with a# TODO: Migrate once in semconv packagecomment. Is that acceptable here?Session duration:
mcp.client/server.session.durationshould be recorded when the session ends. Should the invocation type detectmcp_method_name == "initialize"and record session duration automatically onstop(), or should this be caller-driven viametric_attributes?mcp.resource.urion metrics: Spec marks it as opt-in for operation duration. Should this be a flag on the invocation (e.g.include_resource_uri_in_metrics: bool = False) or left to the caller viametric_attributes?References
model/mcp/spans.yamlmodel/mcp/common.yamlmodel/mcp/metrics.yamlmcp.method.nameenum — 27 values):model/mcp/registry.yamlWould you like to implement this?
Yes — we plan to open a PR once we have alignment on the questions above.