This repository was archived by the owner on Feb 10, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathotel_utils.py
More file actions
123 lines (95 loc) · 3.53 KB
/
Copy pathotel_utils.py
File metadata and controls
123 lines (95 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""
OpenTelemetry context utilities for MCP _meta field propagation.
This module provides utilities for extracting and injecting OpenTelemetry
context using MCP's _meta field convention, which works across all transport
types (stdio, HTTP, SSE).
"""
import asyncio
import functools
from typing import Any, Callable, TypeVar
from opentelemetry import context
from opentelemetry.context import Context
from opentelemetry.propagate import get_global_textmap
F = TypeVar("F", bound=Callable[..., Any])
def extract_otel_context_from_meta(meta: dict | None) -> Context:
"""
Extract OpenTelemetry context from MCP _meta field.
The _meta field may contain traceparent, tracestate, and baggage
following W3C Trace Context specification.
Args:
meta: Dictionary containing trace context fields from MCP request
Returns:
OpenTelemetry context object with extracted trace context
"""
if not meta:
return context.get_current()
# Create a carrier dict with the trace context fields
carrier = {}
if "traceparent" in meta:
carrier["traceparent"] = meta["traceparent"]
if "tracestate" in meta:
carrier["tracestate"] = meta["tracestate"]
if "baggage" in meta:
carrier["baggage"] = meta["baggage"]
# Extract context using OpenTelemetry's propagator
if carrier:
propagator = get_global_textmap()
return propagator.extract(carrier)
return context.get_current()
def inject_otel_context_to_meta() -> dict:
"""
Inject current OpenTelemetry context into _meta field format.
This creates a dictionary suitable for use in MCP request params._meta
field, containing traceparent, tracestate, and baggage if present.
Returns:
Dictionary with trace context fields for _meta field
"""
carrier = {}
propagator = get_global_textmap()
propagator.inject(carrier, context=context.get_current())
return carrier
def with_otel_context_from_meta(func: F) -> F:
"""
Decorator that extracts OpenTelemetry context from MCP request _meta field
and sets it as the current context before executing the function.
This decorator expects the decorated function to accept a _meta parameter
(typically as a keyword argument with default None).
Usage:
@mcp.tool()
@with_otel_context_from_meta
@observe()
def my_tool(arg1: str, _meta: dict = None) -> str:
# This function now runs within the propagated trace context
return "result"
Args:
func: The function to decorate
Returns:
Decorated function that activates the context from _meta
"""
@functools.wraps(func)
def sync_wrapper(*args, **kwargs):
# Extract _meta from kwargs
meta = kwargs.get("_meta")
# Extract and activate the context
ctx = extract_otel_context_from_meta(meta)
token = context.attach(ctx)
try:
return func(*args, **kwargs)
finally:
context.detach(token)
@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
# Extract _meta from kwargs
meta = kwargs.get("_meta")
# Extract and activate the context
ctx = extract_otel_context_from_meta(meta)
token = context.attach(ctx)
try:
return await func(*args, **kwargs)
finally:
context.detach(token)
# Return appropriate wrapper based on function type
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return sync_wrapper