aip-agents-binary 0.5.25b8__py3-none-any.whl → 0.6.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.
- aip_agents/__init__.pyi +19 -0
- aip_agents/a2a/__init__.pyi +3 -0
- aip_agents/a2a/server/__init__.pyi +4 -0
- aip_agents/a2a/server/base_executor.pyi +73 -0
- aip_agents/a2a/server/google_adk_executor.pyi +51 -0
- aip_agents/a2a/server/langflow_executor.pyi +43 -0
- aip_agents/a2a/server/langgraph_executor.pyi +47 -0
- aip_agents/a2a/types.pyi +132 -0
- aip_agents/agent/__init__.pyi +9 -0
- aip_agents/agent/base_agent.pyi +221 -0
- aip_agents/agent/base_langgraph_agent.py +137 -68
- aip_agents/agent/base_langgraph_agent.pyi +233 -0
- aip_agents/agent/google_adk_agent.pyi +141 -0
- aip_agents/agent/google_adk_constants.pyi +3 -0
- aip_agents/agent/hitl/__init__.pyi +6 -0
- aip_agents/agent/hitl/config.pyi +15 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.pyi +42 -0
- aip_agents/agent/hitl/manager.pyi +200 -0
- aip_agents/agent/hitl/models.pyi +3 -0
- aip_agents/agent/hitl/prompt/__init__.pyi +4 -0
- aip_agents/agent/hitl/prompt/base.pyi +24 -0
- aip_agents/agent/hitl/prompt/deferred.pyi +30 -0
- aip_agents/agent/hitl/registry.pyi +101 -0
- aip_agents/agent/interface.pyi +81 -0
- aip_agents/agent/interfaces.pyi +44 -0
- aip_agents/agent/langflow_agent.pyi +133 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.pyi +49 -0
- aip_agents/agent/langgraph_react_agent.py +58 -14
- aip_agents/agent/langgraph_react_agent.pyi +131 -0
- aip_agents/agent/system_instruction_context.pyi +13 -0
- aip_agents/clients/__init__.pyi +4 -0
- aip_agents/clients/langflow/__init__.pyi +4 -0
- aip_agents/clients/langflow/client.pyi +140 -0
- aip_agents/clients/langflow/types.pyi +7 -0
- aip_agents/constants.pyi +7 -0
- aip_agents/examples/__init__.pyi +0 -0
- aip_agents/examples/compare_streaming_client.py +2 -2
- aip_agents/examples/compare_streaming_client.pyi +48 -0
- aip_agents/examples/compare_streaming_server.py +1 -1
- aip_agents/examples/compare_streaming_server.pyi +18 -0
- aip_agents/examples/demo_memory_recall.pyi +58 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langflow_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langflow_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.pyi +16 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.pyi +15 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.pyi +48 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.pyi +48 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.pyi +45 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_google_adk.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain.pyi +5 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_langchain_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.pyi +16 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.pyi +18 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_langflow_agent.pyi +35 -0
- aip_agents/examples/hello_world_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_model_switch_cli.pyi +30 -0
- aip_agents/examples/hello_world_multi_agent_adk.pyi +6 -0
- aip_agents/examples/hello_world_multi_agent_langchain.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_pii_logger.pyi +5 -0
- aip_agents/examples/hello_world_sentry.pyi +21 -0
- aip_agents/examples/hello_world_step_limits.pyi +17 -0
- aip_agents/examples/hello_world_stock_a2a_server.pyi +17 -0
- aip_agents/examples/hello_world_tool_output_client.py +9 -0
- aip_agents/examples/hello_world_tool_output_client.pyi +5 -0
- aip_agents/examples/hello_world_tool_output_server.pyi +19 -0
- aip_agents/examples/hitl_demo.pyi +67 -0
- aip_agents/examples/pii_demo_langgraph_client.pyi +5 -0
- aip_agents/examples/pii_demo_langgraph_server.pyi +20 -0
- aip_agents/examples/pii_demo_multi_agent_client.pyi +5 -0
- aip_agents/examples/pii_demo_multi_agent_server.pyi +40 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +2 -2
- aip_agents/examples/todolist_planning_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +1 -1
- aip_agents/examples/todolist_planning_a2a_langgraph_server.pyi +19 -0
- aip_agents/examples/tools/__init__.pyi +9 -0
- aip_agents/examples/tools/adk_arithmetic_tools.pyi +24 -0
- aip_agents/examples/tools/adk_weather_tool.pyi +18 -0
- aip_agents/examples/tools/data_generator_tool.pyi +15 -0
- aip_agents/examples/tools/data_visualization_tool.pyi +19 -0
- aip_agents/examples/tools/image_artifact_tool.pyi +26 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.pyi +17 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.pyi +20 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.pyi +25 -0
- aip_agents/examples/tools/langchain_weather_tool.pyi +19 -0
- aip_agents/examples/tools/langgraph_streaming_tool.pyi +43 -0
- aip_agents/examples/tools/mock_retrieval_tool.pyi +13 -0
- aip_agents/examples/tools/pii_demo_tools.pyi +54 -0
- aip_agents/examples/tools/random_chart_tool.pyi +20 -0
- aip_agents/examples/tools/serper_tool.pyi +16 -0
- aip_agents/examples/tools/stock_tools.pyi +36 -0
- aip_agents/examples/tools/table_generator_tool.pyi +22 -0
- aip_agents/examples/tools/time_tool.pyi +15 -0
- aip_agents/examples/tools/weather_forecast_tool.pyi +14 -0
- aip_agents/guardrails/__init__.pyi +6 -0
- aip_agents/guardrails/engines/__init__.pyi +4 -0
- aip_agents/guardrails/engines/base.py +6 -6
- aip_agents/guardrails/engines/base.pyi +61 -0
- aip_agents/guardrails/engines/nemo.pyi +46 -0
- aip_agents/guardrails/engines/phrase_matcher.pyi +48 -0
- aip_agents/guardrails/exceptions.pyi +23 -0
- aip_agents/guardrails/manager.pyi +42 -0
- aip_agents/guardrails/middleware.pyi +87 -0
- aip_agents/guardrails/schemas.pyi +43 -0
- aip_agents/guardrails/utils.pyi +19 -0
- aip_agents/mcp/__init__.pyi +0 -0
- aip_agents/mcp/client/__init__.pyi +5 -0
- aip_agents/mcp/client/base_mcp_client.pyi +148 -0
- aip_agents/mcp/client/connection_manager.py +36 -1
- aip_agents/mcp/client/connection_manager.pyi +51 -0
- aip_agents/mcp/client/google_adk/__init__.pyi +3 -0
- aip_agents/mcp/client/google_adk/client.pyi +75 -0
- aip_agents/mcp/client/langchain/__init__.pyi +3 -0
- aip_agents/mcp/client/langchain/client.pyi +48 -0
- aip_agents/mcp/client/persistent_session.py +318 -68
- aip_agents/mcp/client/persistent_session.pyi +122 -0
- aip_agents/mcp/client/session_pool.pyi +101 -0
- aip_agents/mcp/client/transports.py +33 -2
- aip_agents/mcp/client/transports.pyi +132 -0
- aip_agents/mcp/utils/__init__.pyi +0 -0
- aip_agents/mcp/utils/config_validator.pyi +82 -0
- aip_agents/memory/__init__.pyi +5 -0
- aip_agents/memory/adapters/__init__.pyi +4 -0
- aip_agents/memory/adapters/base_adapter.pyi +150 -0
- aip_agents/memory/adapters/mem0.pyi +22 -0
- aip_agents/memory/base.pyi +60 -0
- aip_agents/memory/constants.pyi +25 -0
- aip_agents/memory/factory.pyi +24 -0
- aip_agents/memory/guidance.pyi +3 -0
- aip_agents/memory/simple_memory.pyi +23 -0
- aip_agents/middleware/__init__.pyi +5 -0
- aip_agents/middleware/base.pyi +75 -0
- aip_agents/middleware/manager.pyi +84 -0
- aip_agents/middleware/todolist.pyi +125 -0
- aip_agents/schema/__init__.pyi +9 -0
- aip_agents/schema/a2a.pyi +40 -0
- aip_agents/schema/agent.pyi +65 -0
- aip_agents/schema/hitl.pyi +89 -0
- aip_agents/schema/langgraph.pyi +28 -0
- aip_agents/schema/model_id.pyi +54 -0
- aip_agents/schema/step_limit.pyi +63 -0
- aip_agents/schema/storage.pyi +21 -0
- aip_agents/sentry/__init__.pyi +3 -0
- aip_agents/sentry/sentry.pyi +48 -0
- aip_agents/storage/__init__.pyi +8 -0
- aip_agents/storage/base.pyi +58 -0
- aip_agents/storage/clients/__init__.pyi +3 -0
- aip_agents/storage/clients/minio_client.pyi +137 -0
- aip_agents/storage/config.pyi +29 -0
- aip_agents/storage/providers/__init__.pyi +5 -0
- aip_agents/storage/providers/base.pyi +88 -0
- aip_agents/storage/providers/memory.pyi +79 -0
- aip_agents/storage/providers/object_storage.pyi +98 -0
- aip_agents/tools/__init__.pyi +9 -0
- aip_agents/tools/browser_use/__init__.pyi +14 -0
- aip_agents/tools/browser_use/action_parser.pyi +18 -0
- aip_agents/tools/browser_use/browser_use_tool.py +8 -0
- aip_agents/tools/browser_use/browser_use_tool.pyi +50 -0
- aip_agents/tools/browser_use/llm_config.pyi +52 -0
- aip_agents/tools/browser_use/minio_storage.pyi +109 -0
- aip_agents/tools/browser_use/schemas.pyi +32 -0
- aip_agents/tools/browser_use/session.pyi +4 -0
- aip_agents/tools/browser_use/session_errors.pyi +53 -0
- aip_agents/tools/browser_use/steel_session_recording.pyi +63 -0
- aip_agents/tools/browser_use/streaming.py +2 -0
- aip_agents/tools/browser_use/streaming.pyi +81 -0
- aip_agents/tools/browser_use/structured_data_parser.pyi +86 -0
- aip_agents/tools/browser_use/structured_data_recovery.pyi +43 -0
- aip_agents/tools/browser_use/types.pyi +45 -0
- aip_agents/tools/code_sandbox/__init__.pyi +3 -0
- aip_agents/tools/code_sandbox/constant.pyi +4 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +102 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.pyi +29 -0
- aip_agents/tools/constants.pyi +138 -0
- aip_agents/tools/document_loader/__init__.pyi +7 -0
- aip_agents/tools/document_loader/base_reader.pyi +75 -0
- aip_agents/tools/document_loader/docx_reader_tool.pyi +10 -0
- aip_agents/tools/document_loader/excel_reader_tool.pyi +26 -0
- aip_agents/tools/document_loader/pdf_reader_tool.pyi +11 -0
- aip_agents/tools/document_loader/pdf_splitter.pyi +18 -0
- aip_agents/tools/gl_connector/__init__.pyi +3 -0
- aip_agents/tools/gl_connector/tool.pyi +74 -0
- aip_agents/tools/gl_connector_tools.pyi +39 -0
- aip_agents/tools/memory_search/__init__.pyi +5 -0
- aip_agents/tools/memory_search/base.pyi +69 -0
- aip_agents/tools/memory_search/mem0.pyi +19 -0
- aip_agents/tools/memory_search/schema.pyi +15 -0
- aip_agents/tools/memory_search_tool.pyi +3 -0
- aip_agents/tools/time_tool.pyi +16 -0
- aip_agents/tools/tool_config_injector.pyi +26 -0
- aip_agents/tools/web_search/__init__.pyi +3 -0
- aip_agents/tools/web_search/serper_tool.pyi +19 -0
- aip_agents/types/__init__.pyi +36 -0
- aip_agents/types/a2a_events.pyi +3 -0
- aip_agents/utils/__init__.pyi +11 -0
- aip_agents/utils/a2a_connector.pyi +146 -0
- aip_agents/utils/artifact_helpers.pyi +203 -0
- aip_agents/utils/constants.pyi +10 -0
- aip_agents/utils/datetime/__init__.pyi +4 -0
- aip_agents/utils/datetime/normalization.pyi +95 -0
- aip_agents/utils/datetime/timezone.pyi +48 -0
- aip_agents/utils/env_loader.pyi +10 -0
- aip_agents/utils/event_handler_registry.pyi +23 -0
- aip_agents/utils/file_prompt_utils.pyi +21 -0
- aip_agents/utils/final_response_builder.pyi +34 -0
- aip_agents/utils/formatter_llm_client.pyi +71 -0
- aip_agents/utils/langgraph/__init__.pyi +3 -0
- aip_agents/utils/langgraph/converter.pyi +49 -0
- aip_agents/utils/langgraph/tool_managers/__init__.pyi +5 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.pyi +35 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.pyi +48 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +26 -1
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +56 -0
- aip_agents/utils/langgraph/tool_output_management.py +80 -0
- aip_agents/utils/langgraph/tool_output_management.pyi +329 -0
- aip_agents/utils/logger.pyi +60 -0
- aip_agents/utils/metadata/__init__.pyi +5 -0
- aip_agents/utils/metadata/activity_metadata_helper.pyi +25 -0
- aip_agents/utils/metadata/activity_narrative/__init__.pyi +7 -0
- aip_agents/utils/metadata/activity_narrative/builder.pyi +35 -0
- aip_agents/utils/metadata/activity_narrative/constants.pyi +10 -0
- aip_agents/utils/metadata/activity_narrative/context.pyi +32 -0
- aip_agents/utils/metadata/activity_narrative/formatters.pyi +48 -0
- aip_agents/utils/metadata/activity_narrative/utils.pyi +12 -0
- aip_agents/utils/metadata/schemas/__init__.pyi +4 -0
- aip_agents/utils/metadata/schemas/activity_schema.pyi +18 -0
- aip_agents/utils/metadata/schemas/thinking_schema.pyi +20 -0
- aip_agents/utils/metadata/thinking_metadata_helper.pyi +4 -0
- aip_agents/utils/metadata_helper.pyi +117 -0
- aip_agents/utils/name_preprocessor/__init__.pyi +6 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.pyi +52 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.pyi +38 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.pyi +41 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.pyi +34 -0
- aip_agents/utils/pii/__init__.pyi +5 -0
- aip_agents/utils/pii/pii_handler.pyi +96 -0
- aip_agents/utils/pii/pii_helper.pyi +78 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi +73 -0
- aip_agents/utils/reference_helper.pyi +81 -0
- aip_agents/utils/sse_chunk_transformer.pyi +166 -0
- aip_agents/utils/step_limit_manager.pyi +112 -0
- aip_agents/utils/token_usage_helper.pyi +60 -0
- {aip_agents_binary-0.5.25b8.dist-info → aip_agents_binary-0.6.0.dist-info}/METADATA +51 -48
- aip_agents_binary-0.6.0.dist-info/RECORD +566 -0
- aip_agents_binary-0.5.25b8.dist-info/RECORD +0 -290
- {aip_agents_binary-0.5.25b8.dist-info → aip_agents_binary-0.6.0.dist-info}/WHEEL +0 -0
- {aip_agents_binary-0.5.25b8.dist-info → aip_agents_binary-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -8,6 +8,7 @@ Authors:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
+
from collections.abc import Awaitable, Callable
|
|
11
12
|
from typing import Any
|
|
12
13
|
|
|
13
14
|
from gllm_tools.mcp.client.config import MCPConfiguration
|
|
@@ -69,6 +70,13 @@ class PersistentMCPSession:
|
|
|
69
70
|
|
|
70
71
|
self._initialized = False
|
|
71
72
|
self._lock = asyncio.Lock()
|
|
73
|
+
self._owner_task: asyncio.Task | None = None
|
|
74
|
+
self._owner_ready: asyncio.Event = asyncio.Event()
|
|
75
|
+
self._owner_exception: Exception | None = None
|
|
76
|
+
self._timeout = float(config.get("timeout", 30.0))
|
|
77
|
+
self._request_queue: asyncio.Queue[
|
|
78
|
+
tuple[Callable[..., Awaitable[Any]], tuple[Any, ...], asyncio.Future, bool]
|
|
79
|
+
] = asyncio.Queue()
|
|
72
80
|
|
|
73
81
|
async def initialize(self) -> None:
|
|
74
82
|
"""Initialize session once and cache tools.
|
|
@@ -85,50 +93,28 @@ class PersistentMCPSession:
|
|
|
85
93
|
# Double-check pattern
|
|
86
94
|
if self._initialized:
|
|
87
95
|
return
|
|
96
|
+
if self._owner_task is None or self._owner_task.done():
|
|
97
|
+
self._owner_ready = asyncio.Event()
|
|
98
|
+
self._owner_exception = None
|
|
99
|
+
self._owner_task = asyncio.create_task(self._owner_loop())
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
self.tools = tools_result.tools if tools_result else []
|
|
107
|
-
self._filtered_tools_cache = None # Invalidate cache when tools change
|
|
108
|
-
logger.info(f"Cached {len(self.tools)} tools for {self.server_name}")
|
|
109
|
-
else:
|
|
110
|
-
logger.info(f"No tools available for {self.server_name}")
|
|
111
|
-
|
|
112
|
-
# Warn once per initialization if allowed_tools references unknown names
|
|
113
|
-
if self._allowed_tools_set:
|
|
114
|
-
self._warn_on_unknown_allowed_tools(list(self._allowed_tools_set), self.tools)
|
|
115
|
-
|
|
116
|
-
# Discover resources (for future use)
|
|
117
|
-
if result.capabilities.resources:
|
|
118
|
-
try:
|
|
119
|
-
resources_result = await self.client_session.list_resources()
|
|
120
|
-
if resources_result and resources_result.resources:
|
|
121
|
-
logger.debug(f"Found {len(resources_result.resources)} resources for {self.server_name}")
|
|
122
|
-
except Exception:
|
|
123
|
-
logger.debug(f"Could not list resources for {self.server_name}, skipping")
|
|
124
|
-
|
|
125
|
-
self._initialized = True
|
|
126
|
-
logger.info(f"Session initialization complete for {self.server_name}")
|
|
127
|
-
|
|
128
|
-
except Exception as e:
|
|
129
|
-
logger.error(f"Failed to initialize session for {self.server_name}: {e}", exc_info=True)
|
|
130
|
-
await self._cleanup_on_error()
|
|
131
|
-
raise ConnectionError(f"Failed to initialize MCP session for {self.server_name}: {str(e)}") from e
|
|
101
|
+
try:
|
|
102
|
+
await asyncio.wait_for(self._owner_ready.wait(), timeout=self._timeout)
|
|
103
|
+
except asyncio.CancelledError:
|
|
104
|
+
if self._owner_task and not self._owner_task.done():
|
|
105
|
+
self._owner_task.cancel()
|
|
106
|
+
raise
|
|
107
|
+
except TimeoutError as e:
|
|
108
|
+
logger.error(f"Initialization timed out for {self.server_name} after {self._timeout}s")
|
|
109
|
+
if self._owner_task and not self._owner_task.done():
|
|
110
|
+
self._owner_task.cancel()
|
|
111
|
+
self._owner_exception = ConnectionError(
|
|
112
|
+
f"Initialization timed out for {self.server_name} after {self._timeout}s"
|
|
113
|
+
)
|
|
114
|
+
self._owner_ready.set()
|
|
115
|
+
raise self._owner_exception from e
|
|
116
|
+
if self._owner_exception:
|
|
117
|
+
raise self._owner_exception
|
|
132
118
|
|
|
133
119
|
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
134
120
|
"""Call MCP tool using persistent session.
|
|
@@ -143,7 +129,19 @@ class PersistentMCPSession:
|
|
|
143
129
|
Raises:
|
|
144
130
|
Exception: If tool call fails
|
|
145
131
|
"""
|
|
146
|
-
await self.
|
|
132
|
+
return await self._run_in_owner(self._call_tool_impl, name, arguments)
|
|
133
|
+
|
|
134
|
+
async def _call_tool_impl(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
135
|
+
"""Call MCP tool using the owner task.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
name: Tool name.
|
|
139
|
+
arguments: Tool arguments.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
CallToolResult: Tool call result.
|
|
143
|
+
"""
|
|
144
|
+
await self._ensure_connected_impl()
|
|
147
145
|
|
|
148
146
|
if self._allowed_tools_set and name not in self._allowed_tools_set:
|
|
149
147
|
allowed_display = ", ".join(sorted(self._allowed_tools_set))
|
|
@@ -173,10 +171,9 @@ class PersistentMCPSession:
|
|
|
173
171
|
Raises:
|
|
174
172
|
Exception: If resource reading fails
|
|
175
173
|
"""
|
|
176
|
-
await self.
|
|
177
|
-
return await self._execute_read_resource(uri)
|
|
174
|
+
return await self._run_in_owner(self._execute_read_resource_impl, uri)
|
|
178
175
|
|
|
179
|
-
async def
|
|
176
|
+
async def _execute_read_resource_impl(self, uri: str) -> Any:
|
|
180
177
|
"""Execute the reading of an MCP resource.
|
|
181
178
|
|
|
182
179
|
Args:
|
|
@@ -188,6 +185,7 @@ class PersistentMCPSession:
|
|
|
188
185
|
Raises:
|
|
189
186
|
Exception: If resource reading fails
|
|
190
187
|
"""
|
|
188
|
+
await self._ensure_connected_impl()
|
|
191
189
|
try:
|
|
192
190
|
logger.debug(f"Reading resource '{uri}' on {self.server_name}")
|
|
193
191
|
result = await self.client_session.read_resource(uri)
|
|
@@ -202,7 +200,15 @@ class PersistentMCPSession:
|
|
|
202
200
|
Returns:
|
|
203
201
|
list[Tool]: a copy of list of available tools, filtered to only allowed tools if configured
|
|
204
202
|
"""
|
|
205
|
-
await self.
|
|
203
|
+
return await self._run_in_owner(self._list_tools_impl)
|
|
204
|
+
|
|
205
|
+
async def _list_tools_impl(self) -> list[Tool]:
|
|
206
|
+
"""Return the cached tools list from the owner task.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
list[Tool]: Filtered tool list if allowed tools are configured, otherwise all tools.
|
|
210
|
+
"""
|
|
211
|
+
await self._ensure_connected_impl()
|
|
206
212
|
|
|
207
213
|
if not self._allowed_tools_set:
|
|
208
214
|
return list(self.tools)
|
|
@@ -234,9 +240,13 @@ class PersistentMCPSession:
|
|
|
234
240
|
Raises:
|
|
235
241
|
Exception: If reconnection fails
|
|
236
242
|
"""
|
|
243
|
+
await self._run_in_owner(self._ensure_connected_impl)
|
|
244
|
+
|
|
245
|
+
async def _ensure_connected_impl(self) -> None:
|
|
246
|
+
"""Ensure the session is connected, reconnecting if needed."""
|
|
237
247
|
if not self._initialized or not self.connection_manager.is_connected:
|
|
238
248
|
logger.info(f"Reconnecting session for {self.server_name}")
|
|
239
|
-
await self.
|
|
249
|
+
await self._initialize_impl()
|
|
240
250
|
|
|
241
251
|
def _handle_connection_error(self, e: Exception, operation: str) -> None:
|
|
242
252
|
"""Handle connection-related errors with logging and reconnection marking.
|
|
@@ -255,29 +265,44 @@ class PersistentMCPSession:
|
|
|
255
265
|
"""Disconnect session gracefully.
|
|
256
266
|
|
|
257
267
|
This method cleans up all resources and connections.
|
|
268
|
+
Always succeeds, even if the session was already in an error state.
|
|
258
269
|
"""
|
|
259
270
|
logger.info(f"Disconnecting session for {self.server_name}")
|
|
260
271
|
|
|
261
|
-
|
|
272
|
+
if self._owner_task is None or self._owner_task.done():
|
|
273
|
+
await self._disconnect_impl()
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
await self._run_in_owner(self._disconnect_impl, shutdown=True, ensure_initialized=False)
|
|
278
|
+
except ConnectionError:
|
|
279
|
+
# Owner task already failed; just clean up directly
|
|
280
|
+
logger.debug(f"Owner task already failed for {self.server_name}, cleaning up directly")
|
|
281
|
+
await self._disconnect_impl()
|
|
282
|
+
finally:
|
|
283
|
+
await self._await_owner_shutdown()
|
|
284
|
+
|
|
285
|
+
async def _await_owner_shutdown(self) -> None:
|
|
286
|
+
"""Wait for the owner task to exit, cancelling on timeout."""
|
|
287
|
+
if not self._owner_task:
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
owner_task = self._owner_task
|
|
291
|
+
try:
|
|
292
|
+
await asyncio.wait_for(owner_task, timeout=self._timeout)
|
|
293
|
+
except TimeoutError:
|
|
294
|
+
logger.warning(f"Owner task for {self.server_name} did not exit within {self._timeout}s, cancelling")
|
|
295
|
+
owner_task.cancel()
|
|
262
296
|
try:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
await self.connection_manager.stop()
|
|
273
|
-
|
|
274
|
-
except Exception as e:
|
|
275
|
-
logger.error(f"Error during disconnect for {self.server_name}: {e}")
|
|
276
|
-
finally:
|
|
277
|
-
self._initialized = False
|
|
278
|
-
self.tools.clear()
|
|
279
|
-
self._filtered_tools_cache = None # Clear cache on disconnect
|
|
280
|
-
logger.info(f"Session disconnected for {self.server_name}")
|
|
297
|
+
await owner_task
|
|
298
|
+
except (asyncio.CancelledError, Exception):
|
|
299
|
+
pass
|
|
300
|
+
except asyncio.CancelledError:
|
|
301
|
+
pass
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
finally:
|
|
305
|
+
self._owner_task = None
|
|
281
306
|
|
|
282
307
|
async def _cleanup_on_error(self) -> None:
|
|
283
308
|
"""Internal cleanup method for error scenarios."""
|
|
@@ -306,6 +331,17 @@ class PersistentMCPSession:
|
|
|
306
331
|
"""
|
|
307
332
|
return self._initialized and self.connection_manager.is_connected
|
|
308
333
|
|
|
334
|
+
@property
|
|
335
|
+
def allowed_tools(self) -> list[str] | None:
|
|
336
|
+
"""Return the configured allowed tools, sorted if present.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Sorted list of allowed tool names, or None if unrestricted.
|
|
340
|
+
"""
|
|
341
|
+
if not self._allowed_tools_set:
|
|
342
|
+
return None
|
|
343
|
+
return sorted(self._allowed_tools_set)
|
|
344
|
+
|
|
309
345
|
def update_allowed_tools(self, allowed_tools: list[str] | None) -> bool:
|
|
310
346
|
"""Update the list of allowed tools for this session.
|
|
311
347
|
|
|
@@ -360,3 +396,217 @@ class PersistentMCPSession:
|
|
|
360
396
|
logger.warning(
|
|
361
397
|
f"[{self.server_name}] Tool '{tool_name}' not found in available tools but specified in allowed_tools"
|
|
362
398
|
)
|
|
399
|
+
|
|
400
|
+
async def _owner_loop(self) -> None:
|
|
401
|
+
"""Run the owner task loop and process queued requests.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
None
|
|
405
|
+
"""
|
|
406
|
+
shutdown_requested = False
|
|
407
|
+
try:
|
|
408
|
+
shutdown_requested = await self._initialize_owner()
|
|
409
|
+
if shutdown_requested:
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
while True:
|
|
413
|
+
func, args, future, shutdown = await self._request_queue.get()
|
|
414
|
+
if await self._process_owner_request(func, args, future):
|
|
415
|
+
continue
|
|
416
|
+
if shutdown:
|
|
417
|
+
shutdown_requested = True
|
|
418
|
+
break
|
|
419
|
+
finally:
|
|
420
|
+
# Drain and cancel any pending requests to avoid hanging callers
|
|
421
|
+
await self._drain_pending_requests()
|
|
422
|
+
if not shutdown_requested and (
|
|
423
|
+
self._initialized or self.client_session or self.connection_manager.transport_type is not None
|
|
424
|
+
):
|
|
425
|
+
await self._cleanup_on_error()
|
|
426
|
+
self._owner_task = None
|
|
427
|
+
|
|
428
|
+
async def _drain_pending_requests(self) -> None:
|
|
429
|
+
"""Cancel all pending requests in the queue.
|
|
430
|
+
|
|
431
|
+
This prevents callers from hanging when the owner loop exits unexpectedly.
|
|
432
|
+
"""
|
|
433
|
+
error = ConnectionError(f"Session for {self.server_name} is shutting down")
|
|
434
|
+
while not self._request_queue.empty():
|
|
435
|
+
try:
|
|
436
|
+
_, _, future, _ = self._request_queue.get_nowait()
|
|
437
|
+
if not future.done():
|
|
438
|
+
future.set_exception(error)
|
|
439
|
+
except asyncio.QueueEmpty:
|
|
440
|
+
break
|
|
441
|
+
|
|
442
|
+
async def _initialize_owner(self) -> bool:
|
|
443
|
+
"""Initialize the owner task and signal readiness.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
bool: True when initialization fails and the loop should stop.
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
await self._initialize_impl()
|
|
450
|
+
except Exception as e:
|
|
451
|
+
self._owner_exception = e
|
|
452
|
+
self._owner_ready.set()
|
|
453
|
+
return True
|
|
454
|
+
|
|
455
|
+
self._owner_ready.set()
|
|
456
|
+
return False
|
|
457
|
+
|
|
458
|
+
async def _process_owner_request(
|
|
459
|
+
self,
|
|
460
|
+
func: Callable[..., Awaitable[Any]],
|
|
461
|
+
args: tuple[Any, ...],
|
|
462
|
+
future: asyncio.Future,
|
|
463
|
+
) -> bool:
|
|
464
|
+
"""Process a single queued request.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
func: Coroutine function to execute.
|
|
468
|
+
args: Positional arguments for the function.
|
|
469
|
+
future: Future to resolve with the result or exception.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
bool: True if the request was skipped due to cancellation.
|
|
473
|
+
"""
|
|
474
|
+
if future.cancelled():
|
|
475
|
+
return True
|
|
476
|
+
|
|
477
|
+
try:
|
|
478
|
+
result = await func(*args)
|
|
479
|
+
except asyncio.CancelledError as e:
|
|
480
|
+
# Owner task was cancelled - resolve future to prevent hanging caller
|
|
481
|
+
if not future.cancelled():
|
|
482
|
+
future.set_exception(e)
|
|
483
|
+
raise
|
|
484
|
+
except Exception as e:
|
|
485
|
+
if not future.cancelled():
|
|
486
|
+
future.set_exception(e)
|
|
487
|
+
else:
|
|
488
|
+
if not future.cancelled():
|
|
489
|
+
future.set_result(result)
|
|
490
|
+
return False
|
|
491
|
+
|
|
492
|
+
async def _run_in_owner(
|
|
493
|
+
self,
|
|
494
|
+
func: Callable[..., Awaitable[Any]],
|
|
495
|
+
*args: Any,
|
|
496
|
+
shutdown: bool = False,
|
|
497
|
+
ensure_initialized: bool = True,
|
|
498
|
+
) -> Any:
|
|
499
|
+
"""Execute a coroutine on the owner task.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
func: Coroutine function to execute.
|
|
503
|
+
*args: Positional arguments to pass to func.
|
|
504
|
+
shutdown: Whether this request should shut down the owner loop.
|
|
505
|
+
ensure_initialized: Whether to initialize the owner task if needed.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Any: The result of the coroutine call.
|
|
509
|
+
|
|
510
|
+
Raises:
|
|
511
|
+
ConnectionError: If the owner task died or session is shutting down.
|
|
512
|
+
"""
|
|
513
|
+
if ensure_initialized:
|
|
514
|
+
await self.initialize()
|
|
515
|
+
else:
|
|
516
|
+
# For non-init calls (like disconnect), check if owner is alive
|
|
517
|
+
if self._owner_task is None or self._owner_task.done():
|
|
518
|
+
return await func(*args)
|
|
519
|
+
try:
|
|
520
|
+
await asyncio.wait_for(self._owner_ready.wait(), timeout=self._timeout)
|
|
521
|
+
except TimeoutError as e:
|
|
522
|
+
# Owner task is stuck, cancel it and raise
|
|
523
|
+
if self._owner_task and not self._owner_task.done():
|
|
524
|
+
self._owner_task.cancel()
|
|
525
|
+
raise ConnectionError(
|
|
526
|
+
f"Session for {self.server_name} initialization timed out after {self._timeout}s"
|
|
527
|
+
) from e
|
|
528
|
+
if self._owner_exception:
|
|
529
|
+
# Propagate the error instead of silently returning None
|
|
530
|
+
raise ConnectionError(
|
|
531
|
+
f"Session for {self.server_name} failed: {self._owner_exception}"
|
|
532
|
+
) from self._owner_exception
|
|
533
|
+
|
|
534
|
+
# Check if owner task died after initialization (race condition guard)
|
|
535
|
+
if self._owner_task is None or self._owner_task.done():
|
|
536
|
+
raise ConnectionError(f"Session for {self.server_name} is no longer active")
|
|
537
|
+
|
|
538
|
+
loop = asyncio.get_running_loop()
|
|
539
|
+
future: asyncio.Future = loop.create_future()
|
|
540
|
+
await self._request_queue.put((func, args, future, shutdown))
|
|
541
|
+
return await future
|
|
542
|
+
|
|
543
|
+
async def _initialize_impl(self) -> None:
|
|
544
|
+
"""Initialize the underlying MCP session on the owner task."""
|
|
545
|
+
if self._initialized:
|
|
546
|
+
return
|
|
547
|
+
|
|
548
|
+
try:
|
|
549
|
+
logger.info(f"Initializing persistent session for {self.server_name}")
|
|
550
|
+
|
|
551
|
+
# Start connection manager
|
|
552
|
+
read_stream, write_stream = await self.connection_manager.start()
|
|
553
|
+
|
|
554
|
+
# Create client session
|
|
555
|
+
self.client_session = ClientSession(read_stream, write_stream)
|
|
556
|
+
await self.client_session.__aenter__()
|
|
557
|
+
|
|
558
|
+
# MCP handshake
|
|
559
|
+
result = await self.client_session.initialize()
|
|
560
|
+
logger.debug(f"MCP handshake complete for {self.server_name}: {result.capabilities}")
|
|
561
|
+
|
|
562
|
+
# Discover and cache tools
|
|
563
|
+
if result.capabilities.tools:
|
|
564
|
+
tools_result = await self.client_session.list_tools()
|
|
565
|
+
self.tools = tools_result.tools if tools_result else []
|
|
566
|
+
self._filtered_tools_cache = None # Invalidate cache when tools change
|
|
567
|
+
logger.info(f"Cached {len(self.tools)} tools for {self.server_name}")
|
|
568
|
+
else:
|
|
569
|
+
logger.info(f"No tools available for {self.server_name}")
|
|
570
|
+
|
|
571
|
+
# Warn once per initialization if allowed_tools references unknown names
|
|
572
|
+
if self._allowed_tools_set:
|
|
573
|
+
self._warn_on_unknown_allowed_tools(list(self._allowed_tools_set), self.tools)
|
|
574
|
+
|
|
575
|
+
# Discover resources (for future use)
|
|
576
|
+
if result.capabilities.resources:
|
|
577
|
+
try:
|
|
578
|
+
resources_result = await self.client_session.list_resources()
|
|
579
|
+
if resources_result and resources_result.resources:
|
|
580
|
+
logger.debug(f"Found {len(resources_result.resources)} resources for {self.server_name}")
|
|
581
|
+
except Exception:
|
|
582
|
+
logger.debug(f"Could not list resources for {self.server_name}, skipping")
|
|
583
|
+
|
|
584
|
+
self._initialized = True
|
|
585
|
+
logger.info(f"Session initialization complete for {self.server_name}")
|
|
586
|
+
|
|
587
|
+
except Exception as e:
|
|
588
|
+
logger.error(f"Failed to initialize session for {self.server_name}: {e}", exc_info=True)
|
|
589
|
+
await self._cleanup_on_error()
|
|
590
|
+
raise ConnectionError(f"Failed to initialize MCP session for {self.server_name}: {str(e)}") from e
|
|
591
|
+
|
|
592
|
+
async def _disconnect_impl(self) -> None:
|
|
593
|
+
"""Disconnect the underlying MCP session on the owner task."""
|
|
594
|
+
try:
|
|
595
|
+
# Close client session
|
|
596
|
+
if self.client_session:
|
|
597
|
+
try:
|
|
598
|
+
await self.client_session.__aexit__(None, None, None)
|
|
599
|
+
except Exception as e:
|
|
600
|
+
logger.warning(f"Error closing client session for {self.server_name}: {e}")
|
|
601
|
+
self.client_session = None
|
|
602
|
+
|
|
603
|
+
# Stop connection manager
|
|
604
|
+
await self.connection_manager.stop()
|
|
605
|
+
|
|
606
|
+
except Exception as e:
|
|
607
|
+
logger.error(f"Error during disconnect for {self.server_name}: {e}")
|
|
608
|
+
finally:
|
|
609
|
+
self._initialized = False
|
|
610
|
+
self.tools.clear()
|
|
611
|
+
self._filtered_tools_cache = None # Clear cache on disconnect
|
|
612
|
+
logger.info(f"Session disconnected for {self.server_name}")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from aip_agents.mcp.client.connection_manager import MCPConnectionManager as MCPConnectionManager
|
|
3
|
+
from aip_agents.mcp.utils.config_validator import validate_allowed_tools_list as validate_allowed_tools_list
|
|
4
|
+
from aip_agents.utils.logger import get_logger as get_logger
|
|
5
|
+
from collections.abc import Awaitable as Awaitable
|
|
6
|
+
from gllm_tools.mcp.client.config import MCPConfiguration
|
|
7
|
+
from mcp import ClientSession
|
|
8
|
+
from mcp.types import CallToolResult, Tool as Tool
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
logger: Incomplete
|
|
12
|
+
|
|
13
|
+
class PersistentMCPSession:
|
|
14
|
+
"""Persistent MCP session that reuses connections.
|
|
15
|
+
|
|
16
|
+
This session wrapper manages the connection lifecycle and caches tools
|
|
17
|
+
to avoid repeated initialization overhead. It provides automatic reconnection
|
|
18
|
+
and thread-safe operations.
|
|
19
|
+
|
|
20
|
+
Tool Filtering:
|
|
21
|
+
When allowed_tools is configured, tools are filtered inline during list_tools()
|
|
22
|
+
and permission checked in call_tool() using set lookup.
|
|
23
|
+
"""
|
|
24
|
+
server_name: Incomplete
|
|
25
|
+
config: Incomplete
|
|
26
|
+
connection_manager: Incomplete
|
|
27
|
+
client_session: ClientSession | None
|
|
28
|
+
tools: list[Tool]
|
|
29
|
+
def __init__(self, server_name: str, config: MCPConfiguration, allowed_tools: list[str] | None = None) -> None:
|
|
30
|
+
"""Initialize persistent session.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
server_name: Name of the MCP server
|
|
34
|
+
config: MCP server configuration
|
|
35
|
+
allowed_tools: Optional list of tool names to allow. None or empty means all tools allowed.
|
|
36
|
+
"""
|
|
37
|
+
async def initialize(self) -> None:
|
|
38
|
+
"""Initialize session once and cache tools.
|
|
39
|
+
|
|
40
|
+
This method is idempotent and can be called multiple times safely.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
Exception: If session initialization fails
|
|
44
|
+
"""
|
|
45
|
+
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
46
|
+
"""Call MCP tool using persistent session.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
name (str): Tool name
|
|
50
|
+
arguments (dict[str, Any]): Tool arguments
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
CallToolResult: Tool call result
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
Exception: If tool call fails
|
|
57
|
+
"""
|
|
58
|
+
async def read_resource(self, uri: str) -> Any:
|
|
59
|
+
"""Read an MCP resource using persistent session.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
uri (str): The URI of the resource to read
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Any: The resource content
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
Exception: If resource reading fails
|
|
69
|
+
"""
|
|
70
|
+
async def list_tools(self) -> list[Tool]:
|
|
71
|
+
"""Get cached tools list with allowed tools filtering applied.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
list[Tool]: a copy of list of available tools, filtered to only allowed tools if configured
|
|
75
|
+
"""
|
|
76
|
+
def get_tools_count(self) -> int:
|
|
77
|
+
"""Get count of allowed tools.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Count of allowed tools
|
|
81
|
+
"""
|
|
82
|
+
async def ensure_connected(self) -> None:
|
|
83
|
+
"""Ensure connection is healthy, reconnect if needed.
|
|
84
|
+
|
|
85
|
+
This method provides automatic reconnection capability.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
Exception: If reconnection fails
|
|
89
|
+
"""
|
|
90
|
+
async def disconnect(self) -> None:
|
|
91
|
+
"""Disconnect session gracefully.
|
|
92
|
+
|
|
93
|
+
This method cleans up all resources and connections.
|
|
94
|
+
Always succeeds, even if the session was already in an error state.
|
|
95
|
+
"""
|
|
96
|
+
@property
|
|
97
|
+
def is_initialized(self) -> bool:
|
|
98
|
+
"""Check if session is initialized.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
bool: True if initialized and connected, False otherwise
|
|
102
|
+
"""
|
|
103
|
+
@property
|
|
104
|
+
def allowed_tools(self) -> list[str] | None:
|
|
105
|
+
"""Return the configured allowed tools, sorted if present.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Sorted list of allowed tool names, or None if unrestricted.
|
|
109
|
+
"""
|
|
110
|
+
def update_allowed_tools(self, allowed_tools: list[str] | None) -> bool:
|
|
111
|
+
"""Update the list of allowed tools for this session.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
allowed_tools: New list of allowed tool names or None for no restriction.
|
|
115
|
+
None and empty list both mean 'no restrictions, allow all tools'.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: True if the configuration changed, False otherwise.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If allowed_tools contains invalid entries.
|
|
122
|
+
"""
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from aip_agents.mcp.client.persistent_session import PersistentMCPSession as PersistentMCPSession
|
|
3
|
+
from aip_agents.mcp.utils.config_validator import validate_allowed_tools_config as validate_allowed_tools_config, validate_allowed_tools_list as validate_allowed_tools_list
|
|
4
|
+
from aip_agents.utils.logger import get_logger as get_logger
|
|
5
|
+
from gllm_tools.mcp.client.config import MCPConfiguration
|
|
6
|
+
from mcp.types import Tool as Tool
|
|
7
|
+
|
|
8
|
+
logger: Incomplete
|
|
9
|
+
|
|
10
|
+
class MCPSessionPool:
|
|
11
|
+
"""Manages pool of persistent MCP sessions.
|
|
12
|
+
|
|
13
|
+
This pool provides centralized management of MCP sessions, including
|
|
14
|
+
initialization, tool collection, and resource cleanup. Sessions are
|
|
15
|
+
reused across the agent lifecycle.
|
|
16
|
+
"""
|
|
17
|
+
sessions: dict[str, PersistentMCPSession]
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
"""Initialize empty session pool."""
|
|
20
|
+
async def get_or_create_session(self, server_name: str, config: MCPConfiguration, allowed_tools: list[str] | None = None) -> PersistentMCPSession:
|
|
21
|
+
"""Get existing session or create new one.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
server_name (str): Name of the MCP server
|
|
25
|
+
config (MCPConfiguration): MCP server configuration
|
|
26
|
+
allowed_tools (list[str] | None): Optional list of tool names to allow. None means all tools allowed.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
PersistentMCPSession: Persistent MCP session
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
Exception: If session creation fails
|
|
33
|
+
"""
|
|
34
|
+
async def initialize_all_sessions(self, server_configs: dict[str, MCPConfiguration]) -> None:
|
|
35
|
+
"""Initialize all sessions and cache tools.
|
|
36
|
+
|
|
37
|
+
This method initializes all configured MCP servers concurrently
|
|
38
|
+
for better performance.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
server_configs (dict[str, MCPConfiguration]): Dictionary of server configurations
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
Exception: If any session initialization fails
|
|
45
|
+
"""
|
|
46
|
+
def get_all_active_sessions(self) -> dict[str, PersistentMCPSession]:
|
|
47
|
+
"""Get all active sessions.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
dict[str, PersistentMCPSession]: Dictionary of active sessions by server name
|
|
51
|
+
"""
|
|
52
|
+
@property
|
|
53
|
+
def active_sessions(self) -> list[str]:
|
|
54
|
+
"""Get list of active session names.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
list[str]: List of active session names
|
|
58
|
+
"""
|
|
59
|
+
async def get_all_tools(self) -> list[Tool]:
|
|
60
|
+
"""Get all cached tools from all active sessions.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
list[Tool]: List of all available tools across all sessions
|
|
64
|
+
"""
|
|
65
|
+
async def close_session(self, server_name: str) -> None:
|
|
66
|
+
"""Close specific session.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
server_name (str): Name of the server session to close
|
|
70
|
+
"""
|
|
71
|
+
async def close_all_sessions(self) -> None:
|
|
72
|
+
"""Close all sessions gracefully.
|
|
73
|
+
|
|
74
|
+
This method ensures all resources are cleaned up properly.
|
|
75
|
+
"""
|
|
76
|
+
@property
|
|
77
|
+
def is_initialized(self) -> bool:
|
|
78
|
+
"""Check if session pool is initialized.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
bool: True if initialized, False otherwise
|
|
82
|
+
"""
|
|
83
|
+
@property
|
|
84
|
+
def session_count(self) -> int:
|
|
85
|
+
"""Get number of active sessions.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
int: Number of active sessions
|
|
89
|
+
"""
|
|
90
|
+
def get_session(self, server_name: str) -> PersistentMCPSession:
|
|
91
|
+
"""Get specific session by name.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
server_name (str): Name of the server
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
PersistentMCPSession: The requested session
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
KeyError: If session doesn't exist
|
|
101
|
+
"""
|