Skip to content

MCP 与 Skills 扩展配置

本章目标:

  1. 讲清 extensions_config.json 的结构(mcpServers 映射与 skills 映射)及其字段语义,理解为什么这套「扩展配置」要独立于 config.yaml
  2. 掌握配置文件的四级解析优先级(显式参数 → 环境变量 → 项目根 → 兼容回退),以及与 config.yaml 的职责分工。
  3. 理解运行时通过 Gateway API(PUT /api/mcp/configPUT /api/skills/{name})更新配置后,如何落盘并触发跨进程缓存失效。

TL;DR

extensions_config.json 是 DeerFlow 的「可热更新扩展配置」,用一个 JSON 文件统一描述 MCP 服务器(mcpServers)和技能开关(skills)。它与定义系统骨架的 config.yaml 职责分离:config.yaml 描述模型/工具/沙箱等启动期结构,extensions_config.json 描述用户随时可增删的外部能力。Gateway API 写入该文件后,LangGraph 运行进程通过文件 mtime 比对自动失效 MCP 工具缓存与技能系统提示词缓存,无需重启。本章只讲配置层面;MCP/Skills 的运行机制见第 21 章第 22 章

Overview

为什么不把 MCP 和 skills 也塞进 config.yaml?核心原因是变更频率与责任主体不同

config.yaml 描述的是系统骨架:模型类路径、工具组、沙箱 provider、记忆参数等。这些字段在部署时确定,改动需要谨慎,通常由运维或开发者维护。而 MCP 服务器和技能开关是终端用户在运行时频繁增删的扩展能力——用户可能上午接入一个 GitHub MCP server,下午又关闭某个技能。把这类高频、用户驱动的状态独立成一个专用 JSON 文件,带来三个好处:

  1. 独立写入面:Gateway 的 PUT /api/mcp/configPUT /api/skills/{name} 只需序列化一个结构简单的 JSON 文件,不必触碰庞大且含敏感模型密钥的 config.yaml backend/app/gateway/routers/mcp.py:155
  2. 跨进程缓存可观测:扩展配置是「可选的」——找不到文件就返回空配置 backend/packages/harness/deerflow/config/extensions_config.py:137-139。Gateway 进程写文件,LangGraph 运行进程靠 mtime 比对发现变更 backend/packages/harness/deerflow/mcp/cache.py:49-51
  3. 格式贴合生态:mcpServers 这个 key 名(通过 pydantic alias 映射到 mcp_servers)与社区 MCP 客户端的约定一致 backend/packages/harness/deerflow/config/extensions_config.py:60-64

Architecture

扩展配置子系统由「数据模型」「路径解析」「单例缓存」「Gateway 写入端点」「跨进程失效」五块组成。

Source 列表:

Components / Subsystems

ExtensionsConfig 数据模型

顶层模型只有两个字段:mcp_servers(JSON 中写作 mcpServers,通过 pydantic alias 映射)和 skills。模型设置了 extra="allow"populate_by_name=True,因此既能接受 mcpServers 别名也能接受 mcp_servers,且未知字段(如示例文件中的 mcpInterceptors)不会报错而被保留 backend/packages/harness/deerflow/config/extensions_config.py:57-69

McpServerConfig 描述单个 MCP 服务器:enabled 默认 True;type 默认 stdio(另支持 ssehttp);stdio 用 command + args + env;sse/http 用 url + headers + 可选 oauth backend/packages/harness/deerflow/config/extensions_config.py:36-48

McpOAuthConfig 为 HTTP/SSE 传输提供 OAuth token 注入,支持 client_credentialsrefresh_token 两种 grant,并可配置 token 字段名与刷新提前量(refresh_skew_seconds 默认 60 秒)backend/packages/harness/deerflow/config/extensions_config.py:13-33

SkillStateConfig 极简,只有一个 enabled 布尔 backend/packages/harness/deerflow/config/extensions_config.py:51-54。技能的元数据(名称、描述、license)来自磁盘上的 SKILL.md,而非这里——这个文件只记开关状态。

环境变量解析

from_file() 读入 JSON 后会递归调用 resolve_env_variables():任何以 $ 开头的字符串值都被替换为同名环境变量;若环境变量不存在,则替换为空字符串(而非保留字面 $VAR,避免下游 MCP server 收到无效的 token)backend/packages/harness/deerflow/config/extensions_config.py:151-180。这与 config.yaml$ 解析行为一致,因此可以把 "GITHUB_TOKEN": "$GITHUB_TOKEN" 写进配置而不泄露密钥 extensions_config.example.json:26-28

单例缓存与失效函数

模块级 _extensions_config 单例由 get_extensions_config() 惰性构建 backend/packages/harness/deerflow/config/extensions_config.py:210-222。配套三个管理函数:reload_extensions_config() 重新从文件加载并替换缓存 backend/packages/harness/deerflow/config/extensions_config.py:225-240;reset_extensions_config() 清空缓存;set_extensions_config() 注入 mock(测试用)backend/packages/harness/deerflow/config/extensions_config.py:243-263

注意技能加载路径不依赖这个单例:load_skills() 每次调用都直接 ExtensionsConfig.from_file() 重新读盘并合并 enabled 状态,以便另一个进程的写入能立即被感知 backend/packages/harness/deerflow/skills/storage/skill_storage.py:231-238。技能默认启用规则:配置里没有该技能条目时,publiccustom 分类默认启用 backend/packages/harness/deerflow/config/extensions_config.py:200-204

Gateway 写入端点

