MaiBot/plugins/MaiBot_MCPBridgePlugin/DEVELOPMENT.md

16 KiB
Raw Blame History

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