Compare commits

...

2 Commits

Author SHA1 Message Date
Tao Chen 4aed547907 Address copilot comments 2026-07-21 14:29:25 -07:00
pratikwayase 1354c43d1f fix(foundry_hosting): preserve auth credentials across FoundryToolbox reconnections 2026-07-19 23:08:56 +05:30
2 changed files with 80 additions and 2 deletions
@@ -4,7 +4,8 @@ from __future__ import annotations
import logging
import os
from typing import TYPE_CHECKING
from contextlib import _AsyncGeneratorContextManager # pyright: ignore[reportPrivateUsage]
from typing import TYPE_CHECKING, Any, override
from urllib.parse import urlsplit
import httpx
@@ -170,6 +171,9 @@ class FoundryToolbox(MCPStreamableHTTPTool):
auth=_ToolboxAuth(credential, token_scope),
timeout=timeout,
)
self._credential = credential
self._token_scope = token_scope
self._timeout = timeout
super().__init__(
name=tool_name,
@@ -179,8 +183,22 @@ class FoundryToolbox(MCPStreamableHTTPTool):
load_tools=load_tools,
)
@override
def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]:
"""Get an authenticated MCP HTTP client.
Recreates the underlying HTTP client if it was previously closed.
"""
if self._httpx_client is None:
self._httpx_client = httpx.AsyncClient(
auth=_ToolboxAuth(self._credential, self._token_scope),
timeout=self._timeout,
)
return super().get_mcp_client()
@override
async def close(self) -> None:
"""Close the MCP session and the toolbox-owned HTTP client."""
"""Close the MCP session and toolbox HTTP client while preserving credentials and timeout for reconnection."""
try:
await super().close()
finally:
@@ -1,4 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
# pyright: reportPrivateUsage=false
"""Unit tests for FoundryToolbox."""
@@ -229,3 +230,62 @@ async def test_skills_source_uses_connected_session(monkeypatch: pytest.MonkeyPa
assert result == ["skill-a"]
assert captured["client"] is sentinel_session
class TestFoundryToolboxReconnection:
async def test_close_preserves_credential_for_reconnection(self) -> None:
"""After close(), get_mcp_client() should recreate an authenticated client."""
cred = _FakeCredential("reconnect-token")
toolbox = FoundryToolbox(
cred,
url="https://h/toolboxes/recon/mcp",
timeout=60.0,
)
assert toolbox._credential is cred
assert toolbox._token_scope == "https://ai.azure.com/.default"
assert toolbox._timeout == 60.0
assert toolbox._httpx_client is not None
assert isinstance(toolbox._httpx_client.auth, _ToolboxAuth)
original_auth = toolbox._httpx_client.auth
client = toolbox._httpx_client
client.aclose = AsyncMock()
await toolbox.close()
client.aclose.assert_awaited_once()
assert toolbox._httpx_client is None
assert toolbox._credential is cred
assert toolbox._timeout == 60.0
ctx_manager = toolbox.get_mcp_client()
assert toolbox._httpx_client is not None
assert isinstance(toolbox._httpx_client.auth, _ToolboxAuth)
new_auth = toolbox._httpx_client.auth
assert new_auth is not original_auth
assert new_auth._credential is cred
assert hasattr(ctx_manager, "__aenter__")
assert hasattr(ctx_manager, "__aexit__")
await toolbox.close()
async def test_close_idempotent_with_reconnection(self) -> None:
"""Multiple close() calls don't break reconnection."""
cred = _FakeCredential()
toolbox = FoundryToolbox(
cred,
url="https://h/toolboxes/idem/mcp",
)
await toolbox.close()
await toolbox.close()
toolbox.get_mcp_client()
assert toolbox._httpx_client is not None
assert isinstance(toolbox._httpx_client.auth, _ToolboxAuth)
await toolbox.close()