agentpool 2.1.9__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.
Potentially problematic release.
This version of agentpool might be problematic. Click here for more details.
- acp/README.md +64 -0
- acp/__init__.py +172 -0
- acp/__main__.py +10 -0
- acp/acp_requests.py +285 -0
- acp/agent/__init__.py +6 -0
- acp/agent/connection.py +256 -0
- acp/agent/implementations/__init__.py +6 -0
- acp/agent/implementations/debug_server/__init__.py +1 -0
- acp/agent/implementations/debug_server/cli.py +79 -0
- acp/agent/implementations/debug_server/debug.html +234 -0
- acp/agent/implementations/debug_server/debug_server.py +496 -0
- acp/agent/implementations/testing.py +91 -0
- acp/agent/protocol.py +65 -0
- acp/bridge/README.md +162 -0
- acp/bridge/__init__.py +6 -0
- acp/bridge/__main__.py +91 -0
- acp/bridge/bridge.py +246 -0
- acp/bridge/py.typed +0 -0
- acp/bridge/settings.py +15 -0
- acp/client/__init__.py +7 -0
- acp/client/connection.py +251 -0
- acp/client/implementations/__init__.py +7 -0
- acp/client/implementations/default_client.py +185 -0
- acp/client/implementations/headless_client.py +266 -0
- acp/client/implementations/noop_client.py +110 -0
- acp/client/protocol.py +61 -0
- acp/connection.py +280 -0
- acp/exceptions.py +46 -0
- acp/filesystem.py +524 -0
- acp/notifications.py +832 -0
- acp/py.typed +0 -0
- acp/schema/__init__.py +265 -0
- acp/schema/agent_plan.py +30 -0
- acp/schema/agent_requests.py +126 -0
- acp/schema/agent_responses.py +256 -0
- acp/schema/base.py +39 -0
- acp/schema/capabilities.py +230 -0
- acp/schema/client_requests.py +247 -0
- acp/schema/client_responses.py +96 -0
- acp/schema/common.py +81 -0
- acp/schema/content_blocks.py +188 -0
- acp/schema/mcp.py +82 -0
- acp/schema/messages.py +171 -0
- acp/schema/notifications.py +82 -0
- acp/schema/protocol_stuff.md +3 -0
- acp/schema/session_state.py +160 -0
- acp/schema/session_updates.py +419 -0
- acp/schema/slash_commands.py +51 -0
- acp/schema/terminal.py +15 -0
- acp/schema/tool_call.py +347 -0
- acp/stdio.py +250 -0
- acp/task/__init__.py +53 -0
- acp/task/debug.py +197 -0
- acp/task/dispatcher.py +93 -0
- acp/task/queue.py +69 -0
- acp/task/sender.py +82 -0
- acp/task/state.py +87 -0
- acp/task/supervisor.py +93 -0
- acp/terminal_handle.py +30 -0
- acp/tool_call_reporter.py +199 -0
- acp/tool_call_state.py +178 -0
- acp/transports.py +104 -0
- acp/utils.py +240 -0
- agentpool/__init__.py +63 -0
- agentpool/__main__.py +7 -0
- agentpool/agents/__init__.py +30 -0
- agentpool/agents/acp_agent/__init__.py +5 -0
- agentpool/agents/acp_agent/acp_agent.py +837 -0
- agentpool/agents/acp_agent/acp_converters.py +294 -0
- agentpool/agents/acp_agent/client_handler.py +317 -0
- agentpool/agents/acp_agent/session_state.py +44 -0
- agentpool/agents/agent.py +1264 -0
- agentpool/agents/agui_agent/__init__.py +19 -0
- agentpool/agents/agui_agent/agui_agent.py +677 -0
- agentpool/agents/agui_agent/agui_converters.py +423 -0
- agentpool/agents/agui_agent/chunk_transformer.py +204 -0
- agentpool/agents/agui_agent/event_types.py +83 -0
- agentpool/agents/agui_agent/helpers.py +192 -0
- agentpool/agents/architect.py +71 -0
- agentpool/agents/base_agent.py +177 -0
- agentpool/agents/claude_code_agent/__init__.py +11 -0
- agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
- agentpool/agents/claude_code_agent/converters.py +243 -0
- agentpool/agents/context.py +105 -0
- agentpool/agents/events/__init__.py +61 -0
- agentpool/agents/events/builtin_handlers.py +129 -0
- agentpool/agents/events/event_emitter.py +320 -0
- agentpool/agents/events/events.py +561 -0
- agentpool/agents/events/tts_handlers.py +186 -0
- agentpool/agents/interactions.py +419 -0
- agentpool/agents/slashed_agent.py +244 -0
- agentpool/agents/sys_prompts.py +178 -0
- agentpool/agents/tool_wrapping.py +184 -0
- agentpool/base_provider.py +28 -0
- agentpool/common_types.py +226 -0
- agentpool/config_resources/__init__.py +16 -0
- agentpool/config_resources/acp_assistant.yml +24 -0
- agentpool/config_resources/agents.yml +109 -0
- agentpool/config_resources/agents_template.yml +18 -0
- agentpool/config_resources/agui_test.yml +18 -0
- agentpool/config_resources/claude_code_agent.yml +16 -0
- agentpool/config_resources/claude_style_subagent.md +30 -0
- agentpool/config_resources/external_acp_agents.yml +77 -0
- agentpool/config_resources/opencode_style_subagent.md +19 -0
- agentpool/config_resources/tts_test_agents.yml +78 -0
- agentpool/delegation/__init__.py +8 -0
- agentpool/delegation/base_team.py +504 -0
- agentpool/delegation/message_flow_tracker.py +39 -0
- agentpool/delegation/pool.py +1129 -0
- agentpool/delegation/team.py +325 -0
- agentpool/delegation/teamrun.py +343 -0
- agentpool/docs/__init__.py +5 -0
- agentpool/docs/gen_examples.py +42 -0
- agentpool/docs/utils.py +370 -0
- agentpool/functional/__init__.py +20 -0
- agentpool/functional/py.typed +0 -0
- agentpool/functional/run.py +80 -0
- agentpool/functional/structure.py +136 -0
- agentpool/hooks/__init__.py +20 -0
- agentpool/hooks/agent_hooks.py +247 -0
- agentpool/hooks/base.py +119 -0
- agentpool/hooks/callable.py +140 -0
- agentpool/hooks/command.py +180 -0
- agentpool/hooks/prompt.py +122 -0
- agentpool/jinja_filters.py +132 -0
- agentpool/log.py +224 -0
- agentpool/mcp_server/__init__.py +17 -0
- agentpool/mcp_server/client.py +429 -0
- agentpool/mcp_server/constants.py +32 -0
- agentpool/mcp_server/conversions.py +172 -0
- agentpool/mcp_server/helpers.py +47 -0
- agentpool/mcp_server/manager.py +232 -0
- agentpool/mcp_server/message_handler.py +164 -0
- agentpool/mcp_server/registries/__init__.py +1 -0
- agentpool/mcp_server/registries/official_registry_client.py +345 -0
- agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
- agentpool/mcp_server/tool_bridge.py +548 -0
- agentpool/messaging/__init__.py +58 -0
- agentpool/messaging/compaction.py +928 -0
- agentpool/messaging/connection_manager.py +319 -0
- agentpool/messaging/context.py +66 -0
- agentpool/messaging/event_manager.py +426 -0
- agentpool/messaging/events.py +39 -0
- agentpool/messaging/message_container.py +209 -0
- agentpool/messaging/message_history.py +491 -0
- agentpool/messaging/messagenode.py +377 -0
- agentpool/messaging/messages.py +655 -0
- agentpool/messaging/processing.py +76 -0
- agentpool/mime_utils.py +95 -0
- agentpool/models/__init__.py +21 -0
- agentpool/models/acp_agents/__init__.py +22 -0
- agentpool/models/acp_agents/base.py +308 -0
- agentpool/models/acp_agents/mcp_capable.py +790 -0
- agentpool/models/acp_agents/non_mcp.py +842 -0
- agentpool/models/agents.py +450 -0
- agentpool/models/agui_agents.py +89 -0
- agentpool/models/claude_code_agents.py +238 -0
- agentpool/models/file_agents.py +116 -0
- agentpool/models/file_parsing.py +367 -0
- agentpool/models/manifest.py +658 -0
- agentpool/observability/__init__.py +9 -0
- agentpool/observability/observability_registry.py +97 -0
- agentpool/prompts/__init__.py +1 -0
- agentpool/prompts/base.py +27 -0
- agentpool/prompts/builtin_provider.py +75 -0
- agentpool/prompts/conversion_manager.py +95 -0
- agentpool/prompts/convert.py +96 -0
- agentpool/prompts/manager.py +204 -0
- agentpool/prompts/parts/zed.md +33 -0
- agentpool/prompts/prompts.py +581 -0
- agentpool/py.typed +0 -0
- agentpool/queries/tree-sitter-language-pack/README.md +7 -0
- agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/README.md +24 -0
- agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
- agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
- agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
- agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
- agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
- agentpool/repomap.py +1231 -0
- agentpool/resource_providers/__init__.py +17 -0
- agentpool/resource_providers/aggregating.py +54 -0
- agentpool/resource_providers/base.py +172 -0
- agentpool/resource_providers/codemode/__init__.py +9 -0
- agentpool/resource_providers/codemode/code_executor.py +215 -0
- agentpool/resource_providers/codemode/default_prompt.py +19 -0
- agentpool/resource_providers/codemode/helpers.py +83 -0
- agentpool/resource_providers/codemode/progress_executor.py +212 -0
- agentpool/resource_providers/codemode/provider.py +150 -0
- agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
- agentpool/resource_providers/codemode/remote_provider.py +171 -0
- agentpool/resource_providers/filtering.py +42 -0
- agentpool/resource_providers/mcp_provider.py +246 -0
- agentpool/resource_providers/plan_provider.py +196 -0
- agentpool/resource_providers/pool.py +69 -0
- agentpool/resource_providers/static.py +289 -0
- agentpool/running/__init__.py +20 -0
- agentpool/running/decorators.py +56 -0
- agentpool/running/discovery.py +101 -0
- agentpool/running/executor.py +284 -0
- agentpool/running/injection.py +111 -0
- agentpool/running/py.typed +0 -0
- agentpool/running/run_nodes.py +87 -0
- agentpool/server.py +122 -0
- agentpool/sessions/__init__.py +13 -0
- agentpool/sessions/manager.py +302 -0
- agentpool/sessions/models.py +71 -0
- agentpool/sessions/session.py +239 -0
- agentpool/sessions/store.py +163 -0
- agentpool/skills/__init__.py +5 -0
- agentpool/skills/manager.py +120 -0
- agentpool/skills/registry.py +210 -0
- agentpool/skills/skill.py +36 -0
- agentpool/storage/__init__.py +17 -0
- agentpool/storage/manager.py +419 -0
- agentpool/storage/serialization.py +136 -0
- agentpool/talk/__init__.py +13 -0
- agentpool/talk/registry.py +128 -0
- agentpool/talk/stats.py +159 -0
- agentpool/talk/talk.py +604 -0
- agentpool/tasks/__init__.py +20 -0
- agentpool/tasks/exceptions.py +25 -0
- agentpool/tasks/registry.py +33 -0
- agentpool/testing.py +129 -0
- agentpool/text_templates/__init__.py +39 -0
- agentpool/text_templates/system_prompt.jinja +30 -0
- agentpool/text_templates/tool_call_default.jinja +13 -0
- agentpool/text_templates/tool_call_markdown.jinja +25 -0
- agentpool/text_templates/tool_call_simple.jinja +5 -0
- agentpool/tools/__init__.py +16 -0
- agentpool/tools/base.py +269 -0
- agentpool/tools/exceptions.py +9 -0
- agentpool/tools/manager.py +255 -0
- agentpool/tools/tool_call_info.py +87 -0
- agentpool/ui/__init__.py +2 -0
- agentpool/ui/base.py +89 -0
- agentpool/ui/mock_provider.py +81 -0
- agentpool/ui/stdlib_provider.py +150 -0
- agentpool/utils/__init__.py +44 -0
- agentpool/utils/baseregistry.py +185 -0
- agentpool/utils/count_tokens.py +62 -0
- agentpool/utils/dag.py +184 -0
- agentpool/utils/importing.py +206 -0
- agentpool/utils/inspection.py +334 -0
- agentpool/utils/model_capabilities.py +25 -0
- agentpool/utils/network.py +28 -0
- agentpool/utils/now.py +22 -0
- agentpool/utils/parse_time.py +87 -0
- agentpool/utils/result_utils.py +35 -0
- agentpool/utils/signatures.py +305 -0
- agentpool/utils/streams.py +112 -0
- agentpool/utils/tasks.py +186 -0
- agentpool/vfs_registry.py +250 -0
- agentpool-2.1.9.dist-info/METADATA +336 -0
- agentpool-2.1.9.dist-info/RECORD +474 -0
- agentpool-2.1.9.dist-info/WHEEL +4 -0
- agentpool-2.1.9.dist-info/entry_points.txt +14 -0
- agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
- agentpool_cli/__init__.py +34 -0
- agentpool_cli/__main__.py +66 -0
- agentpool_cli/agent.py +175 -0
- agentpool_cli/cli_types.py +23 -0
- agentpool_cli/common.py +163 -0
- agentpool_cli/create.py +175 -0
- agentpool_cli/history.py +217 -0
- agentpool_cli/log.py +78 -0
- agentpool_cli/py.typed +0 -0
- agentpool_cli/run.py +84 -0
- agentpool_cli/serve_acp.py +177 -0
- agentpool_cli/serve_api.py +69 -0
- agentpool_cli/serve_mcp.py +74 -0
- agentpool_cli/serve_vercel.py +233 -0
- agentpool_cli/store.py +171 -0
- agentpool_cli/task.py +84 -0
- agentpool_cli/utils.py +104 -0
- agentpool_cli/watch.py +54 -0
- agentpool_commands/__init__.py +180 -0
- agentpool_commands/agents.py +199 -0
- agentpool_commands/base.py +45 -0
- agentpool_commands/commands.py +58 -0
- agentpool_commands/completers.py +110 -0
- agentpool_commands/connections.py +175 -0
- agentpool_commands/markdown_utils.py +31 -0
- agentpool_commands/models.py +62 -0
- agentpool_commands/prompts.py +78 -0
- agentpool_commands/py.typed +0 -0
- agentpool_commands/read.py +77 -0
- agentpool_commands/resources.py +210 -0
- agentpool_commands/session.py +48 -0
- agentpool_commands/tools.py +269 -0
- agentpool_commands/utils.py +189 -0
- agentpool_commands/workers.py +163 -0
- agentpool_config/__init__.py +53 -0
- agentpool_config/builtin_tools.py +265 -0
- agentpool_config/commands.py +237 -0
- agentpool_config/conditions.py +301 -0
- agentpool_config/converters.py +30 -0
- agentpool_config/durable.py +331 -0
- agentpool_config/event_handlers.py +600 -0
- agentpool_config/events.py +153 -0
- agentpool_config/forward_targets.py +251 -0
- agentpool_config/hook_conditions.py +331 -0
- agentpool_config/hooks.py +241 -0
- agentpool_config/jinja.py +206 -0
- agentpool_config/knowledge.py +41 -0
- agentpool_config/loaders.py +350 -0
- agentpool_config/mcp_server.py +243 -0
- agentpool_config/nodes.py +202 -0
- agentpool_config/observability.py +191 -0
- agentpool_config/output_types.py +55 -0
- agentpool_config/pool_server.py +267 -0
- agentpool_config/prompt_hubs.py +105 -0
- agentpool_config/prompts.py +185 -0
- agentpool_config/py.typed +0 -0
- agentpool_config/resources.py +33 -0
- agentpool_config/session.py +119 -0
- agentpool_config/skills.py +17 -0
- agentpool_config/storage.py +288 -0
- agentpool_config/system_prompts.py +190 -0
- agentpool_config/task.py +162 -0
- agentpool_config/teams.py +52 -0
- agentpool_config/tools.py +112 -0
- agentpool_config/toolsets.py +1033 -0
- agentpool_config/workers.py +86 -0
- agentpool_prompts/__init__.py +1 -0
- agentpool_prompts/braintrust_hub.py +235 -0
- agentpool_prompts/fabric.py +75 -0
- agentpool_prompts/langfuse_hub.py +79 -0
- agentpool_prompts/promptlayer_provider.py +59 -0
- agentpool_prompts/py.typed +0 -0
- agentpool_server/__init__.py +9 -0
- agentpool_server/a2a_server/__init__.py +5 -0
- agentpool_server/a2a_server/a2a_types.py +41 -0
- agentpool_server/a2a_server/server.py +190 -0
- agentpool_server/a2a_server/storage.py +81 -0
- agentpool_server/acp_server/__init__.py +22 -0
- agentpool_server/acp_server/acp_agent.py +786 -0
- agentpool_server/acp_server/acp_tools.py +43 -0
- agentpool_server/acp_server/commands/__init__.py +18 -0
- agentpool_server/acp_server/commands/acp_commands.py +594 -0
- agentpool_server/acp_server/commands/debug_commands.py +376 -0
- agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
- agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
- agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
- agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
- agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
- agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
- agentpool_server/acp_server/commands/spawn.py +210 -0
- agentpool_server/acp_server/converters.py +235 -0
- agentpool_server/acp_server/input_provider.py +338 -0
- agentpool_server/acp_server/server.py +288 -0
- agentpool_server/acp_server/session.py +969 -0
- agentpool_server/acp_server/session_manager.py +313 -0
- agentpool_server/acp_server/syntax_detection.py +250 -0
- agentpool_server/acp_server/zed_tools.md +90 -0
- agentpool_server/aggregating_server.py +309 -0
- agentpool_server/agui_server/__init__.py +11 -0
- agentpool_server/agui_server/server.py +128 -0
- agentpool_server/base.py +189 -0
- agentpool_server/http_server.py +164 -0
- agentpool_server/mcp_server/__init__.py +6 -0
- agentpool_server/mcp_server/server.py +314 -0
- agentpool_server/mcp_server/zed_wrapper.py +110 -0
- agentpool_server/openai_api_server/__init__.py +5 -0
- agentpool_server/openai_api_server/completions/__init__.py +1 -0
- agentpool_server/openai_api_server/completions/helpers.py +81 -0
- agentpool_server/openai_api_server/completions/models.py +98 -0
- agentpool_server/openai_api_server/responses/__init__.py +1 -0
- agentpool_server/openai_api_server/responses/helpers.py +74 -0
- agentpool_server/openai_api_server/responses/models.py +96 -0
- agentpool_server/openai_api_server/server.py +242 -0
- agentpool_server/py.typed +0 -0
- agentpool_storage/__init__.py +9 -0
- agentpool_storage/base.py +310 -0
- agentpool_storage/file_provider.py +378 -0
- agentpool_storage/formatters.py +129 -0
- agentpool_storage/memory_provider.py +396 -0
- agentpool_storage/models.py +108 -0
- agentpool_storage/py.typed +0 -0
- agentpool_storage/session_store.py +262 -0
- agentpool_storage/sql_provider/__init__.py +21 -0
- agentpool_storage/sql_provider/cli.py +146 -0
- agentpool_storage/sql_provider/models.py +249 -0
- agentpool_storage/sql_provider/queries.py +15 -0
- agentpool_storage/sql_provider/sql_provider.py +444 -0
- agentpool_storage/sql_provider/utils.py +234 -0
- agentpool_storage/text_log_provider.py +275 -0
- agentpool_toolsets/__init__.py +15 -0
- agentpool_toolsets/builtin/__init__.py +33 -0
- agentpool_toolsets/builtin/agent_management.py +239 -0
- agentpool_toolsets/builtin/chain.py +288 -0
- agentpool_toolsets/builtin/code.py +398 -0
- agentpool_toolsets/builtin/debug.py +291 -0
- agentpool_toolsets/builtin/execution_environment.py +381 -0
- agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
- agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
- agentpool_toolsets/builtin/history.py +36 -0
- agentpool_toolsets/builtin/integration.py +85 -0
- agentpool_toolsets/builtin/skills.py +77 -0
- agentpool_toolsets/builtin/subagent_tools.py +324 -0
- agentpool_toolsets/builtin/tool_management.py +90 -0
- agentpool_toolsets/builtin/user_interaction.py +52 -0
- agentpool_toolsets/builtin/workers.py +128 -0
- agentpool_toolsets/composio_toolset.py +96 -0
- agentpool_toolsets/config_creation.py +192 -0
- agentpool_toolsets/entry_points.py +47 -0
- agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
- agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
- agentpool_toolsets/fsspec_toolset/grep.py +450 -0
- agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
- agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
- agentpool_toolsets/mcp_run_toolset.py +61 -0
- agentpool_toolsets/notifications.py +146 -0
- agentpool_toolsets/openapi.py +118 -0
- agentpool_toolsets/py.typed +0 -0
- agentpool_toolsets/search_toolset.py +202 -0
- agentpool_toolsets/semantic_memory_toolset.py +536 -0
- agentpool_toolsets/streaming_tools.py +265 -0
- agentpool_toolsets/vfs_toolset.py +124 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"""FastMCP-based client implementation for AgentPool.
|
|
2
|
+
|
|
3
|
+
This module provides a client for communicating with MCP servers using FastMCP.
|
|
4
|
+
It includes support for contextual progress handlers that extend FastMCP's
|
|
5
|
+
standard progress callbacks with tool execution context (tool name, call ID, and input).
|
|
6
|
+
|
|
7
|
+
The key innovation is the signature injection system that allows MCP tools to work
|
|
8
|
+
seamlessly with PydanticAI's RunContext while providing rich progress information.
|
|
9
|
+
|
|
10
|
+
Elicitation is handled via a forwarding callback pattern: a stable callback is
|
|
11
|
+
registered at connection time, but it delegates to a mutable handler that can
|
|
12
|
+
be swapped per tool call (allowing AgentContext.handle_elicitation to be used).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import contextlib
|
|
18
|
+
from importlib.metadata import version
|
|
19
|
+
import logging
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Self, assert_never
|
|
21
|
+
|
|
22
|
+
import anyio
|
|
23
|
+
from pydantic_ai import RunContext, ToolReturn
|
|
24
|
+
from schemez import FunctionSchema
|
|
25
|
+
|
|
26
|
+
from agentpool.agents.context import AgentContext
|
|
27
|
+
from agentpool.log import get_logger
|
|
28
|
+
from agentpool.mcp_server.constants import MCP_TO_LOGGING
|
|
29
|
+
from agentpool.mcp_server.helpers import extract_text_content, mcp_tool_to_fn_schema
|
|
30
|
+
from agentpool.mcp_server.message_handler import MCPMessageHandler
|
|
31
|
+
from agentpool.tools.base import Tool
|
|
32
|
+
from agentpool.utils.signatures import create_modified_signature
|
|
33
|
+
from agentpool_config.mcp_server import (
|
|
34
|
+
SSEMCPServerConfig,
|
|
35
|
+
StdioMCPServerConfig,
|
|
36
|
+
StreamableHTTPMCPServerConfig,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
42
|
+
|
|
43
|
+
import fastmcp
|
|
44
|
+
from fastmcp.client import ClientTransport
|
|
45
|
+
from fastmcp.client.elicitation import ElicitationHandler
|
|
46
|
+
from fastmcp.client.logging import LogMessage
|
|
47
|
+
from fastmcp.client.messages import MessageHandler, MessageHandlerT
|
|
48
|
+
from fastmcp.client.sampling import SamplingHandler
|
|
49
|
+
from mcp.shared.context import RequestContext
|
|
50
|
+
from mcp.types import (
|
|
51
|
+
BlobResourceContents,
|
|
52
|
+
ContentBlock,
|
|
53
|
+
ElicitRequestParams,
|
|
54
|
+
GetPromptResult,
|
|
55
|
+
Icon,
|
|
56
|
+
Implementation,
|
|
57
|
+
Prompt as MCPPrompt,
|
|
58
|
+
Resource as MCPResource,
|
|
59
|
+
TextResourceContents,
|
|
60
|
+
Tool as MCPTool,
|
|
61
|
+
)
|
|
62
|
+
from pydantic_ai import BinaryContent
|
|
63
|
+
from upathtools.filesystems import MCPFileSystem, MCPToolsFileSystem
|
|
64
|
+
|
|
65
|
+
from agentpool_config.mcp_server import MCPServerConfig
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
logger = get_logger(__name__)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MCPClient:
|
|
72
|
+
"""FastMCP-based client for communicating with MCP servers."""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
config: MCPServerConfig,
|
|
77
|
+
sampling_callback: SamplingHandler[Any, Any] | None = None,
|
|
78
|
+
message_handler: MessageHandlerT | MessageHandler | None = None,
|
|
79
|
+
accessible_roots: list[str] | None = None,
|
|
80
|
+
tool_change_callback: Callable[[], Awaitable[None]] | None = None,
|
|
81
|
+
prompt_change_callback: Callable[[], Awaitable[None]] | None = None,
|
|
82
|
+
resource_change_callback: Callable[[], Awaitable[None]] | None = None,
|
|
83
|
+
client_name: str | None = None,
|
|
84
|
+
client_title: str | None = None,
|
|
85
|
+
client_website_url: str | None = None,
|
|
86
|
+
client_icon_path: str | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
# Mutable handler swapped per call_tool for dynamic elicitation
|
|
89
|
+
self._current_elicitation_handler: ElicitationHandler | None = None
|
|
90
|
+
self.config = config
|
|
91
|
+
self._sampling_callback = sampling_callback
|
|
92
|
+
# Store message handler or mark for lazy creation
|
|
93
|
+
self._message_handler = message_handler
|
|
94
|
+
self._accessible_roots = accessible_roots or []
|
|
95
|
+
self._tool_change_callback = tool_change_callback
|
|
96
|
+
self._prompt_change_callback = prompt_change_callback
|
|
97
|
+
self._resource_change_callback = resource_change_callback
|
|
98
|
+
self._client_name = client_name
|
|
99
|
+
self._client_title = client_title
|
|
100
|
+
self._client_website_url = client_website_url
|
|
101
|
+
self._client_icon_path = client_icon_path
|
|
102
|
+
self._client = self._get_client(self.config)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def connected(self) -> bool:
|
|
106
|
+
"""Check if client is connected by examining session state."""
|
|
107
|
+
return self._client.is_connected()
|
|
108
|
+
|
|
109
|
+
async def __aenter__(self) -> Self:
|
|
110
|
+
"""Enter context manager."""
|
|
111
|
+
try:
|
|
112
|
+
# First attempt with configured auth
|
|
113
|
+
await self._client.__aenter__() # type: ignore[no-untyped-call]
|
|
114
|
+
|
|
115
|
+
except Exception as first_error:
|
|
116
|
+
# OAuth fallback for HTTP/SSE if not already using OAuth
|
|
117
|
+
if not isinstance(self.config, StdioMCPServerConfig) and not self.config.auth.oauth:
|
|
118
|
+
try:
|
|
119
|
+
with contextlib.suppress(Exception):
|
|
120
|
+
await self._client.__aexit__(None, None, None) # type: ignore[no-untyped-call]
|
|
121
|
+
self._client = self._get_client(self.config, force_oauth=True)
|
|
122
|
+
await self._client.__aenter__() # type: ignore[no-untyped-call]
|
|
123
|
+
logger.info("Connected with OAuth fallback")
|
|
124
|
+
except Exception: # noqa: BLE001
|
|
125
|
+
raise first_error from None
|
|
126
|
+
else:
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
async def __aexit__(self, *args: object) -> None:
|
|
132
|
+
"""Exit context manager and cleanup."""
|
|
133
|
+
try:
|
|
134
|
+
await self._client.__aexit__(None, None, None) # type: ignore[no-untyped-call]
|
|
135
|
+
except Exception as e: # noqa: BLE001
|
|
136
|
+
logger.warning("Error during FastMCP client cleanup", error=e)
|
|
137
|
+
|
|
138
|
+
def get_resource_fs(self) -> MCPFileSystem:
|
|
139
|
+
"""Get a filesystem for accessing MCP resources."""
|
|
140
|
+
from upathtools.filesystems import MCPFileSystem
|
|
141
|
+
|
|
142
|
+
return MCPFileSystem(client=self._client)
|
|
143
|
+
|
|
144
|
+
def get_tools_fs(self) -> MCPToolsFileSystem:
|
|
145
|
+
"""Get a filesystem for accessing MCP tools as code."""
|
|
146
|
+
from upathtools.filesystems import MCPToolsFileSystem
|
|
147
|
+
|
|
148
|
+
return MCPToolsFileSystem(client=self._client)
|
|
149
|
+
|
|
150
|
+
async def _log_handler(self, message: LogMessage) -> None:
|
|
151
|
+
"""Handle server log messages."""
|
|
152
|
+
level = MCP_TO_LOGGING.get(message.level, logging.INFO)
|
|
153
|
+
logger.log(level, "MCP Server: ", data=message.data)
|
|
154
|
+
|
|
155
|
+
async def _forwarding_elicitation_callback[T](
|
|
156
|
+
self,
|
|
157
|
+
message: str,
|
|
158
|
+
response_type: type[T],
|
|
159
|
+
params: ElicitRequestParams,
|
|
160
|
+
context: RequestContext[Any, Any],
|
|
161
|
+
) -> T | dict[str, Any] | Any:
|
|
162
|
+
"""Forwarding callback that delegates to current handler.
|
|
163
|
+
|
|
164
|
+
This callback is registered once at connection time, but delegates to
|
|
165
|
+
_current_elicitation_handler which can be swapped per tool call.
|
|
166
|
+
"""
|
|
167
|
+
from fastmcp.client.elicitation import ElicitResult
|
|
168
|
+
|
|
169
|
+
# Try current handler first (set per call_tool)
|
|
170
|
+
if self._current_elicitation_handler:
|
|
171
|
+
return await self._current_elicitation_handler(message, response_type, params, context)
|
|
172
|
+
# No handler available - decline by default
|
|
173
|
+
return ElicitResult(action="decline")
|
|
174
|
+
|
|
175
|
+
def _get_client(
|
|
176
|
+
self, config: MCPServerConfig, force_oauth: bool = False
|
|
177
|
+
) -> fastmcp.Client[Any]:
|
|
178
|
+
"""Create FastMCP client based on config."""
|
|
179
|
+
import fastmcp
|
|
180
|
+
from fastmcp.client import SSETransport, StreamableHttpTransport
|
|
181
|
+
from fastmcp.client.transports import StdioTransport
|
|
182
|
+
from mcp.types import Icon, Implementation
|
|
183
|
+
|
|
184
|
+
transport: ClientTransport
|
|
185
|
+
# Create transport based on config type
|
|
186
|
+
match config:
|
|
187
|
+
case StdioMCPServerConfig(command=command, args=args):
|
|
188
|
+
env = config.get_env_vars()
|
|
189
|
+
transport = StdioTransport(command=command, args=args, env=env)
|
|
190
|
+
oauth = False
|
|
191
|
+
if force_oauth:
|
|
192
|
+
msg = "OAuth is not supported for StdioMCPServerConfig"
|
|
193
|
+
raise ValueError(msg)
|
|
194
|
+
|
|
195
|
+
case SSEMCPServerConfig(url=url, headers=headers, auth=auth):
|
|
196
|
+
transport = SSETransport(url=url, headers=headers)
|
|
197
|
+
oauth = auth.oauth
|
|
198
|
+
|
|
199
|
+
case StreamableHTTPMCPServerConfig(url=url, headers=headers, auth=auth):
|
|
200
|
+
transport = StreamableHttpTransport(url=url, headers=headers)
|
|
201
|
+
oauth = auth.oauth
|
|
202
|
+
case _ as unreachable:
|
|
203
|
+
assert_never(unreachable)
|
|
204
|
+
|
|
205
|
+
# Create message handler if needed
|
|
206
|
+
msg_handler = self._message_handler or MCPMessageHandler(
|
|
207
|
+
self,
|
|
208
|
+
self._tool_change_callback,
|
|
209
|
+
self._prompt_change_callback,
|
|
210
|
+
self._resource_change_callback,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Build client_info if client_name is provided
|
|
214
|
+
client_info: Implementation | None = None
|
|
215
|
+
if self._client_name:
|
|
216
|
+
icons: list[Icon] | None = None
|
|
217
|
+
if self._client_icon_path:
|
|
218
|
+
icons = [Icon(src=self._client_icon_path)]
|
|
219
|
+
client_info = Implementation(
|
|
220
|
+
name=self._client_name,
|
|
221
|
+
version=version("agentpool"),
|
|
222
|
+
title=self._client_title,
|
|
223
|
+
websiteUrl=self._client_website_url,
|
|
224
|
+
icons=icons,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return fastmcp.Client(
|
|
228
|
+
transport,
|
|
229
|
+
log_handler=self._log_handler,
|
|
230
|
+
roots=self._accessible_roots,
|
|
231
|
+
timeout=config.timeout,
|
|
232
|
+
elicitation_handler=self._forwarding_elicitation_callback,
|
|
233
|
+
sampling_handler=self._sampling_callback,
|
|
234
|
+
message_handler=msg_handler,
|
|
235
|
+
auth="oauth" if (force_oauth or oauth) else None,
|
|
236
|
+
client_info=client_info,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
async def list_tools(self) -> list[MCPTool]:
|
|
240
|
+
"""Get available tools directly from the server."""
|
|
241
|
+
if not self.connected:
|
|
242
|
+
msg = "Not connected to MCP server"
|
|
243
|
+
raise RuntimeError(msg)
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
tools = await self._client.list_tools()
|
|
247
|
+
logger.debug("Listed tools from MCP server", num_tools=len(tools))
|
|
248
|
+
except Exception as e: # noqa: BLE001
|
|
249
|
+
logger.warning("Failed to list tools", error=e)
|
|
250
|
+
return []
|
|
251
|
+
else:
|
|
252
|
+
return tools
|
|
253
|
+
|
|
254
|
+
async def list_prompts(self) -> list[MCPPrompt]:
|
|
255
|
+
"""Get available prompts from the server."""
|
|
256
|
+
if not self.connected:
|
|
257
|
+
msg = "Not connected to MCP server"
|
|
258
|
+
raise RuntimeError(msg)
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
return await self._client.list_prompts()
|
|
262
|
+
except Exception as e: # noqa: BLE001
|
|
263
|
+
logger.debug("Failed to list prompts", error=e)
|
|
264
|
+
return []
|
|
265
|
+
|
|
266
|
+
async def list_resources(self) -> list[MCPResource]:
|
|
267
|
+
"""Get available resources from the server."""
|
|
268
|
+
if not self.connected:
|
|
269
|
+
msg = "Not connected to MCP server"
|
|
270
|
+
raise RuntimeError(msg)
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
return await self._client.list_resources()
|
|
274
|
+
except Exception as e:
|
|
275
|
+
msg = f"Failed to list resources: {e}"
|
|
276
|
+
raise RuntimeError(msg) from e
|
|
277
|
+
|
|
278
|
+
async def get_prompt(
|
|
279
|
+
self, name: str, arguments: dict[str, str] | None = None
|
|
280
|
+
) -> GetPromptResult:
|
|
281
|
+
"""Get a specific prompt's content."""
|
|
282
|
+
if not self.connected:
|
|
283
|
+
msg = "Not connected to MCP server"
|
|
284
|
+
raise RuntimeError(msg)
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
return await self._client.get_prompt_mcp(name, arguments)
|
|
288
|
+
except Exception as e:
|
|
289
|
+
msg = f"Failed to get prompt {name!r}: {e}"
|
|
290
|
+
raise RuntimeError(msg) from e
|
|
291
|
+
|
|
292
|
+
def convert_tool(self, tool: MCPTool) -> Tool:
|
|
293
|
+
"""Create a properly typed callable from MCP tool schema."""
|
|
294
|
+
|
|
295
|
+
async def tool_callable(
|
|
296
|
+
ctx: RunContext, agent_ctx: AgentContext[Any], **kwargs: Any
|
|
297
|
+
) -> str | Any | ToolReturn:
|
|
298
|
+
"""Dynamically generated MCP tool wrapper."""
|
|
299
|
+
# Filter out None values for optional params
|
|
300
|
+
schema_props = tool.inputSchema.get("properties", {})
|
|
301
|
+
required_props = set(tool.inputSchema.get("required", []))
|
|
302
|
+
filtered_kwargs = {
|
|
303
|
+
k: v
|
|
304
|
+
for k, v in kwargs.items()
|
|
305
|
+
if k in required_props or (k in schema_props and v is not None)
|
|
306
|
+
}
|
|
307
|
+
return await self.call_tool(tool.name, ctx, filtered_kwargs, agent_ctx)
|
|
308
|
+
|
|
309
|
+
# Set proper signature and annotations with both RunContext and AgentContext
|
|
310
|
+
schema = mcp_tool_to_fn_schema(tool)
|
|
311
|
+
fn_schema = FunctionSchema.from_dict(schema)
|
|
312
|
+
sig = fn_schema.to_python_signature()
|
|
313
|
+
|
|
314
|
+
tool_callable.__signature__ = create_modified_signature( # type: ignore[attr-defined]
|
|
315
|
+
sig, inject={"ctx": RunContext, "agent_ctx": AgentContext}
|
|
316
|
+
)
|
|
317
|
+
annotations = fn_schema.get_annotations()
|
|
318
|
+
annotations["ctx"] = RunContext
|
|
319
|
+
annotations["agent_ctx"] = AgentContext
|
|
320
|
+
# Update return annotation to support multiple types
|
|
321
|
+
annotations["return"] = str | Any | ToolReturn # type: ignore
|
|
322
|
+
tool_callable.__annotations__ = annotations
|
|
323
|
+
tool_callable.__name__ = tool.name
|
|
324
|
+
tool_callable.__doc__ = tool.description or "No description provided."
|
|
325
|
+
return Tool.from_callable(tool_callable, source="mcp")
|
|
326
|
+
|
|
327
|
+
async def call_tool(
|
|
328
|
+
self,
|
|
329
|
+
name: str,
|
|
330
|
+
run_context: RunContext,
|
|
331
|
+
arguments: dict[str, Any] | None = None,
|
|
332
|
+
agent_ctx: AgentContext[Any] | None = None,
|
|
333
|
+
) -> ToolReturn | str | Any:
|
|
334
|
+
"""Call an MCP tool with full PydanticAI return type support."""
|
|
335
|
+
if not self.connected:
|
|
336
|
+
msg = "Not connected to MCP server"
|
|
337
|
+
raise RuntimeError(msg)
|
|
338
|
+
|
|
339
|
+
# Create progress handler that bridges to AgentContext if available
|
|
340
|
+
progress_handler = None
|
|
341
|
+
if agent_ctx:
|
|
342
|
+
|
|
343
|
+
async def fastmcp_progress_handler(
|
|
344
|
+
progress: float,
|
|
345
|
+
total: float | None,
|
|
346
|
+
message: str | None,
|
|
347
|
+
) -> None:
|
|
348
|
+
await agent_ctx.report_progress(progress, total, message or "")
|
|
349
|
+
|
|
350
|
+
progress_handler = fastmcp_progress_handler
|
|
351
|
+
|
|
352
|
+
# Set up per-call elicitation handler from AgentContext
|
|
353
|
+
if agent_ctx:
|
|
354
|
+
|
|
355
|
+
async def elicitation_handler[T](
|
|
356
|
+
message: str,
|
|
357
|
+
response_type: type[T] | None,
|
|
358
|
+
params: ElicitRequestParams,
|
|
359
|
+
context: RequestContext[Any, Any, Any],
|
|
360
|
+
) -> T | dict[str, Any] | Any:
|
|
361
|
+
from fastmcp.client.elicitation import ElicitResult
|
|
362
|
+
from mcp.types import ElicitResult as MCPElicitResult, ErrorData
|
|
363
|
+
|
|
364
|
+
result = await agent_ctx.handle_elicitation(params)
|
|
365
|
+
match result:
|
|
366
|
+
case MCPElicitResult(action="accept", content=content):
|
|
367
|
+
return content
|
|
368
|
+
case MCPElicitResult(action="cancel"):
|
|
369
|
+
return ElicitResult(action="cancel")
|
|
370
|
+
case MCPElicitResult(action="decline"):
|
|
371
|
+
return ElicitResult(action="decline")
|
|
372
|
+
case ErrorData():
|
|
373
|
+
return ElicitResult(action="decline")
|
|
374
|
+
case _:
|
|
375
|
+
return ElicitResult(action="decline")
|
|
376
|
+
|
|
377
|
+
self._current_elicitation_handler = elicitation_handler
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
result = await self._client.call_tool(
|
|
381
|
+
name, arguments, progress_handler=progress_handler
|
|
382
|
+
)
|
|
383
|
+
content = await self._from_mcp_content(result.content)
|
|
384
|
+
# Decision logic for return type
|
|
385
|
+
match (result.data is not None, bool(content)):
|
|
386
|
+
case (True, True): # Both structured data and rich content -> ToolReturn
|
|
387
|
+
return ToolReturn(return_value=result.data, content=content)
|
|
388
|
+
case (True, False): # Only structured data -> return directly
|
|
389
|
+
return result.data
|
|
390
|
+
case (False, True): # Only content -> ToolReturn with content
|
|
391
|
+
msg = "Tool executed successfully"
|
|
392
|
+
return ToolReturn(return_value=msg, content=content)
|
|
393
|
+
case (False, False): # Fallback to text extraction
|
|
394
|
+
return extract_text_content(result.content)
|
|
395
|
+
case _: # Handle unexpected cases
|
|
396
|
+
msg = f"Unexpected MCP content: {result.content}"
|
|
397
|
+
raise ValueError(msg) # noqa: TRY301
|
|
398
|
+
except Exception as e:
|
|
399
|
+
msg = f"MCP tool call failed: {e}"
|
|
400
|
+
raise RuntimeError(msg) from e
|
|
401
|
+
finally:
|
|
402
|
+
# Clear per-call handler
|
|
403
|
+
self._current_elicitation_handler = None
|
|
404
|
+
|
|
405
|
+
async def _from_mcp_content(
|
|
406
|
+
self,
|
|
407
|
+
mcp_content: Sequence[ContentBlock | TextResourceContents | BlobResourceContents],
|
|
408
|
+
) -> list[str | BinaryContent]:
|
|
409
|
+
"""Convert MCP content blocks to PydanticAI content types."""
|
|
410
|
+
from agentpool.mcp_server.conversions import from_mcp_content
|
|
411
|
+
|
|
412
|
+
return await from_mcp_content(mcp_content)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
if __name__ == "__main__":
|
|
416
|
+
path = "/home/phil65/dev/oss/agentpool/tests/mcp_server/server.py"
|
|
417
|
+
# path = Path(__file__).parent / "test_mcp_server.py"
|
|
418
|
+
config = StdioMCPServerConfig(
|
|
419
|
+
command="uv",
|
|
420
|
+
args=["run", str(path)],
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
async def main() -> None:
|
|
424
|
+
async with MCPClient(config=config) as mcp_client:
|
|
425
|
+
# Create MCP filesystem
|
|
426
|
+
fs = mcp_client.get_resource_fs()
|
|
427
|
+
print(await fs._ls(""))
|
|
428
|
+
|
|
429
|
+
anyio.run(main)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""MCP related constants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import mcp
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
MCP_TO_LOGGING: dict[mcp.LoggingLevel, int] = {
|
|
14
|
+
"debug": logging.DEBUG,
|
|
15
|
+
"info": logging.INFO,
|
|
16
|
+
"notice": logging.INFO,
|
|
17
|
+
"warning": logging.WARNING,
|
|
18
|
+
"error": logging.ERROR,
|
|
19
|
+
"critical": logging.CRITICAL,
|
|
20
|
+
"alert": logging.CRITICAL,
|
|
21
|
+
"emergency": logging.CRITICAL,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Map Python logging levels to MCP logging levels
|
|
26
|
+
LOGGING_TO_MCP: dict[int, mcp.LoggingLevel] = {
|
|
27
|
+
logging.DEBUG: "debug",
|
|
28
|
+
logging.INFO: "info",
|
|
29
|
+
logging.WARNING: "warning",
|
|
30
|
+
logging.ERROR: "error",
|
|
31
|
+
logging.CRITICAL: "critical",
|
|
32
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Conversions between internal and MCP types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from typing import TYPE_CHECKING, Any, assert_never
|
|
7
|
+
|
|
8
|
+
from mcp.types import (
|
|
9
|
+
AudioContent,
|
|
10
|
+
BlobResourceContents,
|
|
11
|
+
EmbeddedResource,
|
|
12
|
+
ImageContent,
|
|
13
|
+
PromptMessage,
|
|
14
|
+
ResourceLink,
|
|
15
|
+
TextContent,
|
|
16
|
+
TextResourceContents,
|
|
17
|
+
)
|
|
18
|
+
from pydantic_ai import (
|
|
19
|
+
AudioUrl,
|
|
20
|
+
BinaryContent,
|
|
21
|
+
BinaryImage,
|
|
22
|
+
DocumentUrl,
|
|
23
|
+
FileUrl,
|
|
24
|
+
ImageUrl,
|
|
25
|
+
SystemPromptPart,
|
|
26
|
+
TextPart,
|
|
27
|
+
UserPromptPart,
|
|
28
|
+
VideoUrl,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from agentpool.log import get_logger
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from collections.abc import Sequence
|
|
36
|
+
|
|
37
|
+
from fastmcp import Client
|
|
38
|
+
from mcp.types import ContentBlock
|
|
39
|
+
from pydantic_ai import ModelRequestPart, ModelResponsePart
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
logger = get_logger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def to_mcp_messages(
|
|
46
|
+
part: ModelRequestPart | ModelResponsePart,
|
|
47
|
+
) -> list[PromptMessage]:
|
|
48
|
+
"""Convert internal PromptMessage to MCP PromptMessage."""
|
|
49
|
+
messages = []
|
|
50
|
+
match part:
|
|
51
|
+
case UserPromptPart(content=str() as c):
|
|
52
|
+
content = TextContent(type="text", text=c)
|
|
53
|
+
messages.append(PromptMessage(role="user", content=content))
|
|
54
|
+
case UserPromptPart(content=content_items):
|
|
55
|
+
for item in content_items:
|
|
56
|
+
match item:
|
|
57
|
+
case BinaryContent():
|
|
58
|
+
if item.is_audio:
|
|
59
|
+
encoded = base64.b64encode(item.data).decode("utf-8")
|
|
60
|
+
audio = AudioContent(
|
|
61
|
+
type="audio", data=encoded, mimeType=item.media_type
|
|
62
|
+
)
|
|
63
|
+
messages.append(PromptMessage(role="user", content=audio))
|
|
64
|
+
elif item.is_image:
|
|
65
|
+
encoded = base64.b64encode(item.data).decode("utf-8")
|
|
66
|
+
image = ImageContent(
|
|
67
|
+
type="image", data=encoded, mimeType=item.media_type
|
|
68
|
+
)
|
|
69
|
+
messages.append(PromptMessage(role="user", content=image))
|
|
70
|
+
case FileUrl(url=url):
|
|
71
|
+
content = TextContent(type="text", text=url)
|
|
72
|
+
messages.append(PromptMessage(role="user", content=content))
|
|
73
|
+
|
|
74
|
+
case SystemPromptPart(content=msg):
|
|
75
|
+
messages.append(PromptMessage(role="user", content=TextContent(type="text", text=msg)))
|
|
76
|
+
case TextPart(content=msg):
|
|
77
|
+
messages.append(
|
|
78
|
+
PromptMessage(role="assistant", content=TextContent(type="text", text=msg))
|
|
79
|
+
)
|
|
80
|
+
return messages
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _url_from_mime_type(uri: str, mime_type: str | None) -> FileUrl:
|
|
84
|
+
"""Convert URI to appropriate pydantic-ai URL type based on MIME type."""
|
|
85
|
+
if not mime_type:
|
|
86
|
+
return DocumentUrl(url=uri)
|
|
87
|
+
|
|
88
|
+
if mime_type.startswith("image/"):
|
|
89
|
+
return ImageUrl(url=uri)
|
|
90
|
+
if mime_type.startswith("audio/"):
|
|
91
|
+
return AudioUrl(url=uri)
|
|
92
|
+
if mime_type.startswith("video/"):
|
|
93
|
+
return VideoUrl(url=uri)
|
|
94
|
+
return DocumentUrl(url=uri)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def from_mcp_content(
|
|
98
|
+
mcp_content: Sequence[ContentBlock | TextResourceContents | BlobResourceContents],
|
|
99
|
+
client: Client[Any] | None = None,
|
|
100
|
+
) -> list[str | BinaryContent]:
|
|
101
|
+
"""Convert MCP content blocks to PydanticAI content types.
|
|
102
|
+
|
|
103
|
+
If a FastMCP client is given, this function will try to resolve the ResourceLinks.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
contents: list[Any] = []
|
|
107
|
+
|
|
108
|
+
for block in mcp_content:
|
|
109
|
+
match block:
|
|
110
|
+
case TextContent(text=text):
|
|
111
|
+
contents.append(text)
|
|
112
|
+
case TextResourceContents(text=text):
|
|
113
|
+
contents.append(text)
|
|
114
|
+
case ImageContent(data=data, mimeType=mime_type):
|
|
115
|
+
decoded_data = base64.b64decode(data)
|
|
116
|
+
img = BinaryImage(data=decoded_data, media_type=mime_type)
|
|
117
|
+
contents.append(img)
|
|
118
|
+
case AudioContent(data=data, mimeType=mime_type):
|
|
119
|
+
decoded_data = base64.b64decode(data)
|
|
120
|
+
content = BinaryContent(data=decoded_data, media_type=mime_type)
|
|
121
|
+
contents.append(content)
|
|
122
|
+
case BlobResourceContents(blob=blob):
|
|
123
|
+
decoded_data = base64.b64decode(blob)
|
|
124
|
+
mime = "application/octet-stream"
|
|
125
|
+
content = BinaryContent(data=decoded_data, media_type=mime)
|
|
126
|
+
contents.append(content)
|
|
127
|
+
case ResourceLink(uri=uri, mimeType=mime_type):
|
|
128
|
+
if client:
|
|
129
|
+
try:
|
|
130
|
+
res = await client.read_resource(uri)
|
|
131
|
+
nested = await from_mcp_content(res, client)
|
|
132
|
+
contents.extend(nested)
|
|
133
|
+
continue
|
|
134
|
+
except Exception: # noqa: BLE001
|
|
135
|
+
# Fallback to URL if reading fails
|
|
136
|
+
logger.warning("Failed to read resource", uri=uri)
|
|
137
|
+
# Convert to appropriate URL type based on MIME type
|
|
138
|
+
contents.append(_url_from_mime_type(str(uri), mime_type))
|
|
139
|
+
# mypy doesnt understand exhaustivness check for "nested typing", so we nest match-case
|
|
140
|
+
case EmbeddedResource(resource=resource):
|
|
141
|
+
match resource:
|
|
142
|
+
case TextResourceContents(text=text):
|
|
143
|
+
contents.append(text)
|
|
144
|
+
case BlobResourceContents() as blob_resource:
|
|
145
|
+
contents.append(f"[Binary data: {blob_resource.mimeType}]")
|
|
146
|
+
case _ as unreachable:
|
|
147
|
+
assert_never(unreachable) # ty: ignore
|
|
148
|
+
case _ as unreachable:
|
|
149
|
+
assert_never(unreachable)
|
|
150
|
+
return contents
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def content_block_as_text(content: ContentBlock) -> str:
|
|
154
|
+
match content:
|
|
155
|
+
case TextContent(text=text):
|
|
156
|
+
return text
|
|
157
|
+
case EmbeddedResource(resource=resource):
|
|
158
|
+
match resource:
|
|
159
|
+
case TextResourceContents() as text_contents:
|
|
160
|
+
return text_contents.text
|
|
161
|
+
case BlobResourceContents() as blob_contents:
|
|
162
|
+
return f"[Resource: {blob_contents.uri}]"
|
|
163
|
+
case _ as unreachable:
|
|
164
|
+
assert_never(unreachable) # ty: ignore
|
|
165
|
+
case ResourceLink(uri=uri, description=desc):
|
|
166
|
+
return f"[Resource Link: {uri}] - {desc}" if desc else f"[Resource Link: {uri}]"
|
|
167
|
+
case ImageContent(mimeType=mime_type):
|
|
168
|
+
return f"[Image: {mime_type}]"
|
|
169
|
+
case AudioContent(mimeType=mime_type):
|
|
170
|
+
return f"[Audio: {mime_type}]"
|
|
171
|
+
case _ as unreachable:
|
|
172
|
+
assert_never(unreachable)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Helper functions for MCP server client operations.
|
|
2
|
+
|
|
3
|
+
This module contains stateless utility functions that support MCP tool conversion
|
|
4
|
+
and content handling for PydanticAI integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from agentpool.log import get_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from mcp.types import ContentBlock, Tool as MCPTool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def mcp_tool_to_fn_schema(tool: MCPTool) -> dict[str, Any]:
|
|
22
|
+
"""Convert MCP tool to OpenAI function schema format."""
|
|
23
|
+
return {
|
|
24
|
+
"name": tool.name,
|
|
25
|
+
"description": tool.description or "",
|
|
26
|
+
"parameters": tool.inputSchema or {"type": "object", "properties": {}},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def extract_text_content(mcp_content: list[ContentBlock]) -> str:
|
|
31
|
+
"""Extract text content from MCP content blocks.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
mcp_content: List of MCP content blocks
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
First available text content or fallback string
|
|
38
|
+
"""
|
|
39
|
+
from mcp.types import TextContent
|
|
40
|
+
|
|
41
|
+
for block in mcp_content:
|
|
42
|
+
match block:
|
|
43
|
+
case TextContent(text=text):
|
|
44
|
+
return text
|
|
45
|
+
|
|
46
|
+
# Fallback: stringify the content
|
|
47
|
+
return str(mcp_content[0]) if mcp_content else "Tool executed successfully"
|