Skip to content

Commit 0500433

Browse files
authored
fix: #3046 preserve MCP re-export import errors (#3048)
1 parent 8d7f05b commit 0500433

2 files changed

Lines changed: 114 additions & 3 deletions

File tree

src/agents/mcp/__init__.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
try:
1+
from __future__ import annotations
2+
3+
from importlib import import_module
4+
from typing import TYPE_CHECKING, Any
5+
6+
if TYPE_CHECKING:
27
from .manager import MCPServerManager
38
from .server import (
49
LocalMCPApprovalCallable,
@@ -10,8 +15,6 @@
1015
MCPServerStreamableHttp,
1116
MCPServerStreamableHttpParams,
1217
)
13-
except ImportError:
14-
pass
1518

1619
from .util import (
1720
MCPToolMetaContext,
@@ -24,6 +27,18 @@
2427
create_static_tool_filter,
2528
)
2629

30+
_LAZY_EXPORTS = {
31+
"MCPServer": ".server",
32+
"MCPServerSse": ".server",
33+
"MCPServerSseParams": ".server",
34+
"MCPServerStdio": ".server",
35+
"MCPServerStdioParams": ".server",
36+
"MCPServerStreamableHttp": ".server",
37+
"MCPServerStreamableHttpParams": ".server",
38+
"MCPServerManager": ".manager",
39+
"LocalMCPApprovalCallable": ".server",
40+
}
41+
2742
__all__ = [
2843
"MCPServer",
2944
"MCPServerSse",
@@ -43,3 +58,26 @@
4358
"ToolFilterStatic",
4459
"create_static_tool_filter",
4560
]
61+
62+
63+
def __getattr__(name: str) -> Any:
64+
if name not in _LAZY_EXPORTS:
65+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
66+
67+
module_name = _LAZY_EXPORTS[name]
68+
try:
69+
module = import_module(module_name, __name__)
70+
except ImportError as exc:
71+
raise ImportError(
72+
f"Failed to import {name} from agents.mcp. "
73+
f"The agents.mcp{module_name} module could not be imported; "
74+
"see the chained ImportError for details."
75+
) from exc
76+
77+
value = getattr(module, name)
78+
globals()[name] = value
79+
return value
80+
81+
82+
def __dir__() -> list[str]:
83+
return sorted(set(globals()) | set(__all__))

tests/mcp/test_mcp_imports.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from __future__ import annotations
2+
3+
import importlib
4+
import importlib.abc
5+
import sys
6+
from types import ModuleType
7+
8+
import pytest
9+
10+
_SERVER_EXPORTS = (
11+
"LocalMCPApprovalCallable",
12+
"MCPServer",
13+
"MCPServerSse",
14+
"MCPServerSseParams",
15+
"MCPServerStdio",
16+
"MCPServerStdioParams",
17+
"MCPServerStreamableHttp",
18+
"MCPServerStreamableHttpParams",
19+
)
20+
21+
22+
class _BrokenMCPServerImportFinder(importlib.abc.MetaPathFinder):
23+
def find_spec(
24+
self,
25+
fullname: str,
26+
path: object | None,
27+
target: ModuleType | None = None,
28+
) -> None:
29+
if fullname == "agents.mcp.server":
30+
raise ImportError("simulated dependency import failure")
31+
return None
32+
33+
34+
def _clear_mcp_server_imports(
35+
monkeypatch: pytest.MonkeyPatch,
36+
mcp_module: ModuleType,
37+
) -> None:
38+
monkeypatch.delitem(sys.modules, "agents.mcp.server", raising=False)
39+
monkeypatch.delitem(mcp_module.__dict__, "server", raising=False)
40+
for name in _SERVER_EXPORTS:
41+
monkeypatch.delitem(mcp_module.__dict__, name, raising=False)
42+
43+
44+
def test_mcp_package_import_does_not_eagerly_import_server(
45+
monkeypatch: pytest.MonkeyPatch,
46+
) -> None:
47+
import agents.mcp as mcp_module
48+
49+
_clear_mcp_server_imports(monkeypatch, mcp_module)
50+
finder = _BrokenMCPServerImportFinder()
51+
monkeypatch.setattr(sys, "meta_path", [finder, *sys.meta_path])
52+
53+
reloaded_mcp = importlib.reload(mcp_module)
54+
55+
assert reloaded_mcp.MCPUtil is not None
56+
57+
58+
def test_mcp_server_reexport_preserves_underlying_import_error(
59+
monkeypatch: pytest.MonkeyPatch,
60+
) -> None:
61+
import agents.mcp as mcp_module
62+
63+
_clear_mcp_server_imports(monkeypatch, mcp_module)
64+
finder = _BrokenMCPServerImportFinder()
65+
monkeypatch.setattr(sys, "meta_path", [finder, *sys.meta_path])
66+
namespace: dict[str, object] = {}
67+
68+
with pytest.raises(ImportError) as exc_info:
69+
exec("from agents.mcp import MCPServerStreamableHttp", namespace)
70+
71+
assert "Failed to import MCPServerStreamableHttp from agents.mcp" in str(exc_info.value)
72+
assert isinstance(exc_info.value.__cause__, ImportError)
73+
assert "simulated dependency import failure" in str(exc_info.value.__cause__)

0 commit comments

Comments
 (0)