Skip to content

Commit ea1f4f4

Browse files
authored
fix(mcp-instrumentation) Suppress MCP /ping spans when agent observability is enabled (#748)
- Suppress noisy MCP `/ping` spans when `AGENT_OBSERVABILITY_ENABLED=true` - Ping spans still emitted when agent observability is disabled - Added `test_ping_span_suppression` test with subtests for both enabled/disabled cases
1 parent e1dd254 commit ea1f4f4

3 files changed

Lines changed: 32 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ If your change does not need a CHANGELOG entry, add the "skip changelog" label t
1212

1313
## Unreleased
1414

15+
- fix(mcp-instrumentation): suppress MCP `/ping` spans when agent observability is enabled
16+
([#748](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/748))
1517
- fix(agent-observability): fall back to OTEL_EXPORTER_OTLP_ENDPOINT for unsampled spans; also export unsampled spans to non-AWS endpoints
1618
([#738](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/738))
1719
- feat: auto-detect and mutually exclude AWS native vs third-party agentic instrumentors; add `AWS_AGENTIC_INSTRUMENTATION_OPT_IN` env var to override auto-detection

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/instrumentation/mcp/_wrappers.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Callable, Coroutine, Dict, Optional, Tuple
99
from urllib.parse import urlparse
1010

11+
from amazon.opentelemetry.distro._utils import is_agent_observability_enabled
1112
from amazon.opentelemetry.distro.instrumentation.common.instrumentation_utils import serialize_to_json_string
1213
from opentelemetry import context, trace
1314
from opentelemetry.instrumentation.utils import suppress_http_instrumentation
@@ -60,17 +61,22 @@ def __init__(self, tracer: trace.Tracer, **kwargs: Any) -> None:
6061
self._should_suppress_http_spans = (
6162
os.environ.get(OTEL_MCP_SUPPRESS_HTTP_INSTRUMENTATION, "true").lower() == "true"
6263
)
64+
self._agent_observability_enabled = is_agent_observability_enabled()
6365

64-
@staticmethod
65-
def _should_suppress_mcp_span(message: Any) -> bool:
66+
def _should_suppress_mcp_span(self, message: Any) -> bool:
6667
from mcp import types # pylint: disable=import-outside-toplevel
6768

6869
if isinstance(
6970
message, (types.ClientRequest, types.ClientNotification, types.ServerRequest, types.ServerNotification)
7071
):
7172
message = message.root
7273
# noisy spans most of the time
73-
return isinstance(message, (types.InitializeRequest, types.InitializedNotification))
74+
if isinstance(message, (types.InitializeRequest, types.InitializedNotification)):
75+
return True
76+
# MCP servers hosted on AgentCore get frequent /ping health checks
77+
if self._agent_observability_enabled and isinstance(message, types.PingRequest):
78+
return True
79+
return False
7480

7581
@staticmethod
7682
def _set_mcp_attributes(span: trace.Span, message: Any, request_id: Optional[int]) -> None:

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/instrumentation/mcp/test_mcp_instrumentor.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,27 @@ async def run(session):
473473
finally:
474474
HTTPXClientInstrumentor().uninstrument()
475475

476+
def test_ping_span_suppression(self):
477+
for agent_obs_enabled, expect_suppressed in [("true", True), ("false", False)]:
478+
with self.subTest(agent_observability=agent_obs_enabled):
479+
self.instrumentor.uninstrument()
480+
self.span_exporter.clear()
481+
with unittest.mock.patch.dict(os.environ, {"AGENT_OBSERVABILITY_ENABLED": agent_obs_enabled}):
482+
self.instrumentor.instrument(tracer_provider=self.tracer_provider, propagators=self.propagator)
483+
self.server = self._create_server()
484+
485+
async def run(session):
486+
await session.send_ping()
487+
488+
asyncio.run(self._run_inprocess(run))
489+
spans = self.span_exporter.get_finished_spans()
490+
491+
ping_spans = [s for s in spans if "ping" in s.name.lower()]
492+
if expect_suppressed:
493+
self.assertEqual(len(ping_spans), 0, "Ping spans should be suppressed")
494+
else:
495+
self.assertGreater(len(ping_spans), 0, "Ping spans should not be suppressed")
496+
476497
def test_mcp_respects_active_parent_span(self):
477498
tracer = get_tracer("test", tracer_provider=self.tracer_provider)
478499

0 commit comments

Comments
 (0)