Skip to content

Commit bd28ccc

Browse files
committed
adde semconv file, client span name changed, new folder name, mcpinstrumentor as an entry point
1 parent 43526f6 commit bd28ccc

File tree

11 files changed

+234
-127
lines changed

11 files changed

+234
-127
lines changed

aws-opentelemetry-distro/pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ classifiers = [
2121
"Programming Language :: Python :: 3.9",
2222
"Programming Language :: Python :: 3.10",
2323
"Programming Language :: Python :: 3.11",
24-
"Programming Language :: Python :: 3.12",
25-
"Programming Language :: Python :: 3.13",
2624
]
2725

2826
dependencies = [
@@ -89,12 +87,16 @@ dependencies = [
8987
# If a new patch is added into the list, it must also be added into tox.ini, dev-requirements.txt and _instrumentation_patch
9088
patch = [
9189
"botocore ~= 1.0",
90+
"mcp >= 1.1.0"
9291
]
9392
test = []
9493

9594
[project.entry-points.opentelemetry_configurator]
9695
aws_configurator = "amazon.opentelemetry.distro.aws_opentelemetry_configurator:AwsOpenTelemetryConfigurator"
9796

97+
[project.entry-points.opentelemetry_instrumentor]
98+
mcp = "amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor:MCPInstrumentor"
99+
98100
[project.entry-points.opentelemetry_distro]
99101
aws_distro = "amazon.opentelemetry.distro.aws_opentelemetry_distro:AwsOpenTelemetryDistro"
100102

@@ -111,4 +113,4 @@ include = [
111113
]
112114

113115
[tool.hatch.build.targets.wheel]
114-
packages = ["src/amazon"]
116+
packages = ["src/amazon"]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# MCP Instrumentor
2+
3+
OpenTelemetry instrumentation for Model Context Protocol (MCP).
4+
5+
## Installation
6+
7+
Included in AWS OpenTelemetry Distro:
8+
9+
```bash
10+
pip install aws-opentelemetry-distro
11+
```
12+
13+
## Usage
14+
15+
Automatically enabled with:
16+
17+
```bash
18+
opentelemetry-instrument python your_mcp_app.py
19+
```
20+
21+
## Configuration
22+
23+
- `MCP_INSTRUMENTATION_SERVER_NAME`: Override default server name (default: "mcp server")
24+
25+
## Spans Created
26+
27+
- **Client**: `client.send_request`
28+
- **Server**: `tools/initialize`, `tools/list`, `tools/{tool_name}`
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/mcpinstrumentor/mcpinstrumentor.py renamed to aws-opentelemetry-distro/src/amazon/opentelemetry/distro/instrumentation/mcp/mcp_instrumentor.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from opentelemetry import trace
99
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
1010
from opentelemetry.instrumentation.utils import unwrap
11-
11+
from .semconv import MCPAttributes, MCPSpanNames, MCPOperations, MCPTraceContext, MCPEnvironmentVariables
1212
_instruments = ("mcp >= 1.6.0",)
1313

1414
class MCPInstrumentor(BaseInstrumentor):
@@ -27,9 +27,9 @@ def instrumentation_dependencies() -> Collection[str]:
2727
def _instrument(self, **kwargs: Any) -> None:
2828
tracer_provider = kwargs.get("tracer_provider")
2929
if tracer_provider:
30-
self.tracer = tracer_provider.get_tracer("mcp")
30+
self.tracer = tracer_provider.get_tracer("instrumentation.mcp")
3131
else:
32-
self.tracer = trace.get_tracer("mcp")
32+
self.tracer = trace.get_tracer("instrumentation.mcp")
3333
register_post_import_hook(
3434
lambda _: wrap_function_wrapper(
3535
"mcp.shared.session",
@@ -65,7 +65,7 @@ def _wrap_send_request(
6565
"""
6666

6767
async def async_wrapper():
68-
with self.tracer.start_as_current_span("client.send_request", kind=trace.SpanKind.CLIENT) as span:
68+
with self.tracer.start_as_current_span(MCPSpanNames.CLIENT_SEND_REQUEST, kind=trace.SpanKind.CLIENT) as span:
6969
span_ctx = span.get_span_context()
7070
request = args[0] if len(args) > 0 else kwargs.get("request")
7171
if request:
@@ -107,7 +107,7 @@ async def _wrap_handle_request(
107107
traceparent = None
108108

109109
if req and hasattr(req, "params") and req.params and hasattr(req.params, "meta") and req.params.meta:
110-
traceparent = getattr(req.params.meta, "traceparent", None)
110+
traceparent = getattr(req.params.meta, MCPTraceContext.TRACEPARENT_HEADER, None)
111111
span_context = self._extract_span_context_from_traceparent(traceparent) if traceparent else None
112112
if span_context:
113113
span_name = self._get_mcp_operation(req)
@@ -125,16 +125,24 @@ async def _wrap_handle_request(
125125
def _generate_mcp_attributes(self, span: trace.Span, request: ClientRequest, is_client: bool) -> None:
126126
import mcp.types as types # pylint: disable=import-outside-toplevel,consider-using-from-import
127127

128-
operation = "UnknownOperation"
128+
operation = MCPOperations.UNKNOWN_OPERATION
129+
129130
if isinstance(request, types.ListToolsRequest):
130-
operation = "ListTool"
131-
span.set_attribute("mcp.list_tools", True)
131+
operation = MCPOperations.LIST_TOOL
132+
span.set_attribute(MCPAttributes.MCP_LIST_TOOLS, True)
133+
if is_client:
134+
span.update_name(MCPSpanNames.CLIENT_LIST_TOOLS)
132135
elif isinstance(request, types.CallToolRequest):
133136
operation = request.params.name
134-
span.set_attribute("mcp.call_tool", True)
137+
span.set_attribute(MCPAttributes.MCP_CALL_TOOL, True)
138+
if is_client:
139+
span.update_name(MCPSpanNames.client_call_tool(request.params.name))
135140
elif isinstance(request, types.InitializeRequest):
136-
operation = "Initialize"
137-
span.set_attribute("mcp.initialize", True)
141+
operation = MCPOperations.INITIALIZE
142+
span.set_attribute(MCPAttributes.MCP_INITIALIZE, True)
143+
if is_client:
144+
span.update_name(MCPSpanNames.CLIENT_INITIALIZE)
145+
138146
if is_client:
139147
self._add_client_attributes(span, operation, request)
140148
else:
@@ -148,9 +156,9 @@ def _inject_trace_context(request_data: Dict[str, Any], span_ctx) -> None:
148156
request_data["params"]["_meta"] = {}
149157
trace_id_hex = f"{span_ctx.trace_id:032x}"
150158
span_id_hex = f"{span_ctx.span_id:016x}"
151-
trace_flags = "01"
152-
traceparent = f"00-{trace_id_hex}-{span_id_hex}-{trace_flags}"
153-
request_data["params"]["_meta"]["traceparent"] = traceparent
159+
trace_flags = MCPTraceContext.TRACE_FLAGS_SAMPLED
160+
traceparent = f"{MCPTraceContext.TRACEPARENT_VERSION}-{trace_id_hex}-{span_id_hex}-{trace_flags}"
161+
request_data["params"]["_meta"][MCPTraceContext.TRACEPARENT_HEADER] = traceparent
154162

155163
@staticmethod
156164
def _extract_span_context_from_traceparent(traceparent: str):
@@ -177,24 +185,24 @@ def _get_mcp_operation(req: ClientRequest) -> str:
177185
span_name = "unknown"
178186

179187
if isinstance(req, types.ListToolsRequest):
180-
span_name = "tools/list"
188+
span_name = MCPSpanNames.TOOLS_LIST
181189
elif isinstance(req, types.CallToolRequest):
182-
span_name = f"tools/{req.params.name}"
190+
span_name = MCPSpanNames.tools_call(req.params.name)
183191
elif isinstance(req, types.InitializeRequest):
184-
span_name = "tools/initialize"
192+
span_name = MCPSpanNames.TOOLS_INITIALIZE
185193
return span_name
186194

187195
@staticmethod
188196
def _add_client_attributes(span: trace.Span, operation: str, request: ClientRequest) -> None:
189197
import os # pylint: disable=import-outside-toplevel
190198

191-
service_name = os.environ.get("MCP_INSTRUMENTATION_SERVER_NAME", "mcp server")
192-
span.set_attribute("aws.remote.service", service_name)
193-
span.set_attribute("aws.remote.operation", operation)
199+
service_name = os.environ.get(MCPEnvironmentVariables.SERVER_NAME, "mcp server")
200+
span.set_attribute(MCPAttributes.AWS_REMOTE_SERVICE, service_name)
201+
span.set_attribute(MCPAttributes.AWS_REMOTE_OPERATION, operation)
194202
if hasattr(request, "params") and request.params and hasattr(request.params, "name"):
195-
span.set_attribute("tool.name", request.params.name)
203+
span.set_attribute(MCPAttributes.MCP_TOOL_NAME, request.params.name)
196204

197205
@staticmethod
198206
def _add_server_attributes(span: trace.Span, operation: str, request: ClientRequest) -> None:
199207
if hasattr(request, "params") and request.params and hasattr(request.params, "name"):
200-
span.set_attribute("tool.name", request.params.name)
208+
span.set_attribute(MCPAttributes.MCP_TOOL_NAME, request.params.name)
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""
5+
MCP (Model Context Protocol) Semantic Conventions for OpenTelemetry.
6+
7+
This module defines semantic conventions for MCP instrumentation following
8+
OpenTelemetry standards for consistent telemetry data.
9+
"""
10+
11+
12+
class MCPAttributes:
13+
"""MCP-specific span attributes for OpenTelemetry instrumentation."""
14+
15+
# MCP Operation Type Attributes
16+
MCP_INITIALIZE = "mcp.initialize"
17+
"""
18+
Boolean attribute indicating this span represents an MCP initialize operation.
19+
Set to True when the span tracks session initialization between client and server.
20+
"""
21+
22+
MCP_LIST_TOOLS = "mcp.list_tools"
23+
"""
24+
Boolean attribute indicating this span represents an MCP list tools operation.
25+
Set to True when the span tracks discovery of available tools on the server.
26+
"""
27+
28+
MCP_CALL_TOOL = "mcp.call_tool"
29+
"""
30+
Boolean attribute indicating this span represents an MCP call tool operation.
31+
Set to True when the span tracks execution of a specific tool.
32+
"""
33+
34+
# MCP Tool Information
35+
MCP_TOOL_NAME = "mcp.tool.name"
36+
"""
37+
The name of the MCP tool being called.
38+
Example: "echo", "search", "calculator"
39+
"""
40+
41+
# AWS-specific Remote Service Attributes
42+
AWS_REMOTE_SERVICE = "aws.remote.service"
43+
"""
44+
The name of the remote MCP service being called.
45+
Default: "mcp server" (can be overridden via MCP_INSTRUMENTATION_SERVER_NAME env var)
46+
"""
47+
48+
AWS_REMOTE_OPERATION = "aws.remote.operation"
49+
"""
50+
The specific MCP operation being performed.
51+
Values: "Initialize", "ListTool", or the specific tool name for call operations
52+
"""
53+
54+
55+
class MCPSpanNames:
56+
"""Standard span names for MCP operations."""
57+
58+
# Client-side span names
59+
CLIENT_SEND_REQUEST = "client.send_request"
60+
"""
61+
Span name for client-side MCP request operations.
62+
Used for all outgoing MCP requests (initialize, list tools, call tool).
63+
"""
64+
65+
CLIENT_INITIALIZE = "mcp.initialize"
66+
"""
67+
Span name for client-side MCP initialization requests.
68+
"""
69+
70+
CLIENT_LIST_TOOLS = "mcp.list_tools"
71+
"""
72+
Span name for client-side MCP list tools requests.
73+
"""
74+
75+
@staticmethod
76+
def client_call_tool(tool_name: str) -> str:
77+
"""
78+
Generate span name for client-side MCP tool call requests.
79+
80+
Args:
81+
tool_name: Name of the tool being called
82+
83+
Returns:
84+
Formatted span name like "mcp.call_tool.echo", "mcp.call_tool.search"
85+
"""
86+
return f"mcp.call_tool.{tool_name}"
87+
88+
# Server-side span names
89+
TOOLS_INITIALIZE = "tools/initialize"
90+
"""
91+
Span name for server-side MCP initialization handling.
92+
Tracks server processing of client initialization requests.
93+
"""
94+
95+
TOOLS_LIST = "tools/list"
96+
"""
97+
Span name for server-side MCP list tools handling.
98+
Tracks server processing of tool discovery requests.
99+
"""
100+
101+
@staticmethod
102+
def tools_call(tool_name: str) -> str:
103+
"""
104+
Generate span name for server-side MCP tool call handling.
105+
106+
Args:
107+
tool_name: Name of the tool being called
108+
109+
Returns:
110+
Formatted span name like "tools/echo", "tools/search"
111+
"""
112+
return f"tools/{tool_name}"
113+
114+
115+
class MCPOperations:
116+
"""Standard operation names for MCP semantic conventions."""
117+
118+
INITIALIZE = "Initialize"
119+
"""Operation name for MCP session initialization."""
120+
121+
LIST_TOOL = "ListTool"
122+
"""Operation name for MCP tool discovery."""
123+
124+
UNKNOWN_OPERATION = "UnknownOperation"
125+
"""Fallback operation name for unrecognized MCP operations."""
126+
127+
128+
class MCPTraceContext:
129+
"""Constants for MCP distributed tracing context propagation."""
130+
131+
TRACEPARENT_HEADER = "traceparent"
132+
"""
133+
W3C Trace Context traceparent header name.
134+
Used for propagating trace context in MCP request metadata.
135+
"""
136+
137+
TRACE_FLAGS_SAMPLED = "01"
138+
"""
139+
W3C Trace Context flags indicating the trace is sampled.
140+
"""
141+
142+
TRACEPARENT_VERSION = "00"
143+
"""
144+
W3C Trace Context version identifier.
145+
"""
146+
147+
148+
class MCPEnvironmentVariables:
149+
"""Environment variable names for MCP instrumentation configuration."""
150+
151+
SERVER_NAME = "MCP_INSTRUMENTATION_SERVER_NAME"
152+
"""
153+
Environment variable to override the default MCP server name.
154+
Default value: "mcp server"
155+
"""

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/mcpinstrumentor/README.md

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)