Repository navigation
perf: lazy-load mcp and jsonschema to reduce import time - #1424
reginaldalfret wants to merge 3 commits into
Conversation
Importing claude_agent_sdk previously imported mcp and jsonschema at module initialization time, adding ~1+ seconds of startup latency even for callers only using query(), ClaudeSDKClient, or session utilities without in-process SDK MCP servers. - Defer mcp and jsonschema imports in __init__.py until create_sdk_mcp_server() is called. - Dynamically define and cache ToolAnnotations via module-level __getattr__ on first access, preserving type annotations, docstring, and wire alias resolution. - Import SdkMcpBridge in Query.__init__ only when sdk_mcp_servers is non-empty. - Add tests/test_lazy_import.py verifying subprocess import isolation and on-demand lazy loading. Closes anthropics#1386 Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>
Somay-kousis
left a comment
There was a problem hiding this comment.
Ran this at dd88abe against merge base dd27b70 (Python 3.12, repo .venv). The new tests pass on head: test_lazy_import + test_sdk_mcp_integration + test_mcp_large_output, 166 passed. With src reverted, the 6 lazy-import tests fail, as expected. import claude_agent_sdk no longer loads mcp or jsonschema. Two public objects no longer work under standard introspection.
1. typing.get_type_hints() raises on SdkMcpTool and tool.
from __future__ import annotations (__init__.py:3) turns the annotations at __init__.py:298 (SdkMcpTool.annotations) and __init__.py:305 (tool(..., annotations=...)) into strings that name _McpToolAnnotations. That name now exists only under TYPE_CHECKING (__init__.py:30-32). get_type_hints resolves names against the module __dict__, and module __getattr__ (__init__.py:679) is never consulted for that. This fails even after mcp has been loaded (ToolAnnotations accessed and a server created):
head: get_type_hints(SdkMcpTool) -> NameError name '_McpToolAnnotations' is not defined
get_type_hints(tool) -> NameError name '_McpToolAnnotations' is not defined
base: get_type_hints(SdkMcpTool) -> {... 'annotations': mcp ToolAnnotations | None}
Tools that resolve hints through get_type_hints (sphinx-autodoc-typehints, typeguard) hit this. One fix: in _make_tool_annotations_class() and in the _McpToolAnnotations branch of __getattr__, write both ToolAnnotations and _McpToolAnnotations into globals(). That makes hints resolve once mcp is loaded. A test that calls get_type_hints(SdkMcpTool) after create_sdk_mcp_server() would lock it in.
2. ToolAnnotations instances no longer pickle.
The class is now defined inside _make_tool_annotations_class() (__init__.py:255-287). __module__ is reset, but __qualname__ stays _make_tool_annotations_class.<locals>.ToolAnnotations:
head: pickle.dumps(ToolAnnotations(readOnlyHint=True)) -> AttributeError Can't get local object '_make_tool_annotations_class.<locals>.ToolAnnotations'
base: round-trips
Adding ToolAnnotations.__qualname__ = "ToolAnnotations" next to the __module__ line fixes it. I checked by setting it at runtime on head: the round-trip then succeeds, because pickle looks up claude_agent_sdk.ToolAnnotations and gets the cached class. This matters for multiprocessing and for anything that caches tool definitions.
Not checked: mcp 1.x (ran with the repo's installed mcp only), Python 3.10/3.11, mypy and IDE behaviour under the new TYPE_CHECKING class body, from claude_agent_sdk import *.
Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>
|
Reran on bd72125: One case is left: before anything loads mcp, both still raise |
…hints before mcp load Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>
|
@Somay-kousis Thanks for catching that import-hook / autodoc edge case! In commit 0287cca, I've resolved this by providing a lightweight runtime _McpToolAnnotations placeholder at module initialization (if not TYPE_CHECKING) with module = "mcp.types" and name = "ToolAnnotations". When MCP is subsequently loaded via _make_tool_annotations_class(), it seamlessly updates globals()["_McpToolAnnotations"] to the real class from mcp.types.
|

Summary
Addresses #1386.
Importing \claude_agent_sdk\ previously imported \mcp\ (and \jsonschema) unconditionally at module import time. On typical environments, importing \mcp\ takes ~0.9–1.2 seconds, which constitutes >80% of total \claude_agent_sdk\ import duration. Callers who only use \query(), \ClaudeSDKClient, or session management without defining in-process SDK MCP servers were forced to incur this startup overhead on every process spawn.
Changes
Lazy ToolAnnotations:
Deferred Server Construction:
Deferred MCP Bridge in Query:
Regression Tests:
Test plan
uff check src tests\ passed with zero errors.
uff format --check src tests\ passed with zero errors.
Closes #1386