Skip to content

Commit 4df8976

Browse files
authored
feat(mcp): add instrumentation support for Streamable-HTTP transport and bump mcp dependency to ≥1.8.1 (#1640)
1 parent 5077a06 commit 4df8976

File tree

5 files changed

+71
-9
lines changed

5 files changed

+71
-9
lines changed

python/instrumentation/openinference-instrumentation-mcp/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies = [
3232

3333
[project.optional-dependencies]
3434
instruments = [
35-
"mcp >= 1.8.0",
35+
"mcp >= 1.8.1",
3636
]
3737

3838
[project.entry-points.opentelemetry_instrumentor]

python/instrumentation/openinference-instrumentation-mcp/src/openinference/instrumentation/mcp/__init__.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,43 @@ def instrumentation_dependencies(self) -> Collection[str]:
2121
def _instrument(self, **kwargs: Any) -> None:
2222
register_post_import_hook(
2323
lambda _: wrap_function_wrapper(
24-
"mcp.client.sse", "sse_client", self._transport_wrapper
24+
"mcp.client.streamable_http",
25+
"streamablehttp_client",
26+
self._wrap_transport_with_callback,
27+
),
28+
"mcp.client.streamable_http",
29+
)
30+
31+
register_post_import_hook(
32+
lambda _: wrap_function_wrapper(
33+
"mcp.server.streamable_http",
34+
"StreamableHTTPServerTransport.connect",
35+
self._wrap_plain_transport,
36+
),
37+
"mcp.server.streamable_http",
38+
)
39+
40+
register_post_import_hook(
41+
lambda _: wrap_function_wrapper(
42+
"mcp.client.sse", "sse_client", self._wrap_plain_transport
2543
),
2644
"mcp.client.sse",
2745
)
2846
register_post_import_hook(
2947
lambda _: wrap_function_wrapper(
30-
"mcp.server.sse", "SseServerTransport.connect_sse", self._transport_wrapper
48+
"mcp.server.sse", "SseServerTransport.connect_sse", self._wrap_plain_transport
3149
),
3250
"mcp.server.sse",
3351
)
3452
register_post_import_hook(
3553
lambda _: wrap_function_wrapper(
36-
"mcp.client.stdio", "stdio_client", self._transport_wrapper
54+
"mcp.client.stdio", "stdio_client", self._wrap_plain_transport
3755
),
3856
"mcp.client.stdio",
3957
)
4058
register_post_import_hook(
4159
lambda _: wrap_function_wrapper(
42-
"mcp.server.stdio", "stdio_server", self._transport_wrapper
60+
"mcp.server.stdio", "stdio_server", self._wrap_plain_transport
4361
),
4462
"mcp.server.stdio",
4563
)
@@ -63,7 +81,18 @@ def _uninstrument(self, **kwargs: Any) -> None:
6381
unwrap("mcp.server.stdio", "stdio_server")
6482

6583
@asynccontextmanager
66-
async def _transport_wrapper(
84+
async def _wrap_transport_with_callback(
85+
self, wrapped: Callable[..., Any], instance: Any, args: Any, kwargs: Any
86+
) -> AsyncGenerator[Tuple["InstrumentedStreamReader", "InstrumentedStreamWriter", Any], None]:
87+
async with wrapped(*args, **kwargs) as (read_stream, write_stream, get_session_id_callback):
88+
yield (
89+
InstrumentedStreamReader(read_stream),
90+
InstrumentedStreamWriter(write_stream),
91+
get_session_id_callback,
92+
)
93+
94+
@asynccontextmanager
95+
async def _wrap_plain_transport(
6796
self, wrapped: Callable[..., Any], instance: Any, args: Any, kwargs: Any
6897
) -> AsyncGenerator[Tuple["InstrumentedStreamReader", "InstrumentedStreamWriter"], None]:
6998
async with wrapped(*args, **kwargs) as (read_stream, write_stream):

python/instrumentation/openinference-instrumentation-mcp/test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mcp==1.8.0
1+
mcp==1.8.1
22

33
httpx
44
opentelemetry-exporter-otlp-proto-http

python/instrumentation/openinference-instrumentation-mcp/tests/mcpserver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from openinference.instrumentation.mcp import MCPInstrumentor
99

10-
transport = cast(Literal["sse", "stdio"], os.environ.get("MCP_TRANSPORT"))
10+
transport = cast(Literal["sse", "stdio", "streamable-http"], os.environ.get("MCP_TRANSPORT"))
1111
otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
1212
span_exporter = OTLPSpanExporter(f"{otlp_endpoint}/v1/traces")
1313
tracer_provider = trace_sdk.TracerProvider()

python/instrumentation/openinference-instrumentation-mcp/tests/test_instrumenter.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ async def mcp_client(
2626
# instrumentation through fixtures instead.
2727
from mcp.client.sse import sse_client
2828
from mcp.client.stdio import StdioServerParameters, stdio_client
29+
from mcp.client.streamable_http import streamablehttp_client
2930

3031
async def message_handler(
3132
message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception,
@@ -86,9 +87,41 @@ async def message_handler(
8687
finally:
8788
proc.kill()
8889
await proc.wait()
90+
case "streamable-http":
91+
proc = await asyncio.create_subprocess_exec(
92+
sys.executable,
93+
server_script,
94+
env={
95+
"MCP_TRANSPORT": "streamable-http",
96+
"OTEL_EXPORTER_OTLP_ENDPOINT": otlp_endpoint,
97+
"PYTHONPATH": pythonpath,
98+
},
99+
stdout=subprocess.PIPE,
100+
stderr=subprocess.PIPE,
101+
)
102+
try:
103+
stderr = proc.stderr
104+
assert stderr is not None
105+
for i in range(100):
106+
line = str(await stderr.readline())
107+
if "Uvicorn running on http://0.0.0.0:" in line:
108+
_, rest = line.split("http://0.0.0.0:", 1)
109+
port, _ = rest.split(" ", 1)
110+
async with streamablehttp_client(f"http://localhost:{port}/mcp") as (
111+
reader,
112+
writer,
113+
_,
114+
), ClientSession(reader, writer, message_handler=message_handler) as client:
115+
client._receive_request_type = TestServerRequest
116+
await client.initialize()
117+
yield client
118+
break
119+
finally:
120+
proc.kill()
121+
await proc.wait()
89122

90123

91-
@pytest.mark.parametrize("transport", ["sse", "stdio"])
124+
@pytest.mark.parametrize("transport", ["sse", "stdio", "streamable-http"])
92125
async def test_hello(
93126
transport: str, tracer: Tracer, telemetry: Telemetry, otlp_collector: OTLPServer
94127
) -> None:

0 commit comments

Comments
 (0)