agno 2.0.0rc2__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 +6009 -2874
- 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 +595 -187
- 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 +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- 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 +1011 -566
- 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 +110 -37
- 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 +143 -4
- 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 +60 -6
- agno/models/openai/chat.py +102 -43
- 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 +81 -5
- 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 -175
- 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 +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- 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 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- 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/lumalab.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/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- 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 +222 -7
- agno/utils/gemini.py +181 -23
- 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 +95 -5
- 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/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- 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 +183 -135
- 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 +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -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.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/tools/googlecalendar.py
CHANGED
|
@@ -4,7 +4,7 @@ import uuid
|
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from os import getenv
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, Dict, List, Optional
|
|
7
|
+
from typing import Any, Dict, List, Optional, cast
|
|
8
8
|
|
|
9
9
|
from agno.tools import Toolkit
|
|
10
10
|
from agno.utils.log import log_debug, log_error, log_info
|
|
@@ -164,8 +164,10 @@ class GoogleCalendarTools(Toolkit):
|
|
|
164
164
|
)
|
|
165
165
|
|
|
166
166
|
try:
|
|
167
|
+
service = cast(Resource, self.service)
|
|
168
|
+
|
|
167
169
|
events_result = (
|
|
168
|
-
|
|
170
|
+
service.events()
|
|
169
171
|
.list(
|
|
170
172
|
calendarId=self.calendar_id,
|
|
171
173
|
timeMin=start_date,
|
|
@@ -194,6 +196,7 @@ class GoogleCalendarTools(Toolkit):
|
|
|
194
196
|
timezone: Optional[str] = "UTC",
|
|
195
197
|
attendees: Optional[List[str]] = None,
|
|
196
198
|
add_google_meet_link: Optional[bool] = False,
|
|
199
|
+
notify_attendees: Optional[bool] = False,
|
|
197
200
|
) -> str:
|
|
198
201
|
"""
|
|
199
202
|
Create a new event in the Google Calendar.
|
|
@@ -207,6 +210,7 @@ class GoogleCalendarTools(Toolkit):
|
|
|
207
210
|
timezone (Optional[str]): Timezone for the event (default: UTC)
|
|
208
211
|
attendees (Optional[List[str]]): List of email addresses of the attendees
|
|
209
212
|
add_google_meet_link (Optional[bool]): Whether to add a Google Meet video link to the event
|
|
213
|
+
notify_attendees (Optional[bool]): Whether to send email notifications to attendees (default: False)
|
|
210
214
|
|
|
211
215
|
Returns:
|
|
212
216
|
str: JSON string containing the created Google Calendar event or error message
|
|
@@ -241,12 +245,18 @@ class GoogleCalendarTools(Toolkit):
|
|
|
241
245
|
# Remove None values
|
|
242
246
|
event = {k: v for k, v in event.items() if v is not None}
|
|
243
247
|
|
|
248
|
+
# Determine sendUpdates value based on notify_attendees parameter
|
|
249
|
+
send_updates = "all" if notify_attendees and attendees else "none"
|
|
250
|
+
|
|
251
|
+
service = cast(Resource, self.service)
|
|
252
|
+
|
|
244
253
|
event_result = (
|
|
245
|
-
|
|
254
|
+
service.events()
|
|
246
255
|
.insert(
|
|
247
256
|
calendarId=self.calendar_id,
|
|
248
257
|
body=event,
|
|
249
258
|
conferenceDataVersion=1 if add_google_meet_link else 0,
|
|
259
|
+
sendUpdates=send_updates,
|
|
250
260
|
)
|
|
251
261
|
.execute()
|
|
252
262
|
)
|
|
@@ -267,6 +277,7 @@ class GoogleCalendarTools(Toolkit):
|
|
|
267
277
|
end_date: Optional[str] = None,
|
|
268
278
|
timezone: Optional[str] = None,
|
|
269
279
|
attendees: Optional[List[str]] = None,
|
|
280
|
+
notify_attendees: Optional[bool] = False,
|
|
270
281
|
) -> str:
|
|
271
282
|
"""
|
|
272
283
|
Update an existing event in the Google Calendar.
|
|
@@ -280,13 +291,16 @@ class GoogleCalendarTools(Toolkit):
|
|
|
280
291
|
end_date (Optional[str]): New end date and time in ISO format (YYYY-MM-DDTHH:MM:SS)
|
|
281
292
|
timezone (Optional[str]): New timezone for the event
|
|
282
293
|
attendees (Optional[List[str]]): Updated list of attendee email addresses
|
|
294
|
+
notify_attendees (Optional[bool]): Whether to send email notifications to attendees (default: False)
|
|
283
295
|
|
|
284
296
|
Returns:
|
|
285
297
|
str: JSON string containing the updated Google Calendar event or error message
|
|
286
298
|
"""
|
|
287
299
|
try:
|
|
300
|
+
service = cast(Resource, self.service)
|
|
301
|
+
|
|
288
302
|
# First get the existing event to preserve its structure
|
|
289
|
-
event =
|
|
303
|
+
event = service.events().get(calendarId=self.calendar_id, eventId=event_id).execute()
|
|
290
304
|
|
|
291
305
|
# Update only the fields that are provided
|
|
292
306
|
if title is not None:
|
|
@@ -317,9 +331,15 @@ class GoogleCalendarTools(Toolkit):
|
|
|
317
331
|
except ValueError:
|
|
318
332
|
return json.dumps({"error": f"Invalid end datetime format: {end_date}. Use ISO format."})
|
|
319
333
|
|
|
334
|
+
# Determine sendUpdates value based on notify_attendees parameter
|
|
335
|
+
send_updates = "all" if notify_attendees and attendees else "none"
|
|
336
|
+
|
|
320
337
|
# Update the event
|
|
338
|
+
|
|
321
339
|
updated_event = (
|
|
322
|
-
|
|
340
|
+
service.events()
|
|
341
|
+
.update(calendarId=self.calendar_id, eventId=event_id, body=event, sendUpdates=send_updates)
|
|
342
|
+
.execute()
|
|
323
343
|
)
|
|
324
344
|
|
|
325
345
|
log_debug(f"Event {event_id} updated successfully.")
|
|
@@ -329,18 +349,24 @@ class GoogleCalendarTools(Toolkit):
|
|
|
329
349
|
return json.dumps({"error": f"An error occurred: {error}"})
|
|
330
350
|
|
|
331
351
|
@authenticate
|
|
332
|
-
def delete_event(self, event_id: str) -> str:
|
|
352
|
+
def delete_event(self, event_id: str, notify_attendees: Optional[bool] = True) -> str:
|
|
333
353
|
"""
|
|
334
354
|
Delete an event from the Google Calendar.
|
|
335
355
|
|
|
336
356
|
Args:
|
|
337
357
|
event_id (str): ID of the event to delete
|
|
358
|
+
notify_attendees (Optional[bool]): Whether to send email notifications to attendees (default: False)
|
|
338
359
|
|
|
339
360
|
Returns:
|
|
340
361
|
str: JSON string containing success message or error message
|
|
341
362
|
"""
|
|
342
363
|
try:
|
|
343
|
-
|
|
364
|
+
# Determine sendUpdates value based on notify_attendees parameter
|
|
365
|
+
send_updates = "all" if notify_attendees else "none"
|
|
366
|
+
|
|
367
|
+
service = cast(Resource, self.service)
|
|
368
|
+
|
|
369
|
+
service.events().delete(calendarId=self.calendar_id, eventId=event_id, sendUpdates=send_updates).execute()
|
|
344
370
|
|
|
345
371
|
log_debug(f"Event {event_id} deleted successfully.")
|
|
346
372
|
return json.dumps({"success": True, "message": f"Event {event_id} deleted successfully."})
|
|
@@ -366,6 +392,8 @@ class GoogleCalendarTools(Toolkit):
|
|
|
366
392
|
str: JSON string containing all Google Calendar events or error message
|
|
367
393
|
"""
|
|
368
394
|
try:
|
|
395
|
+
service = cast(Resource, self.service)
|
|
396
|
+
|
|
369
397
|
params = {
|
|
370
398
|
"calendarId": self.calendar_id,
|
|
371
399
|
"maxResults": min(max_results, 100),
|
|
@@ -412,7 +440,7 @@ class GoogleCalendarTools(Toolkit):
|
|
|
412
440
|
if page_token:
|
|
413
441
|
params["pageToken"] = page_token
|
|
414
442
|
|
|
415
|
-
events_result =
|
|
443
|
+
events_result = service.events().list(**params).execute()
|
|
416
444
|
all_events.extend(events_result.get("items", []))
|
|
417
445
|
|
|
418
446
|
page_token = events_result.get("nextPageToken")
|
agno/tools/googlesheets.py
CHANGED
|
@@ -48,13 +48,14 @@ import json
|
|
|
48
48
|
from functools import wraps
|
|
49
49
|
from os import getenv
|
|
50
50
|
from pathlib import Path
|
|
51
|
-
from typing import Any, List, Optional
|
|
51
|
+
from typing import Any, List, Optional, Union
|
|
52
52
|
|
|
53
53
|
from agno.tools import Toolkit
|
|
54
54
|
|
|
55
55
|
try:
|
|
56
56
|
from google.auth.transport.requests import Request
|
|
57
57
|
from google.oauth2.credentials import Credentials
|
|
58
|
+
from google.oauth2.service_account import Credentials as ServiceAccountCredentials
|
|
58
59
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
59
60
|
from googleapiclient.discovery import Resource, build
|
|
60
61
|
except ImportError:
|
|
@@ -91,9 +92,10 @@ class GoogleSheetsTools(Toolkit):
|
|
|
91
92
|
scopes: Optional[List[str]] = None,
|
|
92
93
|
spreadsheet_id: Optional[str] = None,
|
|
93
94
|
spreadsheet_range: Optional[str] = None,
|
|
94
|
-
creds: Optional[Credentials] = None,
|
|
95
|
+
creds: Optional[Union[Credentials, ServiceAccountCredentials]] = None,
|
|
95
96
|
creds_path: Optional[str] = None,
|
|
96
97
|
token_path: Optional[str] = None,
|
|
98
|
+
service_account_path: Optional[str] = None,
|
|
97
99
|
oauth_port: int = 0,
|
|
98
100
|
enable_read_sheet: bool = True,
|
|
99
101
|
enable_create_sheet: bool = False,
|
|
@@ -108,9 +110,10 @@ class GoogleSheetsTools(Toolkit):
|
|
|
108
110
|
scopes (Optional[List[str]]): Custom OAuth scopes. If None, uses write scope by default.
|
|
109
111
|
spreadsheet_id (Optional[str]): ID of the target spreadsheet.
|
|
110
112
|
spreadsheet_range (Optional[str]): Range within the spreadsheet.
|
|
111
|
-
creds (Optional[Credentials]): Pre-existing credentials.
|
|
113
|
+
creds (Optional[Credentials | ServiceAccountCredentials]): Pre-existing credentials.
|
|
112
114
|
creds_path (Optional[str]): Path to credentials file.
|
|
113
115
|
token_path (Optional[str]): Path to token file.
|
|
116
|
+
service_account_path (Optional[str]): Path to a service account file.
|
|
114
117
|
oauth_port (int): Port to use for OAuth authentication. Defaults to 0.
|
|
115
118
|
enable_read_sheet (bool): Enable reading from a sheet.
|
|
116
119
|
enable_create_sheet (bool): Enable creating a sheet.
|
|
@@ -126,6 +129,7 @@ class GoogleSheetsTools(Toolkit):
|
|
|
126
129
|
self.token_path = token_path
|
|
127
130
|
self.oauth_port = oauth_port
|
|
128
131
|
self.service: Optional[Resource] = None
|
|
132
|
+
self.service_account_path = service_account_path
|
|
129
133
|
|
|
130
134
|
# Determine required scopes based on operations if no custom scopes provided
|
|
131
135
|
if scopes is None:
|
|
@@ -171,6 +175,17 @@ class GoogleSheetsTools(Toolkit):
|
|
|
171
175
|
if self.creds and self.creds.valid:
|
|
172
176
|
return
|
|
173
177
|
|
|
178
|
+
service_account_path = self.service_account_path or getenv("GOOGLE_SERVICE_ACCOUNT_FILE")
|
|
179
|
+
|
|
180
|
+
if service_account_path:
|
|
181
|
+
self.creds = ServiceAccountCredentials.from_service_account_file(
|
|
182
|
+
service_account_path,
|
|
183
|
+
scopes=self.scopes,
|
|
184
|
+
)
|
|
185
|
+
if self.creds and self.creds.expired:
|
|
186
|
+
self.creds.refresh(Request())
|
|
187
|
+
return
|
|
188
|
+
|
|
174
189
|
token_file = Path(self.token_path or "token.json")
|
|
175
190
|
creds_file = Path(self.credentials_path or "credentials.json")
|
|
176
191
|
|
|
@@ -178,7 +193,7 @@ class GoogleSheetsTools(Toolkit):
|
|
|
178
193
|
self.creds = Credentials.from_authorized_user_file(str(token_file), self.scopes)
|
|
179
194
|
|
|
180
195
|
if not self.creds or not self.creds.valid:
|
|
181
|
-
if self.creds and self.creds.expired and self.creds.refresh_token:
|
|
196
|
+
if self.creds and self.creds.expired and self.creds.refresh_token: # type: ignore
|
|
182
197
|
self.creds.refresh(Request())
|
|
183
198
|
else:
|
|
184
199
|
client_config = {
|
|
@@ -199,7 +214,7 @@ class GoogleSheetsTools(Toolkit):
|
|
|
199
214
|
flow = InstalledAppFlow.from_client_config(client_config, self.scopes)
|
|
200
215
|
# Opens up a browser window for OAuth authentication
|
|
201
216
|
self.creds = flow.run_local_server(port=self.oauth_port)
|
|
202
|
-
token_file.write_text(self.creds.to_json()) if self.creds else None
|
|
217
|
+
token_file.write_text(self.creds.to_json()) if self.creds else None # type: ignore
|
|
203
218
|
|
|
204
219
|
@authenticate
|
|
205
220
|
def read_sheet(self, spreadsheet_id: Optional[str] = None, spreadsheet_range: Optional[str] = None) -> str:
|
agno/tools/jira.py
CHANGED
|
@@ -22,6 +22,7 @@ class JiraTools(Toolkit):
|
|
|
22
22
|
enable_create_issue: bool = True,
|
|
23
23
|
enable_search_issues: bool = True,
|
|
24
24
|
enable_add_comment: bool = True,
|
|
25
|
+
enable_add_worklog: bool = True,
|
|
25
26
|
all: bool = False,
|
|
26
27
|
**kwargs,
|
|
27
28
|
):
|
|
@@ -55,6 +56,8 @@ class JiraTools(Toolkit):
|
|
|
55
56
|
tools.append(self.search_issues)
|
|
56
57
|
if enable_add_comment or all:
|
|
57
58
|
tools.append(self.add_comment)
|
|
59
|
+
if enable_add_worklog or all:
|
|
60
|
+
tools.append(self.add_worklog)
|
|
58
61
|
|
|
59
62
|
super().__init__(name="jira_tools", tools=tools, **kwargs)
|
|
60
63
|
|
|
@@ -148,3 +151,20 @@ class JiraTools(Toolkit):
|
|
|
148
151
|
except Exception as e:
|
|
149
152
|
logger.error(f"Error adding comment to issue {issue_key}: {e}")
|
|
150
153
|
return json.dumps({"error": str(e)})
|
|
154
|
+
|
|
155
|
+
def add_worklog(self, issue_key: str, time_spent: str, comment: Optional[str] = None) -> str:
|
|
156
|
+
"""
|
|
157
|
+
Adds a worklog entry to log time spent on a specific Jira issue.
|
|
158
|
+
|
|
159
|
+
:param issue_key: The key of the issue to log work against (e.g., 'PROJ-123').
|
|
160
|
+
:param time_spent: The amount of time spent. Use Jira's format, e.g., '2h', '30m', '1d 4h'.
|
|
161
|
+
:param comment: An optional comment describing the work done.
|
|
162
|
+
:return: A JSON string indicating success or containing an error message.
|
|
163
|
+
"""
|
|
164
|
+
try:
|
|
165
|
+
self.jira.add_worklog(issue=issue_key, timeSpent=time_spent, comment=comment)
|
|
166
|
+
log_debug(f"Worklog of '{time_spent}' added to issue {issue_key}")
|
|
167
|
+
return json.dumps({"status": "success", "issue_key": issue_key, "time_spent": time_spent})
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(f"Error adding worklog to issue {issue_key}: {e}")
|
|
170
|
+
return json.dumps({"error": str(e)})
|
agno/tools/knowledge.py
CHANGED
|
@@ -43,15 +43,15 @@ class KnowledgeTools(Toolkit):
|
|
|
43
43
|
if enable_think or all:
|
|
44
44
|
tools.append(self.think)
|
|
45
45
|
if enable_search or all:
|
|
46
|
-
tools.append(self.
|
|
46
|
+
tools.append(self.search_knowledge)
|
|
47
47
|
if enable_analyze or all:
|
|
48
48
|
tools.append(self.analyze)
|
|
49
49
|
|
|
50
50
|
super().__init__(
|
|
51
51
|
name="knowledge_tools",
|
|
52
|
+
tools=tools,
|
|
52
53
|
instructions=self.instructions,
|
|
53
54
|
add_instructions=add_instructions,
|
|
54
|
-
tools=tools,
|
|
55
55
|
**kwargs,
|
|
56
56
|
)
|
|
57
57
|
|
|
@@ -89,7 +89,7 @@ class KnowledgeTools(Toolkit):
|
|
|
89
89
|
log_error(f"Error recording thought: {e}")
|
|
90
90
|
return f"Error recording thought: {e}"
|
|
91
91
|
|
|
92
|
-
def
|
|
92
|
+
def search_knowledge(self, session_state: Dict[str, Any], query: str) -> str:
|
|
93
93
|
"""Use this tool to search the knowledge base for relevant information.
|
|
94
94
|
After thinking through the question, use this tool as many times as needed to search for relevant information.
|
|
95
95
|
|
agno/tools/lumalab.py
CHANGED
|
@@ -4,7 +4,7 @@ from os import getenv
|
|
|
4
4
|
from typing import Any, Dict, List, Literal, Optional, TypedDict
|
|
5
5
|
|
|
6
6
|
from agno.agent import Agent
|
|
7
|
-
from agno.media import
|
|
7
|
+
from agno.media import Video
|
|
8
8
|
from agno.tools import Toolkit
|
|
9
9
|
from agno.tools.function import ToolResult
|
|
10
10
|
from agno.utils.log import log_info, logger
|
|
@@ -109,7 +109,7 @@ class LumaLabTools(Toolkit):
|
|
|
109
109
|
if generation.state == "completed" and generation.assets:
|
|
110
110
|
video_url = generation.assets.video
|
|
111
111
|
if video_url:
|
|
112
|
-
video_artifact =
|
|
112
|
+
video_artifact = Video(id=video_id, url=video_url, eta="completed")
|
|
113
113
|
return ToolResult(
|
|
114
114
|
content=f"Video generated successfully: {video_url}",
|
|
115
115
|
videos=[video_artifact],
|
|
@@ -164,7 +164,7 @@ class LumaLabTools(Toolkit):
|
|
|
164
164
|
if generation.state == "completed" and generation.assets:
|
|
165
165
|
video_url = generation.assets.video
|
|
166
166
|
if video_url:
|
|
167
|
-
video_artifact =
|
|
167
|
+
video_artifact = Video(id=video_id, url=video_url, state="completed")
|
|
168
168
|
return ToolResult(
|
|
169
169
|
content=f"Video generated successfully: {video_url}",
|
|
170
170
|
videos=[video_artifact],
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from agno.tools.mcp.mcp import MCPTools
|
|
2
|
+
from agno.tools.mcp.multi_mcp import MultiMCPTools
|
|
3
|
+
from agno.tools.mcp.params import SSEClientParams, StreamableHTTPClientParams
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"MCPTools",
|
|
7
|
+
"MultiMCPTools",
|
|
8
|
+
"StreamableHTTPClientParams",
|
|
9
|
+
"SSEClientParams",
|
|
10
|
+
]
|
agno/tools/mcp/mcp.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
from dataclasses import asdict
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
from typing import Any, Literal, Optional, Union
|
|
5
|
+
|
|
6
|
+
from agno.tools import Toolkit
|
|
7
|
+
from agno.tools.function import Function
|
|
8
|
+
from agno.tools.mcp.params import SSEClientParams, StreamableHTTPClientParams
|
|
9
|
+
from agno.utils.log import log_debug, log_error, log_info
|
|
10
|
+
from agno.utils.mcp import get_entrypoint_for_tool, prepare_command
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from mcp import ClientSession, StdioServerParameters
|
|
14
|
+
from mcp.client.sse import sse_client
|
|
15
|
+
from mcp.client.stdio import get_default_environment, stdio_client
|
|
16
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
17
|
+
except (ImportError, ModuleNotFoundError):
|
|
18
|
+
raise ImportError("`mcp` not installed. Please install using `pip install mcp`")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MCPTools(Toolkit):
|
|
22
|
+
"""
|
|
23
|
+
A toolkit for integrating Model Context Protocol (MCP) servers with Agno agents.
|
|
24
|
+
This allows agents to access tools, resources, and prompts exposed by MCP servers.
|
|
25
|
+
|
|
26
|
+
Can be used in three ways:
|
|
27
|
+
1. Direct initialization with a ClientSession
|
|
28
|
+
2. As an async context manager with StdioServerParameters
|
|
29
|
+
3. As an async context manager with SSE or Streamable HTTP client parameters
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
command: Optional[str] = None,
|
|
35
|
+
*,
|
|
36
|
+
url: Optional[str] = None,
|
|
37
|
+
env: Optional[dict[str, str]] = None,
|
|
38
|
+
transport: Literal["stdio", "sse", "streamable-http"] = "stdio",
|
|
39
|
+
server_params: Optional[Union[StdioServerParameters, SSEClientParams, StreamableHTTPClientParams]] = None,
|
|
40
|
+
session: Optional[ClientSession] = None,
|
|
41
|
+
timeout_seconds: int = 10,
|
|
42
|
+
client=None,
|
|
43
|
+
include_tools: Optional[list[str]] = None,
|
|
44
|
+
exclude_tools: Optional[list[str]] = None,
|
|
45
|
+
refresh_connection: bool = False,
|
|
46
|
+
tool_name_prefix: Optional[str] = "",
|
|
47
|
+
**kwargs,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Initialize the MCP toolkit.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
session: An initialized MCP ClientSession connected to an MCP server
|
|
54
|
+
server_params: Parameters for creating a new session
|
|
55
|
+
command: The command to run to start the server. Should be used in conjunction with env.
|
|
56
|
+
url: The URL endpoint for SSE or Streamable HTTP connection when transport is "sse" or "streamable-http".
|
|
57
|
+
env: The environment variables to pass to the server. Should be used in conjunction with command.
|
|
58
|
+
client: The underlying MCP client (optional, used to prevent garbage collection)
|
|
59
|
+
timeout_seconds: Read timeout in seconds for the MCP client
|
|
60
|
+
include_tools: Optional list of tool names to include (if None, includes all)
|
|
61
|
+
exclude_tools: Optional list of tool names to exclude (if None, excludes none)
|
|
62
|
+
transport: The transport protocol to use, either "stdio" or "sse" or "streamable-http"
|
|
63
|
+
refresh_connection: If True, the connection and tools will be refreshed on each run
|
|
64
|
+
"""
|
|
65
|
+
super().__init__(name="MCPTools", **kwargs)
|
|
66
|
+
|
|
67
|
+
if transport == "sse":
|
|
68
|
+
log_info("SSE as a standalone transport is deprecated. Please use Streamable HTTP instead.")
|
|
69
|
+
|
|
70
|
+
# Set these after `__init__` to bypass the `_check_tools_filters`
|
|
71
|
+
# because tools are not available until `initialize()` is called.
|
|
72
|
+
self.include_tools = include_tools
|
|
73
|
+
self.exclude_tools = exclude_tools
|
|
74
|
+
self.refresh_connection = refresh_connection
|
|
75
|
+
self.tool_name_prefix = tool_name_prefix
|
|
76
|
+
|
|
77
|
+
if session is None and server_params is None:
|
|
78
|
+
if transport == "sse" and url is None:
|
|
79
|
+
raise ValueError("One of 'url' or 'server_params' parameters must be provided when using SSE transport")
|
|
80
|
+
if transport == "stdio" and command is None:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"One of 'command' or 'server_params' parameters must be provided when using stdio transport"
|
|
83
|
+
)
|
|
84
|
+
if transport == "streamable-http" and url is None:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"One of 'url' or 'server_params' parameters must be provided when using Streamable HTTP transport"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Ensure the received server_params are valid for the given transport
|
|
90
|
+
if server_params is not None:
|
|
91
|
+
if transport == "sse":
|
|
92
|
+
if not isinstance(server_params, SSEClientParams):
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"If using the SSE transport, server_params must be an instance of SSEClientParams."
|
|
95
|
+
)
|
|
96
|
+
elif transport == "stdio":
|
|
97
|
+
if not isinstance(server_params, StdioServerParameters):
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"If using the stdio transport, server_params must be an instance of StdioServerParameters."
|
|
100
|
+
)
|
|
101
|
+
elif transport == "streamable-http":
|
|
102
|
+
if not isinstance(server_params, StreamableHTTPClientParams):
|
|
103
|
+
raise ValueError(
|
|
104
|
+
"If using the streamable-http transport, server_params must be an instance of StreamableHTTPClientParams."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self.timeout_seconds = timeout_seconds
|
|
108
|
+
self.session: Optional[ClientSession] = session
|
|
109
|
+
self.server_params: Optional[Union[StdioServerParameters, SSEClientParams, StreamableHTTPClientParams]] = (
|
|
110
|
+
server_params
|
|
111
|
+
)
|
|
112
|
+
self.transport = transport
|
|
113
|
+
self.url = url
|
|
114
|
+
|
|
115
|
+
# Merge provided env with system env
|
|
116
|
+
if env is not None:
|
|
117
|
+
env = {
|
|
118
|
+
**get_default_environment(),
|
|
119
|
+
**env,
|
|
120
|
+
}
|
|
121
|
+
else:
|
|
122
|
+
env = get_default_environment()
|
|
123
|
+
|
|
124
|
+
if command is not None and transport not in ["sse", "streamable-http"]:
|
|
125
|
+
parts = prepare_command(command)
|
|
126
|
+
cmd = parts[0]
|
|
127
|
+
arguments = parts[1:] if len(parts) > 1 else []
|
|
128
|
+
self.server_params = StdioServerParameters(command=cmd, args=arguments, env=env)
|
|
129
|
+
|
|
130
|
+
self._client = client
|
|
131
|
+
|
|
132
|
+
self._initialized = False
|
|
133
|
+
self._connection_task = None
|
|
134
|
+
self._active_contexts: list[Any] = []
|
|
135
|
+
self._context = None
|
|
136
|
+
self._session_context = None
|
|
137
|
+
|
|
138
|
+
def cleanup():
|
|
139
|
+
"""Cancel active connections"""
|
|
140
|
+
if self._connection_task and not self._connection_task.done():
|
|
141
|
+
self._connection_task.cancel()
|
|
142
|
+
|
|
143
|
+
# Setup cleanup logic before the instance is garbage collected
|
|
144
|
+
self._cleanup_finalizer = weakref.finalize(self, cleanup)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def initialized(self) -> bool:
|
|
148
|
+
return self._initialized
|
|
149
|
+
|
|
150
|
+
async def is_alive(self) -> bool:
|
|
151
|
+
if self.session is None:
|
|
152
|
+
return False
|
|
153
|
+
try:
|
|
154
|
+
await self.session.send_ping()
|
|
155
|
+
return True
|
|
156
|
+
except (RuntimeError, BaseException):
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
async def connect(self, force: bool = False):
|
|
160
|
+
"""Initialize a MCPTools instance and connect to the contextual MCP server"""
|
|
161
|
+
|
|
162
|
+
if force:
|
|
163
|
+
# Clean up the session and context so we force a new connection
|
|
164
|
+
self.session = None
|
|
165
|
+
self._context = None
|
|
166
|
+
self._session_context = None
|
|
167
|
+
self._initialized = False
|
|
168
|
+
self._connection_task = None
|
|
169
|
+
self._active_contexts = []
|
|
170
|
+
|
|
171
|
+
if self._initialized:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
await self._connect()
|
|
176
|
+
except (RuntimeError, BaseException) as e:
|
|
177
|
+
log_error(f"Failed to connect to {str(self)}: {e}")
|
|
178
|
+
|
|
179
|
+
async def _connect(self) -> None:
|
|
180
|
+
"""Connects to the MCP server and initializes the tools"""
|
|
181
|
+
|
|
182
|
+
if self._initialized:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
if self.session is not None:
|
|
186
|
+
await self.initialize()
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
# Create a new studio session
|
|
190
|
+
if self.transport == "sse":
|
|
191
|
+
sse_params = asdict(self.server_params) if self.server_params is not None else {} # type: ignore
|
|
192
|
+
if "url" not in sse_params:
|
|
193
|
+
sse_params["url"] = self.url
|
|
194
|
+
self._context = sse_client(**sse_params) # type: ignore
|
|
195
|
+
client_timeout = min(self.timeout_seconds, sse_params.get("timeout", self.timeout_seconds))
|
|
196
|
+
|
|
197
|
+
# Create a new streamable HTTP session
|
|
198
|
+
elif self.transport == "streamable-http":
|
|
199
|
+
streamable_http_params = asdict(self.server_params) if self.server_params is not None else {} # type: ignore
|
|
200
|
+
if "url" not in streamable_http_params:
|
|
201
|
+
streamable_http_params["url"] = self.url
|
|
202
|
+
self._context = streamablehttp_client(**streamable_http_params) # type: ignore
|
|
203
|
+
params_timeout = streamable_http_params.get("timeout", self.timeout_seconds)
|
|
204
|
+
if isinstance(params_timeout, timedelta):
|
|
205
|
+
params_timeout = int(params_timeout.total_seconds())
|
|
206
|
+
client_timeout = min(self.timeout_seconds, params_timeout)
|
|
207
|
+
|
|
208
|
+
else:
|
|
209
|
+
if self.server_params is None:
|
|
210
|
+
raise ValueError("server_params must be provided when using stdio transport.")
|
|
211
|
+
self._context = stdio_client(self.server_params) # type: ignore
|
|
212
|
+
client_timeout = self.timeout_seconds
|
|
213
|
+
|
|
214
|
+
session_params = await self._context.__aenter__() # type: ignore
|
|
215
|
+
self._active_contexts.append(self._context)
|
|
216
|
+
read, write = session_params[0:2]
|
|
217
|
+
|
|
218
|
+
self._session_context = ClientSession(read, write, read_timeout_seconds=timedelta(seconds=client_timeout)) # type: ignore
|
|
219
|
+
self.session = await self._session_context.__aenter__() # type: ignore
|
|
220
|
+
self._active_contexts.append(self._session_context)
|
|
221
|
+
|
|
222
|
+
# Initialize with the new session
|
|
223
|
+
await self.initialize()
|
|
224
|
+
|
|
225
|
+
async def close(self) -> None:
|
|
226
|
+
"""Close the MCP connection and clean up resources"""
|
|
227
|
+
if not self._initialized:
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
if self._session_context is not None:
|
|
232
|
+
await self._session_context.__aexit__(None, None, None)
|
|
233
|
+
self.session = None
|
|
234
|
+
self._session_context = None
|
|
235
|
+
|
|
236
|
+
if self._context is not None:
|
|
237
|
+
await self._context.__aexit__(None, None, None)
|
|
238
|
+
self._context = None
|
|
239
|
+
except (RuntimeError, BaseException) as e:
|
|
240
|
+
log_error(f"Failed to close MCP connection: {e}")
|
|
241
|
+
|
|
242
|
+
self._initialized = False
|
|
243
|
+
|
|
244
|
+
async def __aenter__(self) -> "MCPTools":
|
|
245
|
+
await self._connect()
|
|
246
|
+
return self
|
|
247
|
+
|
|
248
|
+
async def __aexit__(self, _exc_type, _exc_val, _exc_tb):
|
|
249
|
+
"""Exit the async context manager."""
|
|
250
|
+
if self._session_context is not None:
|
|
251
|
+
await self._session_context.__aexit__(_exc_type, _exc_val, _exc_tb)
|
|
252
|
+
self.session = None
|
|
253
|
+
self._session_context = None
|
|
254
|
+
|
|
255
|
+
if self._context is not None:
|
|
256
|
+
await self._context.__aexit__(_exc_type, _exc_val, _exc_tb)
|
|
257
|
+
self._context = None
|
|
258
|
+
|
|
259
|
+
self._initialized = False
|
|
260
|
+
|
|
261
|
+
async def build_tools(self) -> None:
|
|
262
|
+
"""Build the tools for the MCP toolkit"""
|
|
263
|
+
if self.session is None:
|
|
264
|
+
raise ValueError("Session is not initialized")
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
# Get the list of tools from the MCP server
|
|
268
|
+
available_tools = await self.session.list_tools() # type: ignore
|
|
269
|
+
|
|
270
|
+
self._check_tools_filters(
|
|
271
|
+
available_tools=[tool.name for tool in available_tools.tools],
|
|
272
|
+
include_tools=self.include_tools,
|
|
273
|
+
exclude_tools=self.exclude_tools,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Filter tools based on include/exclude lists
|
|
277
|
+
filtered_tools = []
|
|
278
|
+
for tool in available_tools.tools:
|
|
279
|
+
if self.exclude_tools and tool.name in self.exclude_tools:
|
|
280
|
+
continue
|
|
281
|
+
if self.include_tools is None or tool.name in self.include_tools:
|
|
282
|
+
filtered_tools.append(tool)
|
|
283
|
+
|
|
284
|
+
# Get tool name prefix if available
|
|
285
|
+
tool_name_prefix = ""
|
|
286
|
+
if self.tool_name_prefix is not None:
|
|
287
|
+
tool_name_prefix = self.tool_name_prefix + "_"
|
|
288
|
+
|
|
289
|
+
# Register the tools with the toolkit
|
|
290
|
+
for tool in filtered_tools:
|
|
291
|
+
try:
|
|
292
|
+
# Get an entrypoint for the tool
|
|
293
|
+
entrypoint = get_entrypoint_for_tool(tool, self.session) # type: ignore
|
|
294
|
+
# Create a Function for the tool
|
|
295
|
+
f = Function(
|
|
296
|
+
name=tool_name_prefix + tool.name,
|
|
297
|
+
description=tool.description,
|
|
298
|
+
parameters=tool.inputSchema,
|
|
299
|
+
entrypoint=entrypoint,
|
|
300
|
+
# Set skip_entrypoint_processing to True to avoid processing the entrypoint
|
|
301
|
+
skip_entrypoint_processing=True,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Register the Function with the toolkit
|
|
305
|
+
self.functions[f.name] = f
|
|
306
|
+
log_debug(f"Function: {f.name} registered with {self.name}")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
log_error(f"Failed to register tool {tool.name}: {e}")
|
|
309
|
+
|
|
310
|
+
except (RuntimeError, BaseException) as e:
|
|
311
|
+
log_error(f"Failed to get tools for {str(self)}: {e}")
|
|
312
|
+
raise
|
|
313
|
+
|
|
314
|
+
async def initialize(self) -> None:
|
|
315
|
+
"""Initialize the MCP toolkit by getting available tools from the MCP server"""
|
|
316
|
+
if self._initialized:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
if self.session is None:
|
|
321
|
+
raise ValueError("Session is not initialized")
|
|
322
|
+
|
|
323
|
+
# Initialize the session if not already initialized
|
|
324
|
+
await self.session.initialize()
|
|
325
|
+
|
|
326
|
+
await self.build_tools()
|
|
327
|
+
|
|
328
|
+
self._initialized = True
|
|
329
|
+
|
|
330
|
+
except (RuntimeError, BaseException) as e:
|
|
331
|
+
log_error(f"Failed to initialize MCP toolkit: {e}")
|