PUT /api/mcp/config 接收 mcp_servers 映射,但写文件前会先读取当前配置以保留 skills 段,反之 PUT /api/skills/{name} 写文件时也保留 mcpServers 段——两个端点共享同一个文件,各自只改自己负责的部分 backend/app/gateway/routers/mcp.py:145-156 backend/app/gateway/routers/skills.py:324-333PUT /api/skills/{name} 还会额外调用 refresh_skills_system_prompt_cache_async() 失效系统提示词中的技能清单缓存 backend/app/gateway/routers/skills.py:336-337 backend/packages/harness/deerflow/agents/lead_agent/prompt.py:163-164

Data Flow

下图展示用户在 Gateway UI 关闭一个技能后,配置如何落盘并跨进程失效缓存。

跨进程失效的关键在 mcp/cache.py:Gateway 进程写文件后,LangGraph 运行进程并不会被主动通知。它在 get_cached_mcp_tools() 时调用 _is_cache_stale(),把缓存初始化时记录的 _config_mtime 与当前文件 mtime 比较,只要文件变新就 reset_mcp_tools_cache() 重新加载 backend/packages/harness/deerflow/mcp/cache.py:82-101。这就是为什么 PUT /api/mcp/config 的注释明确写「无需在此 reload/reset,LangGraph Server 会通过 mtime 自动检测」backend/app/gateway/routers/mcp.py:160-161

速查表

extensions_config.json 字段矩阵:

字段路径类型默认值说明Source
mcpServersobject{}MCP 服务器名 → 配置映射(别名 mcp_servers)extensions_config.py:60-64
mcpServers.*.enabledbooltrue是否启用该服务器extensions_config.py:39
mcpServers.*.typestrstdio传输类型:stdio / sse / httpextensions_config.py:40
mcpServers.*.commandstr|nullnullstdio 启动命令extensions_config.py:41
mcpServers.*.argslist[]stdio 命令参数extensions_config.py:42
mcpServers.*.envdict{}注入服务器的环境变量(支持 $VAR)extensions_config.py:43
mcpServers.*.urlstr|nullnullsse/http 服务器 URLextensions_config.py:44
mcpServers.*.headersdict{}sse/http HTTP 头extensions_config.py:45
mcpServers.*.oauthobject|nullnullHTTP/SSE 的 OAuth token 注入配置extensions_config.py:46
mcpServers.*.oauth.grant_typestrclient_credentialsOAuth grant:client_credentials / refresh_tokenextensions_config.py:18-21
mcpServers.*.oauth.refresh_skew_secondsint60过期前多少秒刷新 tokenextensions_config.py:31
mcpServers.*.descriptionstr""人类可读的服务器描述extensions_config.py:47
skillsobject{}技能名 → 状态映射extensions_config.py:65-68
skills.*.enabledbooltrue技能是否启用(无条目时 public/custom 默认启用)extensions_config.py:54
mcpInterceptors(额外字段)listextra="allow" 保留,运行机制见第 21 章extensions_config.example.json:1-4

Configuration

extensions_config.json 路径解析优先级

resolve_config_path() 按以下顺序查找,扩展配置本身是可选的,全部落空时返回 None backend/packages/harness/deerflow/config/extensions_config.py:71-122:

优先级来源行为Source
1显式 config_path 参数文件不存在则抛 FileNotFoundErrorextensions_config.py:95-99
2DEER_FLOW_EXTENSIONS_CONFIG_PATH 环境变量文件不存在则抛 FileNotFoundErrorextensions_config.py:100-104
3项目根的 extensions_config.json,回退 mcp_config.jsonexisting_project_file()project_root() 下查找extensions_config.py:106-108
4monorepo 兼容回退:backend/ 与仓库根下的 extensions_config.json / mcp_config.json依次探测,命中即返回extensions_config.py:110-119
5全部落空返回 None,from_file() 给出空配置extensions_config.py:121-139

其中第 3 步的「项目根」由 project_root() 决定:优先读环境变量 DEER_FLOW_PROJECT_ROOT,否则取当前工作目录 Path.cwd() backend/packages/harness/deerflow/config/runtime_paths.py:7-16

config.yaml 的职责分工

技能的「目录在哪、容器里挂载到哪」由 config.yamlskills 段(SkillsConfig)决定;技能的「哪些开/关」由 extensions_config.jsonskills 段决定。二者互补:

关注点文件字段Source
技能存储实现类config.yamlskills.useskills_config.py:19-22
技能目录主机路径config.yamlskills.path(默认项目根 skills/,可被 DEER_FLOW_SKILLS_PATH 覆盖)skills_config.py:23-59
技能容器挂载路径config.yamlskills.container_path(默认 /mnt/skills)skills_config.py:27-30
单个技能启用开关extensions_config.jsonskills.*.enabledextensions_config.py:51-54
MCP 服务器全部配置extensions_config.jsonmcpServers.*extensions_config.py:36-48

Common Pitfalls/Tips

References

页面关系
04-配置系统与AppConfig上位:config.yaml 主配置体系与 AppConfig,本章的扩展配置与之职责互补
05-模型配置与Model工厂同组:config.yaml 中模型配置的 $ 环境变量解析与本章一致
21-MCP集成下游:本章只讲 MCP 配置,服务器连接、工具加载、OAuth 注入的运行机制见此
22-技能系统下游:本章只讲技能开关配置,技能发现、解析、注入系统提示词的运行机制见此
09-Harness与App分层边界相关:配置模型在 harness 层、写端点在 app 层,跨进程失效设计与分层边界相关

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析