Skip to content

Commit a694ffb

Browse files
committed
OpenTelemetry _meta propagation
1 parent 0fe16dd commit a694ffb

File tree

4 files changed

+637
-98
lines changed

4 files changed

+637
-98
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies = [
4040
"pyjwt[crypto]>=2.10.1",
4141
"typing-extensions>=4.13.0",
4242
"typing-inspection>=0.4.1",
43+
"opentelemetry-api>=1.23.0",
4344
]
4445

4546
[project.optional-dependencies]
@@ -71,6 +72,7 @@ dev = [
7172
"coverage[toml]>=7.10.7,<=7.13",
7273
"pillow>=12.0",
7374
"strict-no-cover",
75+
"opentelemetry-sdk>=1.23.0",
7476
]
7577
docs = [
7678
"mkdocs>=1.6.1",

src/mcp/shared/session.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import anyio
1010
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
11+
from opentelemetry.propagate import inject
1112
from pydantic import BaseModel, TypeAdapter
1213
from typing_extensions import Self
1314

@@ -251,6 +252,9 @@ async def send_request(
251252
response_stream, response_stream_reader = anyio.create_memory_object_stream[JSONRPCResponse | JSONRPCError](1)
252253
self._response_streams[request_id] = response_stream
253254

255+
# Propagate opentelemetry trace context
256+
self._inject_otel_context(request)
257+
254258
# Set up progress token if progress callback is provided
255259
request_data = request.model_dump(by_alias=True, mode="json", exclude_none=True)
256260
if progress_callback is not None:
@@ -295,6 +299,10 @@ async def send_notification(
295299
related_request_id: RequestId | None = None,
296300
) -> None:
297301
"""Emits a notification, which is a one-way message that does not expect a response."""
302+
303+
# Propagate opentelemetry trace context
304+
self._inject_otel_context(notification)
305+
298306
# Some transport implementations may need to set the related_request_id
299307
# to attribute to the notifications to the request that triggered them.
300308
jsonrpc_notification = JSONRPCNotification(
@@ -307,6 +315,28 @@ async def send_notification(
307315
)
308316
await self._write_stream.send(session_message)
309317

318+
def _inject_otel_context(self, request: SendRequestT | SendNotificationT) -> None:
319+
"""Propagate OpenTelemetry context in `_meta`.
320+
321+
See
322+
- SEP414 https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414
323+
- OpenTelemetry semantic conventions
324+
https://github.com/open-telemetry/semantic-conventions/blob/v1.39.0/docs/gen-ai/mcp.md
325+
"""
326+
327+
if request.params is None:
328+
return
329+
330+
carrier: RequestParamsMeta = {}
331+
inject(carrier)
332+
if not carrier:
333+
return
334+
335+
if request.params.meta is None:
336+
request.params.meta = {}
337+
338+
request.params.meta.update(carrier)
339+
310340
async def _send_response(self, request_id: RequestId, response: SendResultT | ErrorData) -> None:
311341
if isinstance(response, ErrorData):
312342
jsonrpc_error = JSONRPCError(jsonrpc="2.0", id=request_id, error=response)

0 commit comments

Comments
 (0)