|
25 | 25 | import sys |
26 | 26 | import types as builtin_types |
27 | 27 | import typing |
28 | | -from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Union, _UnionGenericAlias # type: ignore |
| 28 | +from typing import Annotated, Any, Callable, Dict, List, Literal, Optional, Sequence, Union, _UnionGenericAlias # type: ignore |
29 | 29 | import pydantic |
30 | | -from pydantic import ConfigDict, Field, PrivateAttr, model_validator |
| 30 | +from pydantic import ConfigDict, Field, PrivateAttr, WrapValidator, model_validator |
31 | 31 | from typing_extensions import Self, TypedDict |
32 | 32 | from . import _common |
33 | 33 | from ._operations_converters import ( |
|
63 | 63 | except ImportError: |
64 | 64 | PIL_Image = None |
65 | 65 |
|
66 | | -_is_mcp_imported = False |
67 | 66 | if typing.TYPE_CHECKING: |
68 | 67 | from mcp import types as mcp_types |
69 | 68 | from mcp import ClientSession as McpClientSession |
70 | 69 | from mcp.types import CallToolResult as McpCallToolResult |
71 | | - |
72 | | - _is_mcp_imported = True |
73 | 70 | else: |
74 | 71 | McpClientSession: typing.Type = Any |
75 | 72 | McpCallToolResult: typing.Type = Any |
76 | | - try: |
77 | | - from mcp import types as mcp_types |
78 | | - from mcp import ClientSession as McpClientSession |
79 | | - from mcp.types import CallToolResult as McpCallToolResult |
80 | 73 |
|
81 | | - _is_mcp_imported = True |
82 | | - except ImportError: |
83 | | - McpClientSession = None |
84 | | - McpCallToolResult = None |
| 74 | + |
| 75 | +def _is_mcp_imported() -> bool: |
| 76 | + return 'mcp' in sys.modules |
| 77 | + |
85 | 78 |
|
86 | 79 | if typing.TYPE_CHECKING: |
87 | 80 | import yaml |
@@ -1800,7 +1793,7 @@ class FunctionResponse(_common.BaseModel): |
1800 | 1793 | def from_mcp_response( |
1801 | 1794 | cls, *, name: str, response: McpCallToolResult |
1802 | 1795 | ) -> 'FunctionResponse': |
1803 | | - if not _is_mcp_imported: |
| 1796 | + if not _is_mcp_imported(): |
1804 | 1797 | raise ValueError( |
1805 | 1798 | 'MCP response is not supported. Please ensure that the MCP library is' |
1806 | 1799 | ' imported.' |
@@ -4907,17 +4900,41 @@ class ToolDict(TypedDict, total=False): |
4907 | 4900 |
|
4908 | 4901 |
|
4909 | 4902 | ToolOrDict = Union[Tool, ToolDict] |
4910 | | -if _is_mcp_imported: |
| 4903 | + |
| 4904 | + |
| 4905 | +def _validate_tool_list(v: Any, handler: Any) -> Any: |
| 4906 | + """Pass MCP tool/session objects through Pydantic validation untouched. |
| 4907 | + |
| 4908 | + `Tool` has all-optional fields, so without this wrapper Pydantic's default |
| 4909 | + Union resolution would coerce any non-Tool item (e.g. an `mcp.ClientSession`) |
| 4910 | + into an empty `Tool()`. This wrapper dispatches dict -> Tool and leaves |
| 4911 | + every other type identity-preserved so MCP routing downstream still works. |
| 4912 | + """ |
| 4913 | + if v is None: |
| 4914 | + return None |
| 4915 | + if not isinstance(v, list): |
| 4916 | + return handler(v) |
| 4917 | + out = [] |
| 4918 | + for item in v: |
| 4919 | + if isinstance(item, dict): |
| 4920 | + out.append(Tool.model_validate(item)) |
| 4921 | + else: |
| 4922 | + out.append(item) |
| 4923 | + return out |
| 4924 | + |
| 4925 | + |
| 4926 | +if typing.TYPE_CHECKING: |
4911 | 4927 | ToolUnion = Union[Tool, Callable[..., Any], mcp_types.Tool, McpClientSession] |
4912 | 4928 | ToolUnionDict = Union[ |
4913 | 4929 | ToolDict, Callable[..., Any], mcp_types.Tool, McpClientSession |
4914 | 4930 | ] |
| 4931 | + ToolListUnion = list[ToolUnion] |
| 4932 | + ToolListUnionDict = list[ToolUnionDict] |
4915 | 4933 | else: |
4916 | 4934 | ToolUnion = Union[Tool, Callable[..., Any]] # type: ignore[misc] |
4917 | 4935 | ToolUnionDict = Union[ToolDict, Callable[..., Any]] # type: ignore[misc] |
4918 | | - |
4919 | | -ToolListUnion = list[ToolUnion] |
4920 | | -ToolListUnionDict = list[ToolUnionDict] |
| 4936 | + ToolListUnion = Annotated[list, WrapValidator(_validate_tool_list)] |
| 4937 | + ToolListUnionDict = list[ToolUnionDict] |
4921 | 4938 |
|
4922 | 4939 | SchemaUnion = Union[ |
4923 | 4940 | dict[Any, Any], type, Schema, builtin_types.GenericAlias, VersionedUnionType # type: ignore[valid-type] |
|
0 commit comments