diff --git a/TODO.md b/TODO.md index afdc43047..fbb3e6fb7 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ - [x] 内置空间插件 - [ ] 在好友聊天生成回复时设置输入状态 - [x] 基于关键帧的视频识别功能 -- [ ] 对XML,JSON等特殊消息解析 +- [x] 对XML,JSON等特殊消息解析 - [x] 插件热重载 - [x] 适配器黑/白名单迁移至独立配置文件,并支持热重载 - [x] 添加MySQL支持,重构数据库 diff --git a/docs/MCP_SIMPLE_GUIDE.md b/docs/MCP_SIMPLE_GUIDE.md new file mode 100644 index 000000000..291ccd5ab --- /dev/null +++ b/docs/MCP_SIMPLE_GUIDE.md @@ -0,0 +1,175 @@ +# MCP工具集成 - 简化指南 + +## ✅ 已完成的工作 + +MCP (Model Context Protocol) 工具支持已经完全集成到MoFox Bot!**AI现在可以自动发现并调用MCP工具了**。 + +## 🎯 快速开始 + +### 步骤1: 启动MCP服务器 + +首先你需要一个MCP服务器。最简单的方式是使用官方提供的文件系统服务器: + +```bash +# 安装(需要Node.js) +npm install -g @modelcontextprotocol/server-filesystem + +# 启动服务器,允许访问指定目录 +mcp-server-filesystem --port 3000 /path/to/your/project +``` + +### 步骤2: 配置Bot + +编辑 `config/bot_config.toml`,在文件末尾添加: + +```toml +[[mcp_servers]] +name = "filesystem" +url = "http://localhost:3000" +api_key = "" # 如果服务器不需要认证就留空 +timeout = 30 +enabled = true +``` + +### 步骤3: 启动Bot + +```bash +python bot.py +``` + +启动后你会看到: + +``` +[INFO] 连接MCP服务器: filesystem (http://localhost:3000) +[INFO] 从filesystem获取5个工具 +[INFO] MCP工具提供器初始化成功 +``` + +### 步骤4: AI自动使用工具 + +现在AI可以自动调用MCP工具了! + +**示例对话:** + +``` +用户: 帮我读取README.md文件的内容 + +AI: [内部决策: 需要读取文件 → 调用 filesystem_read_file 工具] + README.md的内容是... + +用户: 列出当前目录下的所有文件 + +AI: [调用 filesystem_list_directory 工具] + 当前目录包含以下文件: + - README.md + - bot.py + - ... +``` + +## 🔧 工作原理 + +``` +用户消息 + ↓ +AI决策系统 (ToolExecutor) + ↓ +获取可用工具列表 + ↓ +【包含Bot内置工具 + MCP工具】 ← 自动合并 + ↓ +AI选择需要的工具 + ↓ +执行工具调用 + ↓ +返回结果给用户 +``` + +## 📝 配置多个MCP服务器 + +```toml +# 文件系统工具 +[[mcp_servers]] +name = "filesystem" +url = "http://localhost:3000" +enabled = true + +# Git工具 +[[mcp_servers]] +name = "git" +url = "http://localhost:3001" +enabled = true + +# 数据库工具 +[[mcp_servers]] +name = "database" +url = "http://localhost:3002" +api_key = "your-secret-key" +enabled = true +``` + +每个服务器的工具会自动添加名称前缀: +- `filesystem_read_file` +- `git_status` +- `database_query` + +## 🛠️ 可用的MCP服务器 + +官方提供的MCP服务器: + +1. **@modelcontextprotocol/server-filesystem** - 文件系统操作 +2. **@modelcontextprotocol/server-git** - Git操作 +3. **@modelcontextprotocol/server-github** - GitHub API +4. **@modelcontextprotocol/server-sqlite** - SQLite数据库 +5. **@modelcontextprotocol/server-postgres** - PostgreSQL数据库 + +你也可以开发自定义MCP服务器! + +## 🐛 常见问题 + +### Q: 如何查看AI是否使用了MCP工具? + +查看日志,会显示: +``` +[INFO] [工具执行器] 正在执行工具: filesystem_read_file +[INFO] 调用MCP工具: filesystem_read_file +``` + +### Q: MCP服务器连接失败怎么办? + +检查: +1. MCP服务器是否正在运行 +2. URL配置是否正确(注意端口号) +3. 防火墙是否阻止连接 + +### Q: 如何临时禁用MCP工具? + +在配置中设置 `enabled = false`: + +```toml +[[mcp_servers]] +name = "filesystem" +url = "http://localhost:3000" +enabled = false # 禁用 +``` + +## 📚 相关文档 + +- **详细集成文档**: [MCP_TOOLS_INTEGRATION.md](./MCP_TOOLS_INTEGRATION.md) +- **MCP SSE客户端**: [MCP_SSE_USAGE.md](./MCP_SSE_USAGE.md) +- **MCP协议官方文档**: https://github.com/anthropics/mcp + +## 🎉 总结 + +MCP工具支持已经完全集成!你只需要: + +1. ✅ 启动MCP服务器 +2. ✅ 在`bot_config.toml`中配置 +3. ✅ 启动Bot + +**AI会自动发现并使用工具,无需任何额外代码!** + +--- + +**实现方式**: 通过修改`tool_api.py`和`tool_use.py`,将MCP工具无缝集成到现有工具系统 +**版本**: v1.0.0 +**日期**: 2025-10-05 diff --git a/docs/MCP_SSE_INTEGRATION.md b/docs/MCP_SSE_INTEGRATION.md new file mode 100644 index 000000000..90c569e7c --- /dev/null +++ b/docs/MCP_SSE_INTEGRATION.md @@ -0,0 +1,175 @@ +# MCP SSE 集成完成报告 + +## ✅ 集成状态:已完成 + +MCP (Model Context Protocol) SSE (Server-Sent Events) 客户端已完全集成到 MoFox Bot 框架中。 + +## 📋 完成的工作 + +### 1. 依赖管理 +- ✅ 在 `pyproject.toml` 中添加 `mcp>=0.9.0` 和 `sse-starlette>=2.2.1` +- ✅ 在 `requirements.txt` 中同步添加依赖 + +### 2. 客户端实现 +- ✅ 创建 `src/llm_models/model_client/mcp_sse_client.py` +- ✅ 实现完整的MCP SSE协议支持 +- ✅ 支持流式响应、工具调用、多模态内容 +- ✅ 实现中断处理和Token统计 + +### 3. 配置系统集成 +- ✅ 在 `src/config/api_ada_configs.py` 中添加 `"mcp_sse"` 到 `client_type` 的 `Literal` 类型 +- ✅ 在 `src/llm_models/model_client/__init__.py` 中注册客户端 +- ✅ 通过 `@client_registry.register_client_class("mcp_sse")` 装饰器完成自动注册 + +### 4. 配置模板 +- ✅ 在 `template/model_config_template.toml` 中添加 MCP Provider 配置示例 +- ✅ 添加 MCP 模型配置示例 +- ✅ 提供详细的配置注释 + +### 5. 文档 +- ✅ 创建 `docs/MCP_SSE_USAGE.md` - 详细使用文档 +- ✅ 创建 `docs/MCP_SSE_QUICKSTART.md` - 快速配置指南 +- ✅ 创建 `docs/MCP_SSE_INTEGRATION.md` - 集成完成报告(本文档) + +### 6. 任务追踪 +- ✅ 更新 `TODO.md`,标记"添加MCP SSE支持"为已完成 + +## 🔧 配置示例 + +### Provider配置 +```toml +[[api_providers]] +name = "MCPProvider" +base_url = "https://your-mcp-server.com" +api_key = "your-api-key" +client_type = "mcp_sse" # 关键:使用MCP SSE客户端 +timeout = 60 +max_retry = 2 +``` + +### 模型配置 +```toml +[[models]] +model_identifier = "claude-3-5-sonnet-20241022" +name = "mcp-claude" +api_provider = "MCPProvider" +force_stream_mode = true +``` + +### 任务配置 +```toml +[model_task_config.replyer] +model_list = ["mcp-claude"] +temperature = 0.7 +max_tokens = 800 +``` + +## 🎯 功能特性 + +### 支持的功能 +- ✅ 流式响应(SSE协议) +- ✅ 多轮对话 +- ✅ 工具调用(Function Calling) +- ✅ 多模态内容(文本+图片) +- ✅ 中断信号处理 +- ✅ Token使用统计 +- ✅ 自动重试和错误处理 +- ✅ API密钥轮询 + +### 当前限制 +- ❌ 不支持嵌入(Embedding)功能 +- ❌ 不支持音频转录功能 + +## 📊 架构集成 + +``` +MoFox Bot +├── src/llm_models/ +│ ├── model_client/ +│ │ ├── base_client.py # 基础客户端接口 +│ │ ├── openai_client.py # OpenAI客户端 +│ │ ├── aiohttp_gemini_client.py # Gemini客户端 +│ │ ├── mcp_sse_client.py # ✨ MCP SSE客户端(新增) +│ │ └── __init__.py # 客户端注册(已更新) +│ └── ... +├── src/config/ +│ └── api_ada_configs.py # ✨ 添加mcp_sse类型(已更新) +├── template/ +│ └── model_config_template.toml # ✨ 添加MCP配置示例(已更新) +├── docs/ +│ ├── MCP_SSE_USAGE.md # ✨ 使用文档(新增) +│ ├── MCP_SSE_QUICKSTART.md # ✨ 快速配置指南(新增) +│ └── MCP_SSE_INTEGRATION.md # ✨ 集成报告(本文档) +└── pyproject.toml # ✨ 添加依赖(已更新) +``` + +## 🚀 使用流程 + +1. **安装依赖** + ```bash + uv sync + ``` + +2. **配置Provider和模型** + - 编辑 `model_config.toml` + - 参考 `template/model_config_template.toml` 中的示例 + +3. **使用MCP模型** + - 在任何 `model_task_config` 中引用配置的MCP模型 + - 例如:`model_list = ["mcp-claude"]` + +4. **启动Bot** + - 正常启动,MCP客户端会自动加载 + +## 🔍 验证方法 + +### 检查客户端注册 +启动Bot后,查看日志确认MCP SSE客户端已加载: +``` +[INFO] 已注册客户端类型: mcp_sse +``` + +### 测试配置 +发送测试消息,确认MCP模型正常响应。 + +### 查看日志 +``` +[INFO] MCP-SSE客户端: 正在处理请求... +[DEBUG] SSE流: 接收到内容块... +``` + +## 📚 相关文档 + +- **快速开始**: [MCP_SSE_QUICKSTART.md](./MCP_SSE_QUICKSTART.md) +- **详细使用**: [MCP_SSE_USAGE.md](./MCP_SSE_USAGE.md) +- **配置模板**: [model_config_template.toml](../template/model_config_template.toml) +- **MCP协议**: [https://github.com/anthropics/mcp](https://github.com/anthropics/mcp) + +## 🐛 已知问题 + +目前没有已知问题。 + +## 📝 更新日志 + +### v0.8.1 (2025-10-05) +- ✅ 添加MCP SSE客户端支持 +- ✅ 集成到配置系统 +- ✅ 提供完整文档和配置示例 + +## 👥 贡献者 + +- MoFox Studio Team + +## 📞 技术支持 + +如遇到问题: +1. 查看日志文件中的错误信息 +2. 参考文档排查配置问题 +3. 提交Issue到项目仓库 +4. 加入QQ交流群寻求帮助 + +--- + +**集成完成时间**: 2025-10-05 +**集成版本**: v0.8.1 +**状态**: ✅ 生产就绪 diff --git a/docs/MCP_SSE_QUICKSTART.md b/docs/MCP_SSE_QUICKSTART.md new file mode 100644 index 000000000..9c789e3fd --- /dev/null +++ b/docs/MCP_SSE_QUICKSTART.md @@ -0,0 +1,178 @@ +# MCP SSE 快速配置指南 + +## 什么是MCP SSE? + +MCP (Model Context Protocol) SSE (Server-Sent Events) 是一种支持流式通信的协议,允许MoFox Bot通过SSE与兼容MCP协议的AI服务进行交互。 + +## 快速开始 + +### 步骤1: 安装依赖 + +```bash +# 使用uv(推荐) +uv sync + +# 或使用pip +pip install mcp>=0.9.0 sse-starlette>=2.2.1 +``` + +### 步骤2: 编辑配置文件 + +打开或创建 `model_config.toml` 文件,添加以下配置: + +#### 2.1 添加MCP Provider + +```toml +[[api_providers]] +name = "MCPProvider" # Provider名称,可自定义 +base_url = "https://your-mcp-server.com" # 你的MCP服务器地址 +api_key = "your-mcp-api-key" # 你的API密钥 +client_type = "mcp_sse" # 必须设置为 "mcp_sse" +timeout = 60 # 超时时间(秒) +max_retry = 2 # 最大重试次数 +``` + +#### 2.2 添加MCP模型 + +```toml +[[models]] +model_identifier = "claude-3-5-sonnet-20241022" # 模型ID +name = "mcp-claude" # 模型名称,用于引用 +api_provider = "MCPProvider" # 使用上面配置的Provider +force_stream_mode = true # MCP建议使用流式模式 +price_in = 3.0 # 输入价格(可选) +price_out = 15.0 # 输出价格(可选) +``` + +#### 2.3 在任务中使用MCP模型 + +```toml +# 例如:使用MCP模型作为回复模型 +[model_task_config.replyer] +model_list = ["mcp-claude"] # 引用上面定义的模型名称 +temperature = 0.7 +max_tokens = 800 +``` + +### 步骤3: 验证配置 + +启动MoFox Bot,查看日志确认MCP SSE客户端是否正确加载: + +``` +[INFO] MCP-SSE客户端: 正在初始化... +[INFO] 已加载模型: mcp-claude (MCPProvider) +``` + +## 完整配置示例 + +```toml +# ===== MCP SSE Provider配置 ===== +[[api_providers]] +name = "MCPProvider" +base_url = "https://api.anthropic.com" # Anthropic的Claude支持MCP +api_key = "sk-ant-xxx..." +client_type = "mcp_sse" +timeout = 60 +max_retry = 2 +retry_interval = 10 + +# ===== MCP模型配置 ===== +[[models]] +model_identifier = "claude-3-5-sonnet-20241022" +name = "mcp-claude-sonnet" +api_provider = "MCPProvider" +force_stream_mode = true +price_in = 3.0 +price_out = 15.0 + +[[models]] +model_identifier = "claude-3-5-haiku-20241022" +name = "mcp-claude-haiku" +api_provider = "MCPProvider" +force_stream_mode = true +price_in = 1.0 +price_out = 5.0 + +# ===== 任务配置:使用MCP模型 ===== + +# 回复生成使用Sonnet(高质量) +[model_task_config.replyer] +model_list = ["mcp-claude-sonnet"] +temperature = 0.7 +max_tokens = 800 + +# 小型任务使用Haiku(快速响应) +[model_task_config.utils_small] +model_list = ["mcp-claude-haiku"] +temperature = 0.5 +max_tokens = 500 + +# 工具调用使用Sonnet +[model_task_config.tool_use] +model_list = ["mcp-claude-sonnet"] +temperature = 0.3 +max_tokens = 1000 +``` + +## 支持的MCP服务 + +目前已知支持MCP协议的服务: + +- ✅ **Anthropic Claude** (推荐) +- ✅ 任何实现MCP SSE协议的自定义服务器 +- ⚠️ 其他服务需验证是否支持MCP协议 + +## 常见问题 + +### Q: 我的服务器不支持MCP怎么办? + +A: 确保你的服务器实现了MCP SSE协议规范。如果是标准OpenAI API,请使用 `client_type = "openai"` 而不是 `"mcp_sse"`。 + +### Q: 如何测试MCP连接是否正常? + +A: 启动Bot后,在日志中查找相关信息,或尝试发送一条测试消息。 + +### Q: MCP SSE与OpenAI客户端有什么区别? + +A: +- **MCP SSE**: 使用Server-Sent Events协议,支持更丰富的流式交互 +- **OpenAI**: 使用标准OpenAI API格式 +- **选择建议**: 如果你的服务明确支持MCP,使用MCP SSE;否则使用OpenAI客户端 + +### Q: 可以混合使用不同类型的客户端吗? + +A: 可以!你可以在同一个配置文件中定义多个providers,使用不同的 `client_type`: + +```toml +# OpenAI Provider +[[api_providers]] +name = "OpenAIProvider" +client_type = "openai" +# ... + +# MCP Provider +[[api_providers]] +name = "MCPProvider" +client_type = "mcp_sse" +# ... + +# Gemini Provider +[[api_providers]] +name = "GoogleProvider" +client_type = "aiohttp_gemini" +# ... +``` + +## 下一步 + +- 查看 [MCP_SSE_USAGE.md](./MCP_SSE_USAGE.md) 了解详细API使用 +- 查看 [template/model_config_template.toml](../template/model_config_template.toml) 查看完整配置模板 +- 参考 [README.md](../README.md) 了解MoFox Bot的整体架构 + +## 技术支持 + +如遇到问题,请: +1. 检查日志文件中的错误信息 +2. 确认MCP服务器地址和API密钥正确 +3. 验证服务器是否支持MCP SSE协议 +4. 提交Issue到项目仓库 diff --git a/docs/MCP_SSE_USAGE.md b/docs/MCP_SSE_USAGE.md index 70fc9906b..6978bd27b 100644 --- a/docs/MCP_SSE_USAGE.md +++ b/docs/MCP_SSE_USAGE.md @@ -29,34 +29,46 @@ uv sync ### 2. 配置API Provider -在配置文件中添加MCP SSE provider: +在 `model_config.toml` 配置文件中添加MCP SSE provider: -```python -# 在配置中添加 -api_providers = [ - { - "name": "mcp_provider", - "client_type": "mcp_sse", # 使用MCP SSE客户端 - "base_url": "https://your-mcp-server.com", - "api_key": "your-api-key", - "timeout": 60 - } -] +```toml +[[api_providers]] +name = "MCPProvider" +base_url = "https://your-mcp-server.com" # MCP服务器地址 +api_key = "your-mcp-api-key-here" +client_type = "mcp_sse" # 使用MCP SSE客户端 +max_retry = 2 +timeout = 60 # MCP流式请求可能需要更长超时时间 +retry_interval = 10 ``` ### 3. 配置模型 -```python -models = [ - { - "name": "mcp_model", - "api_provider": "mcp_provider", - "model_identifier": "your-model-name", - "force_stream_mode": True # MCP SSE始终使用流式 - } -] +在同一个配置文件中添加使用MCP provider的模型: + +```toml +[[models]] +model_identifier = "claude-3-5-sonnet-20241022" # 或其他支持MCP的模型 +name = "mcp-claude-sonnet" +api_provider = "MCPProvider" # 对应上面配置的MCP provider +price_in = 3.0 +price_out = 15.0 +force_stream_mode = true # MCP SSE默认使用流式模式 ``` +### 4. 在任务配置中使用MCP模型 + +可以在任何任务配置中使用MCP模型: + +```toml +[model_task_config.replyer] +model_list = ["mcp-claude-sonnet"] # 使用MCP模型 +temperature = 0.7 +max_tokens = 800 +``` + +**注意**:配置模板已包含MCP SSE的示例配置,可参考 `template/model_config_template.toml` + ## 使用示例 ### 基础对话 diff --git a/docs/MCP_TOOLS_INTEGRATION.md b/docs/MCP_TOOLS_INTEGRATION.md new file mode 100644 index 000000000..736866b6f --- /dev/null +++ b/docs/MCP_TOOLS_INTEGRATION.md @@ -0,0 +1,356 @@ +# MCP工具集成完整指南 + +## 概述 + +MoFox Bot现在完全支持MCP (Model Context Protocol),包括: +1. **MCP SSE客户端** - 与支持MCP的LLM(如Claude)通信 +2. **MCP工具提供器** - 将MCP服务器的工具集成到Bot,让AI能够调用 + +## 架构说明 + +``` +┌─────────────────────────────────────────┐ +│ MoFox Bot AI系统 │ +│ ┌───────────────────────────────────┐ │ +│ │ AI决策层 (ToolExecutor) │ │ +│ │ - 分析用户请求 │ │ +│ │ - 决定调用哪些工具 │ │ +│ └───────────────┬───────────────────┘ │ +│ │ │ +│ ┌───────────────▼───────────────────┐ │ +│ │ 工具注册表 (ComponentRegistry) │ │ +│ │ - Bot内置工具 │ │ +│ │ - MCP动态工具 ✨ │ │ +│ └───────────────┬───────────────────┘ │ +│ │ │ +│ ┌───────────────▼───────────────────┐ │ +│ │ MCP工具提供器插件 │ │ +│ │ - 连接MCP服务器 │ │ +│ │ - 动态注册工具 │ │ +│ └───────────────┬───────────────────┘ │ +└──────────────────┼───────────────────────┘ + │ + ┌──────────────▼──────────────┐ + │ MCP连接器 │ + │ - tools/list │ + │ - tools/call │ + │ - resources/list (未来) │ + └──────────────┬──────────────┘ + │ + ┌──────────────▼──────────────┐ + │ MCP服务器 │ + │ - 文件系统工具 │ + │ - Git工具 │ + │ - 数据库工具 │ + │ - 自定义工具... │ + └─────────────────────────────┘ +``` + +## 完整配置步骤 + +### 步骤1: 启动MCP服务器 + +首先你需要一个运行中的MCP服务器。这里以官方的文件系统MCP服务器为例: + +```bash +# 安装MCP服务器(以filesystem为例) +npm install -g @modelcontextprotocol/server-filesystem + +# 启动服务器 +mcp-server-filesystem --port 3000 /path/to/allowed/directory +``` + +或使用其他MCP服务器: +- **Git MCP**: 提供Git操作工具 +- **数据库MCP**: 提供数据库查询工具 +- **自定义MCP服务器**: 你自己开发的MCP服务器 + +### 步骤2: 配置MCP工具提供器插件 + +编辑配置文件 `config/plugins/mcp_tools_provider.toml`: + +```toml +[plugin] +enabled = true # 启用插件 + +# 配置MCP服务器 +[[mcp_servers]] +name = "filesystem" # 服务器标识名 +url = "http://localhost:3000" # MCP服务器地址 +api_key = "" # API密钥(如果需要) +timeout = 30 # 超时时间 +enabled = true # 是否启用 + +# 可以配置多个MCP服务器 +[[mcp_servers]] +name = "git" +url = "http://localhost:3001" +enabled = true +``` + +### 步骤3: 启动Bot + +```bash +python bot.py +``` + +启动后,你会在日志中看到: + +``` +[INFO] MCP工具提供器插件启动中... +[INFO] 发现 1 个MCP服务器配置 +[INFO] 正在连接MCP服务器: filesystem (http://localhost:3000) +[INFO] 从MCP服务器 'filesystem' 获取到 5 个工具 +[INFO] ✓ 已注册MCP工具: filesystem_read_file +[INFO] ✓ 已注册MCP工具: filesystem_write_file +[INFO] ✓ 已注册MCP工具: filesystem_list_directory +... +[INFO] MCP工具提供器插件启动完成,共注册 5 个工具 +``` + +### 步骤4: AI自动调用MCP工具 + +现在AI可以自动发现并调用这些工具!例如: + +**用户**: "帮我读取项目根目录下的README.md文件" + +**AI决策过程**: +1. 分析用户请求 → 需要读取文件 +2. 查找可用工具 → 发现 `filesystem_read_file` +3. 调用工具 → `filesystem_read_file(path="README.md")` +4. 获取结果 → 文件内容 +5. 生成回复 → "README.md的内容是..." + +## 工具命名规则 + +MCP工具会自动添加服务器名前缀,避免冲突: + +- 原始工具名: `read_file` +- 注册后: `filesystem_read_file` + +如果有多个MCP服务器提供相同名称的工具,它们会被区分开: +- 服务器A: `serverA_search` +- 服务器B: `serverB_search` + +## 配置示例 + +### 示例1: 本地文件操作 + +```toml +[[mcp_servers]] +name = "local_fs" +url = "http://localhost:3000" +enabled = true +``` + +**可用工具**: +- `local_fs_read_file` - 读取文件 +- `local_fs_write_file` - 写入文件 +- `local_fs_list_directory` - 列出目录 + +### 示例2: Git操作 + +```toml +[[mcp_servers]] +name = "git" +url = "http://localhost:3001" +enabled = true +``` + +**可用工具**: +- `git_status` - 查看Git状态 +- `git_commit` - 提交更改 +- `git_log` - 查看提交历史 + +### 示例3: 多服务器配置 + +```toml +[[mcp_servers]] +name = "filesystem" +url = "http://localhost:3000" +enabled = true + +[[mcp_servers]] +name = "database" +url = "http://localhost:3002" +api_key = "db-secret-key" +enabled = true + +[[mcp_servers]] +name = "api_tools" +url = "https://mcp.example.com" +api_key = "your-api-key" +timeout = 60 +enabled = true +``` + +## 开发自定义MCP服务器 + +你可以开发自己的MCP服务器来提供自定义工具: + +```javascript +// 简单的MCP服务器示例 (Node.js) +const express = require('express'); +const app = express(); + +app.use(express.json()); + +// 列出工具 +app.post('/tools/list', (req, res) => { + res.json({ + tools: [ + { + name: 'custom_tool', + description: '自定义工具描述', + inputSchema: { + type: 'object', + properties: { + param1: { + type: 'string', + description: '参数1' + } + }, + required: ['param1'] + } + } + ] + }); +}); + +// 执行工具 +app.post('/tools/call', async (req, res) => { + const { name, arguments: args } = req.body; + + if (name === 'custom_tool') { + // 执行你的逻辑 + const result = await doSomething(args.param1); + + res.json({ + content: [ + { + type: 'text', + text: result + } + ] + }); + } +}); + +app.listen(3000, () => { + console.log('MCP服务器运行在 http://localhost:3000'); +}); +``` + +## 常见问题 + +### Q: MCP服务器连接失败? + +**检查**: +1. MCP服务器是否正在运行 +2. URL配置是否正确 +3. 防火墙是否阻止连接 +4. 查看日志中的具体错误信息 + +### Q: 工具注册成功但AI不调用? + +**原因**: +- 工具描述不够清晰 +- 参数定义不明确 + +**解决**: +在MCP服务器端优化工具的`description`和`inputSchema` + +### Q: 如何禁用某个MCP服务器? + +在配置中设置: +```toml +[[mcp_servers]] +enabled = false # 禁用 +``` + +### Q: 如何查看已注册的MCP工具? + +查看启动日志,或在Bot运行时检查组件注册表。 + +## MCP协议规范 + +MCP服务器必须实现以下端点: + +### 1. POST /tools/list +列出所有可用工具 + +**响应**: +```json +{ + "tools": [ + { + "name": "tool_name", + "description": "工具描述", + "inputSchema": { + "type": "object", + "properties": { ... }, + "required": [...] + } + } + ] +} +``` + +### 2. POST /tools/call +执行工具 + +**请求**: +```json +{ + "name": "tool_name", + "arguments": { ... } +} +``` + +**响应**: +```json +{ + "content": [ + { + "type": "text", + "text": "执行结果" + } + ] +} +``` + +## 高级功能 + +### 动态刷新工具列表 + +工具列表默认缓存5分钟。如果MCP服务器更新了工具,Bot会自动在下次缓存过期后刷新。 + +### 错误处理 + +MCP工具调用失败时,会返回错误信息给AI,AI可以据此做出相应处理或提示用户。 + +### 性能优化 + +- 工具列表有缓存机制 +- 支持并发工具调用 +- 自动重试机制 + +## 相关文档 + +- [MCP SSE使用指南](./MCP_SSE_USAGE.md) +- [MCP协议官方文档](https://github.com/anthropics/mcp) +- [插件开发文档](../README.md) + +## 更新日志 + +### v1.0.0 (2025-10-05) +- ✅ 完整的MCP工具集成 +- ✅ 动态工具注册 +- ✅ 多服务器支持 +- ✅ 自动错误处理 + +--- + +**集成状态**: ✅ 生产就绪 +**版本**: v1.0.0 +**更新时间**: 2025-10-05 diff --git a/src/chat/memory_system/memory_system.py b/src/chat/memory_system/memory_system.py index a0c08c56e..e2fd710e8 100644 --- a/src/chat/memory_system/memory_system.py +++ b/src/chat/memory_system/memory_system.py @@ -1111,7 +1111,6 @@ class MemorySystem: from src.chat.message_receive.chat_stream import get_chat_manager chat_manager = get_chat_manager() - # ChatManager.get_stream 是异步方法,需要 await,否则会产生 "coroutine was never awaited" 警告 chat_stream = await chat_manager.get_stream(stream_id) if chat_stream and hasattr(chat_stream, "context_manager"): history_limit = self._determine_history_limit(context) diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index 054acce63..13eebb548 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -615,6 +615,7 @@ class ChatterActionManager: """禁用批量存储模式""" self._batch_storage_enabled = False self._current_chat_id = None + self._pending_actions = [] # 清空队列 logger.debug("已禁用批量存储模式") def add_action_to_batch(self, action_name: str, action_data: dict, thinking_id: str = "", diff --git a/src/chat/replyer/replyer_manager.py b/src/chat/replyer/replyer_manager.py index 3e11c8a2f..4f3f4f428 100644 --- a/src/chat/replyer/replyer_manager.py +++ b/src/chat/replyer/replyer_manager.py @@ -37,7 +37,6 @@ class ReplyerManager: target_stream = chat_stream if not target_stream: if chat_manager := get_chat_manager(): - # get_stream 为异步,需要等待 target_stream = await chat_manager.get_stream(stream_id) if not target_stream: diff --git a/src/config/api_ada_configs.py b/src/config/api_ada_configs.py index de7479efb..a57c1b264 100644 --- a/src/config/api_ada_configs.py +++ b/src/config/api_ada_configs.py @@ -12,8 +12,8 @@ class APIProvider(ValidatedConfigBase): name: str = Field(..., min_length=1, description="API提供商名称") base_url: str = Field(..., description="API基础URL") api_key: str | list[str] = Field(..., min_length=1, description="API密钥,支持单个密钥或密钥列表轮询") - client_type: Literal["openai", "gemini", "aiohttp_gemini"] = Field( - default="openai", description="客户端类型(如openai/google等,默认为openai)" + client_type: Literal["openai", "gemini", "aiohttp_gemini", "mcp_sse"] = Field( + default="openai", description="客户端类型(如openai/google/mcp_sse等,默认为openai)" ) max_retry: int = Field(default=2, ge=0, description="最大重试次数(单个模型API调用失败,最多重试的次数)") timeout: int = Field( diff --git a/src/main.py b/src/main.py index d3a2bc387..fae1ce5f4 100644 --- a/src/main.py +++ b/src/main.py @@ -339,6 +339,16 @@ MoFox_Bot(第三方修改版) # 处理所有缓存的事件订阅(插件加载完成后) event_manager.process_all_pending_subscriptions() + + # 初始化MCP工具提供器 + try: + mcp_config = global_config.get("mcp_servers", []) + if mcp_config: + from src.plugin_system.utils.mcp_tool_provider import mcp_tool_provider + await mcp_tool_provider.initialize(mcp_config) + logger.info("MCP工具提供器初始化成功") + except Exception as e: + logger.info(f"MCP工具提供器未配置或初始化失败: {e}") # 初始化表情管理器 get_emoji_manager().initialize() diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index c3eefe6ca..803a2d739 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -9,17 +9,18 @@ """ import traceback -from typing import Any +from typing import TYPE_CHECKING, Any from rich.traceback import install from src.chat.message_receive.chat_stream import ChatStream -from src.chat.replyer.default_generator import DefaultReplyer -from src.chat.replyer.replyer_manager import replyer_manager from src.chat.utils.utils import process_llm_response from src.common.logger import get_logger from src.plugin_system.base.component_types import ActionInfo +if TYPE_CHECKING: + from src.chat.replyer.default_generator import DefaultReplyer + install(extra_lines=3) # 日志记录器 @@ -35,7 +36,7 @@ async def get_replyer( chat_stream: ChatStream | None = None, chat_id: str | None = None, request_type: str = "replyer", -) -> DefaultReplyer | None: +) -> Any | None: """获取回复器对象 优先使用chat_stream,如果没有则使用chat_id直接查找。 @@ -56,6 +57,8 @@ async def get_replyer( raise ValueError("chat_stream 和 chat_id 不可均为空") try: logger.debug(f"[GeneratorAPI] 正在获取回复器,chat_id: {chat_id}, chat_stream: {'有' if chat_stream else '无'}") + # 动态导入避免循环依赖 + from src.chat.replyer.replyer_manager import replyer_manager return await replyer_manager.get_replyer( chat_stream=chat_stream, chat_id=chat_id, diff --git a/src/plugin_system/apis/tool_api.py b/src/plugin_system/apis/tool_api.py index 6b949b2e5..662a3693b 100644 --- a/src/plugin_system/apis/tool_api.py +++ b/src/plugin_system/apis/tool_api.py @@ -17,7 +17,12 @@ def get_tool_instance(tool_name: str) -> BaseTool | None: plugin_config = None tool_class: type[BaseTool] = component_registry.get_component_class(tool_name, ComponentType.TOOL) # type: ignore - return tool_class(plugin_config) if tool_class else None + if tool_class: + return tool_class(plugin_config) + + # 如果不是常规工具,检查是否是MCP工具 + # MCP工具不需要返回实例,会在execute_tool_call中特殊处理 + return None def get_llm_available_tool_definitions(): @@ -29,4 +34,16 @@ def get_llm_available_tool_definitions(): from src.plugin_system.core import component_registry llm_available_tools = component_registry.get_llm_available_tools() - return [(name, tool_class.get_tool_definition()) for name, tool_class in llm_available_tools.items()] + tool_definitions = [(name, tool_class.get_tool_definition()) for name, tool_class in llm_available_tools.items()] + + # 添加MCP工具 + try: + from src.plugin_system.utils.mcp_tool_provider import mcp_tool_provider + mcp_tools = mcp_tool_provider.get_mcp_tool_definitions() + tool_definitions.extend(mcp_tools) + if mcp_tools: + logger.debug(f"已添加 {len(mcp_tools)} 个MCP工具到可用工具列表") + except Exception as e: + logger.debug(f"获取MCP工具失败(可能未配置): {e}") + + return tool_definitions diff --git a/src/plugin_system/core/tool_use.py b/src/plugin_system/core/tool_use.py index e74ded8ab..62b138d8f 100644 --- a/src/plugin_system/core/tool_use.py +++ b/src/plugin_system/core/tool_use.py @@ -279,6 +279,23 @@ class ToolExecutor: logger.info( f"{self.log_prefix} 正在执行工具: [bold green]{function_name}[/bold green] | 参数: {function_args}" ) + + # 检查是否是MCP工具 + try: + from src.plugin_system.utils.mcp_tool_provider import mcp_tool_provider + if function_name in mcp_tool_provider.mcp_tools: + logger.info(f"{self.log_prefix}执行MCP工具: {function_name}") + result = await mcp_tool_provider.call_mcp_tool(function_name, function_args) + return { + "tool_call_id": tool_call.call_id, + "role": "tool", + "name": function_name, + "type": "function", + "content": result.get("content", ""), + } + except Exception as e: + logger.debug(f"检查MCP工具时出错: {e}") + function_args["llm_called"] = True # 标记为LLM调用 # 检查是否是二步工具的第二步调用 diff --git a/src/plugin_system/utils/mcp_connector.py b/src/plugin_system/utils/mcp_connector.py new file mode 100644 index 000000000..2cb065918 --- /dev/null +++ b/src/plugin_system/utils/mcp_connector.py @@ -0,0 +1,235 @@ +""" +MCP (Model Context Protocol) 连接器 +负责连接MCP服务器,获取和执行工具 +""" + +import asyncio +from typing import Any + +import aiohttp +import orjson + +from src.common.logger import get_logger + +logger = get_logger("MCP连接器") + + +class MCPConnector: + """MCP服务器连接器""" + + def __init__(self, server_url: str, api_key: str | None = None, timeout: int = 30): + """ + 初始化MCP连接器 + + Args: + server_url: MCP服务器URL + api_key: API密钥(可选) + timeout: 超时时间(秒) + """ + self.server_url = server_url.rstrip("/") + self.api_key = api_key + self.timeout = timeout + self._session: aiohttp.ClientSession | None = None + self._tools_cache: dict[str, dict[str, Any]] = {} + self._cache_timestamp: float = 0 + self._cache_ttl: int = 300 # 工具列表缓存5分钟 + + async def _get_session(self) -> aiohttp.ClientSession: + """获取或创建aiohttp会话""" + if self._session is None or self._session.closed: + timeout = aiohttp.ClientTimeout(total=self.timeout) + self._session = aiohttp.ClientSession(timeout=timeout) + return self._session + + async def close(self): + """关闭连接""" + if self._session and not self._session.closed: + await self._session.close() + + def _build_headers(self) -> dict[str, str]: + """构建请求头""" + headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + if self.api_key: + headers["Authorization"] = f"Bearer {self.api_key}" + return headers + + async def list_tools(self, force_refresh: bool = False) -> dict[str, dict[str, Any]]: + """ + 获取MCP服务器提供的工具列表 + + Args: + force_refresh: 是否强制刷新缓存 + + Returns: + Dict[str, Dict]: 工具字典,key为工具名,value为工具定义 + """ + import time + + # 检查缓存 + if not force_refresh and self._tools_cache and (time.time() - self._cache_timestamp) < self._cache_ttl: + logger.debug("使用缓存的MCP工具列表") + return self._tools_cache + + logger.info(f"正在从MCP服务器获取工具列表: {self.server_url}") + + try: + session = await self._get_session() + url = f"{self.server_url}/tools/list" + + async with session.post(url, headers=self._build_headers(), json={}) as response: + if response.status != 200: + error_text = await response.text() + logger.error(f"获取MCP工具列表失败: HTTP {response.status} - {error_text}") + return {} + + data = await response.json() + + # 解析工具列表 + tools = {} + tool_list = data.get("tools", []) + + for tool_def in tool_list: + tool_name = tool_def.get("name") + if not tool_name: + continue + + tools[tool_name] = { + "name": tool_name, + "description": tool_def.get("description", ""), + "input_schema": tool_def.get("inputSchema", {}), + } + + logger.info(f"成功获取 {len(tools)} 个MCP工具") + self._tools_cache = tools + self._cache_timestamp = time.time() + + return tools + + except aiohttp.ClientError as e: + logger.error(f"连接MCP服务器失败: {e}") + return {} + except Exception as e: + logger.error(f"获取MCP工具列表时发生错误: {e}") + return {} + + async def call_tool(self, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]: + """ + 调用MCP服务器上的工具 + + Args: + tool_name: 工具名称 + arguments: 工具参数 + + Returns: + Dict: 工具执行结果 + """ + logger.info(f"调用MCP工具: {tool_name}") + logger.debug(f"工具参数: {arguments}") + + try: + session = await self._get_session() + url = f"{self.server_url}/tools/call" + + payload = {"name": tool_name, "arguments": arguments} + + async with session.post(url, headers=self._build_headers(), json=payload) as response: + if response.status != 200: + error_text = await response.text() + logger.error(f"MCP工具调用失败: HTTP {response.status} - {error_text}") + return { + "success": False, + "error": f"HTTP {response.status}: {error_text}", + "content": f"调用MCP工具 {tool_name} 失败", + } + + result = await response.json() + + # 提取内容 + content = result.get("content", []) + if isinstance(content, list) and len(content) > 0: + # MCP返回的是content数组 + text_content = [] + for item in content: + if isinstance(item, dict): + if item.get("type") == "text": + text_content.append(item.get("text", "")) + else: + text_content.append(str(item)) + + result_text = "\n".join(text_content) if text_content else str(content) + else: + result_text = str(content) + + logger.info(f"MCP工具 {tool_name} 执行成功") + return {"success": True, "content": result_text, "raw_result": result} + + except aiohttp.ClientError as e: + logger.error(f"调用MCP工具失败(网络错误): {e}") + return {"success": False, "error": str(e), "content": f"网络错误:无法调用工具 {tool_name}"} + except Exception as e: + logger.error(f"调用MCP工具时发生错误: {e}") + return {"success": False, "error": str(e), "content": f"调用工具 {tool_name} 时发生错误"} + + async def list_resources(self) -> list[dict[str, Any]]: + """ + 获取MCP服务器提供的资源列表 + + Returns: + List[Dict]: 资源列表 + """ + logger.info(f"正在从MCP服务器获取资源列表: {self.server_url}") + + try: + session = await self._get_session() + url = f"{self.server_url}/resources/list" + + async with session.post(url, headers=self._build_headers(), json={}) as response: + if response.status != 200: + error_text = await response.text() + logger.error(f"获取MCP资源列表失败: HTTP {response.status} - {error_text}") + return [] + + data = await response.json() + resources = data.get("resources", []) + + logger.info(f"成功获取 {len(resources)} 个MCP资源") + return resources + + except Exception as e: + logger.error(f"获取MCP资源列表时发生错误: {e}") + return [] + + async def read_resource(self, resource_uri: str) -> dict[str, Any]: + """ + 读取MCP资源 + + Args: + resource_uri: 资源URI + + Returns: + Dict: 资源内容 + """ + logger.info(f"读取MCP资源: {resource_uri}") + + try: + session = await self._get_session() + url = f"{self.server_url}/resources/read" + + payload = {"uri": resource_uri} + + async with session.post(url, headers=self._build_headers(), json=payload) as response: + if response.status != 200: + error_text = await response.text() + logger.error(f"读取MCP资源失败: HTTP {response.status} - {error_text}") + return {"success": False, "error": error_text} + + result = await response.json() + logger.info(f"成功读取MCP资源: {resource_uri}") + return {"success": True, "content": result.get("contents", [])} + + except Exception as e: + logger.error(f"读取MCP资源时发生错误: {e}") + return {"success": False, "error": str(e)} diff --git a/src/plugin_system/utils/mcp_tool_provider.py b/src/plugin_system/utils/mcp_tool_provider.py new file mode 100644 index 000000000..ad306ee68 --- /dev/null +++ b/src/plugin_system/utils/mcp_tool_provider.py @@ -0,0 +1,174 @@ +""" +MCP工具提供器 - 简化版 +直接集成到工具系统,无需复杂的插件架构 +""" + +import asyncio +from typing import Any + +from src.common.logger import get_logger +from src.plugin_system.utils.mcp_connector import MCPConnector + +logger = get_logger("MCP工具提供器") + + +class MCPToolProvider: + """MCP工具提供器单例""" + + _instance = None + _initialized = False + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + if not MCPToolProvider._initialized: + self.connectors: dict[str, MCPConnector] = {} + self.mcp_tools: dict[str, dict[str, Any]] = {} + """格式: {tool_full_name: {"connector": connector, "original_name": name, "definition": def}}""" + MCPToolProvider._initialized = True + + async def initialize(self, mcp_servers: list[dict]): + """ + 初始化MCP服务器连接 + + Args: + mcp_servers: MCP服务器配置列表 + """ + logger.info(f"初始化MCP工具提供器,共{len(mcp_servers)}个服务器") + + for server_config in mcp_servers: + await self._connect_server(server_config) + + logger.info(f"MCP工具提供器初始化完成,共注册{len(self.mcp_tools)}个工具") + + async def _connect_server(self, config: dict): + """连接单个MCP服务器""" + name = config.get("name", "unnamed") + url = config.get("url") + api_key = config.get("api_key") + enabled = config.get("enabled", True) + + if not enabled or not url: + return + + logger.info(f"连接MCP服务器: {name} ({url})") + + connector = MCPConnector(url, api_key, config.get("timeout", 30)) + self.connectors[name] = connector + + try: + tools = await connector.list_tools() + + for tool_name, tool_def in tools.items(): + # 使用服务器名作前缀 + full_name = f"{name}_{tool_name}" + self.mcp_tools[full_name] = { + "connector": connector, + "original_name": tool_name, + "definition": tool_def, + "server_name": name, + } + + logger.info(f"从{name}获取{len(tools)}个工具") + + except Exception as e: + logger.error(f"连接MCP服务器{name}失败: {e}") + + def get_mcp_tool_definitions(self) -> list[tuple[str, dict[str, Any]]]: + """ + 获取所有MCP工具定义(适配Bot的工具格式) + + Returns: + List[Tuple[str, dict]]: [(tool_name, tool_definition), ...] + """ + definitions = [] + + for full_name, tool_info in self.mcp_tools.items(): + mcp_def = tool_info["definition"] + input_schema = mcp_def.get("input_schema", {}) + + # 转换为Bot的工具格式 + bot_tool_def = { + "name": full_name, + "description": mcp_def.get("description", f"MCP工具: {full_name}"), + "parameters": self._convert_schema_to_parameters(input_schema), + } + + definitions.append((full_name, bot_tool_def)) + + return definitions + + def _convert_schema_to_parameters(self, schema: dict) -> list[tuple]: + """ + 将MCP的JSON Schema转换为Bot的参数格式 + + Args: + schema: MCP的inputSchema + + Returns: + Bot的parameters格式 + """ + from src.plugin_system.base.component_types import ToolParamType + + parameters = [] + properties = schema.get("properties", {}) + required = schema.get("required", []) + + type_mapping = { + "string": ToolParamType.STRING, + "integer": ToolParamType.INTEGER, + "number": ToolParamType.FLOAT, + "boolean": ToolParamType.BOOLEAN, + } + + for param_name, param_def in properties.items(): + param_type = type_mapping.get(param_def.get("type", "string"), ToolParamType.STRING) + description = param_def.get("description", "") + is_required = param_name in required + enum_values = param_def.get("enum", None) + + parameters.append((param_name, param_type, description, is_required, enum_values)) + + return parameters + + async def call_mcp_tool(self, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]: + """ + 调用MCP工具 + + Args: + tool_name: 工具全名(包含前缀) + arguments: 参数 + + Returns: + 工具执行结果 + """ + if tool_name not in self.mcp_tools: + return {"content": f"MCP工具{tool_name}不存在"} + + tool_info = self.mcp_tools[tool_name] + connector = tool_info["connector"] + original_name = tool_info["original_name"] + + logger.info(f"调用MCP工具: {tool_name}") + + result = await connector.call_tool(original_name, arguments) + + if result.get("success"): + return {"content": result.get("content", "")} + else: + return {"content": f"工具执行失败: {result.get('error', '未知错误')}"} + + async def close(self): + """关闭所有连接""" + for name, connector in self.connectors.items(): + try: + await connector.close() + except Exception as e: + logger.error(f"关闭MCP连接{name}失败: {e}") + + +# 全局单例 +mcp_tool_provider = MCPToolProvider() diff --git a/src/plugins/built_in/affinity_flow_chatter/plan_executor.py b/src/plugins/built_in/affinity_flow_chatter/plan_executor.py index 10d44b1a8..91ea6ccc7 100644 --- a/src/plugins/built_in/affinity_flow_chatter/plan_executor.py +++ b/src/plugins/built_in/affinity_flow_chatter/plan_executor.py @@ -441,7 +441,7 @@ class ChatterPlanExecutor: # 通过 chat_id 获取真实的 chat_stream 对象 from src.plugin_system.apis.chat_api import get_chat_manager chat_manager = get_chat_manager() - chat_stream = chat_manager.get_stream(plan.chat_id) + chat_stream = await chat_manager.get_stream(plan.chat_id) if chat_stream: # 调用 action_manager 的批量存储 diff --git a/src/plugins/built_in/proactive_thinker/proacive_thinker_event.py b/src/plugins/built_in/proactive_thinker/proacive_thinker_event.py index 1899361c4..f7e1fb2d7 100644 --- a/src/plugins/built_in/proactive_thinker/proacive_thinker_event.py +++ b/src/plugins/built_in/proactive_thinker/proacive_thinker_event.py @@ -11,7 +11,7 @@ from src.common.logger import get_logger from src.config.config import global_config from src.manager.async_task_manager import AsyncTask, async_task_manager from src.plugin_system import BaseEventHandler, EventType -from src.plugin_system.apis import chat_api, person_api +from src.plugin_system.apis import chat_api, message_api, person_api from src.plugin_system.base.base_event import HandlerResult from .proactive_thinker_executor import ProactiveThinkerExecutor @@ -160,7 +160,9 @@ class ProactiveThinkingTask(AsyncTask): continue # 检查冷却时间 - time_since_last_active = time.time() - stream.last_active_time + recent_messages = await message_api.get_recent_messages(chat_id=stream.stream_id, limit=1,limit_mode="latest") + last_message_time = recent_messages[0]["time"] if recent_messages else stream.create_time + time_since_last_active = time.time() - last_message_time if time_since_last_active > next_interval: logger.info( f"【日常唤醒-私聊】聊天流 {stream.stream_id} 已冷却 {time_since_last_active:.2f} 秒,触发主动对话。" @@ -184,7 +186,9 @@ class ProactiveThinkingTask(AsyncTask): # 检查群聊是否在白名单内 if not enabled_groups or f"qq:{stream.group_info.group_id}" in enabled_groups: # 检查冷却时间 - time_since_last_active = time.time() - stream.last_active_time + recent_messages = await message_api.get_recent_messages(chat_id=stream.stream_id, limit=1) + last_message_time = recent_messages[0]["time"] if recent_messages else stream.create_time + time_since_last_active = time.time() - last_message_time if time_since_last_active > next_interval: logger.info( f"【日常唤醒-群聊】聊天流 {stream.stream_id} 已冷却 {time_since_last_active:.2f} 秒,触发主动对话。" diff --git a/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py b/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py index e651ecfd5..d9fb6d2a2 100644 --- a/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py +++ b/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py @@ -1,8 +1,10 @@ +import time from datetime import datetime from typing import Any import orjson +from src.chat.utils.chat_message_builder import build_readable_actions, get_actions_by_timestamp_with_chat from src.common.logger import get_logger from src.config.config import global_config, model_config from src.mood.mood_manager import mood_manager @@ -140,23 +142,20 @@ class ProactiveThinkerExecutor: else "今天没有日程安排。" ) - recent_messages = await message_api.get_recent_messages(stream.stream_id) + recent_messages = await message_api.get_recent_messages(stream.stream_id,limit=50,limit_mode="latest",hours=12) recent_chat_history = ( await message_api.build_readable_messages_to_str(recent_messages) if recent_messages else "无" ) - action_history_list = await database_api.db_query( - database_api.MODEL_MAPPING["ActionRecords"], - filters={"chat_id": stream_id, "action_name": "proactive_decision"}, - limit=3, - order_by=["-time"], - ) - action_history_context = ( - "\n".join([f"- {a['action_data']}" for a in action_history_list if isinstance(a, dict)]) - if isinstance(action_history_list, list) - else "无" + action_history_list = await get_actions_by_timestamp_with_chat( + chat_id=stream.stream_id, + timestamp_start=time.time() - 3600 * 24, #过去24小时 + timestamp_end=time.time(), + limit=7, ) + action_history_context = build_readable_actions(actions=action_history_list) + # 2. 构建基础上下文 mood_state = "暂时没有" if global_config.mood.enable_mood: diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 6db677b13..9e64beee6 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -603,4 +603,28 @@ enabled_group_chats = [] # 对于白名单中不活跃的私聊,是否允许进行一次“冷启动”问候 enable_cold_start = true # 冷启动后,该私聊的下一次主动思考需要等待的最小时间(秒) -cold_start_cooldown = 86400 # 默认24小时 \ No newline at end of file +cold_start_cooldown = 86400 # 默认24小时 + +# ===== MCP (Model Context Protocol) 工具服务器配置 ===== +# MCP允许连接外部工具服务器,AI可以调用这些工具来执行各种任务 +# 例如:文件操作、Git操作、数据库查询等 + +# 示例MCP服务器配置(需要取消注释才能启用) +#[[mcp_servers]] +#name = "filesystem" # 服务器名称,工具将以此为前缀(如 filesystem_read_file) +#url = "http://localhost:3000" # MCP服务器地址 +#api_key = "" # API密钥(如果服务器需要认证) +#timeout = 30 # 超时时间(秒) +#enabled = true # 是否启用此服务器 + +# 可以配置多个MCP服务器 +#[[mcp_servers]] +#name = "git_tools" +#url = "http://localhost:3001" +#enabled = true + +# 详细说明: +# 1. MCP服务器需要单独启动,Bot启动后会自动连接 +# 2. 每个服务器提供的工具会自动注册到Bot的工具系统 +# 3. AI会自动发现并在需要时调用这些工具 +# 4. 详细文档请参考: docs/MCP_TOOLS_INTEGRATION.md \ No newline at end of file diff --git a/template/model_config_template.toml b/template/model_config_template.toml index 69e992a96..b0858c6f4 100644 --- a/template/model_config_template.toml +++ b/template/model_config_template.toml @@ -30,6 +30,15 @@ max_retry = 2 timeout = 30 retry_interval = 10 +[[api_providers]] # MCP SSE协议支持(Model Context Protocol via Server-Sent Events) +name = "MCPProvider" +base_url = "https://your-mcp-server.com" # MCP服务器地址 +api_key = "your-mcp-api-key-here" +client_type = "mcp_sse" # 使用MCP SSE客户端 +max_retry = 2 +timeout = 60 # MCP流式请求可能需要更长超时时间 +retry_interval = 10 + # 内容混淆功能示例配置(可选) [[api_providers]] name = "ExampleProviderWithObfuscation" # 启用混淆功能的API提供商示例 @@ -121,6 +130,15 @@ api_provider = "SiliconFlow" price_in = 4.0 price_out = 16.0 +# MCP SSE模型示例配置 +#[[models]] +#model_identifier = "claude-3-5-sonnet-20241022" # 或其他支持MCP的模型 +#name = "mcp-claude-sonnet" +#api_provider = "MCPProvider" # 对应上面配置的MCP provider +#price_in = 3.0 +#price_out = 15.0 +#force_stream_mode = true # MCP SSE默认使用流式模式 + [model_task_config.utils] # 在麦麦的一些组件中使用的模型,例如表情包模块,取名模块,关系模块,是麦麦必须的模型 model_list = ["siliconflow-deepseek-ai/DeepSeek-V3.1-Terminus"] # 使用的模型列表,每个子项对应上面的模型名称(name) temperature = 0.2 # 模型温度,新V3建议0.1-0.3