mirror of https://github.com/Mai-with-u/MaiBot.git
16 KiB
16 KiB
MCP 桥接插件 - 开发文档
本文档面向 AI 助手或开发者进行插件开发/维护。
前置知识
本插件基于 MaiBot 插件系统开发,需要了解:
- MaiBot 插件框架:
BasePlugin,BaseTool,BaseCommand,BaseEventHandler - 配置系统:
ConfigField,config_schema - 组件注册:
component_registry.register_component()
详见项目根目录 .kiro/steering/plugin-dev.md。
版本历史
| 版本 | 主要功能 |
|---|---|
| v1.5.4 | 易用性优化:新增 MCP 服务器获取快捷入口 |
| v1.5.3 | 配置优化:新增智能心跳 WebUI 配置项 |
| v1.5.2 | 性能优化:智能心跳间隔,根据服务器稳定性动态调整 |
| v1.5.1 | 易用性优化:新增「快速添加服务器」表单式配置 |
| v1.5.0 | 性能优化:服务器并行连接,大幅减少启动时间 |
| v1.4.4 | 修复首次生成默认配置文件时多行字符串导致 TOML 解析失败 |
| v1.4.3 | 修复 WebUI 保存配置后多行字符串格式错误导致配置文件无法读取 |
| v1.4.2 | HTTP 鉴权头支持(headers 字段) |
| v1.4.0 | 工具禁用、调用追踪、缓存、权限控制、WebUI 易用性改进 |
| v1.3.0 | 结果后处理(LLM 摘要提炼) |
| v1.2.0 | Resources/Prompts 支持(实验性) |
| v1.1.x | 心跳检测、自动重连、调用统计、/mcp 命令 |
| v1.0.0 | 基础 MCP 桥接 |
项目结构
MCPBridgePlugin/
├── plugin.py # 主插件逻辑(1800+ 行)
├── mcp_client.py # MCP 客户端封装(800+ 行)
├── _manifest.json # 插件清单
├── config.example.toml # 配置示例
├── requirements.txt # 依赖:mcp>=1.0.0
├── README.md # 用户文档
└── DEVELOPMENT.md # 开发文档(本文件)
核心模块详解
1. mcp_client.py - MCP 客户端
负责与 MCP 服务器通信,可独立于 MaiBot 运行测试。
数据类
class TransportType(Enum):
STDIO = "stdio" # 本地进程
SSE = "sse" # Server-Sent Events
HTTP = "http" # HTTP
STREAMABLE_HTTP = "streamable_http" # HTTP Streamable(推荐)
@dataclass
class MCPServerConfig:
name: str # 服务器唯一标识
enabled: bool = True
transport: TransportType = TransportType.STDIO
command: str = "" # stdio: 启动命令
args: List[str] = field(default_factory=list) # stdio: 参数
env: Dict[str, str] = field(default_factory=dict) # stdio: 环境变量
url: str = "" # http/sse: 服务器 URL
@dataclass
class MCPToolInfo:
name: str # 工具原始名称
description: str
input_schema: Dict[str, Any] # JSON Schema
server_name: str
@dataclass
class MCPCallResult:
success: bool
content: str = ""
error: Optional[str] = None
duration_ms: float = 0.0
@dataclass
class MCPResourceInfo:
uri: str
name: str
description: str = ""
mime_type: Optional[str] = None
server_name: str = ""
@dataclass
class MCPPromptInfo:
name: str
description: str = ""
arguments: List[Dict[str, Any]] = field(default_factory=list)
server_name: str = ""
MCPClientSession
管理单个 MCP 服务器连接。
class MCPClientSession:
def __init__(self, config: MCPServerConfig): ...
async def connect(self) -> bool:
"""连接服务器,返回是否成功"""
async def disconnect(self) -> None:
"""断开连接"""
async def call_tool(self, tool_name: str, arguments: Dict) -> MCPCallResult:
"""调用工具"""
async def check_health(self) -> bool:
"""健康检查(用于心跳)"""
async def fetch_resources(self) -> bool:
"""获取资源列表"""
async def read_resource(self, uri: str) -> MCPCallResult:
"""读取资源"""
async def fetch_prompts(self) -> bool:
"""获取提示模板列表"""
async def get_prompt(self, name: str, arguments: Optional[Dict]) -> MCPCallResult:
"""获取提示模板"""
@property
def tools(self) -> List[MCPToolInfo]: ...
@property
def resources(self) -> List[MCPResourceInfo]: ...
@property
def prompts(self) -> List[MCPPromptInfo]: ...
@property
def is_connected(self) -> bool: ...
MCPClientManager
全局单例,管理多服务器。
class MCPClientManager:
def configure(self, settings: Dict) -> None:
"""配置超时、重试等参数"""
async def add_server(self, config: MCPServerConfig) -> bool:
"""添加并连接服务器"""
async def remove_server(self, server_name: str) -> bool:
"""移除服务器"""
async def reconnect_server(self, server_name: str) -> bool:
"""重连服务器"""
async def call_tool(self, tool_key: str, arguments: Dict) -> MCPCallResult:
"""调用工具,tool_key 格式: mcp_{server}_{tool}"""
async def start_heartbeat(self) -> None:
"""启动心跳检测"""
async def shutdown(self) -> None:
"""关闭所有连接"""
def get_status(self) -> Dict[str, Any]:
"""获取状态"""
def get_all_stats(self) -> Dict[str, Any]:
"""获取统计信息"""
def set_status_change_callback(self, callback: Callable) -> None:
"""设置状态变化回调"""
@property
def all_tools(self) -> Dict[str, Tuple[MCPToolInfo, MCPClientSession]]: ...
@property
def all_resources(self) -> Dict[str, Tuple[MCPResourceInfo, MCPClientSession]]: ...
@property
def all_prompts(self) -> Dict[str, Tuple[MCPPromptInfo, MCPClientSession]]: ...
@property
def disconnected_servers(self) -> List[str]: ...
# 全局单例
mcp_manager = MCPClientManager()
2. plugin.py - MaiBot 插件
v1.4.0 新增模块
# ============ 调用追踪 ============
@dataclass
class ToolCallRecord:
call_id: str # UUID
timestamp: float
tool_name: str
server_name: str
chat_id: str = ""
user_id: str = ""
user_query: str = ""
arguments: Dict = field(default_factory=dict)
raw_result: str = ""
processed_result: str = ""
duration_ms: float = 0.0
success: bool = True
error: str = ""
post_processed: bool = False
cache_hit: bool = False
class ToolCallTracer:
def configure(self, enabled: bool, max_records: int, log_enabled: bool, log_path: Path): ...
def record(self, record: ToolCallRecord) -> None: ...
def get_recent(self, n: int = 10) -> List[ToolCallRecord]: ...
def get_by_tool(self, tool_name: str) -> List[ToolCallRecord]: ...
def clear(self) -> None: ...
tool_call_tracer = ToolCallTracer()
# ============ 调用缓存 ============
@dataclass
class CacheEntry:
tool_name: str
args_hash: str # MD5(tool_name + sorted_json_args)
result: str
created_at: float
expires_at: float
hit_count: int = 0
class ToolCallCache:
def configure(self, enabled: bool, ttl: int, max_entries: int, exclude_tools: str): ...
def get(self, tool_name: str, args: Dict) -> Optional[str]: ...
def set(self, tool_name: str, args: Dict, result: str) -> None: ...
def clear(self) -> None: ...
def get_stats(self) -> Dict[str, Any]: ...
tool_call_cache = ToolCallCache()
# ============ 权限控制 ============
class PermissionChecker:
def configure(self, enabled: bool, default_mode: str, rules_json: str,
quick_deny_groups: str = "", quick_allow_users: str = ""): ...
def check(self, tool_name: str, chat_id: str, user_id: str, is_group: bool) -> bool: ...
def get_rules_for_tool(self, tool_name: str) -> List[Dict]: ...
permission_checker = PermissionChecker()
工具代理
class MCPToolProxy(BaseTool):
"""所有 MCP 工具的基类"""
# 类属性(动态子类覆盖)
name: str = ""
description: str = ""
parameters: List[Tuple] = []
available_for_llm: bool = True
# MCP 属性
_mcp_tool_key: str = ""
_mcp_original_name: str = ""
_mcp_server_name: str = ""
async def execute(self, function_args: Dict) -> Dict[str, Any]:
"""执行流程:
1. 权限检查 → 拒绝则返回错误
2. 缓存检查 → 命中则返回缓存
3. 调用 MCP 服务器
4. 存入缓存
5. 后处理(可选)
6. 记录追踪
7. 返回结果
"""
def create_mcp_tool_class(tool_key: str, tool_info: MCPToolInfo,
tool_prefix: str, disabled: bool = False) -> Type[MCPToolProxy]:
"""动态创建工具类"""
内置工具
class MCPStatusTool(BaseTool):
"""mcp_status - 查询状态/工具/资源/模板/统计/追踪/缓存"""
name = "mcp_status"
parameters = [
("query_type", STRING, "查询类型", False,
["status", "tools", "resources", "prompts", "stats", "trace", "cache", "all"]),
("server_name", STRING, "服务器名称", False, None),
]
class MCPReadResourceTool(BaseTool):
"""mcp_read_resource - 读取资源"""
name = "mcp_read_resource"
class MCPGetPromptTool(BaseTool):
"""mcp_get_prompt - 获取提示模板"""
name = "mcp_get_prompt"
命令
class MCPStatusCommand(BaseCommand):
"""处理 /mcp 命令"""
command_pattern = r"^[//]mcp(?:\s+(?P<subcommand>status|tools|stats|reconnect|trace|cache|perm))?(?:\s+(?P<arg>\S+))?$"
# 子命令处理
async def _handle_reconnect(self, server_name): ...
async def _handle_trace(self, arg): ...
async def _handle_cache(self, arg): ...
async def _handle_perm(self, arg): ...
事件处理器
class MCPStartupHandler(BaseEventHandler):
"""ON_START - 连接服务器、注册工具"""
event_type = EventType.ON_START
class MCPStopHandler(BaseEventHandler):
"""ON_STOP - 关闭连接"""
event_type = EventType.ON_STOP
主插件类
@register_plugin
class MCPBridgePlugin(BasePlugin):
plugin_name = "mcp_bridge_plugin"
python_dependencies = ["mcp"]
config_section_descriptions = {
"guide": "📖 快速入门",
"servers": "🔌 服务器配置",
"status": "📊 运行状态",
"plugin": "插件开关",
"settings": "⚙️ 高级设置",
"tools": "🔧 工具管理",
"permissions": "🔐 权限控制",
}
config_schema = {
"guide": { "quick_start": ConfigField(...) },
"plugin": { "enabled": ConfigField(...) },
"settings": {
# 基础:tool_prefix, connect_timeout, call_timeout, auto_connect, retry_*
# 心跳:heartbeat_enabled, heartbeat_interval, auto_reconnect, max_reconnect_attempts
# 高级:enable_resources, enable_prompts
# 后处理:post_process_*
# 追踪:trace_*
# 缓存:cache_*
},
"tools": { "tool_list", "disabled_tools" },
"permissions": { "perm_enabled", "perm_default_mode", "quick_deny_groups", "quick_allow_users", "perm_rules" },
"servers": { "list" },
"status": { "connection_status" },
}
def __init__(self):
# 配置 mcp_manager, tool_call_tracer, tool_call_cache, permission_checker
async def _async_connect_servers(self):
# 解析配置 → 连接服务器 → 注册工具(检查禁用列表)
def _update_status_display(self):
# 更新 WebUI 状态显示
def _update_tool_list_display(self):
# 更新工具清单显示
数据流
MaiBot 启动
│
▼
MCPBridgePlugin.__init__()
├─ mcp_manager.configure(settings)
├─ tool_call_tracer.configure(...)
├─ tool_call_cache.configure(...)
└─ permission_checker.configure(...)
│
▼
ON_START 事件 → MCPStartupHandler.execute()
│
▼
_async_connect_servers()
├─ 解析 servers.list JSON
├─ 遍历服务器配置
│ ├─ mcp_manager.add_server(config)
│ ├─ 获取工具列表
│ ├─ 检查 disabled_tools
│ └─ component_registry.register_component(tool_info, tool_class)
├─ _update_status_display()
└─ _update_tool_list_display()
│
▼
mcp_manager.start_heartbeat()
│
▼
LLM 调用工具 → MCPToolProxy.execute(function_args)
├─ 1. permission_checker.check() → 拒绝则返回错误
├─ 2. tool_call_cache.get() → 命中则跳到步骤 5
├─ 3. mcp_manager.call_tool()
├─ 4. tool_call_cache.set()
├─ 5. _post_process_result() (如果启用且超过阈值)
├─ 6. tool_call_tracer.record()
└─ 7. 返回 {"name": ..., "content": ...}
│
▼
ON_STOP 事件 → MCPStopHandler.execute()
│
▼
mcp_manager.shutdown()
mcp_tool_registry.clear()
配置项速查
settings(高级设置)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| tool_prefix | str | "mcp" | 工具名前缀 |
| connect_timeout | float | 30.0 | 连接超时(秒) |
| call_timeout | float | 60.0 | 调用超时(秒) |
| auto_connect | bool | true | 自动连接 |
| retry_attempts | int | 3 | 重试次数 |
| retry_interval | float | 5.0 | 重试间隔 |
| heartbeat_enabled | bool | true | 心跳检测 |
| heartbeat_interval | float | 60.0 | 心跳间隔 |
| auto_reconnect | bool | true | 自动重连 |
| max_reconnect_attempts | int | 3 | 最大重连次数 |
| enable_resources | bool | false | Resources 支持 |
| enable_prompts | bool | false | Prompts 支持 |
| post_process_enabled | bool | false | 结果后处理 |
| post_process_threshold | int | 500 | 后处理阈值 |
| trace_enabled | bool | true | 调用追踪 |
| trace_max_records | int | 100 | 追踪记录上限 |
| cache_enabled | bool | false | 调用缓存 |
| cache_ttl | int | 300 | 缓存 TTL |
| cache_max_entries | int | 200 | 最大缓存条目 |
permissions(权限控制)
| 配置项 | 说明 |
|---|---|
| perm_enabled | 启用权限控制 |
| perm_default_mode | allow_all / deny_all |
| quick_deny_groups | 禁用群列表(每行一个群号) |
| quick_allow_users | 管理员白名单(每行一个 QQ 号) |
| perm_rules | 高级规则 JSON |
扩展开发示例
添加新命令子命令
# 1. 修改 command_pattern
command_pattern = r"^[//]mcp(?:\s+(?P<subcommand>status|...|newcmd))?..."
# 2. 在 execute() 添加分支
if subcommand == "newcmd":
return await self._handle_newcmd(arg)
# 3. 实现处理方法
async def _handle_newcmd(self, arg: str = None):
# 处理逻辑
await self.send_text("结果")
return (True, None, True)
添加新配置项
# 1. config_schema 添加
"settings": {
"new_option": ConfigField(
type=bool,
default=False,
description="新选项说明",
label="🆕 新选项",
order=50,
),
}
# 2. 在 __init__ 或相应方法中读取
new_option = settings.get("new_option", False)
添加新的全局模块
# 1. 定义数据类和管理类
@dataclass
class NewRecord:
...
class NewManager:
def configure(self, ...): ...
def do_something(self, ...): ...
new_manager = NewManager()
# 2. 在 MCPBridgePlugin.__init__ 中配置
new_manager.configure(...)
# 3. 在 MCPToolProxy.execute() 中使用
result = new_manager.do_something(...)
调试
# 导入
from plugins.MCPBridgePlugin.mcp_client import mcp_manager
from plugins.MCPBridgePlugin.plugin import tool_call_tracer, tool_call_cache, permission_checker
# 检查状态
mcp_manager.get_status()
mcp_manager.get_all_stats()
# 追踪记录
tool_call_tracer.get_recent(10)
# 缓存状态
tool_call_cache.get_stats()
# 手动调用
result = await mcp_manager.call_tool("mcp_server_tool", {"arg": "value"})
依赖
- MaiBot >= 0.11.6
- Python >= 3.10
- mcp >= 1.0.0
许可证
AGPL-3.0