Skip to content

Commit d207ad3

Browse files
Josina20JosinaJoy
andauthored
Fix MCPStreamableHTTPTool auth headers not being passed correctly (#146)
* Fix MCPStreamableHTTPTool auth headers not being passed correctly * code review * code review * Add unit tests for httpx client configuration in MCP tool registration Tests verify that: - Authorization header is set on httpx.AsyncClient - User-Agent header is set on httpx.AsyncClient - MCP_HTTP_CLIENT_TIMEOUT_SECONDS constant (90s) is used - MCPStreamableHTTPTool receives http_client param (not headers) - httpx clients are tracked in _http_clients for cleanup - cleanup() properly closes all httpx clients These tests prevent regression to the bug where passing headers directly to MCPStreamableHTTPTool was silently ignored. * Add end-to-end httpx client lifecycle tests New TestHttpxClientLifecycle class with 4 tests: - test_full_client_lifecycle_single_server: verifies client created by add_tool_servers_to_agent() is properly closed by cleanup() - test_full_client_lifecycle_multiple_servers: verifies all clients are tracked and cleaned up when multiple MCP servers are configured - test_cleanup_idempotent_no_clients: verifies cleanup() is safe when no clients exist - test_cleanup_called_twice_after_creating_clients: verifies calling cleanup() multiple times doesn't cause issues These tests ensure connection/file-descriptor leaks are prevented by verifying the full client lifecycle from creation to cleanup. * Add httpx as runtime dependency for agentframework tooling extension httpx is now used directly in mcp_tool_registration_service.py to create AsyncClient instances with pre-configured headers. Without this explicit dependency, consumers could hit ModuleNotFoundError if httpx isn't available through transitive dependencies. * Fix linting issues in MCP tool registration tests - Remove unused httpx import - Remove unused mock_mcp_tool variable assignments - Sort imports properly - Remove trailing whitespace from blank lines * Apply ruff formatting to test file --------- Co-authored-by: Josina Joy <josjoy@microsoft.com>
1 parent fb1b171 commit d207ad3

File tree

3 files changed

+752
-3
lines changed

3 files changed

+752
-3
lines changed

libraries/microsoft-agents-a365-tooling-extensions-agentframework/microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from agent_framework import ChatAgent, ChatMessage, ChatMessageStoreProtocol, MCPStreamableHTTPTool
1010
from agent_framework.azure import AzureOpenAIChatClient
1111
from agent_framework.openai import OpenAIChatClient
12+
import httpx
1213

1314
from microsoft_agents.hosting.core import Authorization, TurnContext
1415

@@ -24,6 +25,10 @@
2425
)
2526

2627

28+
# Default timeout for MCP server HTTP requests (in seconds)
29+
MCP_HTTP_CLIENT_TIMEOUT_SECONDS = 90.0
30+
31+
2732
class McpToolRegistrationService:
2833
"""
2934
Provides MCP tool registration services for Agent Framework agents.
@@ -46,6 +51,7 @@ def __init__(self, logger: Optional[logging.Logger] = None):
4651
logger=self._logger
4752
)
4853
self._connected_servers = []
54+
self._http_clients: List[httpx.AsyncClient] = []
4955

5056
async def add_tool_servers_to_agent(
5157
self,
@@ -114,11 +120,17 @@ async def add_tool_servers_to_agent(
114120
self._orchestrator_name
115121
)
116122

117-
# Create and configure MCPStreamableHTTPTool
123+
# Create httpx client with auth headers configured
124+
http_client = httpx.AsyncClient(
125+
headers=headers, timeout=MCP_HTTP_CLIENT_TIMEOUT_SECONDS
126+
)
127+
self._http_clients.append(http_client)
128+
129+
# Create and configure MCPStreamableHTTPTool with http_client
118130
mcp_tools = MCPStreamableHTTPTool(
119131
name=server_name,
120132
url=config.url,
121-
headers=headers,
133+
http_client=http_client,
122134
description=f"MCP tools from {server_name}",
123135
)
124136

@@ -339,12 +351,21 @@ async def send_chat_history_from_store(
339351
async def cleanup(self):
340352
"""Clean up any resources used by the service."""
341353
try:
354+
# Close MCP server connections
342355
for plugin in self._connected_servers:
343356
try:
344357
if hasattr(plugin, "close"):
345358
await plugin.close()
346359
except Exception as cleanup_ex:
347-
self._logger.debug(f"Error during cleanup: {cleanup_ex}")
360+
self._logger.debug(f"Error during plugin cleanup: {cleanup_ex}")
348361
self._connected_servers.clear()
362+
363+
# Close httpx clients to prevent connection/file descriptor leaks
364+
for http_client in self._http_clients:
365+
try:
366+
await http_client.aclose()
367+
except Exception as client_ex:
368+
self._logger.debug(f"Error closing http client: {client_ex}")
369+
self._http_clients.clear()
349370
except Exception as ex:
350371
self._logger.debug(f"Error during service cleanup: {ex}")

libraries/microsoft-agents-a365-tooling-extensions-agentframework/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies = [
2828
"agent-framework-azure-ai >= 1.0.0b251114",
2929
"azure-identity >= 1.12.0",
3030
"typing-extensions >= 4.0.0",
31+
"httpx >= 0.27.0",
3132
]
3233

3334
[project.urls]

0 commit comments

Comments
 (0)