mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: feature: Inject OpenTelemetry trace context into MCP requests and update… (#3780)
* feat: Inject OpenTelemetry trace context into MCP requests and update documentation * Update python/samples/getting_started/observability/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update python/packages/core/tests/core/test_mcp.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: move opentelemetry import to module level OpenTelemetry is a hard dependency of agent-framework-core (per pyproject.toml), so the try/except ImportError guard was dead code. Move the import to the top of the file to fail fast on missing dependencies instead of silently hiding installation issues. --------- Co-authored-by: Pete Roden <Pete.Roden@microsoft.com> Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
bf7056a131
commit
36d52a1f9f
@@ -2591,3 +2591,70 @@ async def test_mcp_tool_filters_framework_kwargs():
|
||||
assert "thread" not in arguments
|
||||
assert "conversation_id" not in arguments
|
||||
assert "options" not in arguments
|
||||
|
||||
|
||||
# region: OTel trace context propagation via _meta
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"use_span,expect_traceparent",
|
||||
[
|
||||
(True, True),
|
||||
(False, False),
|
||||
],
|
||||
)
|
||||
async def test_mcp_tool_call_tool_otel_meta(use_span, expect_traceparent, span_exporter):
|
||||
"""call_tool propagates OTel trace context via meta only when a span is active."""
|
||||
from opentelemetry import trace
|
||||
|
||||
class TestServer(MCPTool):
|
||||
async def connect(self):
|
||||
self.session = Mock(spec=ClientSession)
|
||||
self.session.list_tools = AsyncMock(
|
||||
return_value=types.ListToolsResult(
|
||||
tools=[
|
||||
types.Tool(
|
||||
name="test_tool",
|
||||
description="Test tool",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {"param": {"type": "string"}},
|
||||
"required": ["param"],
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
self.session.call_tool = AsyncMock(
|
||||
return_value=types.CallToolResult(content=[types.TextContent(type="text", text="result")])
|
||||
)
|
||||
|
||||
def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]:
|
||||
return None
|
||||
|
||||
server = TestServer(name="test_server")
|
||||
async with server:
|
||||
await server.load_tools()
|
||||
|
||||
if use_span:
|
||||
tracer = trace.get_tracer("test")
|
||||
with tracer.start_as_current_span("test_span"):
|
||||
await server.functions[0].invoke(param="test_value")
|
||||
else:
|
||||
# Use an invalid span to ensure no trace context is injected;
|
||||
# call server.call_tool directly to bypass FunctionTool.invoke's own span.
|
||||
with trace.use_span(trace.NonRecordingSpan(trace.INVALID_SPAN_CONTEXT)):
|
||||
await server.call_tool("test_tool", param="test_value")
|
||||
|
||||
meta = server.session.call_tool.call_args.kwargs.get("meta")
|
||||
if expect_traceparent:
|
||||
# When a valid span is active, we expect some propagation fields to be injected,
|
||||
# but we do not assume any specific header name to keep this test propagator-agnostic.
|
||||
assert meta is not None
|
||||
assert isinstance(meta, dict)
|
||||
assert len(meta) > 0
|
||||
else:
|
||||
assert meta is None
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
Reference in New Issue
Block a user