扩展 Skill 与 MCP
本页面向要往 CHARACTOID 里加 Skill 包或 MCP 服务器的开发者。产品概念见 扩展:Skill、Tool、MCP;这里写落地步骤、配置形状和失败怎么查。代码:agents/skills.py、agents/skill_parser.py、agents/skill_sources.py、agents/mcp_grants.py、integrations/mcp/、app/routers/skills.py、app/routers/mcp.py、app/startup/lifespan.py。
Skill 最小概念
Skill 是可以启用、停用、信任和分配工具的指令包。它适合表达某一类任务的操作规则,而不是保存敏感凭据。
解析器读到的常见字段:
name: example_skill
description: 处理示例任务
instructions: |
先读取状态,再决定是否执行修改。
缺参数就停,不要猜测本地路径。
tool_names:
- example_status
- example_apply实际 schema 以当前 Skill API 为准。非内置 Skill 应经过信任和权限检查,才能出现在某个角色的可见集合里。
Skill 只声明工具名。工具本身仍要在 ToolSpec 里存在(内置或 MCP 发现)。Skill 不会凭空创造执行函数。
Skill 来源
通常有三类来源:
- 内置:随仓库发布,默认可信,但仍按角色启用。
- 本地目录 / 安装包:需要信任标记,避免随便丢一个 YAML 就获得删库工具。
- 管理 API 安装:走
skill_install一类工具或路由,写进受管位置。
加载顺序和冲突策略以 skill_sources.py 为准。同名 Skill 不要靠“谁后加载谁赢”这种隐式行为;文档和 API 应能查出最终生效的是哪一份。
MCP 服务配置
配置文件默认是 data/mcp_servers.json。启动时 MCPManager 读取它并 connect_all(register=True)。
{
"name": "example",
"description": "本地示例服务",
"transport": "stdio",
"command": "python",
"args": ["server.py"],
"env": {},
"enabled": true
}传输:
| transport | 含义 | 风险 |
|---|---|---|
| stdio | 拉起本机进程,从标准输入输出说话 | 等于执行本机代码 |
| streamable_http | HTTP 流式传输 | 要校验地址和鉴权 |
| sse | Server-Sent Events | 旧路径,注意超时和断线 |
mcp_allow_arbitrary_stdio 允许任意命令时非常危险,只应在自己完全控制的本机开发环境打开。
客户端在 langchain-mcp-adapters 0.3.x 之后是无状态的:拉清单和调工具各开会话。不要以为管理器里有一条永远活着的连接。
超时与只读注解
integrations/mcp/client.py:
- 连接超时 90 秒(冷启动可能装依赖);
- 只读工具 20 秒;
- 变更工具 60 秒。
只读来自 MCP 注解 readOnlyHint / read_only_hint。没声明只读,就按变更工具处理:更长超时,并且默认需要确认。
超时返回结构化 JSON:{"status":"timeout","error":"MCP tool timed out after Ns"}。模型应据此停手,而不是换一种说法再打一次。
授权不是连接
连上 MCP 只是发现工具并注册 ToolSpec.server=该服务名。某个角色能不能用,还要看 mcp_grants 和能力分配。
推荐流程:
保存服务
→ 测试连接
→ 查看发现的工具与注解
→ 检查副作用分类
→ 限定允许角色
→ 启用
→ 从对话中调用
→ 断开时清理会话和子进程跳过“限定允许角色”会把外部工具变成全局能力。这和 CHARACTOID 的角色边界相反。
安全规则
- 不把 Token 写进仓库、Skill instructions 或公开事件;
- stdio 命令视为本机代码执行入口;
- 修改数据的 MCP 工具必须标记并确认;
- 设置连接和工具调用超时;
- 断开服务时清理会话和子进程;
- 不要把 MCP 返回的本地路径写进 StructuredHandoff。
调试
- 先看服务是否 enabled、进程是否还在;
- 再看工具清单是否注册进
tool_specs(); - 再看角色 grants;
- 再看这次 Run 的事件里工具名和超时;
- 最后才看 MCP 服务器自己的日志。
常见误判:服务连上了,但角色没授权;或者工具清单有了,但 requires_confirmation 让 Run 停在 waiting_approval,用户以为“没反应”。
相关页面
平台级全局 MCP 不参与按角色授权
角色侧 PUT /api/personas/{persona_id}/mcp-grants 会遍历 manager.list_configs()。若 GLOBAL_ALL in server.allowed_persona_ids,直接 continue:
- 这些服务器对所有角色可见;
- 不能通过某个角色的 grants PUT 把它们从全局集合里拿掉;
GET .../mcp-grants里global: true,authorized也为 true。
MCP 管理器未就绪时,角色 MCP 接口返回 503 MCP 管理器尚未就绪。先查 lifespan 有没有 connect_all,再查 grants。
把某个服务配成全局,等于放弃“按角色授权”的边界。只应留给真正的平台级工具,而不是某个角色的私有 MCP。
源码合同(中档补全)
ToolSpec 仍以 registry 为准
ToolSpec 的稳定字段是 name、specialist、tool、requires_confirmation、mutates_data、server。页面或旧笔记里多出来的字段不要写进新 Worker。specialist 必须是 canonical Worker 名,例如 knowledge_worker,不要写别名 knowledge。
默认 args schema 很窄:只有 request: string,additionalProperties: false。文件、路径、命令应走 StructuredHandoff.input_refs 和 Run 状态,禁止把宿主机 path / command / python / shell 放进 handoff。
MCP * 不能与具体角色共存
integrations/mcp/config.py:GLOBAL_ALL = "*"。
agents/assignment.py:next_allowed_persona_ids:
- 对
persona_id == "*"勾选 → 列表变成["*"];取消 →[] - 当前已是
*时再给某个角色勾选,仍保持["*"] - 当前已是
*时取消某一个角色:展开为「除该角色外的现有角色」,未来新角色默认关,除非再次显式*
sync_mcp_wildcard_policy 写明:Star 与具体 persona override 不得共存。全局授权后会清掉各角色残留 True/False,避免撤销后工具仍开、或全局授权后某角色被 False 挡住。
PUT /api/personas/{id}/mcp-grants:若服务器已含 GLOBAL_ALL,直接 continue,不能用某个角色的 PUT 拆掉全员授权。GET 此时 global: true 且 authorized: true。管理器未启动 → 503 MCP 管理器尚未就绪。
Skill zip:压缩包或解压后超过 25MB 都是 400。解压文件数大约 500 封顶。上传成功后默认不执行脚本;instructions 只进 system prompt,会占 Token。
MCP stdio 就是起子进程。断开或停用时应杀掉会话相关进程,不要把 MCP 返回的本机路径写进 StructuredHandoff。工具清单以 tool_specs() 为准,前端「插件已启用」不等于 Supervisor 能选到它。