1- from typing import Any
2-
3- import mcp .types as mt
1+ import httpx
42from common .core .logging import setup_logging
53from common .core .otel import (
64 add_otel_trace_context ,
75 build_otel_log_provider ,
86 build_tracer_provider ,
97 make_structlog_otel_processor ,
108)
11- from fastmcp .server .middleware import CallNext , Middleware , MiddlewareContext
12- from fastmcp .tools .base import ToolResult
139from opentelemetry import baggage , trace
14- from opentelemetry import context as otel_context
10+ from opentelemetry . baggage . propagation import W3CBaggagePropagator
1511from opentelemetry .context import Context
16- from opentelemetry .sdk .trace import Span , SpanProcessor
12+ from opentelemetry .sdk .trace import ReadableSpan , Span , SpanProcessor
1713from structlog .typing import Processor
1814
1915from flagsmith_mcp import config
2218APPLICATION_LOGGERS = ["flagsmith_mcp" , "fastmcp" , "mcp" ]
2319
2420
25- def is_flagsmith_baggage_key (key : str ) -> bool :
26- return key .startswith ("flagsmith." )
27-
28-
29- class FlagsmithBaggageSpanProcessor (SpanProcessor ):
30- """Copy flagsmith.* baggage entries onto started spans.
31-
32- The off-the-shelf opentelemetry-processor-baggage reads baggage from
33- the span's parent context, but FastMCP starts its server spans from a
34- context extracted from the request _meta, which carries no baggage.
35- Read the current context, where middleware attached the entries.
36- """
21+ class ClientInfoSpanProcessor (SpanProcessor ):
22+ """Annotate started spans with the MCP client identity."""
3723
3824 def on_start (self , span : Span , parent_context : Context | None = None ) -> None :
39- for key , value in baggage . get_all (). items () :
40- if is_flagsmith_baggage_key ( key ):
41- span .set_attribute (key , str ( value ) )
25+ if ( client_info := get_client_info ()) is not None :
26+ span . set_attribute ( "flagsmith.client.name" , client_info . name )
27+ span .set_attribute ("flagsmith.client.version" , client_info . version )
4228
4329
44- class BaggageMiddleware (Middleware ):
45- """Attach the client identity and tool name as W3C Baggage: the single
46- source both for span attributes (via BaggageSpanProcessor) and for
47- propagation to the Flagsmith API by the instrumented upstream client."""
48-
49- async def on_request (
50- self ,
51- context : MiddlewareContext [mt .Request [Any , Any ]],
52- call_next : CallNext [mt .Request [Any , Any ], Any ],
53- ) -> Any :
54- if (client_info := get_client_info ()) is None :
55- return await call_next (context )
56- ctx = baggage .set_baggage ("flagsmith.client.name" , client_info .name )
57- ctx = baggage .set_baggage (
58- "flagsmith.client.version" , client_info .version , context = ctx
59- )
60- token = otel_context .attach (ctx )
61- try :
62- return await call_next (context )
63- finally :
64- otel_context .detach (token )
65-
66- async def on_call_tool (
67- self ,
68- context : MiddlewareContext [mt .CallToolRequestParams ],
69- call_next : CallNext [mt .CallToolRequestParams , ToolResult ],
70- ) -> ToolResult :
71- token = otel_context .attach (
72- baggage .set_baggage ("flagsmith.tool.name" , context .message .name )
73- )
74- try :
75- return await call_next (context )
76- finally :
77- otel_context .detach (token )
30+ async def propagate_span_attributes (request : httpx .Request ) -> None :
31+ span = trace .get_current_span ()
32+ if not isinstance (span , ReadableSpan ):
33+ return
34+ ctx : Context | None = None
35+ for key , value in (span .attributes or {}).items ():
36+ ctx = baggage .set_baggage (key , str (value ), context = ctx )
37+ if ctx is not None :
38+ W3CBaggagePropagator ().inject (request .headers , context = ctx )
7839
7940
8041def setup_telemetry (settings : config .Settings ) -> None :
@@ -97,7 +58,7 @@ def setup_telemetry(settings: config.Settings) -> None:
9758 endpoint = f"{ endpoint } /v1/traces" ,
9859 service_name = settings .otel_service_name ,
9960 )
100- tracer_provider .add_span_processor (FlagsmithBaggageSpanProcessor ())
61+ tracer_provider .add_span_processor (ClientInfoSpanProcessor ())
10162 trace .set_tracer_provider (tracer_provider )
10263 setup_logging (
10364 log_level = settings .log_level ,
0 commit comments