Skip to content

Commit 0bccb25

Browse files
committed
tmp merge commit
2 parents ab27726 + 526d9ac commit 0bccb25

File tree

4 files changed

+446
-4
lines changed

4 files changed

+446
-4
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.24.0",
7476
]
7577
docs = [
7678
"mkdocs>=1.6.1",

src/mcp/shared/session.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import anyio
1111
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
12+
from opentelemetry.propagate import inject
1213
from pydantic import BaseModel, TypeAdapter
1314
from typing_extensions import Self
1415

@@ -266,6 +267,9 @@ async def send_request(
266267
# Store the callback for this request
267268
self._progress_callbacks[request_id] = progress_callback
268269

270+
# Propagate opentelemetry trace context
271+
self._inject_otel_context(request_data)
272+
269273
try:
270274
jsonrpc_request = JSONRPCRequest(jsonrpc="2.0", id=request_id, **request_data)
271275
await self._write_stream.send(SessionMessage(message=jsonrpc_request, metadata=metadata))
@@ -298,18 +302,37 @@ async def send_notification(
298302
related_request_id: RequestId | None = None,
299303
) -> None:
300304
"""Emits a notification, which is a one-way message that does not expect a response."""
305+
306+
request_data = notification.model_dump(by_alias=True, mode="json", exclude_none=True)
307+
# Propagate opentelemetry trace context
308+
self._inject_otel_context(request_data)
309+
jsonrpc_notification = JSONRPCNotification(jsonrpc="2.0", **request_data)
310+
301311
# Some transport implementations may need to set the related_request_id
302312
# to attribute to the notifications to the request that triggered them.
303-
jsonrpc_notification = JSONRPCNotification(
304-
jsonrpc="2.0",
305-
**notification.model_dump(by_alias=True, mode="json", exclude_none=True),
306-
)
307313
session_message = SessionMessage(
308314
message=jsonrpc_notification,
309315
metadata=ServerMessageMetadata(related_request_id=related_request_id) if related_request_id else None,
310316
)
311317
await self._write_stream.send(session_message)
312318

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

0 commit comments

Comments
 (0)