fast-agent-mcp 0.2.18__py3-none-any.whl → 0.2.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fast_agent_mcp-0.2.18.dist-info → fast_agent_mcp-0.2.20.dist-info}/METADATA +15 -15
- {fast_agent_mcp-0.2.18.dist-info → fast_agent_mcp-0.2.20.dist-info}/RECORD +20 -21
- mcp_agent/__init__.py +1 -2
- mcp_agent/agents/base_agent.py +6 -2
- mcp_agent/agents/workflow/parallel_agent.py +53 -38
- mcp_agent/agents/workflow/router_agent.py +22 -17
- mcp_agent/config.py +5 -4
- mcp_agent/context.py +15 -11
- mcp_agent/core/fastagent.py +248 -217
- mcp_agent/executor/executor.py +8 -9
- mcp_agent/llm/augmented_llm.py +37 -3
- mcp_agent/llm/providers/augmented_llm_anthropic.py +1 -1
- mcp_agent/llm/providers/augmented_llm_openai.py +5 -2
- mcp_agent/mcp/mcp_aggregator.py +114 -119
- mcp_agent/mcp/mcp_connection_manager.py +2 -1
- mcp_agent/mcp_server/agent_server.py +4 -1
- mcp_agent/mcp_server_registry.py +1 -0
- mcp_agent/logging/tracing.py +0 -138
- {fast_agent_mcp-0.2.18.dist-info → fast_agent_mcp-0.2.20.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.18.dist-info → fast_agent_mcp-0.2.20.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.18.dist-info → fast_agent_mcp-0.2.20.dist-info}/licenses/LICENSE +0 -0
mcp_agent/mcp/mcp_aggregator.py
CHANGED
@@ -12,8 +12,6 @@ from typing import (
|
|
12
12
|
|
13
13
|
from mcp import GetPromptResult, ReadResourceResult
|
14
14
|
from mcp.client.session import ClientSession
|
15
|
-
from mcp.server.lowlevel.server import Server
|
16
|
-
from mcp.server.stdio import stdio_server
|
17
15
|
from mcp.types import (
|
18
16
|
CallToolResult,
|
19
17
|
ListToolsResult,
|
@@ -21,6 +19,7 @@ from mcp.types import (
|
|
21
19
|
TextContent,
|
22
20
|
Tool,
|
23
21
|
)
|
22
|
+
from opentelemetry import trace
|
24
23
|
from pydantic import AnyUrl, BaseModel, ConfigDict
|
25
24
|
|
26
25
|
from mcp_agent.context_dependent import ContextDependent
|
@@ -42,6 +41,14 @@ SEP = "-"
|
|
42
41
|
T = TypeVar("T")
|
43
42
|
R = TypeVar("R")
|
44
43
|
|
44
|
+
def create_namespaced_name(server_name: str, resource_name: str) -> str:
|
45
|
+
"""Create a namespaced resource name from server and resource names"""
|
46
|
+
return f"{server_name}{SEP}{resource_name}"
|
47
|
+
|
48
|
+
def is_namespaced_name(name: str) -> bool:
|
49
|
+
"""Check if a name is already namespaced"""
|
50
|
+
return SEP in name
|
51
|
+
|
45
52
|
|
46
53
|
class NamespacedTool(BaseModel):
|
47
54
|
"""
|
@@ -230,8 +237,7 @@ class MCPAggregator(ContextDependent):
|
|
230
237
|
|
231
238
|
async def fetch_prompts(client: ClientSession, server_name: str) -> List[Prompt]:
|
232
239
|
# Only fetch prompts if the server supports them
|
233
|
-
|
234
|
-
if not capabilities or not capabilities.prompts:
|
240
|
+
if not await self.server_supports_feature(server_name, "prompts"):
|
235
241
|
logger.debug(f"Server '{server_name}' does not support prompts")
|
236
242
|
return []
|
237
243
|
|
@@ -277,7 +283,7 @@ class MCPAggregator(ContextDependent):
|
|
277
283
|
# Process tools
|
278
284
|
self._server_to_tool_map[server_name] = []
|
279
285
|
for tool in tools:
|
280
|
-
namespaced_tool_name =
|
286
|
+
namespaced_tool_name = create_namespaced_name(server_name, tool.name)
|
281
287
|
namespaced_tool = NamespacedTool(
|
282
288
|
tool=tool,
|
283
289
|
server_name=server_name,
|
@@ -319,6 +325,41 @@ class MCPAggregator(ContextDependent):
|
|
319
325
|
except Exception as e:
|
320
326
|
logger.debug(f"Error getting capabilities for server '{server_name}': {e}")
|
321
327
|
return None
|
328
|
+
|
329
|
+
async def validate_server(self, server_name: str) -> bool:
|
330
|
+
"""
|
331
|
+
Validate that a server exists in our server list.
|
332
|
+
|
333
|
+
Args:
|
334
|
+
server_name: Name of the server to validate
|
335
|
+
|
336
|
+
Returns:
|
337
|
+
True if the server exists, False otherwise
|
338
|
+
"""
|
339
|
+
valid = server_name in self.server_names
|
340
|
+
if not valid:
|
341
|
+
logger.debug(f"Server '{server_name}' not found")
|
342
|
+
return valid
|
343
|
+
|
344
|
+
async def server_supports_feature(self, server_name: str, feature: str) -> bool:
|
345
|
+
"""
|
346
|
+
Check if a server supports a specific feature.
|
347
|
+
|
348
|
+
Args:
|
349
|
+
server_name: Name of the server to check
|
350
|
+
feature: Feature to check for (e.g., "prompts", "resources")
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
True if the server supports the feature, False otherwise
|
354
|
+
"""
|
355
|
+
if not await self.validate_server(server_name):
|
356
|
+
return False
|
357
|
+
|
358
|
+
capabilities = await self.get_capabilities(server_name)
|
359
|
+
if not capabilities:
|
360
|
+
return False
|
361
|
+
|
362
|
+
return getattr(capabilities, feature, False)
|
322
363
|
|
323
364
|
async def list_servers(self) -> List[str]:
|
324
365
|
"""Return the list of server names aggregated by this agent."""
|
@@ -419,40 +460,45 @@ class MCPAggregator(ContextDependent):
|
|
419
460
|
Returns:
|
420
461
|
Tuple of (server_name, local_resource_name)
|
421
462
|
"""
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
server_name,
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
#
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
463
|
+
# First, check if this is a direct hit in our namespaced tool map
|
464
|
+
# This handles both namespaced and non-namespaced direct lookups
|
465
|
+
if resource_type == "tool" and name in self._namespaced_tool_map:
|
466
|
+
namespaced_tool = self._namespaced_tool_map[name]
|
467
|
+
return namespaced_tool.server_name, namespaced_tool.tool.name
|
468
|
+
|
469
|
+
# Next, attempt to interpret as a namespaced name
|
470
|
+
if is_namespaced_name(name):
|
471
|
+
parts = name.split(SEP, 1)
|
472
|
+
server_name, local_name = parts[0], parts[1]
|
473
|
+
|
474
|
+
# Validate that the parsed server actually exists
|
475
|
+
if server_name in self.server_names:
|
476
|
+
return server_name, local_name
|
477
|
+
|
478
|
+
# If the server name doesn't exist, it might be a tool with a hyphen in its name
|
479
|
+
# Fall through to the next checks
|
480
|
+
|
481
|
+
# For tools, search all servers for the tool by exact name match
|
482
|
+
if resource_type == "tool":
|
483
|
+
for server_name, tools in self._server_to_tool_map.items():
|
484
|
+
for namespaced_tool in tools:
|
485
|
+
if namespaced_tool.tool.name == name:
|
486
|
+
return server_name, name
|
487
|
+
|
488
|
+
# For all other resource types, use the first server
|
489
|
+
return (self.server_names[0] if self.server_names else None, name)
|
445
490
|
|
446
491
|
async def call_tool(self, name: str, arguments: dict | None = None) -> CallToolResult:
|
447
492
|
"""
|
448
|
-
Call a namespaced tool, e.g., 'server_name
|
493
|
+
Call a namespaced tool, e.g., 'server_name-tool_name'.
|
449
494
|
"""
|
450
495
|
if not self.initialized:
|
451
496
|
await self.load_servers()
|
452
497
|
|
498
|
+
# Use the common parser to get server and tool name
|
453
499
|
server_name, local_tool_name = await self._parse_resource_name(name, "tool")
|
454
500
|
|
455
|
-
if server_name is None
|
501
|
+
if server_name is None:
|
456
502
|
logger.error(f"Error: Tool '{name}' not found")
|
457
503
|
return CallToolResult(
|
458
504
|
isError=True,
|
@@ -469,16 +515,20 @@ class MCPAggregator(ContextDependent):
|
|
469
515
|
},
|
470
516
|
)
|
471
517
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
518
|
+
tracer = trace.get_tracer(__name__)
|
519
|
+
with tracer.start_as_current_span(f"MCP Tool: {server_name}/{local_tool_name}"):
|
520
|
+
trace.get_current_span().set_attribute("tool_name", local_tool_name)
|
521
|
+
trace.get_current_span().set_attribute("server_name", server_name)
|
522
|
+
return await self._execute_on_server(
|
523
|
+
server_name=server_name,
|
524
|
+
operation_type="tool",
|
525
|
+
operation_name=local_tool_name,
|
526
|
+
method_name="call_tool",
|
527
|
+
method_args={"name": local_tool_name, "arguments": arguments},
|
528
|
+
error_factory=lambda msg: CallToolResult(
|
529
|
+
isError=True, content=[TextContent(type="text", text=msg)]
|
530
|
+
),
|
531
|
+
)
|
482
532
|
|
483
533
|
async def get_prompt(
|
484
534
|
self,
|
@@ -501,27 +551,37 @@ class MCPAggregator(ContextDependent):
|
|
501
551
|
if not self.initialized:
|
502
552
|
await self.load_servers()
|
503
553
|
|
504
|
-
#
|
505
|
-
if
|
506
|
-
|
507
|
-
|
508
|
-
|
554
|
+
# If server_name is explicitly provided, use it
|
555
|
+
if server_name:
|
556
|
+
local_prompt_name = prompt_name
|
557
|
+
# Otherwise, check if prompt_name is namespaced and validate the server exists
|
558
|
+
elif is_namespaced_name(prompt_name):
|
559
|
+
parts = prompt_name.split(SEP, 1)
|
560
|
+
potential_server = parts[0]
|
561
|
+
|
562
|
+
# Only treat as namespaced if the server part is valid
|
563
|
+
if potential_server in self.server_names:
|
564
|
+
server_name = potential_server
|
565
|
+
local_prompt_name = parts[1]
|
566
|
+
else:
|
567
|
+
# The hyphen is part of the prompt name, not a namespace separator
|
568
|
+
local_prompt_name = prompt_name
|
569
|
+
# Otherwise, use prompt_name as-is for searching
|
509
570
|
else:
|
510
571
|
local_prompt_name = prompt_name
|
511
|
-
|
512
|
-
|
572
|
+
# We'll search all servers below
|
573
|
+
|
513
574
|
# If we have a specific server to check
|
514
575
|
if server_name:
|
515
|
-
if
|
576
|
+
if not await self.validate_server(server_name):
|
516
577
|
logger.error(f"Error: Server '{server_name}' not found")
|
517
578
|
return GetPromptResult(
|
518
579
|
description=f"Error: Server '{server_name}' not found",
|
519
580
|
messages=[],
|
520
581
|
)
|
521
|
-
|
582
|
+
|
522
583
|
# Check if server supports prompts
|
523
|
-
|
524
|
-
if not capabilities or not capabilities.prompts:
|
584
|
+
if not await self.server_supports_feature(server_name, "prompts"):
|
525
585
|
logger.debug(f"Server '{server_name}' does not support prompts")
|
526
586
|
return GetPromptResult(
|
527
587
|
description=f"Server '{server_name}' does not support prompts",
|
@@ -559,7 +619,7 @@ class MCPAggregator(ContextDependent):
|
|
559
619
|
|
560
620
|
# Add namespaced name and source server to the result
|
561
621
|
if result and result.messages:
|
562
|
-
result.namespaced_name =
|
622
|
+
result.namespaced_name = create_namespaced_name(server_name, local_prompt_name)
|
563
623
|
|
564
624
|
# Store the arguments in the result for display purposes
|
565
625
|
if arguments:
|
@@ -611,7 +671,7 @@ class MCPAggregator(ContextDependent):
|
|
611
671
|
f"Successfully retrieved prompt '{local_prompt_name}' from server '{s_name}'"
|
612
672
|
)
|
613
673
|
# Add namespaced name using the actual server where found
|
614
|
-
result.namespaced_name =
|
674
|
+
result.namespaced_name = create_namespaced_name(s_name, local_prompt_name)
|
615
675
|
|
616
676
|
# Store the arguments in the result for display purposes
|
617
677
|
if arguments:
|
@@ -659,7 +719,7 @@ class MCPAggregator(ContextDependent):
|
|
659
719
|
f"Found prompt '{local_prompt_name}' on server '{s_name}' (not in cache)"
|
660
720
|
)
|
661
721
|
# Add namespaced name using the actual server where found
|
662
|
-
result.namespaced_name =
|
722
|
+
result.namespaced_name = create_namespaced_name(s_name, local_prompt_name)
|
663
723
|
|
664
724
|
# Store the arguments in the result for display purposes
|
665
725
|
if arguments:
|
@@ -937,68 +997,3 @@ class MCPAggregator(ContextDependent):
|
|
937
997
|
logger.error(f"Error fetching resources from {s_name}: {e}")
|
938
998
|
|
939
999
|
return results
|
940
|
-
|
941
|
-
|
942
|
-
class MCPCompoundServer(Server):
|
943
|
-
"""
|
944
|
-
A compound server (server-of-servers) that aggregates multiple MCP servers and is itself an MCP server
|
945
|
-
"""
|
946
|
-
|
947
|
-
def __init__(self, server_names: List[str], name: str = "MCPCompoundServer") -> None:
|
948
|
-
super().__init__(name)
|
949
|
-
self.aggregator = MCPAggregator(server_names)
|
950
|
-
|
951
|
-
# Register handlers for tools, prompts, and resources
|
952
|
-
self.list_tools()(self._list_tools)
|
953
|
-
self.call_tool()(self._call_tool)
|
954
|
-
self.get_prompt()(self._get_prompt)
|
955
|
-
self.list_prompts()(self._list_prompts)
|
956
|
-
|
957
|
-
async def _list_tools(self) -> List[Tool]:
|
958
|
-
"""List all tools aggregated from connected MCP servers."""
|
959
|
-
tools_result = await self.aggregator.list_tools()
|
960
|
-
return tools_result.tools
|
961
|
-
|
962
|
-
async def _call_tool(self, name: str, arguments: dict | None = None) -> CallToolResult:
|
963
|
-
"""Call a specific tool from the aggregated servers."""
|
964
|
-
try:
|
965
|
-
result = await self.aggregator.call_tool(name=name, arguments=arguments)
|
966
|
-
return result.content
|
967
|
-
except Exception as e:
|
968
|
-
return CallToolResult(
|
969
|
-
isError=True,
|
970
|
-
content=[TextContent(type="text", text=f"Error calling tool: {e}")],
|
971
|
-
)
|
972
|
-
|
973
|
-
async def _get_prompt(
|
974
|
-
self, name: str = None, arguments: dict[str, str] = None
|
975
|
-
) -> GetPromptResult:
|
976
|
-
"""
|
977
|
-
Get a prompt from the aggregated servers.
|
978
|
-
|
979
|
-
Args:
|
980
|
-
name: Name of the prompt to get (optionally namespaced)
|
981
|
-
arguments: Optional dictionary of string arguments for prompt templating
|
982
|
-
"""
|
983
|
-
try:
|
984
|
-
result = await self.aggregator.get_prompt(prompt_name=name, arguments=arguments)
|
985
|
-
return result
|
986
|
-
except Exception as e:
|
987
|
-
return GetPromptResult(description=f"Error getting prompt: {e}", messages=[])
|
988
|
-
|
989
|
-
async def _list_prompts(self, server_name: str = None) -> Dict[str, List[Prompt]]:
|
990
|
-
"""List available prompts from the aggregated servers."""
|
991
|
-
try:
|
992
|
-
return await self.aggregator.list_prompts(server_name=server_name)
|
993
|
-
except Exception as e:
|
994
|
-
logger.error(f"Error listing prompts: {e}")
|
995
|
-
return {}
|
996
|
-
|
997
|
-
async def run_stdio_async(self) -> None:
|
998
|
-
"""Run the server using stdio transport."""
|
999
|
-
async with stdio_server() as (read_stream, write_stream):
|
1000
|
-
await self.run(
|
1001
|
-
read_stream=read_stream,
|
1002
|
-
write_stream=write_stream,
|
1003
|
-
initialization_options=self.create_initialization_options(),
|
1004
|
-
)
|
@@ -262,8 +262,9 @@ class MCPConnectionManager(ContextDependent):
|
|
262
262
|
if config.transport == "stdio":
|
263
263
|
server_params = StdioServerParameters(
|
264
264
|
command=config.command,
|
265
|
-
args=config.args,
|
265
|
+
args=config.args if config.args is not None else [],
|
266
266
|
env={**get_default_environment(), **(config.env or {})},
|
267
|
+
cwd=config.cwd,
|
267
268
|
)
|
268
269
|
# Create custom error handler to ensure all output is captured
|
269
270
|
error_handler = get_stderr_handler(server_name)
|
@@ -110,7 +110,10 @@ class AgentMCPServer:
|
|
110
110
|
|
111
111
|
# Register handlers for SIGINT (Ctrl+C) and SIGTERM
|
112
112
|
for sig, is_term in [(signal.SIGINT, False), (signal.SIGTERM, True)]:
|
113
|
-
|
113
|
+
import platform
|
114
|
+
|
115
|
+
if platform.system() != "Windows":
|
116
|
+
loop.add_signal_handler(sig, lambda term=is_term: handle_signal(term))
|
114
117
|
|
115
118
|
logger.debug("Signal handlers installed")
|
116
119
|
|
mcp_agent/mcp_server_registry.py
CHANGED
mcp_agent/logging/tracing.py
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Telemetry manager that defines distributed tracing decorators for OpenTelemetry traces/spans
|
3
|
-
for the Logger module for MCP Agent
|
4
|
-
"""
|
5
|
-
|
6
|
-
import asyncio
|
7
|
-
import functools
|
8
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple
|
9
|
-
|
10
|
-
from opentelemetry import trace
|
11
|
-
from opentelemetry.context import Context as OtelContext
|
12
|
-
from opentelemetry.propagate import extract as otel_extract
|
13
|
-
from opentelemetry.trace import SpanKind, Status, StatusCode, set_span_in_context
|
14
|
-
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
15
|
-
|
16
|
-
from mcp_agent.context_dependent import ContextDependent
|
17
|
-
|
18
|
-
if TYPE_CHECKING:
|
19
|
-
from mcp_agent.context import Context
|
20
|
-
|
21
|
-
|
22
|
-
class TelemetryManager(ContextDependent):
|
23
|
-
"""
|
24
|
-
Simple manager for creating OpenTelemetry spans automatically.
|
25
|
-
Decorator usage: @telemetry.traced("SomeSpanName")
|
26
|
-
"""
|
27
|
-
|
28
|
-
def __init__(self, context: Optional["Context"] = None, **kwargs) -> None:
|
29
|
-
# If needed, configure resources, exporters, etc.
|
30
|
-
# E.g.: from opentelemetry.sdk.trace import TracerProvider
|
31
|
-
# trace.set_tracer_provider(TracerProvider(...))
|
32
|
-
super().__init__(context=context, **kwargs)
|
33
|
-
|
34
|
-
def traced(
|
35
|
-
self,
|
36
|
-
name: str | None = None,
|
37
|
-
kind: SpanKind = SpanKind.INTERNAL,
|
38
|
-
attributes: Dict[str, Any] = None,
|
39
|
-
) -> Callable:
|
40
|
-
"""
|
41
|
-
Decorator that automatically creates and manages a span for a function.
|
42
|
-
Works for both async and sync functions.
|
43
|
-
"""
|
44
|
-
|
45
|
-
def decorator(func):
|
46
|
-
span_name = name or f"{func.__module__}.{func.__qualname__}"
|
47
|
-
|
48
|
-
tracer = self.context.tracer or trace.get_tracer("mcp_agent")
|
49
|
-
|
50
|
-
@functools.wraps(func)
|
51
|
-
async def async_wrapper(*args, **kwargs):
|
52
|
-
with tracer.start_as_current_span(span_name, kind=kind) as span:
|
53
|
-
if attributes:
|
54
|
-
for k, v in attributes.items():
|
55
|
-
span.set_attribute(k, v)
|
56
|
-
# Record simple args
|
57
|
-
self._record_args(span, args, kwargs)
|
58
|
-
try:
|
59
|
-
res = await func(*args, **kwargs)
|
60
|
-
return res
|
61
|
-
except Exception as e:
|
62
|
-
span.record_exception(e)
|
63
|
-
span.set_status(Status(StatusCode.ERROR))
|
64
|
-
raise
|
65
|
-
|
66
|
-
@functools.wraps(func)
|
67
|
-
def sync_wrapper(*args, **kwargs):
|
68
|
-
with tracer.start_as_current_span(span_name, kind=kind) as span:
|
69
|
-
if attributes:
|
70
|
-
for k, v in attributes.items():
|
71
|
-
span.set_attribute(k, v)
|
72
|
-
# Record simple args
|
73
|
-
self._record_args(span, args, kwargs)
|
74
|
-
try:
|
75
|
-
res = func(*args, **kwargs)
|
76
|
-
return res
|
77
|
-
except Exception as e:
|
78
|
-
span.record_exception(e)
|
79
|
-
span.set_status(Status(StatusCode.ERROR))
|
80
|
-
raise
|
81
|
-
|
82
|
-
if asyncio.iscoroutinefunction(func):
|
83
|
-
return async_wrapper
|
84
|
-
else:
|
85
|
-
return sync_wrapper
|
86
|
-
|
87
|
-
return decorator
|
88
|
-
|
89
|
-
def _record_args(self, span, args, kwargs) -> None:
|
90
|
-
"""Optionally record primitive args as span attributes."""
|
91
|
-
for i, arg in enumerate(args):
|
92
|
-
if isinstance(arg, (str, int, float, bool)):
|
93
|
-
span.set_attribute(f"arg_{i}", str(arg))
|
94
|
-
for k, v in kwargs.items():
|
95
|
-
if isinstance(v, (str, int, float, bool)):
|
96
|
-
span.set_attribute(k, str(v))
|
97
|
-
|
98
|
-
|
99
|
-
class MCPRequestTrace:
|
100
|
-
"""Helper class for trace context propagation in MCP"""
|
101
|
-
|
102
|
-
@staticmethod
|
103
|
-
def start_span_from_mcp_request(
|
104
|
-
method: str, params: Dict[str, Any]
|
105
|
-
) -> Tuple[trace.Span, OtelContext]:
|
106
|
-
"""Extract trace context from incoming MCP request and start a new span"""
|
107
|
-
# Extract trace context from _meta if present
|
108
|
-
carrier = {}
|
109
|
-
_meta = params.get("_meta", {})
|
110
|
-
if "traceparent" in _meta:
|
111
|
-
carrier["traceparent"] = _meta["traceparent"]
|
112
|
-
if "tracestate" in _meta:
|
113
|
-
carrier["tracestate"] = _meta["tracestate"]
|
114
|
-
|
115
|
-
# Extract context and start span
|
116
|
-
ctx = otel_extract(carrier, context=OtelContext())
|
117
|
-
tracer = trace.get_tracer(__name__)
|
118
|
-
span = tracer.start_span(method, context=ctx, kind=SpanKind.SERVER)
|
119
|
-
return span, set_span_in_context(span)
|
120
|
-
|
121
|
-
@staticmethod
|
122
|
-
def inject_trace_context(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
123
|
-
"""Inject current trace context into outgoing MCP request arguments"""
|
124
|
-
carrier = {}
|
125
|
-
TraceContextTextMapPropagator().inject(carrier)
|
126
|
-
|
127
|
-
# Create or update _meta with trace context
|
128
|
-
_meta = arguments.get("_meta", {})
|
129
|
-
if "traceparent" in carrier:
|
130
|
-
_meta["traceparent"] = carrier["traceparent"]
|
131
|
-
if "tracestate" in carrier:
|
132
|
-
_meta["tracestate"] = carrier["tracestate"]
|
133
|
-
arguments["_meta"] = _meta
|
134
|
-
|
135
|
-
return arguments
|
136
|
-
|
137
|
-
|
138
|
-
telemetry = TelemetryManager()
|
File without changes
|
File without changes
|
File without changes
|