Skip to content

Python: MCPStreamableHTTPTool uses deprecated httpx client, preventing session isolation #3074

@ecoer-lumen

Description

@ecoer-lumen

When using MCPStreamableHTTPTool with sequential agent runs in long-running Python process (FastAPI), httpx client never closes, causing session/transport reuse and 409 errors from Microsoft Foundry when multiple sequential agent runs in the same process reuse the same httpx connection pool.

Environment

  • agent-framework-core: 1.0.0b251218
  • mcp: 1.25.0
  • httpx: 0.28.1
  • Python: 3.13

Reproduction

import pytest
from agent_framework import MCPStreamableHTTPTool

@pytest.mark.asyncio
async def test_httpx_client_reuse():

    # Create and use first tool
    tool1 = MCPStreamableHTTPTool(
        name="test", 
        url="http://localhost:8081/mcp",
        load_tools=False,
        load_prompts=False,
        approval_mode="never_require",
        terminate_on_close=False,
        timeout=30,
    )
    await tool1.connect()
    client1_id = id(getattr(tool1, "_httpx_client", None))
    await tool1.close()
    del tool1
    
    # Create and use second tool
    tool2 = MCPStreamableHTTPTool(
        name="test",
        url="http://localhost:8081/mcp", 
        load_tools=False,
        load_prompts=False,
        approval_mode="never_require",
        terminate_on_close=False,
        timeout=30,
    )
    await tool2.connect()
    client2_id = id(getattr(tool2, "_httpx_client", None))
    
    # BUG: Same httpx client object reused (should be different)
    print(f"Client 1 ID: {client1_id}")
    print(f"Client 2 ID: {client2_id}")
    assert client1_id == client2_id, "BUG: httpx client reused!"
    
    await tool2.close()

Root Cause

agent_framework/_mcp.py#L981-L1001 uses the deprecated streamablehttp_client() which creates an httpx client that is never closed:

def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]:
    args: dict[str, Any] = {"url": self.url, ...}
    return streamablehttp_client(**args)

When MCPTool.close() is called, it only closes the MCP session, not the httpx client.

Proposed Solution

The fix involves 3 changes to agent_framework/_mcp.py:

1. Add httpx import and new API

import httpx
from mcp.client.streamable_http import streamable_http_client, streamablehttp_client

2. Track httpx client in MCPStreamableHTTPTool

In __init__() (line ~982):

self._httpx_client: httpx.AsyncClient | None = None

3. Create and manage httpx client

Replace get_mcp_client() (lines 985-1005):

def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]:
    """Get an MCP streamable HTTP client."""
    timeout_value = self.timeout if self.timeout is not None else 30.0
    sse_timeout_value = self.sse_read_timeout if self.sse_read_timeout is not None else 300.0
    
    # Create and track httpx client
    self._httpx_client = httpx.AsyncClient(
        headers=self.headers,
        timeout=httpx.Timeout(timeout_value, read=sse_timeout_value),
        **self._client_kwargs
    )
    
    # Use new API instead of deprecated streamablehttp_client()
    return streamable_http_client(
        url=self.url,
        http_client=self._httpx_client,
        terminate_on_close=self.terminate_on_close if self.terminate_on_close is not None else True,
    )

4. Override close() to cleanup httpx client

Add after get_mcp_client():

async def close(self) -> None:
    """Disconnect from the MCP server and close httpx client."""
    await super().close()
    
    if self._httpx_client is not None:
        await self._httpx_client.aclose()
        self._httpx_client = None

This ensures httpx clients are properly closed and connection pools don't leak between tool instances.

Metadata

Metadata

Labels

pythonv1.0Features being tracked for the version 1.0 GA

Type

No type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions