pyagentic-core 2.3.0a12__tar.gz → 2.4.0a3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyagentic_core-2.3.0a12/pyagentic_core.egg-info → pyagentic_core-2.4.0a3}/PKG-INFO +5 -1
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/mkdocs.yml +1 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/__init__.py +2 -1
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_agent/_agent.py +191 -10
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_info.py +13 -0
- pyagentic_core-2.4.0a3/pyagentic/_base/_mcp.py +264 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_metaclasses.py +51 -4
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_spec.py +48 -1
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_tool.py +25 -9
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/cli/__init__.py +2 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/cli/_run.py +79 -7
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/llm/_anthropic.py +57 -13
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/models/response.py +2 -1
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/__init__.py +7 -5
- pyagentic_core-2.4.0a3/pyagentic/serve/_agent_ref.py +254 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/_app.py +53 -10
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/_client_session.py +2 -22
- pyagentic_core-2.3.0a12/pyagentic/serve/_agent_ref.py → pyagentic_core-2.4.0a3/pyagentic/serve/_container.py +143 -305
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/_docker.py +5 -1
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/_manifest.py +33 -4
- pyagentic_core-2.4.0a3/pyagentic/serve/_mcp_server.py +78 -0
- pyagentic_core-2.4.0a3/pyagentic/serve/_sse.py +60 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3/pyagentic_core.egg-info}/PKG-INFO +5 -1
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic_core.egg-info/SOURCES.txt +15 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic_core.egg-info/requires.txt +5 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyproject.toml +4 -1
- pyagentic_core-2.4.0a3/tests/cli/test_init.py +106 -0
- pyagentic_core-2.4.0a3/tests/serve/__init__.py +0 -0
- pyagentic_core-2.4.0a3/tests/serve/test_agent_ref.py +451 -0
- pyagentic_core-2.4.0a3/tests/serve/test_app.py +151 -0
- pyagentic_core-2.4.0a3/tests/serve/test_client_session.py +265 -0
- pyagentic_core-2.4.0a3/tests/serve/test_discovery.py +46 -0
- pyagentic_core-2.4.0a3/tests/serve/test_docker.py +74 -0
- pyagentic_core-2.4.0a3/tests/serve/test_manifest.py +136 -0
- pyagentic_core-2.4.0a3/tests/serve/test_metaclass_models.py +135 -0
- pyagentic_core-2.4.0a3/tests/serve/test_sessions.py +102 -0
- pyagentic_core-2.4.0a3/tests/tracing/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/uv.lock +752 -36
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/.github/workflows/docs.yml +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/.github/workflows/release.yml +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/.github/workflows/testing.yml +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/.gitignore +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/CHANGELOG.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/LICENSE +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/README.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/agent-linking.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/deploy/building.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/deploy/creating-a-project.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/deploy/index.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/deploy/running.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/diagrams/declaration.svg +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/diagrams/instantiation.svg +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/diagrams/runtime.svg +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/diagrams/source/README.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/diagrams/source/declaration.d2 +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/diagrams/source/instantiation.d2 +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/diagrams/source/runtime.d2 +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/execution-modes.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/getting-started.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/images/langfuse.png +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/index.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/inheritance.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/observability.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/phases.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/policies.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/reference/architecture.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/reference/modules.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/responses.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/states.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/structured-output.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/docs/tools.md +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_agent/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_agent/_agent_linking.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_agent/_agent_state.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_exceptions.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_ref.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_state.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_base/_validation.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_utils/_typing.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_utils/_warnings.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/_version_scheme.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/cli/__main__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/cli/_build.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/cli/_init.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/cli/_publish.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/cli/_templates.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/llm/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/llm/_gemini.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/llm/_mock.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/llm/_openai.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/llm/_openaiv1.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/llm/_provider.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/logging.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/models/llm.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/models/tracing.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/policies/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/policies/_events.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/policies/_policy.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/_discovery.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/_exceptions.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/serve/_sessions.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/tracing/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/tracing/_basic.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/tracing/_langfuse.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/tracing/_tracer.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic/updates.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic_core.egg-info/dependency_links.txt +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic_core.egg-info/entry_points.txt +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/pyagentic_core.egg-info/top_level.txt +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/setup.cfg +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/setup.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_agent.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_agent_inheritance.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_agent_linking.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_agent_provider.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_params.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_phases.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_state.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_tool.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/_base/test_validator.py +0 -0
- {pyagentic_core-2.3.0a12/tests/tracing → pyagentic_core-2.4.0a3/tests/cli}/__init__.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/conftest.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/models/test_responses.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/tracing/test_basic_tracer.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/tracing/test_models.py +0 -0
- {pyagentic_core-2.3.0a12 → pyagentic_core-2.4.0a3}/tests/tracing/test_tracer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyagentic-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0a3
|
|
4
4
|
Summary: Build LLM Agents in a Pythonic way
|
|
5
5
|
Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -23,12 +23,16 @@ Requires-Dist: google-generativeai>=0.8.0
|
|
|
23
23
|
Requires-Dist: transitions>=0.9.3
|
|
24
24
|
Requires-Dist: ty>=0.0.15
|
|
25
25
|
Requires-Dist: jinja2>=3.1.6
|
|
26
|
+
Requires-Dist: fastmcp>=3.4.0
|
|
27
|
+
Provides-Extra: mcp
|
|
28
|
+
Requires-Dist: fastmcp>=2.0.0; extra == "mcp"
|
|
26
29
|
Provides-Extra: deploy
|
|
27
30
|
Requires-Dist: typer>=0.15.0; extra == "deploy"
|
|
28
31
|
Requires-Dist: fastapi>=0.115.0; extra == "deploy"
|
|
29
32
|
Requires-Dist: uvicorn>=0.34.0; extra == "deploy"
|
|
30
33
|
Requires-Dist: sse-starlette>=2.0.0; extra == "deploy"
|
|
31
34
|
Requires-Dist: httpx>=0.28.0; extra == "deploy"
|
|
35
|
+
Requires-Dist: fastmcp>=2.0.0; extra == "deploy"
|
|
32
36
|
Dynamic: license-file
|
|
33
37
|
|
|
34
38
|
# PyAgentic
|
|
@@ -38,9 +38,10 @@ Main Components:
|
|
|
38
38
|
from pyagentic._base._spec import spec
|
|
39
39
|
from pyagentic._base._agent._agent import BaseAgent, AgentExtension
|
|
40
40
|
from pyagentic._base._agent._agent_linking import Link
|
|
41
|
+
from pyagentic._base._mcp import MCPLink
|
|
41
42
|
from pyagentic._base._tool import tool
|
|
42
43
|
|
|
43
44
|
from pyagentic._base._state import State
|
|
44
45
|
from pyagentic._base._ref import ref
|
|
45
46
|
|
|
46
|
-
__all__ = ["BaseAgent", "AgentExtension", "tool", "spec", "State", "Link", "ref"]
|
|
47
|
+
__all__ = ["BaseAgent", "AgentExtension", "tool", "spec", "State", "Link", "MCPLink", "ref"]
|
|
@@ -27,6 +27,7 @@ from pyagentic._base._agent._agent_state import _AgentState
|
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
29
29
|
from pyagentic._base._agent._agent_linking import _LinkedAgentDefinition
|
|
30
|
+
from pyagentic._base._mcp import _MCPDefinition
|
|
30
31
|
|
|
31
32
|
from pyagentic.models.response import ToolResponse, AgentResponse
|
|
32
33
|
from pyagentic.models.llm import Message, ToolCall, LLMResponse
|
|
@@ -161,6 +162,7 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
161
162
|
__tool_defs__: ClassVar[dict[str, _ToolDefinition]] # Registered @tool methods
|
|
162
163
|
__state_defs__: ClassVar[dict[str, _StateDefinition]] # State field definitions
|
|
163
164
|
__linked_agents__: ClassVar[dict[str, "_LinkedAgentDefinition"]] # Linked agent definitions
|
|
165
|
+
__mcp_defs__: ClassVar[dict[str, "_MCPDefinition"]] # MCP server definitions
|
|
164
166
|
|
|
165
167
|
# User-set Class Attributes (defined in subclass)
|
|
166
168
|
__system_message__: ClassVar[str] # Required: system prompt for the agent
|
|
@@ -234,6 +236,126 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
234
236
|
if self.__tool_defs__ and not self.provider.__supports_tool_calls__:
|
|
235
237
|
raise Exception("Tools are not supported with this provider")
|
|
236
238
|
|
|
239
|
+
async def _ensure_mcp_connected(self):
|
|
240
|
+
"""Lazily connect to all configured MCP servers and merge their tools.
|
|
241
|
+
|
|
242
|
+
Called once at the top of ``_get_tool_defs()``. On first invocation
|
|
243
|
+
it connects to each MCP server, fetches available tools, applies
|
|
244
|
+
whitelist/blacklist filtering, converts them to ``_MCPToolDefinition``
|
|
245
|
+
instances, and shadows the class-level ``__tool_defs__`` and
|
|
246
|
+
``__tool_response_models__`` with instance-level merged dicts.
|
|
247
|
+
|
|
248
|
+
Populates ``_mcp_tool_routing`` for dispatching tool calls to the
|
|
249
|
+
correct MCP client.
|
|
250
|
+
"""
|
|
251
|
+
if getattr(self, "_mcp_connected", False):
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
if not self.__mcp_defs__:
|
|
255
|
+
self._mcp_connected = True
|
|
256
|
+
self._mcp_clients = {}
|
|
257
|
+
self._mcp_tool_routing = {}
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
from fastmcp import Client as MCPClient
|
|
262
|
+
except ImportError:
|
|
263
|
+
raise ImportError(
|
|
264
|
+
"fastmcp is required for MCP support. "
|
|
265
|
+
"Install it with: pip install pyagentic-core[mcp]"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
from pyagentic._base._mcp import (
|
|
269
|
+
_MCPToolDefinition,
|
|
270
|
+
mcp_tool_to_tool_def,
|
|
271
|
+
_json_schema_to_parameters,
|
|
272
|
+
)
|
|
273
|
+
from pyagentic.models.response import ToolResponse
|
|
274
|
+
|
|
275
|
+
self._mcp_clients = {}
|
|
276
|
+
self._mcp_tool_routing = {}
|
|
277
|
+
mcp_tool_defs = {}
|
|
278
|
+
mcp_response_models = {}
|
|
279
|
+
|
|
280
|
+
for field_name, mcp_def in self.__mcp_defs__.items():
|
|
281
|
+
info = mcp_def.info
|
|
282
|
+
server = info.server
|
|
283
|
+
|
|
284
|
+
# Auto-detect transport
|
|
285
|
+
if isinstance(server, str) and server.startswith(("http://", "https://")):
|
|
286
|
+
client = MCPClient(server)
|
|
287
|
+
elif isinstance(server, str) and info.args is not None:
|
|
288
|
+
from fastmcp.client.transports import StdioTransport
|
|
289
|
+
|
|
290
|
+
transport = StdioTransport(command=server, args=info.args)
|
|
291
|
+
client = MCPClient(transport)
|
|
292
|
+
else:
|
|
293
|
+
# In-process FastMCP server object
|
|
294
|
+
client = MCPClient(server)
|
|
295
|
+
|
|
296
|
+
# Connect and fetch tools
|
|
297
|
+
await client.__aenter__()
|
|
298
|
+
self._mcp_clients[field_name] = client
|
|
299
|
+
|
|
300
|
+
tools = await client.list_tools()
|
|
301
|
+
|
|
302
|
+
# Apply whitelist/blacklist filtering
|
|
303
|
+
if info.tools is not None:
|
|
304
|
+
allowed = set(info.tools)
|
|
305
|
+
tools = [t for t in tools if t.name in allowed]
|
|
306
|
+
if info.exclude_tools is not None:
|
|
307
|
+
excluded = set(info.exclude_tools)
|
|
308
|
+
tools = [t for t in tools if t.name not in excluded]
|
|
309
|
+
|
|
310
|
+
# Convert to _MCPToolDefinition and register routing
|
|
311
|
+
for mcp_tool in tools:
|
|
312
|
+
tool_def = mcp_tool_to_tool_def(
|
|
313
|
+
mcp_tool, field_name, info.prefix
|
|
314
|
+
)
|
|
315
|
+
# Apply phases from MCPInfo to each tool definition
|
|
316
|
+
if info.phases:
|
|
317
|
+
tool_def.phases = info.phases
|
|
318
|
+
mcp_tool_defs[tool_def.name] = tool_def
|
|
319
|
+
self._mcp_tool_routing[tool_def.name] = (
|
|
320
|
+
client,
|
|
321
|
+
tool_def.mcp_original_name,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Build a simple response model for MCP tools
|
|
325
|
+
params = _json_schema_to_parameters(tool_def.json_schema)
|
|
326
|
+
simple_def = _ToolDefinition(
|
|
327
|
+
name=tool_def.name,
|
|
328
|
+
description=tool_def.description,
|
|
329
|
+
parameters=params,
|
|
330
|
+
return_type=str,
|
|
331
|
+
)
|
|
332
|
+
mcp_response_models[tool_def.name] = ToolResponse.from_tool_def(
|
|
333
|
+
simple_def
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Shadow class-level dicts with instance-level merged versions
|
|
337
|
+
self.__tool_defs__ = {**self.__class__.__tool_defs__, **mcp_tool_defs}
|
|
338
|
+
self.__tool_response_models__ = {
|
|
339
|
+
**self.__class__.__tool_response_models__,
|
|
340
|
+
**mcp_response_models,
|
|
341
|
+
}
|
|
342
|
+
self._mcp_connected = True
|
|
343
|
+
|
|
344
|
+
async def close(self):
|
|
345
|
+
"""Tear down all MCP client connections.
|
|
346
|
+
|
|
347
|
+
Should be called when the agent is no longer needed to clean up
|
|
348
|
+
any open connections or subprocesses.
|
|
349
|
+
"""
|
|
350
|
+
for client in getattr(self, "_mcp_clients", {}).values():
|
|
351
|
+
try:
|
|
352
|
+
await client.__aexit__(None, None, None)
|
|
353
|
+
except Exception:
|
|
354
|
+
pass
|
|
355
|
+
self._mcp_clients = {}
|
|
356
|
+
self._mcp_tool_routing = {}
|
|
357
|
+
self._mcp_connected = False
|
|
358
|
+
|
|
237
359
|
def __post_init__(self):
|
|
238
360
|
"""
|
|
239
361
|
Post-initialization hook called after agent instance is created.
|
|
@@ -312,8 +434,9 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
312
434
|
response_format=self.__response_format__,
|
|
313
435
|
**kwargs,
|
|
314
436
|
)
|
|
437
|
+
usage_details = response.usage.model_dump() if response.usage else {}
|
|
315
438
|
self.tracer.set_attributes(
|
|
316
|
-
usage_details=
|
|
439
|
+
usage_details=usage_details, model=self.provider._model
|
|
317
440
|
)
|
|
318
441
|
return response
|
|
319
442
|
except Exception as e:
|
|
@@ -393,11 +516,17 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
393
516
|
Returns:
|
|
394
517
|
ToolResponse: The response from the tool execution
|
|
395
518
|
"""
|
|
396
|
-
# Add tool call message to conversation history
|
|
397
|
-
self.state._messages.append(self.provider.to_tool_call_message(tool_call))
|
|
398
519
|
self.tracer.set_attributes(**tool_call.__dict__)
|
|
399
520
|
logger.info(f"Calling {tool_call.name} with kwargs: {tool_call.arguments}")
|
|
400
521
|
|
|
522
|
+
# Check if this is an MCP-routed tool (before appending provider-specific message)
|
|
523
|
+
mcp_routing = getattr(self, "_mcp_tool_routing", {})
|
|
524
|
+
if tool_call.name in mcp_routing:
|
|
525
|
+
return await self._process_mcp_tool_call(tool_call, call_depth)
|
|
526
|
+
|
|
527
|
+
# Add tool call message to conversation history
|
|
528
|
+
self.state._messages.append(self.provider.to_tool_call_message(tool_call))
|
|
529
|
+
|
|
401
530
|
# Look up the tool definition and bound method
|
|
402
531
|
try:
|
|
403
532
|
tool_def = self.__tool_defs__[tool_call.name]
|
|
@@ -413,11 +542,11 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
413
542
|
except ValidationError as e:
|
|
414
543
|
# Handle validation errors for tool arguments
|
|
415
544
|
result = f"Function Args were invalid: {str(e)}"
|
|
416
|
-
compiled_args =
|
|
545
|
+
compiled_args = None
|
|
417
546
|
self.tracer.record_exception(str(e))
|
|
418
547
|
logger.exception(e)
|
|
419
548
|
try:
|
|
420
|
-
if compiled_args:
|
|
549
|
+
if compiled_args is not None:
|
|
421
550
|
result = await _safe_run(handler, **compiled_args)
|
|
422
551
|
self.tracer.set_attributes(result=result)
|
|
423
552
|
except TypeError as e:
|
|
@@ -451,16 +580,65 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
451
580
|
raw_kwargs=tool_call.arguments, call_depth=call_depth, output=result, **compiled_args
|
|
452
581
|
)
|
|
453
582
|
|
|
583
|
+
@traced(SpanKind.TOOL)
|
|
584
|
+
async def _process_mcp_tool_call(self, tool_call: ToolCall, call_depth: int) -> ToolResponse:
|
|
585
|
+
"""Processes a tool call routed to an MCP server.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
tool_call (ToolCall): The tool call to execute via MCP.
|
|
589
|
+
call_depth (int): Current depth in the tool calling loop.
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
ToolResponse: The response from the MCP tool execution.
|
|
593
|
+
"""
|
|
594
|
+
# Add tool call to conversation history (provider-specific format)
|
|
595
|
+
self.state._messages.append(self.provider.to_tool_call_message(tool_call))
|
|
596
|
+
|
|
597
|
+
client, original_name = self._mcp_tool_routing[tool_call.name]
|
|
598
|
+
kwargs = json.loads(tool_call.arguments)
|
|
599
|
+
|
|
600
|
+
try:
|
|
601
|
+
mcp_result = await client.call_tool(original_name, kwargs)
|
|
602
|
+
# CallToolResult has .content (list of content blocks)
|
|
603
|
+
parts = []
|
|
604
|
+
for block in mcp_result.content:
|
|
605
|
+
if hasattr(block, "text"):
|
|
606
|
+
parts.append(block.text)
|
|
607
|
+
else:
|
|
608
|
+
parts.append(str(block))
|
|
609
|
+
result = "\n".join(parts)
|
|
610
|
+
self.tracer.set_attributes(result=result)
|
|
611
|
+
except Exception as e:
|
|
612
|
+
self.tracer.record_exception(str(e))
|
|
613
|
+
logger.exception(e)
|
|
614
|
+
result = f"MCP tool `{tool_call.name}` failed: {e}. Please kindly state to the user that it failed, provide state, and ask if they want to try again." # noqa E501
|
|
615
|
+
|
|
616
|
+
# Add result to conversation history
|
|
617
|
+
self.state._messages.append(
|
|
618
|
+
self.provider.to_tool_call_result_message(result=result, id_=tool_call.id)
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
if self.phases:
|
|
622
|
+
self.state._update_state_machine(phases=self.phases)
|
|
623
|
+
|
|
624
|
+
# Return a base ToolResponse (not a class-time typed subclass,
|
|
625
|
+
# since MCP tools are discovered at runtime)
|
|
626
|
+
return ToolResponse(
|
|
627
|
+
raw_kwargs=tool_call.arguments, call_depth=call_depth, output=result
|
|
628
|
+
)
|
|
629
|
+
|
|
454
630
|
async def _get_tool_defs(self) -> list[_ToolDefinition]:
|
|
455
631
|
"""
|
|
456
|
-
Builds a list of tool definitions from @tool methods and
|
|
632
|
+
Builds a list of tool definitions from @tool methods, linked agents, and MCP servers.
|
|
457
633
|
|
|
458
634
|
Resolves any StateRef references in tool parameters using the current agent_reference,
|
|
459
|
-
allowing tools to dynamically reference state values.
|
|
635
|
+
allowing tools to dynamically reference state values. Lazily connects to MCP servers
|
|
636
|
+
on first call.
|
|
460
637
|
|
|
461
638
|
Returns:
|
|
462
639
|
list[_ToolDefinition]: List of resolved tool definitions ready for LLM
|
|
463
640
|
"""
|
|
641
|
+
await self._ensure_mcp_connected()
|
|
464
642
|
tool_defs = []
|
|
465
643
|
|
|
466
644
|
# Add all @tool decorated methods
|
|
@@ -540,9 +718,6 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
540
718
|
# Add user message to conversation state
|
|
541
719
|
self.state.add_user_message(input_)
|
|
542
720
|
|
|
543
|
-
# Build tool definitions (including linked agents as tools)
|
|
544
|
-
tool_defs = await self._get_tool_defs()
|
|
545
|
-
|
|
546
721
|
# Track responses and prevent duplicate processing
|
|
547
722
|
tool_responses: list = []
|
|
548
723
|
agent_responses: list = []
|
|
@@ -553,6 +728,10 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
553
728
|
final_ai_output: str | None = None
|
|
554
729
|
|
|
555
730
|
while depth < self.max_call_depth:
|
|
731
|
+
# Rebuild tool definitions each iteration so phase transitions
|
|
732
|
+
# that occurred during tool execution are reflected
|
|
733
|
+
tool_defs = await self._get_tool_defs()
|
|
734
|
+
|
|
556
735
|
# Ask the LLM what to do next (may return tool calls or final text)
|
|
557
736
|
response = await self._process_llm_inference(tool_defs=tool_defs)
|
|
558
737
|
yield response
|
|
@@ -610,6 +789,8 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
610
789
|
final_ai_output = response.parsed if response.parsed else response.text
|
|
611
790
|
|
|
612
791
|
# Build the structured response
|
|
792
|
+
if final_ai_output is None:
|
|
793
|
+
final_ai_output = ""
|
|
613
794
|
response_fields = {
|
|
614
795
|
"final_output": final_ai_output,
|
|
615
796
|
"state": self.state,
|
|
@@ -83,3 +83,16 @@ class ParamInfo(_SpecInfo):
|
|
|
83
83
|
description: MaybeRef[str] | None = None
|
|
84
84
|
required: MaybeRef[bool] | None = False
|
|
85
85
|
values: MaybeRef[list[str]] | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class MCPInfo(_SpecInfo):
|
|
90
|
+
"""Descriptor for configuring MCP server connections."""
|
|
91
|
+
|
|
92
|
+
server: Any = None
|
|
93
|
+
args: list[str] | None = None
|
|
94
|
+
tools: list[str] | None = None
|
|
95
|
+
exclude_tools: list[str] | None = None
|
|
96
|
+
prefix: bool | str = True
|
|
97
|
+
condition: Callable | None = None
|
|
98
|
+
phases: list[str] | None = None
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP (Model Context Protocol) integration types and helpers.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- ``MCPLink``: Type annotation marker for MCP server connections
|
|
6
|
+
- ``_MCPDefinition``: Pairs a field name with its ``MCPInfo`` config
|
|
7
|
+
- ``_MCPToolDefinition``: A ``_ToolDefinition`` subclass for MCP-sourced tools
|
|
8
|
+
- ``mcp_tool_to_tool_def()``: Converts a fastmcp ``Tool`` into ``_MCPToolDefinition``
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any, Self
|
|
15
|
+
|
|
16
|
+
from pyagentic._base._info import MCPInfo, ParamInfo
|
|
17
|
+
from pyagentic._base._tool import _ToolDefinition
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MCPLink:
|
|
21
|
+
"""Type annotation marker for MCP server connections.
|
|
22
|
+
|
|
23
|
+
Used as a type annotation on agent class fields so the metaclass can
|
|
24
|
+
detect MCP server configurations. Paired with ``spec.MCPLink()`` which
|
|
25
|
+
returns an ``MCPInfo`` descriptor.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
```python
|
|
29
|
+
class MyAgent(BaseAgent):
|
|
30
|
+
__system_message__ = "You are helpful"
|
|
31
|
+
|
|
32
|
+
fs: MCPLink = spec.MCPLink(
|
|
33
|
+
"npx",
|
|
34
|
+
args=["@modelcontextprotocol/server-filesystem", "/tmp"],
|
|
35
|
+
tools=["read_file", "write_file"],
|
|
36
|
+
prefix=True,
|
|
37
|
+
)
|
|
38
|
+
```
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class _MCPDefinition:
|
|
46
|
+
"""Pairs an agent field name with its MCP configuration."""
|
|
47
|
+
|
|
48
|
+
field_name: str
|
|
49
|
+
info: MCPInfo
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class _MCPToolDefinition(_ToolDefinition):
|
|
54
|
+
"""A tool definition sourced from an MCP server.
|
|
55
|
+
|
|
56
|
+
Overrides the base ``_ToolDefinition`` to work with raw JSON Schema
|
|
57
|
+
from MCP instead of Python type introspection.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
mcp_field_name: The agent field name (e.g. ``"fs"``).
|
|
61
|
+
mcp_original_name: The original tool name on the MCP server.
|
|
62
|
+
json_schema: Raw JSON Schema for the tool's input parameters.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
mcp_field_name: str = ""
|
|
66
|
+
mcp_original_name: str = ""
|
|
67
|
+
json_schema: dict = field(default_factory=dict)
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
name: str,
|
|
73
|
+
description: str,
|
|
74
|
+
json_schema: dict,
|
|
75
|
+
mcp_field_name: str,
|
|
76
|
+
mcp_original_name: str,
|
|
77
|
+
):
|
|
78
|
+
super().__init__(
|
|
79
|
+
name=name,
|
|
80
|
+
description=description,
|
|
81
|
+
parameters={},
|
|
82
|
+
return_type=str,
|
|
83
|
+
)
|
|
84
|
+
self.mcp_field_name = mcp_field_name
|
|
85
|
+
self.mcp_original_name = mcp_original_name
|
|
86
|
+
self.json_schema = json_schema
|
|
87
|
+
|
|
88
|
+
# JSON Schema keywords not supported by Anthropic's strict tool mode
|
|
89
|
+
_UNSUPPORTED_SCHEMA_KEYS = frozenset({
|
|
90
|
+
"$schema", "exclusiveMaximum", "exclusiveMinimum",
|
|
91
|
+
"maxLength", "minLength", "maxItems", "minItems",
|
|
92
|
+
"uniqueItems", "pattern", "format",
|
|
93
|
+
"maximum", "minimum", "multipleOf",
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def _strip_unsupported(cls, obj: dict) -> dict:
|
|
98
|
+
"""Recursively strip unsupported JSON Schema keys and enforce strict mode.
|
|
99
|
+
|
|
100
|
+
Also injects ``additionalProperties: false`` on every ``object``
|
|
101
|
+
type node, as required by Anthropic's strict tool mode.
|
|
102
|
+
"""
|
|
103
|
+
cleaned = {}
|
|
104
|
+
for key, value in obj.items():
|
|
105
|
+
if key in cls._UNSUPPORTED_SCHEMA_KEYS:
|
|
106
|
+
continue
|
|
107
|
+
if isinstance(value, dict):
|
|
108
|
+
cleaned[key] = cls._strip_unsupported(value)
|
|
109
|
+
else:
|
|
110
|
+
cleaned[key] = value
|
|
111
|
+
# Anthropic strict mode requires additionalProperties: false
|
|
112
|
+
# on every object-typed node in the schema
|
|
113
|
+
if cleaned.get("type") == "object":
|
|
114
|
+
cleaned["additionalProperties"] = False
|
|
115
|
+
return cleaned
|
|
116
|
+
|
|
117
|
+
def _clean_schema(self) -> dict:
|
|
118
|
+
"""Return a cleaned copy of the MCP JSON Schema.
|
|
119
|
+
|
|
120
|
+
Strips meta-keys and unsupported validation keywords that LLM
|
|
121
|
+
APIs don't accept, and ensures ``type`` and ``properties`` are
|
|
122
|
+
present.
|
|
123
|
+
"""
|
|
124
|
+
schema = self._strip_unsupported(
|
|
125
|
+
dict(self.json_schema) if self.json_schema else {}
|
|
126
|
+
)
|
|
127
|
+
if "type" not in schema:
|
|
128
|
+
schema["type"] = "object"
|
|
129
|
+
if "properties" not in schema:
|
|
130
|
+
schema["properties"] = {}
|
|
131
|
+
return schema
|
|
132
|
+
|
|
133
|
+
def to_openai_spec(self) -> dict:
|
|
134
|
+
"""Emit the raw JSON Schema from the MCP server.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
dict: An OpenAI-compliant tool specification dictionary.
|
|
138
|
+
"""
|
|
139
|
+
return {
|
|
140
|
+
"type": "function",
|
|
141
|
+
"name": self.name,
|
|
142
|
+
"description": self.description,
|
|
143
|
+
"parameters": self._clean_schema(),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def to_anthropic_spec(self) -> dict:
|
|
147
|
+
"""Emit Anthropic-formatted tool spec with strict mode from MCP JSON Schema.
|
|
148
|
+
|
|
149
|
+
Uses ``strict: true`` and ``additionalProperties: false`` to guarantee
|
|
150
|
+
schema conformance via grammar-constrained sampling.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
dict: An Anthropic-compliant tool specification dictionary.
|
|
154
|
+
"""
|
|
155
|
+
schema = self._clean_schema()
|
|
156
|
+
schema["additionalProperties"] = False
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"name": self.name,
|
|
160
|
+
"description": self.description,
|
|
161
|
+
"strict": True,
|
|
162
|
+
"input_schema": schema,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
def compile_args(self, **kwargs) -> dict[str, Any]:
|
|
166
|
+
"""Pass-through: MCP handles its own validation.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
**kwargs: Raw keyword arguments from the LLM tool call.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
dict[str, Any]: The same kwargs, unmodified.
|
|
173
|
+
"""
|
|
174
|
+
return kwargs
|
|
175
|
+
|
|
176
|
+
def resolve(self, agent_reference: dict) -> Self:
|
|
177
|
+
"""MCP tools have no StateRefs to resolve.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
agent_reference (dict): The agent reference dict (unused).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Self: Returns self unchanged.
|
|
184
|
+
"""
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _json_schema_to_parameters(
|
|
189
|
+
schema: dict,
|
|
190
|
+
) -> dict[str, tuple[type, ParamInfo]]:
|
|
191
|
+
"""Convert a JSON Schema properties dict to ``(type, ParamInfo)`` pairs.
|
|
192
|
+
|
|
193
|
+
This is a simple mapping used for response model compatibility. Complex
|
|
194
|
+
schemas fall back to ``str``.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
schema (dict): JSON Schema with ``properties`` and optionally ``required``.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
dict[str, tuple[type, ParamInfo]]: Mapping of parameter names to
|
|
201
|
+
``(python_type, ParamInfo)`` tuples.
|
|
202
|
+
"""
|
|
203
|
+
_JSON_TYPE_MAP = {
|
|
204
|
+
"string": str,
|
|
205
|
+
"integer": int,
|
|
206
|
+
"number": float,
|
|
207
|
+
"boolean": bool,
|
|
208
|
+
}
|
|
209
|
+
properties = schema.get("properties", {})
|
|
210
|
+
required_fields = set(schema.get("required", []))
|
|
211
|
+
params: dict[str, tuple[type, ParamInfo]] = {}
|
|
212
|
+
|
|
213
|
+
for prop_name, prop_schema in properties.items():
|
|
214
|
+
json_type = prop_schema.get("type", "string")
|
|
215
|
+
py_type = _JSON_TYPE_MAP.get(json_type, str)
|
|
216
|
+
is_required = prop_name in required_fields
|
|
217
|
+
description = prop_schema.get("description")
|
|
218
|
+
params[prop_name] = (
|
|
219
|
+
py_type,
|
|
220
|
+
ParamInfo(required=is_required, description=description),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return params
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def mcp_tool_to_tool_def(
|
|
227
|
+
mcp_tool: Any,
|
|
228
|
+
field_name: str,
|
|
229
|
+
prefix: bool | str,
|
|
230
|
+
) -> _MCPToolDefinition:
|
|
231
|
+
"""Convert a fastmcp ``Tool`` object to an ``_MCPToolDefinition``.
|
|
232
|
+
|
|
233
|
+
Applies prefix logic: when *prefix* is ``True``, the tool name becomes
|
|
234
|
+
``{field_name}__{original_name}``. When *prefix* is a string, it is
|
|
235
|
+
used instead of *field_name*.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
mcp_tool (Any): A fastmcp ``Tool`` object with ``name``,
|
|
239
|
+
``description``, and ``inputSchema`` attributes.
|
|
240
|
+
field_name (str): The agent field name (e.g. ``"fs"``).
|
|
241
|
+
prefix (bool | str): Prefix mode — ``True`` uses *field_name*,
|
|
242
|
+
a string uses that value, ``False`` uses no prefix.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
_MCPToolDefinition: The converted tool definition.
|
|
246
|
+
"""
|
|
247
|
+
original_name = mcp_tool.name
|
|
248
|
+
description = mcp_tool.description or ""
|
|
249
|
+
input_schema = mcp_tool.inputSchema if hasattr(mcp_tool, "inputSchema") else {}
|
|
250
|
+
|
|
251
|
+
if prefix is True:
|
|
252
|
+
prefixed_name = f"{field_name}__{original_name}"
|
|
253
|
+
elif isinstance(prefix, str) and prefix:
|
|
254
|
+
prefixed_name = f"{prefix}__{original_name}"
|
|
255
|
+
else:
|
|
256
|
+
prefixed_name = original_name
|
|
257
|
+
|
|
258
|
+
return _MCPToolDefinition(
|
|
259
|
+
name=prefixed_name,
|
|
260
|
+
description=description,
|
|
261
|
+
json_schema=input_schema,
|
|
262
|
+
mcp_field_name=field_name,
|
|
263
|
+
mcp_original_name=original_name,
|
|
264
|
+
)
|