camel-ai 0.2.59__py3-none-any.whl → 0.2.82__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 camel-ai might be problematic. Click here for more details.
- camel/__init__.py +3 -3
- camel/agents/__init__.py +2 -2
- camel/agents/_types.py +9 -4
- camel/agents/_utils.py +40 -2
- camel/agents/base.py +2 -2
- camel/agents/chat_agent.py +5012 -902
- camel/agents/critic_agent.py +2 -2
- camel/agents/deductive_reasoner_agent.py +56 -56
- camel/agents/embodied_agent.py +2 -2
- camel/agents/knowledge_graph_agent.py +20 -20
- camel/agents/mcp_agent.py +39 -36
- camel/agents/multi_hop_generator_agent.py +3 -3
- camel/agents/programmed_agent_instruction.py +2 -2
- camel/agents/repo_agent.py +4 -3
- camel/agents/role_assignment_agent.py +2 -2
- camel/agents/search_agent.py +2 -2
- camel/agents/task_agent.py +2 -2
- camel/agents/tool_agents/__init__.py +2 -2
- camel/agents/tool_agents/base.py +2 -2
- camel/agents/tool_agents/hugging_face_tool_agent.py +3 -3
- camel/benchmarks/__init__.py +2 -2
- camel/benchmarks/apibank.py +5 -5
- camel/benchmarks/apibench.py +2 -2
- camel/benchmarks/base.py +2 -2
- camel/benchmarks/browsecomp.py +44 -33
- camel/benchmarks/gaia.py +17 -13
- camel/benchmarks/mock_website/README.md +94 -0
- camel/benchmarks/mock_website/mock_web.py +299 -0
- camel/benchmarks/mock_website/requirements.txt +3 -0
- camel/benchmarks/mock_website/shopping_mall/app.py +465 -0
- camel/benchmarks/mock_website/task.json +104 -0
- camel/benchmarks/nexus.py +3 -3
- camel/benchmarks/ragbench.py +2 -2
- camel/bots/__init__.py +2 -2
- camel/bots/discord/__init__.py +2 -2
- camel/bots/discord/discord_app.py +2 -2
- camel/bots/discord/discord_installation.py +2 -2
- camel/bots/discord/discord_store.py +3 -3
- camel/bots/slack/__init__.py +2 -2
- camel/bots/slack/models.py +4 -4
- camel/bots/slack/slack_app.py +2 -2
- camel/bots/telegram_bot.py +2 -2
- camel/configs/__init__.py +26 -2
- camel/configs/aihubmix_config.py +90 -0
- camel/configs/aiml_config.py +2 -2
- camel/configs/amd_config.py +70 -0
- camel/configs/anthropic_config.py +8 -7
- camel/configs/base_config.py +2 -2
- camel/configs/bedrock_config.py +5 -3
- camel/configs/cerebras_config.py +98 -0
- camel/configs/cohere_config.py +3 -3
- camel/configs/cometapi_config.py +106 -0
- camel/configs/crynux_config.py +94 -0
- camel/configs/deepseek_config.py +9 -8
- camel/configs/gemini_config.py +6 -4
- camel/configs/groq_config.py +6 -4
- camel/configs/internlm_config.py +6 -4
- camel/configs/litellm_config.py +2 -2
- camel/configs/lmstudio_config.py +6 -4
- camel/configs/minimax_config.py +95 -0
- camel/configs/mistral_config.py +3 -3
- camel/configs/modelscope_config.py +5 -3
- camel/configs/moonshot_config.py +2 -2
- camel/configs/nebius_config.py +105 -0
- camel/configs/netmind_config.py +2 -2
- camel/configs/novita_config.py +2 -2
- camel/configs/nvidia_config.py +2 -2
- camel/configs/ollama_config.py +2 -2
- camel/configs/openai_config.py +8 -3
- camel/configs/openrouter_config.py +6 -4
- camel/configs/ppio_config.py +2 -2
- camel/configs/qianfan_config.py +85 -0
- camel/configs/qwen_config.py +2 -2
- camel/configs/reka_config.py +3 -3
- camel/configs/samba_config.py +8 -6
- camel/configs/sglang_config.py +2 -2
- camel/configs/siliconflow_config.py +2 -2
- camel/configs/togetherai_config.py +2 -2
- camel/configs/vllm_config.py +4 -2
- camel/configs/watsonx_config.py +2 -2
- camel/configs/yi_config.py +6 -4
- camel/configs/zhipuai_config.py +6 -4
- camel/{data_collector → data_collectors}/__init__.py +2 -2
- camel/{data_collector → data_collectors}/alpaca_collector.py +19 -10
- camel/{data_collector → data_collectors}/base.py +2 -2
- camel/{data_collector → data_collectors}/sharegpt_collector.py +3 -3
- camel/datagen/__init__.py +2 -2
- camel/datagen/cot_datagen.py +32 -37
- camel/datagen/evol_instruct/__init__.py +2 -2
- camel/datagen/evol_instruct/evol_instruct.py +2 -2
- camel/datagen/evol_instruct/scorer.py +24 -25
- camel/datagen/evol_instruct/templates.py +48 -48
- camel/datagen/self_improving_cot.py +5 -5
- camel/datagen/self_instruct/__init__.py +2 -2
- camel/datagen/self_instruct/filter/__init__.py +2 -2
- camel/datagen/self_instruct/filter/filter_function.py +2 -2
- camel/datagen/self_instruct/filter/filter_registry.py +2 -2
- camel/datagen/self_instruct/filter/instruction_filter.py +2 -2
- camel/datagen/self_instruct/self_instruct.py +2 -2
- camel/datagen/self_instruct/templates.py +47 -47
- camel/datagen/source2synth/__init__.py +2 -2
- camel/datagen/source2synth/data_processor.py +2 -2
- camel/datagen/source2synth/models.py +2 -2
- camel/datagen/source2synth/user_data_processor_config.py +2 -2
- camel/datahubs/__init__.py +2 -2
- camel/datahubs/base.py +2 -2
- camel/datahubs/huggingface.py +2 -2
- camel/datahubs/models.py +2 -2
- camel/datasets/__init__.py +2 -2
- camel/datasets/base_generator.py +41 -12
- camel/datasets/few_shot_generator.py +18 -18
- camel/datasets/models.py +3 -3
- camel/datasets/self_instruct_generator.py +2 -2
- camel/datasets/static_dataset.py +152 -2
- camel/embeddings/__init__.py +2 -2
- camel/embeddings/azure_embedding.py +2 -2
- camel/embeddings/base.py +2 -2
- camel/embeddings/gemini_embedding.py +2 -2
- camel/embeddings/jina_embedding.py +10 -3
- camel/embeddings/mistral_embedding.py +2 -2
- camel/embeddings/openai_compatible_embedding.py +2 -2
- camel/embeddings/openai_embedding.py +2 -2
- camel/embeddings/sentence_transformers_embeddings.py +4 -4
- camel/embeddings/together_embedding.py +2 -2
- camel/embeddings/vlm_embedding.py +11 -4
- camel/environments/__init__.py +14 -2
- camel/environments/models.py +2 -2
- camel/environments/multi_step.py +2 -2
- camel/environments/rlcards_env.py +860 -0
- camel/environments/single_step.py +30 -5
- camel/environments/tic_tac_toe.py +3 -3
- camel/extractors/__init__.py +2 -2
- camel/extractors/base.py +2 -2
- camel/extractors/python_strategies.py +2 -2
- camel/generators.py +2 -2
- camel/human.py +2 -2
- camel/interpreters/__init__.py +4 -2
- camel/interpreters/base.py +16 -3
- camel/interpreters/docker/Dockerfile +53 -7
- camel/interpreters/docker_interpreter.py +70 -11
- camel/interpreters/e2b_interpreter.py +59 -11
- camel/interpreters/internal_python_interpreter.py +81 -4
- camel/interpreters/interpreter_error.py +2 -2
- camel/interpreters/ipython_interpreter.py +23 -5
- camel/interpreters/microsandbox_interpreter.py +395 -0
- camel/interpreters/subprocess_interpreter.py +36 -4
- camel/loaders/__init__.py +17 -5
- camel/loaders/apify_reader.py +2 -2
- camel/loaders/base_io.py +2 -2
- camel/loaders/base_loader.py +85 -0
- camel/loaders/chunkr_reader.py +128 -93
- camel/loaders/crawl4ai_reader.py +2 -2
- camel/loaders/firecrawl_reader.py +6 -6
- camel/loaders/jina_url_reader.py +2 -2
- camel/loaders/markitdown.py +2 -2
- camel/loaders/mineru_extractor.py +2 -2
- camel/loaders/mistral_reader.py +148 -0
- camel/loaders/scrapegraph_reader.py +2 -2
- camel/loaders/unstructured_io.py +2 -2
- camel/logger.py +5 -5
- camel/memories/__init__.py +2 -2
- camel/memories/agent_memories.py +86 -3
- camel/memories/base.py +36 -2
- camel/memories/blocks/__init__.py +2 -2
- camel/memories/blocks/chat_history_block.py +126 -9
- camel/memories/blocks/vectordb_block.py +10 -3
- camel/memories/context_creators/__init__.py +2 -2
- camel/memories/context_creators/score_based.py +31 -239
- camel/memories/records.py +98 -13
- camel/messages/__init__.py +2 -2
- camel/messages/base.py +193 -46
- camel/messages/conversion/__init__.py +2 -2
- camel/messages/conversion/alpaca.py +2 -2
- camel/messages/conversion/conversation_models.py +2 -2
- camel/messages/conversion/sharegpt/__init__.py +2 -2
- camel/messages/conversion/sharegpt/function_call_formatter.py +2 -2
- camel/messages/conversion/sharegpt/hermes/__init__.py +2 -2
- camel/messages/conversion/sharegpt/hermes/hermes_function_formatter.py +2 -2
- camel/messages/func_message.py +54 -17
- camel/models/__init__.py +18 -2
- camel/models/_utils.py +3 -3
- camel/models/aihubmix_model.py +83 -0
- camel/models/aiml_model.py +11 -18
- camel/models/amd_model.py +101 -0
- camel/models/anthropic_model.py +127 -20
- camel/models/aws_bedrock_model.py +12 -35
- camel/models/azure_openai_model.py +263 -63
- camel/models/base_audio_model.py +5 -3
- camel/models/base_model.py +195 -26
- camel/models/cerebras_model.py +83 -0
- camel/models/cohere_model.py +81 -21
- camel/models/cometapi_model.py +83 -0
- camel/models/crynux_model.py +87 -0
- camel/models/deepseek_model.py +61 -59
- camel/models/fish_audio_model.py +8 -2
- camel/models/gemini_model.py +439 -30
- camel/models/groq_model.py +11 -19
- camel/models/internlm_model.py +11 -18
- camel/models/litellm_model.py +94 -34
- camel/models/lmstudio_model.py +17 -20
- camel/models/minimax_model.py +83 -0
- camel/models/mistral_model.py +84 -19
- camel/models/model_factory.py +49 -6
- camel/models/model_manager.py +33 -11
- camel/models/modelscope_model.py +13 -193
- camel/models/moonshot_model.py +195 -21
- camel/models/nebius_model.py +83 -0
- camel/models/nemotron_model.py +19 -9
- camel/models/netmind_model.py +11 -18
- camel/models/novita_model.py +11 -18
- camel/models/nvidia_model.py +11 -18
- camel/models/ollama_model.py +14 -21
- camel/models/openai_audio_models.py +2 -2
- camel/models/openai_compatible_model.py +234 -27
- camel/models/openai_model.py +255 -39
- camel/models/openrouter_model.py +11 -19
- camel/models/ppio_model.py +11 -18
- camel/models/qianfan_model.py +89 -0
- camel/models/qwen_model.py +13 -193
- camel/models/reka_model.py +90 -21
- camel/models/reward/__init__.py +2 -2
- camel/models/reward/base_reward_model.py +2 -2
- camel/models/reward/evaluator.py +2 -2
- camel/models/reward/nemotron_model.py +2 -2
- camel/models/reward/skywork_model.py +2 -2
- camel/models/samba_model.py +117 -49
- camel/models/sglang_model.py +162 -42
- camel/models/siliconflow_model.py +12 -35
- camel/models/stub_model.py +10 -7
- camel/models/togetherai_model.py +11 -18
- camel/models/vllm_model.py +10 -18
- camel/models/volcano_model.py +16 -20
- camel/models/watsonx_model.py +69 -19
- camel/models/yi_model.py +11 -18
- camel/models/zhipuai_model.py +70 -18
- camel/parsers/__init__.py +18 -0
- camel/parsers/mcp_tool_call_parser.py +176 -0
- camel/personas/__init__.py +2 -2
- camel/personas/persona.py +2 -2
- camel/personas/persona_hub.py +2 -2
- camel/prompts/__init__.py +2 -2
- camel/prompts/ai_society.py +2 -2
- camel/prompts/base.py +2 -2
- camel/prompts/code.py +2 -2
- camel/prompts/evaluation.py +2 -2
- camel/prompts/generate_text_embedding_data.py +2 -2
- camel/prompts/image_craft.py +2 -2
- camel/prompts/misalignment.py +2 -2
- camel/prompts/multi_condition_image_craft.py +2 -2
- camel/prompts/object_recognition.py +2 -2
- camel/prompts/persona_hub.py +3 -3
- camel/prompts/prompt_templates.py +2 -2
- camel/prompts/role_description_prompt_template.py +2 -2
- camel/prompts/solution_extraction.py +8 -8
- camel/prompts/task_prompt_template.py +2 -2
- camel/prompts/translation.py +2 -2
- camel/prompts/video_description_prompt.py +3 -3
- camel/responses/__init__.py +2 -2
- camel/responses/agent_responses.py +2 -2
- camel/retrievers/__init__.py +2 -2
- camel/retrievers/auto_retriever.py +23 -3
- camel/retrievers/base.py +2 -2
- camel/retrievers/bm25_retriever.py +3 -4
- camel/retrievers/cohere_rerank_retriever.py +2 -2
- camel/retrievers/hybrid_retrival.py +4 -4
- camel/retrievers/vector_retriever.py +2 -2
- camel/runtimes/Dockerfile.multi-toolkit +90 -0
- camel/{runtime → runtimes}/__init__.py +2 -2
- camel/runtimes/api.py +153 -0
- camel/{runtime → runtimes}/base.py +2 -2
- camel/{runtime → runtimes}/configs.py +13 -13
- camel/{runtime → runtimes}/daytona_runtime.py +18 -19
- camel/{runtime → runtimes}/docker_runtime.py +13 -13
- camel/{runtime → runtimes}/llm_guard_runtime.py +28 -28
- camel/{runtime → runtimes}/remote_http_runtime.py +12 -12
- camel/{runtime → runtimes}/ubuntu_docker_runtime.py +3 -3
- camel/{runtime → runtimes}/utils/__init__.py +2 -2
- camel/{runtime → runtimes}/utils/function_risk_toolkit.py +2 -2
- camel/{runtime → runtimes}/utils/ignore_risk_toolkit.py +2 -2
- camel/schemas/__init__.py +2 -2
- camel/schemas/base.py +2 -2
- camel/schemas/openai_converter.py +3 -3
- camel/schemas/outlines_converter.py +2 -2
- camel/services/agent_openapi_server.py +380 -0
- camel/societies/__init__.py +4 -2
- camel/societies/babyagi_playing.py +2 -2
- camel/societies/role_playing.py +201 -80
- camel/societies/workforce/__init__.py +10 -3
- camel/societies/workforce/base.py +9 -5
- camel/societies/workforce/events.py +143 -0
- camel/societies/workforce/prompts.py +258 -33
- camel/societies/workforce/role_playing_worker.py +95 -30
- camel/societies/workforce/single_agent_worker.py +659 -30
- camel/societies/workforce/structured_output_handler.py +512 -0
- camel/societies/workforce/task_channel.py +182 -38
- camel/societies/workforce/utils.py +784 -18
- camel/societies/workforce/worker.py +96 -28
- camel/societies/workforce/workflow_memory_manager.py +1746 -0
- camel/societies/workforce/workforce.py +5730 -366
- camel/societies/workforce/workforce_callback.py +103 -0
- camel/societies/workforce/workforce_logger.py +647 -0
- camel/societies/workforce/workforce_metrics.py +33 -0
- camel/storages/__init__.py +10 -2
- camel/storages/graph_storages/__init__.py +2 -2
- camel/storages/graph_storages/base.py +2 -2
- camel/storages/graph_storages/graph_element.py +2 -2
- camel/storages/graph_storages/nebula_graph.py +4 -4
- camel/storages/graph_storages/neo4j_graph.py +7 -7
- camel/storages/key_value_storages/__init__.py +2 -2
- camel/storages/key_value_storages/base.py +2 -2
- camel/storages/key_value_storages/in_memory.py +2 -2
- camel/storages/key_value_storages/json.py +17 -4
- camel/storages/key_value_storages/mem0_cloud.py +50 -49
- camel/storages/key_value_storages/redis.py +2 -2
- camel/storages/object_storages/__init__.py +2 -2
- camel/storages/object_storages/amazon_s3.py +2 -2
- camel/storages/object_storages/azure_blob.py +2 -2
- camel/storages/object_storages/base.py +2 -2
- camel/storages/object_storages/google_cloud.py +3 -3
- camel/storages/vectordb_storages/__init__.py +12 -2
- camel/storages/vectordb_storages/base.py +2 -2
- camel/storages/vectordb_storages/chroma.py +731 -0
- camel/storages/vectordb_storages/faiss.py +712 -0
- camel/storages/vectordb_storages/milvus.py +2 -2
- camel/storages/vectordb_storages/oceanbase.py +16 -17
- camel/storages/vectordb_storages/pgvector.py +349 -0
- camel/storages/vectordb_storages/qdrant.py +6 -6
- camel/storages/vectordb_storages/surreal.py +372 -0
- camel/storages/vectordb_storages/tidb.py +11 -8
- camel/storages/vectordb_storages/weaviate.py +714 -0
- camel/tasks/__init__.py +2 -2
- camel/tasks/task.py +366 -27
- camel/tasks/task_prompt.py +3 -3
- camel/terminators/__init__.py +2 -2
- camel/terminators/base.py +2 -2
- camel/terminators/response_terminator.py +2 -2
- camel/terminators/token_limit_terminator.py +2 -2
- camel/toolkits/__init__.py +58 -10
- camel/toolkits/aci_toolkit.py +66 -21
- camel/toolkits/arxiv_toolkit.py +8 -8
- camel/toolkits/ask_news_toolkit.py +2 -2
- camel/toolkits/async_browser_toolkit.py +174 -575
- camel/toolkits/audio_analysis_toolkit.py +3 -3
- camel/toolkits/base.py +65 -7
- camel/toolkits/bohrium_toolkit.py +318 -0
- camel/toolkits/browser_toolkit.py +306 -566
- camel/toolkits/browser_toolkit_commons.py +568 -0
- camel/toolkits/code_execution.py +67 -11
- camel/toolkits/context_summarizer_toolkit.py +684 -0
- camel/toolkits/craw4ai_toolkit.py +93 -0
- camel/toolkits/dappier_toolkit.py +12 -8
- camel/toolkits/data_commons_toolkit.py +2 -2
- camel/toolkits/dingtalk.py +1135 -0
- camel/toolkits/earth_science_toolkit.py +5367 -0
- camel/toolkits/edgeone_pages_mcp_toolkit.py +49 -0
- camel/toolkits/excel_toolkit.py +910 -70
- camel/toolkits/file_toolkit.py +1402 -0
- camel/toolkits/function_tool.py +128 -20
- camel/toolkits/github_toolkit.py +148 -43
- camel/toolkits/gmail_toolkit.py +1839 -0
- camel/toolkits/google_calendar_toolkit.py +40 -6
- camel/toolkits/google_drive_mcp_toolkit.py +54 -0
- camel/toolkits/google_maps_toolkit.py +2 -2
- camel/toolkits/google_scholar_toolkit.py +2 -2
- camel/toolkits/human_toolkit.py +36 -12
- camel/toolkits/hybrid_browser_toolkit/__init__.py +18 -0
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +185 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +246 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +1973 -0
- camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +4589 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package.json +33 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-scripts.js +125 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +1929 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +233 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +589 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/index.ts +7 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +129 -0
- camel/toolkits/hybrid_browser_toolkit/ts/tsconfig.json +27 -0
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +319 -0
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +1037 -0
- camel/toolkits/hybrid_browser_toolkit_py/__init__.py +17 -0
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +575 -0
- camel/toolkits/hybrid_browser_toolkit_py/agent.py +311 -0
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +787 -0
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +490 -0
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2390 -0
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +233 -0
- camel/toolkits/hybrid_browser_toolkit_py/stealth_script.js +0 -0
- camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +1043 -0
- camel/toolkits/image_analysis_toolkit.py +3 -3
- camel/toolkits/image_generation_toolkit.py +390 -0
- camel/toolkits/jina_reranker_toolkit.py +195 -79
- camel/toolkits/klavis_toolkit.py +7 -3
- camel/toolkits/linkedin_toolkit.py +2 -2
- camel/toolkits/markitdown_toolkit.py +104 -0
- camel/toolkits/math_toolkit.py +66 -12
- camel/toolkits/mcp_toolkit.py +841 -600
- camel/toolkits/memory_toolkit.py +7 -3
- camel/toolkits/meshy_toolkit.py +2 -2
- camel/toolkits/message_agent_toolkit.py +608 -0
- camel/toolkits/message_integration.py +724 -0
- camel/toolkits/mineru_toolkit.py +2 -2
- camel/toolkits/minimax_mcp_toolkit.py +195 -0
- camel/toolkits/networkx_toolkit.py +2 -2
- camel/toolkits/note_taking_toolkit.py +277 -0
- camel/toolkits/notion_mcp_toolkit.py +224 -0
- camel/toolkits/notion_toolkit.py +2 -2
- camel/toolkits/open_api_specs/biztoc/__init__.py +2 -2
- camel/toolkits/open_api_specs/biztoc/ai-plugin.json +1 -1
- camel/toolkits/open_api_specs/coursera/__init__.py +2 -2
- camel/toolkits/open_api_specs/create_qr_code/__init__.py +2 -2
- camel/toolkits/open_api_specs/klarna/__init__.py +2 -2
- camel/toolkits/open_api_specs/nasa_apod/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/ai-plugin.json +1 -1
- camel/toolkits/open_api_specs/outschool/openapi.yaml +1 -1
- camel/toolkits/open_api_specs/outschool/paths/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/paths/get_classes.py +2 -2
- camel/toolkits/open_api_specs/outschool/paths/search_teachers.py +2 -2
- camel/toolkits/open_api_specs/security_config.py +2 -2
- camel/toolkits/open_api_specs/speak/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/ai-plugin.json +1 -1
- camel/toolkits/open_api_specs/web_scraper/paths/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/paths/scraper.py +2 -2
- camel/toolkits/open_api_toolkit.py +2 -2
- camel/toolkits/openbb_toolkit.py +7 -3
- camel/toolkits/origene_mcp_toolkit.py +56 -0
- camel/toolkits/page_script.js +86 -74
- camel/toolkits/playwright_mcp_toolkit.py +27 -32
- camel/toolkits/pptx_toolkit.py +790 -0
- camel/toolkits/pubmed_toolkit.py +2 -2
- camel/toolkits/pulse_mcp_search_toolkit.py +2 -2
- camel/toolkits/pyautogui_toolkit.py +2 -2
- camel/toolkits/reddit_toolkit.py +2 -2
- camel/toolkits/resend_toolkit.py +168 -0
- camel/toolkits/retrieval_toolkit.py +2 -2
- camel/toolkits/screenshot_toolkit.py +213 -0
- camel/toolkits/search_toolkit.py +539 -146
- camel/toolkits/searxng_toolkit.py +2 -2
- camel/toolkits/semantic_scholar_toolkit.py +2 -2
- camel/toolkits/slack_toolkit.py +108 -58
- camel/toolkits/sql_toolkit.py +712 -0
- camel/toolkits/stripe_toolkit.py +2 -2
- camel/toolkits/sympy_toolkit.py +3 -3
- camel/toolkits/task_planning_toolkit.py +134 -0
- camel/toolkits/terminal_toolkit/__init__.py +18 -0
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +1070 -0
- camel/toolkits/terminal_toolkit/utils.py +532 -0
- camel/toolkits/thinking_toolkit.py +3 -3
- camel/toolkits/twitter_toolkit.py +8 -3
- camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
- camel/toolkits/video_analysis_toolkit.py +112 -29
- camel/toolkits/video_download_toolkit.py +22 -16
- camel/toolkits/weather_toolkit.py +2 -2
- camel/toolkits/web_deploy_toolkit.py +1219 -0
- camel/toolkits/wechat_official_toolkit.py +483 -0
- camel/toolkits/whatsapp_toolkit.py +2 -2
- camel/toolkits/wolfram_alpha_toolkit.py +53 -25
- camel/toolkits/zapier_toolkit.py +7 -3
- camel/types/__init__.py +4 -4
- camel/types/agents/__init__.py +2 -2
- camel/types/agents/tool_calling_record.py +6 -3
- camel/types/enums.py +454 -35
- camel/types/mcp_registries.py +2 -2
- camel/types/openai_types.py +4 -4
- camel/types/unified_model_type.py +43 -6
- camel/utils/__init__.py +20 -2
- camel/utils/async_func.py +2 -2
- camel/utils/chunker/__init__.py +2 -2
- camel/utils/chunker/base.py +2 -2
- camel/utils/chunker/code_chunker.py +2 -2
- camel/utils/chunker/uio_chunker.py +2 -2
- camel/utils/commons.py +65 -7
- camel/utils/constants.py +5 -2
- camel/utils/context_utils.py +1134 -0
- camel/utils/deduplication.py +2 -2
- camel/utils/filename.py +2 -2
- camel/utils/langfuse.py +258 -0
- camel/utils/mcp.py +140 -6
- camel/utils/mcp_client.py +1056 -0
- camel/utils/message_summarizer.py +148 -0
- camel/utils/response_format.py +2 -2
- camel/utils/token_counting.py +45 -22
- camel/utils/tool_result.py +44 -0
- camel/verifiers/__init__.py +2 -2
- camel/verifiers/base.py +2 -2
- camel/verifiers/math_verifier.py +2 -2
- camel/verifiers/models.py +2 -2
- camel/verifiers/physics_verifier.py +2 -2
- camel/verifiers/python_verifier.py +2 -2
- {camel_ai-0.2.59.dist-info → camel_ai-0.2.82.dist-info}/METADATA +349 -108
- camel_ai-0.2.82.dist-info/RECORD +507 -0
- {camel_ai-0.2.59.dist-info → camel_ai-0.2.82.dist-info}/WHEEL +1 -1
- {camel_ai-0.2.59.dist-info → camel_ai-0.2.82.dist-info}/licenses/LICENSE +1 -1
- camel/loaders/pandas_reader.py +0 -368
- camel/runtime/api.py +0 -97
- camel/toolkits/dalle_toolkit.py +0 -171
- camel/toolkits/file_write_toolkit.py +0 -395
- camel/toolkits/openai_agent_toolkit.py +0 -135
- camel/toolkits/terminal_toolkit.py +0 -1037
- camel_ai-0.2.59.dist-info/RECORD +0 -410
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
# ========= Copyright 2023-2025 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2025 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
"""
|
|
15
|
+
Unified MCP Client
|
|
16
|
+
|
|
17
|
+
This module provides a unified interface for connecting to MCP servers
|
|
18
|
+
using different transport protocols (stdio, sse, streamable-http, websocket).
|
|
19
|
+
The client can automatically detect the transport type based on configuration.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import inspect
|
|
23
|
+
from contextlib import asynccontextmanager
|
|
24
|
+
from datetime import timedelta
|
|
25
|
+
from enum import Enum
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import (
|
|
28
|
+
Any,
|
|
29
|
+
Callable,
|
|
30
|
+
Dict,
|
|
31
|
+
List,
|
|
32
|
+
Optional,
|
|
33
|
+
Set,
|
|
34
|
+
Union,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
import httpx
|
|
38
|
+
import mcp.types as types
|
|
39
|
+
from pydantic import BaseModel, model_validator
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
from mcp.shared._httpx_utils import create_mcp_http_client
|
|
43
|
+
except ImportError:
|
|
44
|
+
|
|
45
|
+
def create_mcp_http_client(
|
|
46
|
+
headers: Optional[Dict[str, str]] = None,
|
|
47
|
+
timeout: Optional[httpx.Timeout] = None,
|
|
48
|
+
auth: Optional[httpx.Auth] = None,
|
|
49
|
+
) -> httpx.AsyncClient:
|
|
50
|
+
"""Fallback implementation if not available."""
|
|
51
|
+
kwargs: Dict[str, Any] = {"follow_redirects": True}
|
|
52
|
+
|
|
53
|
+
if timeout is None:
|
|
54
|
+
kwargs["timeout"] = httpx.Timeout(30.0)
|
|
55
|
+
else:
|
|
56
|
+
kwargs["timeout"] = timeout
|
|
57
|
+
|
|
58
|
+
if headers is not None:
|
|
59
|
+
kwargs["headers"] = headers
|
|
60
|
+
|
|
61
|
+
if auth is not None:
|
|
62
|
+
kwargs["auth"] = auth
|
|
63
|
+
|
|
64
|
+
return httpx.AsyncClient(**kwargs)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
from mcp import ClientSession
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TransportType(str, Enum):
|
|
71
|
+
r"""Supported transport types."""
|
|
72
|
+
|
|
73
|
+
STDIO = "stdio"
|
|
74
|
+
SSE = "sse"
|
|
75
|
+
STREAMABLE_HTTP = "streamable_http"
|
|
76
|
+
WEBSOCKET = "websocket"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ServerConfig(BaseModel):
|
|
80
|
+
r"""Unified server configuration that automatically detects transport type.
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
# STDIO server
|
|
84
|
+
config = ServerConfig(
|
|
85
|
+
command="npx",
|
|
86
|
+
args=["-y", "@modelcontextprotocol/server-filesystem", "/path"]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# HTTP/SSE server
|
|
90
|
+
config = ServerConfig(
|
|
91
|
+
url="https://api.example.com/mcp",
|
|
92
|
+
headers={"Authorization": "Bearer token"}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# WebSocket server
|
|
96
|
+
config = ServerConfig(url="ws://localhost:8080/mcp")
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# STDIO configuration
|
|
100
|
+
command: Optional[str] = None
|
|
101
|
+
args: Optional[List[str]] = None
|
|
102
|
+
env: Optional[Dict[str, str]] = None
|
|
103
|
+
cwd: Optional[Union[str, Path]] = None
|
|
104
|
+
|
|
105
|
+
# HTTP/WebSocket configuration
|
|
106
|
+
url: Optional[str] = None
|
|
107
|
+
headers: Optional[Dict[str, Any]] = None
|
|
108
|
+
|
|
109
|
+
# Common configuration
|
|
110
|
+
timeout: float = 30.0
|
|
111
|
+
encoding: str = "utf-8"
|
|
112
|
+
|
|
113
|
+
# Advanced options
|
|
114
|
+
sse_read_timeout: float = 300.0 # 5 minutes
|
|
115
|
+
terminate_on_close: bool = True
|
|
116
|
+
|
|
117
|
+
# New transport type parameter
|
|
118
|
+
type: Optional[str] = None
|
|
119
|
+
|
|
120
|
+
# Legacy parameter for backward compatibility
|
|
121
|
+
prefer_sse: bool = False
|
|
122
|
+
|
|
123
|
+
@model_validator(mode='after')
|
|
124
|
+
def validate_config(self):
|
|
125
|
+
r"""Validate that either command or url is provided."""
|
|
126
|
+
if not self.command and not self.url:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"Either 'command' (for stdio) or 'url' "
|
|
129
|
+
"(for http/websocket) must be provided"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if self.command and self.url:
|
|
133
|
+
raise ValueError("Cannot specify both 'command' and 'url'")
|
|
134
|
+
|
|
135
|
+
# Validate type if provided
|
|
136
|
+
if self.type is not None:
|
|
137
|
+
valid_types = {"stdio", "sse", "streamable_http", "websocket"}
|
|
138
|
+
if self.type not in valid_types:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Invalid type: "
|
|
141
|
+
f"'{self.type}'. "
|
|
142
|
+
f"Valid options: {valid_types}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Issue deprecation warning if prefer_sse is used
|
|
146
|
+
if self.prefer_sse and self.type is None:
|
|
147
|
+
import warnings
|
|
148
|
+
|
|
149
|
+
warnings.warn(
|
|
150
|
+
"The 'prefer_sse' parameter is deprecated. "
|
|
151
|
+
"Use 'type=\"sse\"' instead.",
|
|
152
|
+
DeprecationWarning,
|
|
153
|
+
stacklevel=2,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def transport_type(self) -> TransportType:
|
|
160
|
+
r"""Automatically detect transport type based on configuration."""
|
|
161
|
+
# Use explicit transport type if provided
|
|
162
|
+
if self.type is not None:
|
|
163
|
+
transport_map = {
|
|
164
|
+
"stdio": TransportType.STDIO,
|
|
165
|
+
"sse": TransportType.SSE,
|
|
166
|
+
"streamable_http": TransportType.STREAMABLE_HTTP,
|
|
167
|
+
"websocket": TransportType.WEBSOCKET,
|
|
168
|
+
}
|
|
169
|
+
return transport_map[self.type]
|
|
170
|
+
|
|
171
|
+
# If no type is provided, fall back to automatic detection
|
|
172
|
+
if self.command:
|
|
173
|
+
return TransportType.STDIO
|
|
174
|
+
elif self.url:
|
|
175
|
+
if self.url.startswith(("ws://", "wss://")):
|
|
176
|
+
return TransportType.WEBSOCKET
|
|
177
|
+
elif self.url.startswith(("http://", "https://")):
|
|
178
|
+
# Default to StreamableHTTP, unless user prefers SSE
|
|
179
|
+
if self.prefer_sse:
|
|
180
|
+
return TransportType.SSE
|
|
181
|
+
else:
|
|
182
|
+
return TransportType.STREAMABLE_HTTP
|
|
183
|
+
else:
|
|
184
|
+
raise ValueError(f"Unsupported URL scheme: {self.url}")
|
|
185
|
+
else:
|
|
186
|
+
raise ValueError("Cannot determine transport type")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class MCPClient:
|
|
190
|
+
r"""Unified MCP client that automatically detects and connects to servers
|
|
191
|
+
using the appropriate transport protocol.
|
|
192
|
+
|
|
193
|
+
This client provides a unified interface for connecting to Model Context
|
|
194
|
+
Protocol (MCP) servers using different transport protocols including STDIO,
|
|
195
|
+
HTTP/HTTPS, WebSocket, and Server-Sent Events (SSE). The client
|
|
196
|
+
automatically detects the appropriate transport type based on the
|
|
197
|
+
configuration provided.
|
|
198
|
+
|
|
199
|
+
The client should be used as an async context manager for automatic
|
|
200
|
+
connectionmanagement.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
config (Union[ServerConfig, Dict[str, Any]]): Server configuration
|
|
204
|
+
as either a :obj:`ServerConfig` object or a dictionary that will
|
|
205
|
+
be converted to a :obj:`ServerConfig`. The configuration determines
|
|
206
|
+
the transport type and connection parameters.
|
|
207
|
+
client_info (Optional[types.Implementation], optional): Client
|
|
208
|
+
implementation information to send to the server during
|
|
209
|
+
initialization. (default: :obj:`None`)
|
|
210
|
+
timeout (Optional[float], optional): Timeout for waiting for messages
|
|
211
|
+
from the server in seconds. (default: :obj:`10.0`)
|
|
212
|
+
|
|
213
|
+
Examples:
|
|
214
|
+
STDIO server:
|
|
215
|
+
|
|
216
|
+
.. code-block:: python
|
|
217
|
+
|
|
218
|
+
async with MCPClient({
|
|
219
|
+
"command": "npx",
|
|
220
|
+
"args": [
|
|
221
|
+
"-y",
|
|
222
|
+
"@modelcontextprotocol/server-filesystem",
|
|
223
|
+
"/path"
|
|
224
|
+
]
|
|
225
|
+
}) as client:
|
|
226
|
+
tools = client.get_tools()
|
|
227
|
+
result = await client.call_tool("tool_name", {"arg": "value"})
|
|
228
|
+
|
|
229
|
+
HTTP server:
|
|
230
|
+
|
|
231
|
+
.. code-block:: python
|
|
232
|
+
|
|
233
|
+
async with MCPClient({
|
|
234
|
+
"url": "https://api.example.com/mcp",
|
|
235
|
+
"headers": {"Authorization": "Bearer token"}
|
|
236
|
+
}) as client:
|
|
237
|
+
tools = client.get_tools()
|
|
238
|
+
|
|
239
|
+
WebSocket server:
|
|
240
|
+
|
|
241
|
+
.. code-block:: python
|
|
242
|
+
|
|
243
|
+
async with MCPClient({"url": "ws://localhost:8080/mcp"}) as client:
|
|
244
|
+
tools = client.get_tools()
|
|
245
|
+
|
|
246
|
+
Attributes:
|
|
247
|
+
config (ServerConfig): The server configuration object.
|
|
248
|
+
client_info (Optional[types.Implementation]): Client implementation
|
|
249
|
+
information.
|
|
250
|
+
read_timeout_seconds (timedelta): Timeout for reading from the server.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
def __init__(
|
|
254
|
+
self,
|
|
255
|
+
config: Union[ServerConfig, Dict[str, Any]],
|
|
256
|
+
client_info: Optional[types.Implementation] = None,
|
|
257
|
+
timeout: Optional[float] = 10.0,
|
|
258
|
+
):
|
|
259
|
+
# Convert dict config to ServerConfig if needed
|
|
260
|
+
if isinstance(config, dict):
|
|
261
|
+
config = ServerConfig(**config)
|
|
262
|
+
|
|
263
|
+
self.config = config
|
|
264
|
+
|
|
265
|
+
# Validate transport type early (this will raise ValueError if invalid)
|
|
266
|
+
_ = self.config.transport_type
|
|
267
|
+
|
|
268
|
+
self.client_info = client_info
|
|
269
|
+
self.read_timeout_seconds = timedelta(seconds=timeout or 10.0)
|
|
270
|
+
|
|
271
|
+
self._session: Optional[ClientSession] = None
|
|
272
|
+
self._tools: List[types.Tool] = []
|
|
273
|
+
self._connection_context = None
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def transport_type(self) -> TransportType:
|
|
277
|
+
r"""Get the detected transport type."""
|
|
278
|
+
return self.config.transport_type
|
|
279
|
+
|
|
280
|
+
async def __aenter__(self):
|
|
281
|
+
r"""Async context manager entry point.
|
|
282
|
+
|
|
283
|
+
Establishes connection to the MCP server and initializes the session.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
MCPClient: The connected client instance.
|
|
287
|
+
"""
|
|
288
|
+
await self._establish_connection()
|
|
289
|
+
return self
|
|
290
|
+
|
|
291
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
292
|
+
r"""Async context manager exit point.
|
|
293
|
+
|
|
294
|
+
Cleans up the connection and resources.
|
|
295
|
+
"""
|
|
296
|
+
await self._cleanup_connection()
|
|
297
|
+
|
|
298
|
+
async def _establish_connection(self):
|
|
299
|
+
r"""Establish connection to the MCP server."""
|
|
300
|
+
try:
|
|
301
|
+
self._connection_context = self._create_transport()
|
|
302
|
+
streams = await self._connection_context.__aenter__()
|
|
303
|
+
|
|
304
|
+
# Handle extra returns safely
|
|
305
|
+
read_stream, write_stream = streams[:2]
|
|
306
|
+
|
|
307
|
+
self._session = ClientSession(
|
|
308
|
+
read_stream=read_stream,
|
|
309
|
+
write_stream=write_stream,
|
|
310
|
+
client_info=self.client_info,
|
|
311
|
+
read_timeout_seconds=self.read_timeout_seconds,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Start the session's message processing loop
|
|
315
|
+
await self._session.__aenter__()
|
|
316
|
+
|
|
317
|
+
# Initialize the session and load tools
|
|
318
|
+
await self._session.initialize()
|
|
319
|
+
tools_response = await self._session.list_tools()
|
|
320
|
+
self._tools = tools_response.tools if tools_response else []
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
# Clean up on error
|
|
324
|
+
await self._cleanup_connection()
|
|
325
|
+
|
|
326
|
+
# Convert complex exceptions to simpler, more understandable ones
|
|
327
|
+
from camel.logger import get_logger
|
|
328
|
+
|
|
329
|
+
logger = get_logger(__name__)
|
|
330
|
+
|
|
331
|
+
error_msg = self._simplify_connection_error(e)
|
|
332
|
+
logger.error(f"MCP connection failed: {error_msg}")
|
|
333
|
+
|
|
334
|
+
# Raise a simpler exception
|
|
335
|
+
raise ConnectionError(error_msg) from e
|
|
336
|
+
|
|
337
|
+
async def _cleanup_connection(self):
|
|
338
|
+
r"""Clean up connection resources."""
|
|
339
|
+
try:
|
|
340
|
+
if self._session:
|
|
341
|
+
try:
|
|
342
|
+
await self._session.__aexit__(None, None, None)
|
|
343
|
+
except Exception:
|
|
344
|
+
pass # Ignore cleanup errors
|
|
345
|
+
finally:
|
|
346
|
+
self._session = None
|
|
347
|
+
|
|
348
|
+
if self._connection_context:
|
|
349
|
+
try:
|
|
350
|
+
await self._connection_context.__aexit__(None, None, None)
|
|
351
|
+
except Exception:
|
|
352
|
+
pass # Ignore cleanup errors
|
|
353
|
+
finally:
|
|
354
|
+
self._connection_context = None
|
|
355
|
+
|
|
356
|
+
# Add a small delay to allow subprocess cleanup on Windows
|
|
357
|
+
# This prevents "Event loop is closed" errors during shutdown
|
|
358
|
+
import asyncio
|
|
359
|
+
import sys
|
|
360
|
+
|
|
361
|
+
if sys.platform == "win32":
|
|
362
|
+
await asyncio.sleep(0.01)
|
|
363
|
+
|
|
364
|
+
finally:
|
|
365
|
+
# Ensure state is reset
|
|
366
|
+
self._tools = []
|
|
367
|
+
|
|
368
|
+
def _simplify_connection_error(self, error: Exception) -> str:
|
|
369
|
+
r"""Convert complex MCP connection errors to simple, understandable
|
|
370
|
+
messages.
|
|
371
|
+
"""
|
|
372
|
+
error_str = str(error).lower()
|
|
373
|
+
|
|
374
|
+
# Handle different types of errors
|
|
375
|
+
if "exceptiongroup" in error_str or "taskgroup" in error_str:
|
|
376
|
+
# Often happens when the command fails to start
|
|
377
|
+
if "processlookuperror" in error_str:
|
|
378
|
+
return (
|
|
379
|
+
f"Failed to start MCP server command "
|
|
380
|
+
f"'{self.config.command}'. The command may have "
|
|
381
|
+
"exited unexpectedly."
|
|
382
|
+
)
|
|
383
|
+
elif "cancelled" in error_str:
|
|
384
|
+
return (
|
|
385
|
+
"Connection to MCP server was cancelled, "
|
|
386
|
+
"likely due to timeout or server startup failure."
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
return (
|
|
390
|
+
f"MCP server process error. The server command "
|
|
391
|
+
f"'{self.config.command}' failed to start properly."
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
elif "timeout" in error_str:
|
|
395
|
+
return (
|
|
396
|
+
f"Connection timeout after {self.config.timeout}s. "
|
|
397
|
+
"The MCP server may be taking too long to respond."
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
elif "not found" in error_str or "404" in error_str:
|
|
401
|
+
command_parts = []
|
|
402
|
+
if self.config.command:
|
|
403
|
+
command_parts.append(self.config.command)
|
|
404
|
+
if self.config.args:
|
|
405
|
+
command_parts.extend(self.config.args)
|
|
406
|
+
command_str = (
|
|
407
|
+
' '.join(command_parts) if command_parts else "unknown command"
|
|
408
|
+
)
|
|
409
|
+
return (
|
|
410
|
+
f"MCP server package not found. Check if '{command_str}' "
|
|
411
|
+
"is correct."
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
elif "permission" in error_str:
|
|
415
|
+
return (
|
|
416
|
+
f"Permission denied when trying to run MCP server "
|
|
417
|
+
f"command '{self.config.command}'."
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
elif "connection" in error_str:
|
|
421
|
+
if self.config.url:
|
|
422
|
+
return f"Failed to connect to MCP server at {self.config.url}."
|
|
423
|
+
else:
|
|
424
|
+
return "Connection failed to MCP server."
|
|
425
|
+
|
|
426
|
+
else:
|
|
427
|
+
# Generic fallback
|
|
428
|
+
command_info = (
|
|
429
|
+
f"'{self.config.command}'"
|
|
430
|
+
if self.config.command
|
|
431
|
+
else f"URL: {self.config.url}"
|
|
432
|
+
)
|
|
433
|
+
return (
|
|
434
|
+
f"MCP connection failed for {command_info}. Error: "
|
|
435
|
+
f"{str(error)[:100]}{'...' if len(str(error)) > 100 else ''}"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
@asynccontextmanager
|
|
439
|
+
async def _create_transport(self):
|
|
440
|
+
"""Create the appropriate transport based on detected type."""
|
|
441
|
+
transport_type = self.config.transport_type
|
|
442
|
+
|
|
443
|
+
if transport_type == TransportType.STDIO:
|
|
444
|
+
from mcp import StdioServerParameters
|
|
445
|
+
from mcp.client.stdio import stdio_client
|
|
446
|
+
|
|
447
|
+
# Ensure command is not None for STDIO
|
|
448
|
+
if not self.config.command:
|
|
449
|
+
raise ValueError("Command is required for STDIO transport")
|
|
450
|
+
|
|
451
|
+
server_params = StdioServerParameters(
|
|
452
|
+
command=self.config.command,
|
|
453
|
+
args=self.config.args or [],
|
|
454
|
+
env=self.config.env,
|
|
455
|
+
cwd=self.config.cwd,
|
|
456
|
+
encoding=self.config.encoding,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
async with stdio_client(server_params) as (
|
|
460
|
+
read_stream,
|
|
461
|
+
write_stream,
|
|
462
|
+
):
|
|
463
|
+
yield read_stream, write_stream
|
|
464
|
+
|
|
465
|
+
elif transport_type == TransportType.SSE:
|
|
466
|
+
from mcp.client.sse import sse_client
|
|
467
|
+
|
|
468
|
+
# Ensure URL is not None for SSE
|
|
469
|
+
if not self.config.url:
|
|
470
|
+
raise ValueError("URL is required for SSE transport")
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
# Try with httpx_client_factory first (newer versions)
|
|
474
|
+
async with sse_client(
|
|
475
|
+
url=self.config.url,
|
|
476
|
+
headers=self.config.headers,
|
|
477
|
+
timeout=self.config.timeout,
|
|
478
|
+
sse_read_timeout=self.config.sse_read_timeout,
|
|
479
|
+
httpx_client_factory=create_mcp_http_client,
|
|
480
|
+
) as (read_stream, write_stream):
|
|
481
|
+
yield read_stream, write_stream
|
|
482
|
+
except TypeError:
|
|
483
|
+
# Fall back to basic call without httpx_client_factory
|
|
484
|
+
async with sse_client(
|
|
485
|
+
url=self.config.url,
|
|
486
|
+
headers=self.config.headers,
|
|
487
|
+
timeout=self.config.timeout,
|
|
488
|
+
sse_read_timeout=self.config.sse_read_timeout,
|
|
489
|
+
) as (read_stream, write_stream):
|
|
490
|
+
yield read_stream, write_stream
|
|
491
|
+
|
|
492
|
+
elif transport_type == TransportType.STREAMABLE_HTTP:
|
|
493
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
494
|
+
|
|
495
|
+
# Ensure URL is not None for StreamableHTTP
|
|
496
|
+
if not self.config.url:
|
|
497
|
+
raise ValueError(
|
|
498
|
+
"URL is required for StreamableHTTP transport"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
try:
|
|
502
|
+
# Try with httpx_client_factory first (newer versions)
|
|
503
|
+
async with streamablehttp_client(
|
|
504
|
+
url=self.config.url,
|
|
505
|
+
headers=self.config.headers,
|
|
506
|
+
timeout=timedelta(seconds=self.config.timeout),
|
|
507
|
+
sse_read_timeout=timedelta(
|
|
508
|
+
seconds=self.config.sse_read_timeout
|
|
509
|
+
),
|
|
510
|
+
terminate_on_close=self.config.terminate_on_close,
|
|
511
|
+
httpx_client_factory=create_mcp_http_client,
|
|
512
|
+
) as (read_stream, write_stream, get_session_id):
|
|
513
|
+
yield read_stream, write_stream, get_session_id
|
|
514
|
+
except TypeError:
|
|
515
|
+
# Fall back to basic call without httpx_client_factory
|
|
516
|
+
async with streamablehttp_client(
|
|
517
|
+
url=self.config.url,
|
|
518
|
+
headers=self.config.headers,
|
|
519
|
+
timeout=timedelta(seconds=self.config.timeout),
|
|
520
|
+
sse_read_timeout=timedelta(
|
|
521
|
+
seconds=self.config.sse_read_timeout
|
|
522
|
+
),
|
|
523
|
+
terminate_on_close=self.config.terminate_on_close,
|
|
524
|
+
) as (read_stream, write_stream, get_session_id):
|
|
525
|
+
yield read_stream, write_stream, get_session_id
|
|
526
|
+
|
|
527
|
+
elif transport_type == TransportType.WEBSOCKET:
|
|
528
|
+
from mcp.client.websocket import websocket_client
|
|
529
|
+
|
|
530
|
+
# Ensure URL is not None for WebSocket
|
|
531
|
+
if not self.config.url:
|
|
532
|
+
raise ValueError("URL is required for WebSocket transport")
|
|
533
|
+
|
|
534
|
+
async with websocket_client(url=self.config.url) as (
|
|
535
|
+
read_stream,
|
|
536
|
+
write_stream,
|
|
537
|
+
):
|
|
538
|
+
yield read_stream, write_stream
|
|
539
|
+
|
|
540
|
+
else:
|
|
541
|
+
raise ValueError(f"Unsupported transport type: {transport_type}")
|
|
542
|
+
|
|
543
|
+
@property
|
|
544
|
+
def session(self) -> Optional[ClientSession]:
|
|
545
|
+
r"""Get the current session if connected."""
|
|
546
|
+
return self._session
|
|
547
|
+
|
|
548
|
+
def is_connected(self) -> bool:
|
|
549
|
+
r"""Check if the client is currently connected."""
|
|
550
|
+
return self._session is not None
|
|
551
|
+
|
|
552
|
+
async def list_mcp_tools(self):
|
|
553
|
+
r"""Retrieves the list of available tools from the connected MCP
|
|
554
|
+
server.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
ListToolsResult: Result containing available MCP tools.
|
|
558
|
+
"""
|
|
559
|
+
if not self._session:
|
|
560
|
+
return "MCP Client is not connected. Call `connection()` first."
|
|
561
|
+
try:
|
|
562
|
+
return await self._session.list_tools()
|
|
563
|
+
except Exception as e:
|
|
564
|
+
raise e
|
|
565
|
+
|
|
566
|
+
def list_mcp_tools_sync(self):
|
|
567
|
+
r"""Synchronously retrieves the list of available tools from the
|
|
568
|
+
connected MCP server.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
ListToolsResult: Result containing available MCP tools.
|
|
572
|
+
"""
|
|
573
|
+
from camel.utils.commons import run_async
|
|
574
|
+
|
|
575
|
+
return run_async(self.list_mcp_tools)()
|
|
576
|
+
|
|
577
|
+
def generate_function_from_mcp_tool(
|
|
578
|
+
self, mcp_tool: types.Tool
|
|
579
|
+
) -> Callable:
|
|
580
|
+
r"""Dynamically generates a Python callable function corresponding to
|
|
581
|
+
a given MCP tool.
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
mcp_tool (types.Tool): The MCP tool definition received from the
|
|
585
|
+
MCP server.
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
Callable: A dynamically created Python function that wraps
|
|
589
|
+
the MCP tool and works in both sync and async contexts.
|
|
590
|
+
"""
|
|
591
|
+
func_name = mcp_tool.name
|
|
592
|
+
func_desc = mcp_tool.description or "No description provided."
|
|
593
|
+
parameters_schema = mcp_tool.inputSchema.get("properties", {})
|
|
594
|
+
required_params = mcp_tool.inputSchema.get("required", [])
|
|
595
|
+
|
|
596
|
+
type_map = {
|
|
597
|
+
"string": str,
|
|
598
|
+
"integer": int,
|
|
599
|
+
"number": float,
|
|
600
|
+
"boolean": bool,
|
|
601
|
+
"array": list,
|
|
602
|
+
"object": dict,
|
|
603
|
+
}
|
|
604
|
+
annotations = {} # used to type hints
|
|
605
|
+
defaults: Dict[str, Any] = {} # store default values
|
|
606
|
+
|
|
607
|
+
func_params = []
|
|
608
|
+
for param_name, param_schema in parameters_schema.items():
|
|
609
|
+
param_type = param_schema.get("type", "Any")
|
|
610
|
+
param_type = type_map.get(param_type, Any)
|
|
611
|
+
|
|
612
|
+
annotations[param_name] = param_type
|
|
613
|
+
if param_name not in required_params:
|
|
614
|
+
defaults[param_name] = None
|
|
615
|
+
|
|
616
|
+
func_params.append(param_name)
|
|
617
|
+
|
|
618
|
+
# Create the async version of the function
|
|
619
|
+
async def async_mcp_call(**kwargs) -> str:
|
|
620
|
+
r"""Async version of MCP tool call."""
|
|
621
|
+
missing_params: Set[str] = set(required_params) - set(
|
|
622
|
+
kwargs.keys()
|
|
623
|
+
)
|
|
624
|
+
if missing_params:
|
|
625
|
+
from camel.logger import get_logger
|
|
626
|
+
|
|
627
|
+
logger = get_logger(__name__)
|
|
628
|
+
logger.warning(
|
|
629
|
+
f"Missing required parameters: {missing_params}"
|
|
630
|
+
)
|
|
631
|
+
return "Missing required parameters."
|
|
632
|
+
|
|
633
|
+
if not self._session:
|
|
634
|
+
from camel.logger import get_logger
|
|
635
|
+
|
|
636
|
+
logger = get_logger(__name__)
|
|
637
|
+
logger.error(
|
|
638
|
+
"MCP Client is not connected. Call `connection()` first."
|
|
639
|
+
)
|
|
640
|
+
raise RuntimeError(
|
|
641
|
+
"MCP Client is not connected. Call `connection()` first."
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
try:
|
|
645
|
+
result = await self._session.call_tool(func_name, kwargs)
|
|
646
|
+
except Exception as e:
|
|
647
|
+
from camel.logger import get_logger
|
|
648
|
+
|
|
649
|
+
logger = get_logger(__name__)
|
|
650
|
+
logger.error(f"Failed to call MCP tool '{func_name}': {e!s}")
|
|
651
|
+
raise e
|
|
652
|
+
|
|
653
|
+
if not result.content or len(result.content) == 0:
|
|
654
|
+
return "No data available for this request."
|
|
655
|
+
|
|
656
|
+
# Handle different content types
|
|
657
|
+
try:
|
|
658
|
+
content = result.content[0]
|
|
659
|
+
if content.type == "text":
|
|
660
|
+
return content.text
|
|
661
|
+
elif content.type == "image":
|
|
662
|
+
# Return image URL or data URI if available
|
|
663
|
+
if hasattr(content, "url") and content.url:
|
|
664
|
+
return f"Image available at: {content.url}"
|
|
665
|
+
return "Image content received (data URI not shown)"
|
|
666
|
+
elif content.type == "embedded_resource":
|
|
667
|
+
# Return resource information if available
|
|
668
|
+
if hasattr(content, "name") and content.name:
|
|
669
|
+
return f"Embedded resource: {content.name}"
|
|
670
|
+
return "Embedded resource received"
|
|
671
|
+
else:
|
|
672
|
+
msg = f"Received content of type '{content.type}'"
|
|
673
|
+
return f"{msg} which is not fully supported yet."
|
|
674
|
+
except (IndexError, AttributeError) as e:
|
|
675
|
+
from camel.logger import get_logger
|
|
676
|
+
|
|
677
|
+
logger = get_logger(__name__)
|
|
678
|
+
logger.error(
|
|
679
|
+
f"Error processing content from MCP tool response: {e!s}"
|
|
680
|
+
)
|
|
681
|
+
raise e
|
|
682
|
+
|
|
683
|
+
def adaptive_dynamic_function(**kwargs) -> str:
|
|
684
|
+
r"""Adaptive function that works in both sync and async contexts.
|
|
685
|
+
|
|
686
|
+
This function detects if it's being called from an async context
|
|
687
|
+
and behaves accordingly.
|
|
688
|
+
|
|
689
|
+
Args:
|
|
690
|
+
kwargs: Keyword arguments corresponding to MCP tool parameters.
|
|
691
|
+
|
|
692
|
+
Returns:
|
|
693
|
+
str: The textual result returned by the MCP tool.
|
|
694
|
+
|
|
695
|
+
Raises:
|
|
696
|
+
TimeoutError: If the operation times out.
|
|
697
|
+
RuntimeError: If there are issues with async execution.
|
|
698
|
+
"""
|
|
699
|
+
import asyncio
|
|
700
|
+
import concurrent.futures
|
|
701
|
+
|
|
702
|
+
try:
|
|
703
|
+
# Check if we're in an async context with a running loop
|
|
704
|
+
loop = asyncio.get_running_loop() # noqa: F841
|
|
705
|
+
# If we get here, we're in an async context with a running loop
|
|
706
|
+
# We need to run the async function in a separate thread with
|
|
707
|
+
# a new loop
|
|
708
|
+
|
|
709
|
+
def run_in_thread():
|
|
710
|
+
# Create a new event loop for this thread
|
|
711
|
+
new_loop = asyncio.new_event_loop()
|
|
712
|
+
asyncio.set_event_loop(new_loop)
|
|
713
|
+
try:
|
|
714
|
+
return new_loop.run_until_complete(
|
|
715
|
+
async_mcp_call(**kwargs)
|
|
716
|
+
)
|
|
717
|
+
except Exception as e:
|
|
718
|
+
# Preserve the original exception context
|
|
719
|
+
raise RuntimeError(
|
|
720
|
+
f"MCP call failed in thread: {e}"
|
|
721
|
+
) from e
|
|
722
|
+
finally:
|
|
723
|
+
new_loop.close()
|
|
724
|
+
|
|
725
|
+
# Run in a separate thread to avoid event loop conflicts
|
|
726
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
727
|
+
future = executor.submit(run_in_thread)
|
|
728
|
+
try:
|
|
729
|
+
return future.result(
|
|
730
|
+
timeout=self.read_timeout_seconds.total_seconds()
|
|
731
|
+
)
|
|
732
|
+
except concurrent.futures.TimeoutError:
|
|
733
|
+
raise TimeoutError(
|
|
734
|
+
f"MCP call timed out after "
|
|
735
|
+
f"{self.read_timeout_seconds.total_seconds()}"
|
|
736
|
+
f" seconds"
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
except RuntimeError as e:
|
|
740
|
+
# Only handle the specific "no running event loop" case
|
|
741
|
+
if (
|
|
742
|
+
"no running event loop" in str(e).lower()
|
|
743
|
+
or "no current event loop" in str(e).lower()
|
|
744
|
+
):
|
|
745
|
+
# No event loop is running, we can safely use run_async
|
|
746
|
+
from camel.utils.commons import run_async
|
|
747
|
+
|
|
748
|
+
run_async_func = run_async(async_mcp_call)
|
|
749
|
+
return run_async_func(**kwargs)
|
|
750
|
+
else:
|
|
751
|
+
# Re-raise other RuntimeErrors
|
|
752
|
+
raise
|
|
753
|
+
|
|
754
|
+
# Add an async_call method to the function for explicit async usage
|
|
755
|
+
adaptive_dynamic_function.async_call = async_mcp_call # type: ignore[attr-defined]
|
|
756
|
+
|
|
757
|
+
adaptive_dynamic_function.__name__ = func_name
|
|
758
|
+
adaptive_dynamic_function.__doc__ = func_desc
|
|
759
|
+
adaptive_dynamic_function.__annotations__ = annotations
|
|
760
|
+
|
|
761
|
+
sig = inspect.Signature(
|
|
762
|
+
parameters=[
|
|
763
|
+
inspect.Parameter(
|
|
764
|
+
name=param,
|
|
765
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
766
|
+
default=defaults.get(param, inspect.Parameter.empty),
|
|
767
|
+
annotation=annotations[param],
|
|
768
|
+
)
|
|
769
|
+
for param in func_params
|
|
770
|
+
]
|
|
771
|
+
)
|
|
772
|
+
adaptive_dynamic_function.__signature__ = sig # type: ignore[attr-defined]
|
|
773
|
+
|
|
774
|
+
return adaptive_dynamic_function
|
|
775
|
+
|
|
776
|
+
def _build_tool_schema(self, mcp_tool: types.Tool) -> Dict[str, Any]:
|
|
777
|
+
r"""Build tool schema for OpenAI function calling format."""
|
|
778
|
+
input_schema = mcp_tool.inputSchema
|
|
779
|
+
properties = input_schema.get("properties", {})
|
|
780
|
+
required = input_schema.get("required", [])
|
|
781
|
+
|
|
782
|
+
parameters = {
|
|
783
|
+
"type": "object",
|
|
784
|
+
"properties": properties,
|
|
785
|
+
"required": required,
|
|
786
|
+
"additionalProperties": False,
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return {
|
|
790
|
+
"type": "function",
|
|
791
|
+
"function": {
|
|
792
|
+
"name": mcp_tool.name,
|
|
793
|
+
"description": mcp_tool.description
|
|
794
|
+
or "No description provided.",
|
|
795
|
+
"parameters": parameters,
|
|
796
|
+
},
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
def get_tools(self):
|
|
800
|
+
r"""Get available tools as CAMEL FunctionTool objects.
|
|
801
|
+
|
|
802
|
+
Retrieves all available tools from the connected MCP server and
|
|
803
|
+
converts them to CAMEL-compatible :obj:`FunctionTool` objects. The
|
|
804
|
+
tools are automatically wrapped to handle the MCP protocol
|
|
805
|
+
communication.
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
List[FunctionTool]: A list of :obj:`FunctionTool` objects
|
|
809
|
+
representing the available tools from the MCP server. Returns
|
|
810
|
+
an empty list if the client is not connected.
|
|
811
|
+
|
|
812
|
+
Note:
|
|
813
|
+
This method requires an active connection to the MCP server.
|
|
814
|
+
If the client is not connected, an empty list will be returned.
|
|
815
|
+
"""
|
|
816
|
+
if not self.is_connected():
|
|
817
|
+
return []
|
|
818
|
+
|
|
819
|
+
# Import FunctionTool here to avoid circular imports
|
|
820
|
+
try:
|
|
821
|
+
from camel.toolkits import FunctionTool
|
|
822
|
+
except ImportError:
|
|
823
|
+
from camel.logger import get_logger
|
|
824
|
+
|
|
825
|
+
logger = get_logger(__name__)
|
|
826
|
+
logger.error(
|
|
827
|
+
"Failed to import FunctionTool. Please ensure "
|
|
828
|
+
"camel.toolkits is available."
|
|
829
|
+
)
|
|
830
|
+
return []
|
|
831
|
+
|
|
832
|
+
camel_tools = []
|
|
833
|
+
for tool in self._tools:
|
|
834
|
+
try:
|
|
835
|
+
# Generate the function and build the tool schema
|
|
836
|
+
func = self.generate_function_from_mcp_tool(tool)
|
|
837
|
+
schema = self._build_tool_schema(tool)
|
|
838
|
+
|
|
839
|
+
# Create CAMEL FunctionTool
|
|
840
|
+
camel_tool = FunctionTool(
|
|
841
|
+
func,
|
|
842
|
+
openai_tool_schema=schema,
|
|
843
|
+
)
|
|
844
|
+
camel_tools.append(camel_tool)
|
|
845
|
+
except Exception as e:
|
|
846
|
+
# Log error but continue with other tools
|
|
847
|
+
from camel.logger import get_logger
|
|
848
|
+
|
|
849
|
+
logger = get_logger(__name__)
|
|
850
|
+
logger.warning(f"Failed to convert tool {tool.name}: {e}")
|
|
851
|
+
|
|
852
|
+
return camel_tools
|
|
853
|
+
|
|
854
|
+
def get_text_tools(self) -> str:
|
|
855
|
+
r"""Get a text description of available tools.
|
|
856
|
+
|
|
857
|
+
Returns:
|
|
858
|
+
str: Text description of tools
|
|
859
|
+
"""
|
|
860
|
+
if not self.is_connected():
|
|
861
|
+
return "Client not connected"
|
|
862
|
+
|
|
863
|
+
if not self._tools:
|
|
864
|
+
return "No tools available"
|
|
865
|
+
|
|
866
|
+
tool_descriptions = []
|
|
867
|
+
for tool in self._tools:
|
|
868
|
+
desc = tool.description or 'No description'
|
|
869
|
+
description = f"- {tool.name}: {desc}"
|
|
870
|
+
tool_descriptions.append(description)
|
|
871
|
+
|
|
872
|
+
return "\n".join(tool_descriptions)
|
|
873
|
+
|
|
874
|
+
async def call_tool(
|
|
875
|
+
self, tool_name: str, arguments: Dict[str, Any]
|
|
876
|
+
) -> Any:
|
|
877
|
+
r"""Call a tool by name with the provided arguments.
|
|
878
|
+
|
|
879
|
+
Executes a specific tool on the connected MCP server with the given
|
|
880
|
+
arguments. The tool must be available in the server's tool list.
|
|
881
|
+
|
|
882
|
+
Args:
|
|
883
|
+
tool_name (str): The name of the tool to call. Must match a tool
|
|
884
|
+
name returned by :obj:`get_tools()`.
|
|
885
|
+
arguments (Dict[str, Any]): A dictionary of arguments to pass to
|
|
886
|
+
the tool. The argument names and types must match the tool's
|
|
887
|
+
expected parameters.
|
|
888
|
+
|
|
889
|
+
Returns:
|
|
890
|
+
Any: The result returned by the tool execution. The type and
|
|
891
|
+
structure depend on the specific tool being called.
|
|
892
|
+
|
|
893
|
+
Raises:
|
|
894
|
+
RuntimeError: If the client is not connected to an MCP server.
|
|
895
|
+
ValueError: If the specified tool name is not found in the list
|
|
896
|
+
of available tools.
|
|
897
|
+
|
|
898
|
+
Example:
|
|
899
|
+
.. code-block:: python
|
|
900
|
+
|
|
901
|
+
# Call a file reading tool
|
|
902
|
+
result = await client.call_tool(
|
|
903
|
+
"read_file",
|
|
904
|
+
{"path": "/tmp/example.txt"}
|
|
905
|
+
)
|
|
906
|
+
"""
|
|
907
|
+
if not self.is_connected():
|
|
908
|
+
raise RuntimeError("Client is not connected")
|
|
909
|
+
|
|
910
|
+
# Check if tool exists
|
|
911
|
+
tool_names = [tool.name for tool in self._tools]
|
|
912
|
+
if tool_name not in tool_names:
|
|
913
|
+
available_tools = ', '.join(tool_names)
|
|
914
|
+
raise ValueError(
|
|
915
|
+
f"Tool '{tool_name}' not found. "
|
|
916
|
+
f"Available tools: {available_tools}"
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
# Call the tool using the correct API
|
|
920
|
+
if self._session is None:
|
|
921
|
+
raise RuntimeError("Client session is not available")
|
|
922
|
+
|
|
923
|
+
result = await self._session.call_tool(
|
|
924
|
+
name=tool_name, arguments=arguments
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
return result
|
|
928
|
+
|
|
929
|
+
def call_tool_sync(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
|
|
930
|
+
r"""Synchronously call a tool by name with the provided arguments.
|
|
931
|
+
|
|
932
|
+
Args:
|
|
933
|
+
tool_name (str): The name of the tool to call.
|
|
934
|
+
arguments (Dict[str, Any]): A dictionary of arguments to pass to
|
|
935
|
+
the tool.
|
|
936
|
+
|
|
937
|
+
Returns:
|
|
938
|
+
Any: The result returned by the tool execution.
|
|
939
|
+
"""
|
|
940
|
+
from camel.utils.commons import run_async
|
|
941
|
+
|
|
942
|
+
return run_async(self.call_tool)(tool_name, arguments)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def create_mcp_client(
|
|
946
|
+
config: Union[Dict[str, Any], ServerConfig], **kwargs: Any
|
|
947
|
+
) -> MCPClient:
|
|
948
|
+
r"""Create an MCP client from configuration.
|
|
949
|
+
|
|
950
|
+
Factory function that creates an :obj:`MCPClient` instance from various
|
|
951
|
+
configuration formats. This is the recommended way to create MCP clients
|
|
952
|
+
as it handles configuration validation and type conversion automatically.
|
|
953
|
+
|
|
954
|
+
Args:
|
|
955
|
+
config (Union[Dict[str, Any], ServerConfig]): Server configuration
|
|
956
|
+
as either a dictionary or a :obj:`ServerConfig` object. If a
|
|
957
|
+
dictionary is provided, it will be automatically converted to
|
|
958
|
+
a :obj:`ServerConfig`.
|
|
959
|
+
**kwargs: Additional keyword arguments passed to the :obj:`MCPClient`
|
|
960
|
+
constructor, such as :obj:`client_info`, :obj:`timeout`.
|
|
961
|
+
|
|
962
|
+
Returns:
|
|
963
|
+
MCPClient: A configured :obj:`MCPClient` instance ready for use as
|
|
964
|
+
an async context manager.
|
|
965
|
+
|
|
966
|
+
Examples:
|
|
967
|
+
STDIO server:
|
|
968
|
+
|
|
969
|
+
.. code-block:: python
|
|
970
|
+
|
|
971
|
+
async with create_mcp_client({
|
|
972
|
+
"command": "npx",
|
|
973
|
+
"args": [
|
|
974
|
+
"-y",
|
|
975
|
+
"@modelcontextprotocol/server-filesystem",
|
|
976
|
+
"/path",
|
|
977
|
+
],
|
|
978
|
+
}) as client:
|
|
979
|
+
tools = client.get_tools()
|
|
980
|
+
|
|
981
|
+
HTTP server:
|
|
982
|
+
|
|
983
|
+
.. code-block:: python
|
|
984
|
+
|
|
985
|
+
async with create_mcp_client({
|
|
986
|
+
"url": "https://api.example.com/mcp",
|
|
987
|
+
"headers": {"Authorization": "Bearer token"}
|
|
988
|
+
}) as client:
|
|
989
|
+
result = await client.call_tool("tool_name", {"arg": "value"})
|
|
990
|
+
|
|
991
|
+
WebSocket server:
|
|
992
|
+
|
|
993
|
+
.. code-block:: python
|
|
994
|
+
|
|
995
|
+
async with create_mcp_client({
|
|
996
|
+
"url": "ws://localhost:8080/mcp"
|
|
997
|
+
}) as client:
|
|
998
|
+
tools = client.get_tools()
|
|
999
|
+
"""
|
|
1000
|
+
return MCPClient(config, **kwargs)
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def create_mcp_client_from_config_file(
|
|
1004
|
+
config_path: Union[str, Path], server_name: str, **kwargs: Any
|
|
1005
|
+
) -> MCPClient:
|
|
1006
|
+
r"""Create an MCP client from a configuration file.
|
|
1007
|
+
|
|
1008
|
+
Args:
|
|
1009
|
+
config_path (Union[str, Path]): Path to configuration file (JSON).
|
|
1010
|
+
server_name (str): Name of the server in the config.
|
|
1011
|
+
**kwargs: Additional arguments passed to MCPClient constructor.
|
|
1012
|
+
|
|
1013
|
+
Returns:
|
|
1014
|
+
MCPClient: MCPClient instance as an async context manager.
|
|
1015
|
+
Example config file:
|
|
1016
|
+
{
|
|
1017
|
+
"mcpServers": {
|
|
1018
|
+
"filesystem": {
|
|
1019
|
+
"command": "npx",
|
|
1020
|
+
"args": [
|
|
1021
|
+
"-y",
|
|
1022
|
+
"@modelcontextprotocol/server-filesystem",
|
|
1023
|
+
"/path"
|
|
1024
|
+
]
|
|
1025
|
+
},
|
|
1026
|
+
"remote-server": {
|
|
1027
|
+
"url": "https://api.example.com/mcp",
|
|
1028
|
+
"headers": {"Authorization": "Bearer token"}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
Usage:
|
|
1034
|
+
.. code-block:: python
|
|
1035
|
+
|
|
1036
|
+
async with create_mcp_client_from_config_file(
|
|
1037
|
+
"config.json", "filesystem"
|
|
1038
|
+
) as client:
|
|
1039
|
+
tools = client.get_tools()
|
|
1040
|
+
"""
|
|
1041
|
+
import json
|
|
1042
|
+
|
|
1043
|
+
config_path = Path(config_path)
|
|
1044
|
+
with open(config_path, 'r') as f:
|
|
1045
|
+
config_data = json.load(f)
|
|
1046
|
+
|
|
1047
|
+
servers = config_data.get("mcpServers", {})
|
|
1048
|
+
if server_name not in servers:
|
|
1049
|
+
available = list(servers.keys())
|
|
1050
|
+
raise ValueError(
|
|
1051
|
+
f"Server '{server_name}' not found in config. "
|
|
1052
|
+
f"Available: {available}"
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
server_config = servers[server_name]
|
|
1056
|
+
return create_mcp_client(server_config, **kwargs)
|