agno 2.0.1__py3-none-any.whl → 2.3.0__py3-none-any.whl
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.
- agno/agent/agent.py +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
from contextlib import AsyncExitStack
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import List, Literal, Optional, Union
|
|
7
|
+
|
|
8
|
+
from agno.tools import Toolkit
|
|
9
|
+
from agno.tools.function import Function
|
|
10
|
+
from agno.tools.mcp.params import SSEClientParams, StreamableHTTPClientParams
|
|
11
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
12
|
+
from agno.utils.mcp import get_entrypoint_for_tool, prepare_command
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from mcp import ClientSession, StdioServerParameters
|
|
16
|
+
from mcp.client.sse import sse_client
|
|
17
|
+
from mcp.client.stdio import get_default_environment, stdio_client
|
|
18
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
19
|
+
except (ImportError, ModuleNotFoundError):
|
|
20
|
+
raise ImportError("`mcp` not installed. Please install using `pip install mcp`")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MultiMCPTools(Toolkit):
|
|
24
|
+
"""
|
|
25
|
+
A toolkit for integrating multiple Model Context Protocol (MCP) servers with Agno agents.
|
|
26
|
+
This allows agents to access tools, resources, and prompts exposed by MCP servers.
|
|
27
|
+
|
|
28
|
+
Can be used in three ways:
|
|
29
|
+
1. Direct initialization with a ClientSession
|
|
30
|
+
2. As an async context manager with StdioServerParameters
|
|
31
|
+
3. As an async context manager with SSE or Streamable HTTP endpoints
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
commands: Optional[List[str]] = None,
|
|
37
|
+
urls: Optional[List[str]] = None,
|
|
38
|
+
urls_transports: Optional[List[Literal["sse", "streamable-http"]]] = None,
|
|
39
|
+
*,
|
|
40
|
+
env: Optional[dict[str, str]] = None,
|
|
41
|
+
server_params_list: Optional[
|
|
42
|
+
list[Union[SSEClientParams, StdioServerParameters, StreamableHTTPClientParams]]
|
|
43
|
+
] = None,
|
|
44
|
+
timeout_seconds: int = 10,
|
|
45
|
+
client=None,
|
|
46
|
+
include_tools: Optional[list[str]] = None,
|
|
47
|
+
exclude_tools: Optional[list[str]] = None,
|
|
48
|
+
refresh_connection: bool = False,
|
|
49
|
+
allow_partial_failure: bool = False,
|
|
50
|
+
**kwargs,
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Initialize the MCP toolkit.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
commands: List of commands to run to start the servers. Should be used in conjunction with env.
|
|
57
|
+
urls: List of URLs for SSE and/or Streamable HTTP endpoints.
|
|
58
|
+
urls_transports: List of transports to use for the given URLs.
|
|
59
|
+
server_params_list: List of StdioServerParameters or SSEClientParams or StreamableHTTPClientParams for creating new sessions.
|
|
60
|
+
env: The environment variables to pass to the servers. Should be used in conjunction with commands.
|
|
61
|
+
client: The underlying MCP client (optional, used to prevent garbage collection).
|
|
62
|
+
timeout_seconds: Timeout in seconds for managing timeouts for Client Session if Agent or Tool doesn't respond.
|
|
63
|
+
include_tools: Optional list of tool names to include (if None, includes all).
|
|
64
|
+
exclude_tools: Optional list of tool names to exclude (if None, excludes none).
|
|
65
|
+
allow_partial_failure: If True, allows toolkit to initialize even if some MCP servers fail to connect. If False, any failure will raise an exception.
|
|
66
|
+
refresh_connection: If True, the connection and tools will be refreshed on each run
|
|
67
|
+
"""
|
|
68
|
+
super().__init__(name="MultiMCPTools", **kwargs)
|
|
69
|
+
|
|
70
|
+
if urls_transports is not None:
|
|
71
|
+
if "sse" in urls_transports:
|
|
72
|
+
log_info("SSE as a standalone transport is deprecated. Please use Streamable HTTP instead.")
|
|
73
|
+
|
|
74
|
+
if urls is not None:
|
|
75
|
+
if urls_transports is None:
|
|
76
|
+
log_warning(
|
|
77
|
+
"The default transport 'streamable-http' will be used. You can explicitly set the transports by providing the urls_transports parameter."
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
if len(urls) != len(urls_transports):
|
|
81
|
+
raise ValueError("urls and urls_transports must be of the same length")
|
|
82
|
+
|
|
83
|
+
# Set these after `__init__` to bypass the `_check_tools_filters`
|
|
84
|
+
# beacuse tools are not available until `initialize()` is called.
|
|
85
|
+
self.include_tools = include_tools
|
|
86
|
+
self.exclude_tools = exclude_tools
|
|
87
|
+
self.refresh_connection = refresh_connection
|
|
88
|
+
|
|
89
|
+
if server_params_list is None and commands is None and urls is None:
|
|
90
|
+
raise ValueError("Either server_params_list or commands or urls must be provided")
|
|
91
|
+
|
|
92
|
+
self.server_params_list: List[Union[SSEClientParams, StdioServerParameters, StreamableHTTPClientParams]] = (
|
|
93
|
+
server_params_list or []
|
|
94
|
+
)
|
|
95
|
+
self.timeout_seconds = timeout_seconds
|
|
96
|
+
self.commands: Optional[List[str]] = commands
|
|
97
|
+
self.urls: Optional[List[str]] = urls
|
|
98
|
+
# Merge provided env with system env
|
|
99
|
+
if env is not None:
|
|
100
|
+
env = {
|
|
101
|
+
**get_default_environment(),
|
|
102
|
+
**env,
|
|
103
|
+
}
|
|
104
|
+
else:
|
|
105
|
+
env = get_default_environment()
|
|
106
|
+
|
|
107
|
+
if commands is not None:
|
|
108
|
+
for command in commands:
|
|
109
|
+
parts = prepare_command(command)
|
|
110
|
+
cmd = parts[0]
|
|
111
|
+
arguments = parts[1:] if len(parts) > 1 else []
|
|
112
|
+
self.server_params_list.append(StdioServerParameters(command=cmd, args=arguments, env=env))
|
|
113
|
+
|
|
114
|
+
if urls is not None:
|
|
115
|
+
if urls_transports is not None:
|
|
116
|
+
for url, transport in zip(urls, urls_transports):
|
|
117
|
+
if transport == "streamable-http":
|
|
118
|
+
self.server_params_list.append(StreamableHTTPClientParams(url=url))
|
|
119
|
+
else:
|
|
120
|
+
self.server_params_list.append(SSEClientParams(url=url))
|
|
121
|
+
else:
|
|
122
|
+
for url in urls:
|
|
123
|
+
self.server_params_list.append(StreamableHTTPClientParams(url=url))
|
|
124
|
+
|
|
125
|
+
self._async_exit_stack = AsyncExitStack()
|
|
126
|
+
|
|
127
|
+
self._client = client
|
|
128
|
+
|
|
129
|
+
self._initialized = False
|
|
130
|
+
self._connection_task = None
|
|
131
|
+
self._successful_connections = 0
|
|
132
|
+
self._sessions: list[ClientSession] = []
|
|
133
|
+
|
|
134
|
+
self.allow_partial_failure = allow_partial_failure
|
|
135
|
+
|
|
136
|
+
def cleanup():
|
|
137
|
+
"""Cancel active connections"""
|
|
138
|
+
if self._connection_task and not self._connection_task.done():
|
|
139
|
+
self._connection_task.cancel()
|
|
140
|
+
|
|
141
|
+
# Setup cleanup logic before the instance is garbage collected
|
|
142
|
+
self._cleanup_finalizer = weakref.finalize(self, cleanup)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def initialized(self) -> bool:
|
|
146
|
+
return self._initialized
|
|
147
|
+
|
|
148
|
+
async def is_alive(self) -> bool:
|
|
149
|
+
try:
|
|
150
|
+
for session in self._sessions:
|
|
151
|
+
await session.send_ping()
|
|
152
|
+
return True
|
|
153
|
+
except (RuntimeError, BaseException):
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
async def connect(self, force: bool = False):
|
|
157
|
+
"""Initialize a MultiMCPTools instance and connect to the MCP servers"""
|
|
158
|
+
|
|
159
|
+
if force:
|
|
160
|
+
# Clean up the session and context so we force a new connection
|
|
161
|
+
self._sessions = []
|
|
162
|
+
self._successful_connections = 0
|
|
163
|
+
self._initialized = False
|
|
164
|
+
self._connection_task = None
|
|
165
|
+
|
|
166
|
+
if self._initialized:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
await self._connect()
|
|
171
|
+
except (RuntimeError, BaseException) as e:
|
|
172
|
+
log_error(f"Failed to connect to {str(self)}: {e}")
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
async def create_and_connect(
|
|
176
|
+
cls,
|
|
177
|
+
commands: Optional[List[str]] = None,
|
|
178
|
+
urls: Optional[List[str]] = None,
|
|
179
|
+
urls_transports: Optional[List[Literal["sse", "streamable-http"]]] = None,
|
|
180
|
+
*,
|
|
181
|
+
env: Optional[dict[str, str]] = None,
|
|
182
|
+
server_params_list: Optional[
|
|
183
|
+
List[Union[SSEClientParams, StdioServerParameters, StreamableHTTPClientParams]]
|
|
184
|
+
] = None,
|
|
185
|
+
timeout_seconds: int = 5,
|
|
186
|
+
client=None,
|
|
187
|
+
include_tools: Optional[list[str]] = None,
|
|
188
|
+
exclude_tools: Optional[list[str]] = None,
|
|
189
|
+
refresh_connection: bool = False,
|
|
190
|
+
**kwargs,
|
|
191
|
+
) -> "MultiMCPTools":
|
|
192
|
+
"""Initialize a MultiMCPTools instance and connect to the MCP servers"""
|
|
193
|
+
instance = cls(
|
|
194
|
+
commands=commands,
|
|
195
|
+
urls=urls,
|
|
196
|
+
urls_transports=urls_transports,
|
|
197
|
+
env=env,
|
|
198
|
+
server_params_list=server_params_list,
|
|
199
|
+
timeout_seconds=timeout_seconds,
|
|
200
|
+
client=client,
|
|
201
|
+
include_tools=include_tools,
|
|
202
|
+
exclude_tools=exclude_tools,
|
|
203
|
+
refresh_connection=refresh_connection,
|
|
204
|
+
**kwargs,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
await instance._connect()
|
|
208
|
+
return instance
|
|
209
|
+
|
|
210
|
+
async def _connect(self) -> None:
|
|
211
|
+
"""Connects to the MCP servers and initializes the tools"""
|
|
212
|
+
if self._initialized:
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
server_connection_errors = []
|
|
216
|
+
|
|
217
|
+
for server_params in self.server_params_list:
|
|
218
|
+
try:
|
|
219
|
+
# Handle stdio connections
|
|
220
|
+
if isinstance(server_params, StdioServerParameters):
|
|
221
|
+
stdio_transport = await self._async_exit_stack.enter_async_context(stdio_client(server_params))
|
|
222
|
+
read, write = stdio_transport
|
|
223
|
+
session = await self._async_exit_stack.enter_async_context(
|
|
224
|
+
ClientSession(read, write, read_timeout_seconds=timedelta(seconds=self.timeout_seconds))
|
|
225
|
+
)
|
|
226
|
+
await self.initialize(session)
|
|
227
|
+
self._successful_connections += 1
|
|
228
|
+
|
|
229
|
+
# Handle SSE connections
|
|
230
|
+
elif isinstance(server_params, SSEClientParams):
|
|
231
|
+
client_connection = await self._async_exit_stack.enter_async_context(
|
|
232
|
+
sse_client(**asdict(server_params))
|
|
233
|
+
)
|
|
234
|
+
read, write = client_connection
|
|
235
|
+
session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
|
|
236
|
+
await self.initialize(session)
|
|
237
|
+
self._successful_connections += 1
|
|
238
|
+
|
|
239
|
+
# Handle Streamable HTTP connections
|
|
240
|
+
elif isinstance(server_params, StreamableHTTPClientParams):
|
|
241
|
+
client_connection = await self._async_exit_stack.enter_async_context(
|
|
242
|
+
streamablehttp_client(**asdict(server_params))
|
|
243
|
+
)
|
|
244
|
+
read, write = client_connection[0:2]
|
|
245
|
+
session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
|
|
246
|
+
await self.initialize(session)
|
|
247
|
+
self._successful_connections += 1
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
if not self.allow_partial_failure:
|
|
251
|
+
raise ValueError(f"MCP connection failed: {e}")
|
|
252
|
+
|
|
253
|
+
log_error(f"Failed to initialize MCP server with params {server_params}: {e}")
|
|
254
|
+
server_connection_errors.append(str(e))
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
if self._successful_connections > 0:
|
|
258
|
+
await self.build_tools()
|
|
259
|
+
|
|
260
|
+
if self._successful_connections == 0 and server_connection_errors:
|
|
261
|
+
raise ValueError(f"All MCP connections failed: {server_connection_errors}")
|
|
262
|
+
|
|
263
|
+
if not self._initialized and self._successful_connections > 0:
|
|
264
|
+
self._initialized = True
|
|
265
|
+
|
|
266
|
+
async def close(self) -> None:
|
|
267
|
+
"""Close the MCP connections and clean up resources"""
|
|
268
|
+
if not self._initialized:
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
await self._async_exit_stack.aclose()
|
|
273
|
+
self._sessions = []
|
|
274
|
+
self._successful_connections = 0
|
|
275
|
+
|
|
276
|
+
except (RuntimeError, BaseException) as e:
|
|
277
|
+
log_error(f"Failed to close MCP connections: {e}")
|
|
278
|
+
|
|
279
|
+
self._initialized = False
|
|
280
|
+
|
|
281
|
+
async def __aenter__(self) -> "MultiMCPTools":
|
|
282
|
+
"""Enter the async context manager."""
|
|
283
|
+
try:
|
|
284
|
+
await self._connect()
|
|
285
|
+
except (RuntimeError, BaseException) as e:
|
|
286
|
+
log_error(f"Failed to connect to {str(self)}: {e}")
|
|
287
|
+
return self
|
|
288
|
+
|
|
289
|
+
async def __aexit__(
|
|
290
|
+
self,
|
|
291
|
+
exc_type: Union[type[BaseException], None],
|
|
292
|
+
exc_val: Union[BaseException, None],
|
|
293
|
+
exc_tb: Union[TracebackType, None],
|
|
294
|
+
):
|
|
295
|
+
"""Exit the async context manager."""
|
|
296
|
+
await self._async_exit_stack.aclose()
|
|
297
|
+
self._initialized = False
|
|
298
|
+
self._successful_connections = 0
|
|
299
|
+
|
|
300
|
+
async def build_tools(self) -> None:
|
|
301
|
+
for session in self._sessions:
|
|
302
|
+
# Get the list of tools from the MCP server
|
|
303
|
+
available_tools = await session.list_tools()
|
|
304
|
+
|
|
305
|
+
# Filter tools based on include/exclude lists
|
|
306
|
+
filtered_tools = []
|
|
307
|
+
for tool in available_tools.tools:
|
|
308
|
+
if self.exclude_tools and tool.name in self.exclude_tools:
|
|
309
|
+
continue
|
|
310
|
+
if self.include_tools is None or tool.name in self.include_tools:
|
|
311
|
+
filtered_tools.append(tool)
|
|
312
|
+
|
|
313
|
+
# Register the tools with the toolkit
|
|
314
|
+
for tool in filtered_tools:
|
|
315
|
+
try:
|
|
316
|
+
# Get an entrypoint for the tool
|
|
317
|
+
entrypoint = get_entrypoint_for_tool(tool, session)
|
|
318
|
+
|
|
319
|
+
# Create a Function for the tool
|
|
320
|
+
f = Function(
|
|
321
|
+
name=tool.name,
|
|
322
|
+
description=tool.description,
|
|
323
|
+
parameters=tool.inputSchema,
|
|
324
|
+
entrypoint=entrypoint,
|
|
325
|
+
# Set skip_entrypoint_processing to True to avoid processing the entrypoint
|
|
326
|
+
skip_entrypoint_processing=True,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Register the Function with the toolkit
|
|
330
|
+
self.functions[f.name] = f
|
|
331
|
+
log_debug(f"Function: {f.name} registered with {self.name}")
|
|
332
|
+
except Exception as e:
|
|
333
|
+
log_error(f"Failed to register tool {tool.name}: {e}")
|
|
334
|
+
raise
|
|
335
|
+
|
|
336
|
+
async def initialize(self, session: ClientSession) -> None:
|
|
337
|
+
"""Initialize the MCP toolkit by getting available tools from the MCP server"""
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
# Initialize the session if not already initialized
|
|
341
|
+
await session.initialize()
|
|
342
|
+
|
|
343
|
+
self._sessions.append(session)
|
|
344
|
+
self._initialized = True
|
|
345
|
+
except Exception as e:
|
|
346
|
+
log_error(f"Failed to get MCP tools: {e}")
|
|
347
|
+
raise
|
agno/tools/mcp/params.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class SSEClientParams:
|
|
8
|
+
"""Parameters for SSE client connection."""
|
|
9
|
+
|
|
10
|
+
url: str
|
|
11
|
+
headers: Optional[Dict[str, Any]] = None
|
|
12
|
+
timeout: Optional[float] = 5
|
|
13
|
+
sse_read_timeout: Optional[float] = 60 * 5
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class StreamableHTTPClientParams:
|
|
18
|
+
"""Parameters for Streamable HTTP client connection."""
|
|
19
|
+
|
|
20
|
+
url: str
|
|
21
|
+
headers: Optional[Dict[str, Any]] = None
|
|
22
|
+
timeout: Optional[timedelta] = timedelta(seconds=30)
|
|
23
|
+
sse_read_timeout: Optional[timedelta] = timedelta(seconds=60 * 5)
|
|
24
|
+
terminate_on_close: Optional[bool] = None
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Union
|
|
2
|
+
from warnings import warn
|
|
3
|
+
|
|
4
|
+
from agno.tools.function import Function
|
|
5
|
+
from agno.tools.mcp import MCPTools
|
|
6
|
+
from agno.utils.log import logger
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from toolbox_core import ToolboxClient # type: ignore
|
|
10
|
+
except ImportError:
|
|
11
|
+
raise ImportError("`toolbox_core` not installed. Please install using `pip install -U toolbox-core`.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MCPToolsMeta(type):
|
|
15
|
+
"""Metaclass for MCPTools to ensure proper initialization with AgentOS"""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def __name__(cls):
|
|
19
|
+
return "MCPTools"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MCPToolbox(MCPTools, metaclass=MCPToolsMeta):
|
|
23
|
+
"""
|
|
24
|
+
A toolkit that combines MCPTools server connectivity with MCP Toolbox for Databases client (toolbox-core).
|
|
25
|
+
|
|
26
|
+
MCPToolbox connects to an MCP Toolbox server and registers all available tools, then uses
|
|
27
|
+
toolbox-core to filter those tools by toolset or tool name. This enables agents to
|
|
28
|
+
receive only the specific tools they need while maintaining full MCP execution capabilities.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
url: str,
|
|
34
|
+
toolsets: Optional[List[str]] = None,
|
|
35
|
+
tool_name: Optional[str] = None,
|
|
36
|
+
headers: Optional[Dict[str, Any]] = None,
|
|
37
|
+
transport: Literal["stdio", "sse", "streamable-http"] = "streamable-http",
|
|
38
|
+
append_mcp_to_url: bool = True,
|
|
39
|
+
**kwargs,
|
|
40
|
+
):
|
|
41
|
+
"""Initialize MCPToolbox with filtering capabilities.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
url (str): Base URL for the toolbox service.
|
|
45
|
+
toolsets (Optional[List[str]], optional): List of toolset names to filter tools by. Defaults to None.
|
|
46
|
+
tool_name (Optional[str], optional): Single tool name to load. Defaults to None.
|
|
47
|
+
headers (Optional[Dict[str, Any]], optional): Headers for toolbox-core client requests. Defaults to None.
|
|
48
|
+
transport (Literal["stdio", "sse", "streamable-http"], optional): MCP transport protocol. Defaults to "streamable-http".
|
|
49
|
+
append_mcp_to_url (bool, optional): Whether to append "/mcp" to the URL if it doesn't end with it. Defaults to True.
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
if append_mcp_to_url and not url.endswith("/mcp"):
|
|
53
|
+
url = url + "/mcp"
|
|
54
|
+
|
|
55
|
+
super().__init__(url=url, transport=transport, **kwargs)
|
|
56
|
+
|
|
57
|
+
self.name = "toolbox_client"
|
|
58
|
+
self.toolbox_url = url
|
|
59
|
+
self.toolsets = toolsets
|
|
60
|
+
self.tool_name = tool_name
|
|
61
|
+
self.headers = headers
|
|
62
|
+
self._core_client_initialized = False
|
|
63
|
+
|
|
64
|
+
# Validate that only one of toolsets or tool_name is provided
|
|
65
|
+
filter_params = [toolsets, tool_name]
|
|
66
|
+
non_none_params = [p for p in filter_params if p is not None]
|
|
67
|
+
if len(non_none_params) > 1:
|
|
68
|
+
raise ValueError("Only one of toolsets or tool_name can be specified")
|
|
69
|
+
|
|
70
|
+
async def connect(self):
|
|
71
|
+
"""Initialize MCPToolbox instance and connect to the MCP server."""
|
|
72
|
+
# First, connect to MCP server and load all available tools
|
|
73
|
+
await super().connect()
|
|
74
|
+
|
|
75
|
+
if self._core_client_initialized:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Then, connect to the ToolboxClient and filter tools based on toolsets or tool_name
|
|
79
|
+
await self._connect_toolbox_client()
|
|
80
|
+
|
|
81
|
+
async def _connect_toolbox_client(self):
|
|
82
|
+
try:
|
|
83
|
+
if self.toolsets is not None or self.tool_name is not None:
|
|
84
|
+
self.__core_client = ToolboxClient(
|
|
85
|
+
url=self.toolbox_url,
|
|
86
|
+
client_headers=self.headers,
|
|
87
|
+
)
|
|
88
|
+
self._core_client_initialized = True
|
|
89
|
+
|
|
90
|
+
if self.toolsets is not None:
|
|
91
|
+
# Load multiple toolsets
|
|
92
|
+
all_functions = await self.load_multiple_toolsets(toolset_names=self.toolsets)
|
|
93
|
+
# Replace functions dict with filtered subset
|
|
94
|
+
filtered_functions = {func.name: func for func in all_functions}
|
|
95
|
+
self.functions = filtered_functions
|
|
96
|
+
elif self.tool_name is not None:
|
|
97
|
+
tool = await self.load_tool(tool_name=self.tool_name)
|
|
98
|
+
# Replace functions dict with just this single tool
|
|
99
|
+
self.functions = {tool.name: tool}
|
|
100
|
+
except Exception as e:
|
|
101
|
+
raise RuntimeError(f"Failed to connect to ToolboxClient: {e}") from e
|
|
102
|
+
|
|
103
|
+
def _handle_auth_params(
|
|
104
|
+
self,
|
|
105
|
+
auth_token_getters: dict[str, Callable[[], str]] = {},
|
|
106
|
+
auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
|
|
107
|
+
auth_headers: Optional[dict[str, Callable[[], str]]] = None,
|
|
108
|
+
):
|
|
109
|
+
"""handle authentication parameters for toolbox-core client"""
|
|
110
|
+
if auth_tokens:
|
|
111
|
+
if auth_token_getters:
|
|
112
|
+
warn(
|
|
113
|
+
"Both `auth_token_getters` and `auth_tokens` are provided. `auth_tokens` is deprecated, and `auth_token_getters` will be used.",
|
|
114
|
+
DeprecationWarning,
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
warn(
|
|
118
|
+
"Argument `auth_tokens` is deprecated. Use `auth_token_getters` instead.",
|
|
119
|
+
DeprecationWarning,
|
|
120
|
+
)
|
|
121
|
+
auth_token_getters = auth_tokens
|
|
122
|
+
|
|
123
|
+
if auth_headers:
|
|
124
|
+
if auth_token_getters:
|
|
125
|
+
warn(
|
|
126
|
+
"Both `auth_token_getters` and `auth_headers` are provided. `auth_headers` is deprecated, and `auth_token_getters` will be used.",
|
|
127
|
+
DeprecationWarning,
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
warn(
|
|
131
|
+
"Argument `auth_headers` is deprecated. Use `auth_token_getters` instead.",
|
|
132
|
+
DeprecationWarning,
|
|
133
|
+
)
|
|
134
|
+
auth_token_getters = auth_headers
|
|
135
|
+
return auth_token_getters
|
|
136
|
+
|
|
137
|
+
async def load_tool(
|
|
138
|
+
self,
|
|
139
|
+
tool_name: str,
|
|
140
|
+
auth_token_getters: dict[str, Callable[[], str]] = {},
|
|
141
|
+
auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
|
|
142
|
+
auth_headers: Optional[dict[str, Callable[[], str]]] = None,
|
|
143
|
+
bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
|
|
144
|
+
) -> Function:
|
|
145
|
+
"""Loads the tool with the given tool name from the Toolbox service.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
tool_name (str): The name of the tool to load.
|
|
149
|
+
auth_token_getters (dict[str, Callable[[], str]], optional): A mapping of authentication source names to functions that retrieve ID tokens. Defaults to {}.
|
|
150
|
+
auth_tokens (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
|
|
151
|
+
auth_headers (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
|
|
152
|
+
bound_params (dict[str, Union[Any, Callable[[], Any]]], optional): A mapping of parameter names to their bound values. Defaults to {}.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
RuntimeError: If the tool is not found in the MCP functions registry.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Function: The loaded tool function.
|
|
159
|
+
"""
|
|
160
|
+
auth_token_getters = self._handle_auth_params(
|
|
161
|
+
auth_token_getters=auth_token_getters,
|
|
162
|
+
auth_tokens=auth_tokens,
|
|
163
|
+
auth_headers=auth_headers,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
core_sync_tool = await self.__core_client.load_tool(
|
|
167
|
+
name=tool_name,
|
|
168
|
+
auth_token_getters=auth_token_getters,
|
|
169
|
+
bound_params=bound_params,
|
|
170
|
+
)
|
|
171
|
+
# Return the Function object from our MCP functions registry
|
|
172
|
+
if core_sync_tool._name in self.functions:
|
|
173
|
+
return self.functions[core_sync_tool._name]
|
|
174
|
+
else:
|
|
175
|
+
raise RuntimeError(f"Tool '{tool_name}' was not found in MCP functions registry")
|
|
176
|
+
|
|
177
|
+
async def load_toolset(
|
|
178
|
+
self,
|
|
179
|
+
toolset_name: Optional[str] = None,
|
|
180
|
+
auth_token_getters: dict[str, Callable[[], str]] = {},
|
|
181
|
+
auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
|
|
182
|
+
auth_headers: Optional[dict[str, Callable[[], str]]] = None,
|
|
183
|
+
bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
|
|
184
|
+
strict: bool = False,
|
|
185
|
+
) -> List[Function]:
|
|
186
|
+
"""Loads tools from the configured toolset.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
toolset_name (Optional[str], optional): The name of the toolset to load. Defaults to None.
|
|
190
|
+
auth_token_getters (dict[str, Callable[[], str]], optional): A mapping of authentication source names to functions that retrieve ID tokens. Defaults to {}.
|
|
191
|
+
auth_tokens (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
|
|
192
|
+
auth_headers (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
|
|
193
|
+
bound_params (dict[str, Union[Any, Callable[[], Any]]], optional): A mapping of parameter names to their bound values. Defaults to {}.
|
|
194
|
+
strict (bool, optional): If True, raises an error if *any* loaded tool instance fails
|
|
195
|
+
to utilize all of the given parameters or auth tokens. (if any
|
|
196
|
+
provided). If False (default), raises an error only if a
|
|
197
|
+
user-provided parameter or auth token cannot be applied to *any*
|
|
198
|
+
loaded tool across the set.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
List[Function]: A list of all tools loaded from the Toolbox.
|
|
202
|
+
"""
|
|
203
|
+
auth_token_getters = self._handle_auth_params(
|
|
204
|
+
auth_token_getters=auth_token_getters,
|
|
205
|
+
auth_tokens=auth_tokens,
|
|
206
|
+
auth_headers=auth_headers,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
core_sync_tools = await self.__core_client.load_toolset(
|
|
210
|
+
name=toolset_name,
|
|
211
|
+
auth_token_getters=auth_token_getters,
|
|
212
|
+
bound_params=bound_params,
|
|
213
|
+
strict=strict,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
tools = []
|
|
217
|
+
for core_sync_tool in core_sync_tools:
|
|
218
|
+
if core_sync_tool._name in self.functions:
|
|
219
|
+
tools.append(self.functions[core_sync_tool._name])
|
|
220
|
+
else:
|
|
221
|
+
logger.debug(f"Tool '{core_sync_tool._name}' from toolset '{toolset_name}' not available in MCP server")
|
|
222
|
+
return tools
|
|
223
|
+
|
|
224
|
+
async def load_multiple_toolsets(
|
|
225
|
+
self,
|
|
226
|
+
toolset_names: List[str],
|
|
227
|
+
auth_token_getters: dict[str, Callable[[], str]] = {},
|
|
228
|
+
bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
|
|
229
|
+
strict: bool = False,
|
|
230
|
+
) -> List[Function]:
|
|
231
|
+
"""Load tools from multiple toolsets.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
toolset_names (List[str]): A list of toolset names to load.
|
|
235
|
+
auth_token_getters (dict[str, Callable[[], str]], optional): A mapping of authentication source names to functions that retrieve ID tokens. Defaults to {}.
|
|
236
|
+
bound_params (dict[str, Union[Any, Callable[[], Any]]], optional): A mapping of parameter names to their bound values. Defaults to {}.
|
|
237
|
+
strict (bool, optional): If True, raises an error if *any* loaded tool instance fails to utilize all of the given parameters or auth tokens. Defaults to False.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
List[Function]: A list of all tools loaded from the specified toolsets.
|
|
241
|
+
"""
|
|
242
|
+
all_tools = []
|
|
243
|
+
for toolset_name in toolset_names:
|
|
244
|
+
tools = await self.load_toolset(
|
|
245
|
+
toolset_name=toolset_name,
|
|
246
|
+
auth_token_getters=auth_token_getters,
|
|
247
|
+
bound_params=bound_params,
|
|
248
|
+
strict=strict,
|
|
249
|
+
)
|
|
250
|
+
all_tools.extend(tools)
|
|
251
|
+
return all_tools
|
|
252
|
+
|
|
253
|
+
async def close(self):
|
|
254
|
+
"""Close the underlying asynchronous client."""
|
|
255
|
+
if self._core_client_initialized and hasattr(self, "_MCPToolbox__core_client"):
|
|
256
|
+
await self.__core_client.close()
|
|
257
|
+
await super().close()
|
|
258
|
+
|
|
259
|
+
async def load_toolset_safe(self, toolset_name: str) -> List[str]:
|
|
260
|
+
"""Safely load a toolset and return tool names."""
|
|
261
|
+
try:
|
|
262
|
+
tools = await self.load_toolset(toolset_name)
|
|
263
|
+
return [tool.name for tool in tools]
|
|
264
|
+
except Exception as e:
|
|
265
|
+
raise RuntimeError(f"Failed to load toolset '{toolset_name}': {e}") from e
|
|
266
|
+
|
|
267
|
+
def get_client(self) -> ToolboxClient:
|
|
268
|
+
"""Get the underlying ToolboxClient."""
|
|
269
|
+
if not self._core_client_initialized:
|
|
270
|
+
raise RuntimeError("ToolboxClient not initialized. Call connect() first.")
|
|
271
|
+
return self.__core_client
|
|
272
|
+
|
|
273
|
+
async def __aenter__(self):
|
|
274
|
+
"""Initialize the direct toolbox client."""
|
|
275
|
+
await super().__aenter__()
|
|
276
|
+
await self.connect()
|
|
277
|
+
return self
|
|
278
|
+
|
|
279
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
280
|
+
"""Clean up the toolbox client."""
|
|
281
|
+
# Close ToolboxClient first, then MCP client
|
|
282
|
+
if self._core_client_initialized and hasattr(self, "_MCPToolbox__core_client"):
|
|
283
|
+
await self.__core_client.close()
|
|
284
|
+
await super().__aexit__(exc_type, exc_val, exc_tb)
|