Skip to content

扩展 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 是可以启用、停用、信任和分配工具的指令包。它适合表达某一类任务的操作规则,而不是保存敏感凭据。

解析器读到的常见字段:

yaml
name: example_skill
description: 处理示例任务
instructions: |
  先读取状态,再决定是否执行修改。
  缺参数就停,不要猜测本地路径。
tool_names:
  - example_status
  - example_apply

实际 schema 以当前 Skill API 为准。非内置 Skill 应经过信任和权限检查,才能出现在某个角色的可见集合里。

Skill 只声明工具名。工具本身仍要在 ToolSpec 里存在(内置或 MCP 发现)。Skill 不会凭空创造执行函数。

Skill 来源 ​

通常有三类来源:

  1. 内置:随仓库发布,默认可信,但仍按角色启用。
  2. 本地目录 / 安装包:需要信任标记,避免随便丢一个 YAML 就获得删库工具。
  3. 管理 API 安装:走 skill_install 一类工具或路由,写进受管位置。

加载顺序和冲突策略以 skill_sources.py 为准。同名 Skill 不要靠“谁后加载谁赢”这种隐式行为;文档和 API 应能查出最终生效的是哪一份。

MCP 服务配置 ​

配置文件默认是 data/mcp_servers.json。启动时 MCPManager 读取它并 connect_all(register=True)。

json
{
  "name": "example",
  "description": "本地示例服务",
  "transport": "stdio",
  "command": "python",
  "args": ["server.py"],
  "env": {},
  "enabled": true
}

传输:

transport含义风险
stdio拉起本机进程,从标准输入输出说话等于执行本机代码
streamable_httpHTTP 流式传输要校验地址和鉴权
sseServer-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 和能力分配。

推荐流程:

text
保存服务
→ 测试连接
→ 查看发现的工具与注解
→ 检查副作用分类
→ 限定允许角色
→ 启用
→ 从对话中调用
→ 断开时清理会话和子进程

跳过“限定允许角色”会把外部工具变成全局能力。这和 CHARACTOID 的角色边界相反。

安全规则 ​

  • 不把 Token 写进仓库、Skill instructions 或公开事件;
  • stdio 命令视为本机代码执行入口;
  • 修改数据的 MCP 工具必须标记并确认;
  • 设置连接和工具调用超时;
  • 断开服务时清理会话和子进程;
  • 不要把 MCP 返回的本地路径写进 StructuredHandoff。

调试 ​

  1. 先看服务是否 enabled、进程是否还在;
  2. 再看工具清单是否注册进 tool_specs();
  3. 再看角色 grants;
  4. 再看这次 Run 的事件里工具名和超时;
  5. 最后才看 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 能选到它。