Reactive Resume 的 dsh-plugin:把简历 MCP 服务接入 DeepSeek Harness 并注入编辑引导提示词
本文基于仓库中的 packages/dsh-plugin 及其文档 README.md 展开,讲清 dsh-plugin-reactive-resume 这个 DeepSeek Harness(DSH)插件是如何用一行配置把 Reactive Resume 的简历与求职申请数据接入 Agent 会话的:安装与配置方式、四个配置项的默认值与校验规则、dsh.bundle 声明如何让它“安装即挂载、缺密钥即静默”、注入系统提示词的编辑引导指南(RFC 6902 补丁语义、UUID 键控条目、锁定恢复流程)的完整内容,以及工具改名防护的测试机制。读完你可以直接在自己的 Harness profile 中安装并配置该插件,并理解它背后与 MCP 服务端 的协作关系。
一、插件定位:MCP 桥接 + 提示词贡献
packages/dsh-plugin/README.md 对插件的一句话定位是:把 Reactive Resume 连接到 DeepSeek Harness,让用户可以直接在一个 Harness 会话中读取、创建和编辑简历与求职申请(job applications)。
从源码结构看,这个插件的价值由两部分组成(见 src/index.ts 的 apply() 实现):
- 桥接 MCP 工具:把 Reactive Resume 对外发布的 MCP server(Streamable HTTP 传输)挂载到 Harness 的工具注册表
ctx.tools中,让模型可以看到并调用所有简历/申请相关工具; - 贡献系统提示词小节:向
ctx.systemPrompt注入一段“补丁编写指南”(patch guide),专门纠正模型在编辑简历时容易犯的错误——先读后改、按已发布的 schema 构造 RFC 6902 路径、用 UUID 键控的 section 条目、处理锁定简历。
README 特别指出,第一点你可以用一行原生的 @deepseek-ai/dsh-mcp-client 配置自己完成,但提示词小节的贡献做不到——这正是这个包存在的理由。
二、安装:一条命令,声明式自动挂载
安装命令(继承自 README.md):
dsh plugin --profile <name> add dsh-plugin-reactive-resume
安装行为的关键在于包的 package.json 中声明了 dsh.bundle:
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
这意味着 profile 会把这个包识别为一个 bundle 层(layer),dsh plugin add 时自动把 cordis.patch.yml 合并进 profile 的 bundle 栈。该补丁文件的内容只有一条 insert 指令:
- insert:
- id: reactive-resume
name: dsh-plugin-reactive-resume
config:
apiKey: !!js process.env.RXRESUME_API_KEY ?? ''
注意 ?? '':这保证了在安装但尚未配置密钥时,插件行加载后什么也不挂载,仅记录一条警告日志,因此“安装插件”永远不会把 profile 弄到无法启动(unbootable)的状态。这一点在 src/index.ts 的 apply() 开头得到印证:
if (config.apiKey === "") {
ctx.logger.warn("no apiKey configured — set one at %s/dashboard/settings/api-keys to enable the tools", config.url);
return;
}
同时 src/config.ts 中 apiKey 刻意没有 .required(),源码注释解释了原因:bundle patch 在安装时就挂载了这行配置,所以缺失密钥必须是“惰性空操作”,而不是让整个 profile 在启动时因校验错误而崩溃。
三、配置:API Key、选项表与自托管
3.1 获取并注入 API Key
按照 README.md 的说明,在 https://rxresu.me/dashboard/settings/api-keys(自托管则为你实例的同路径)铸造一个 API key,并把它导出为环境变量 RXRESUME_API_KEY——bundle patch 读取的就是这个变量。
如果要显式设置密钥或覆盖其他任意选项,从 profile 自己的 cordis.patch.yml 中按 id 打补丁(同一行“最后写入生效”):
- id: reactive-resume
config:
apiKey: !!js process.env.RXRESUME_API_KEY
3.2 全部配置项
完整继承 README 的选项表,并结合 src/config.ts 的 schemastery 校验 schema 补充了约束细节:
| Key | 默认值 | 描述 | 源码中的校验规则 |
|---|---|---|---|
apiKey |
''(空字符串) |
来自 <url>/dashboard/settings/api-keys 的 API key。为空时不挂载任何工具,仅记录警告 |
z.string().default(""),刻意不加 .required() |
url |
https://rxresu.me |
你的实例 origin。自托管时设置此项 | z.string().default("https://rxresu.me");apply() 会先执行 url.replace(/\/+$/, "") 去掉尾部斜杠 |
serverName |
resume |
工具命名空间。工具到达模型时的名字为 mcp__<serverName>__<rawName> |
必须匹配正则 /^[A-Za-z0-9_-]{1,32}$/,且需在各存活的 MCP 实例间唯一;非法值在解析期(apply 运行前)即被拒绝 |
toolCallTimeoutMs |
60000 |
单次工具调用的超时时间(毫秒) | z.natural(),必须为自然数 |
配置解析使用 @deepseek-ai/schemastery 完成;src/index.ts 同时 re-export 了接口和 schema 两种形态的 Config,Cordis 在插件启动前会读取 schema 导出做配置校验。
3.3 自托管实例
- id: reactive-resume
config:
apiKey: !!js process.env.RXRESUME_API_KEY
url: http://localhost:3000
这与 cordis.patch.yml 顶部注释给出的示例一致。url 接受任意 origin,本地开发实例可原样工作。
四、apply() 的完整调用链:从配置到挂载
当 apiKey 非空时,src/index.ts 的 apply() 依次做两件事:
第一步:挂载 MCP 桥接。 通过 ctx.plugin(mcpClient, ...) 拉起 @deepseek-ai/dsh-mcp-client:
await ctx.plugin(mcpClient, {
transport: "streamable-http",
serverName: config.serverName,
url: `${origin}/mcp`,
headers: { "x-api-key": config.apiKey },
toolCallTimeoutMs: config.toolCallTimeoutMs,
failOnStartupError: true,
});
几个值得注意的实现细节:
- 端点固定为实例 origin 下的
/mcp,传输方式为streamable-http; - 鉴权走
x-api-key请求头。服务端对应实现在 apps/server/src/mcp/auth.ts:authenticateRequest()先尝试Authorization: Bearer的 OAuth token 校验,失败或无 Bearer 时回退检查x-api-key头并调用auth.api.verifyApiKey验证,两者皆不通过则抛出AuthError。API key 路径无需任何交互式 OAuth 流程; failOnStartupError: true让“错误的 API key”在激活阶段就变成响亮的启动失败,而不是变成一批在调用时才静默失败的工具。
第二步:注入提示词小节。
ctx.systemPrompt.section({
name: `reactive-resume:${config.serverName}`,
order: 150,
text: buildPatchGuide(config.serverName),
});
小节名带上 serverName 做命名空间隔离,是为了支持同时挂载多个实例(例如官方账号 + 一个自托管账号并存)时不产生小节名冲突。order: 150 落在 DSH 提示词装配约定的工具指引区间(100–199)内。
五、提示词引导指南:告诉模型“简历编辑的正确姿势”
buildPatchGuide(serverName)(src/prompt.ts)是插件的实质内容。它以 ## Reactive Resume 开头,声明这些工具操作的是用户真实、存活的简历和求职申请,任何修改立即生效并在其账户中可见。指南全文按四个主题组织,其中每个工具名都按模型实际看到的形式(`mcp__<serverName>__<rawName>`)动态生成:
先读后写(Reading before writing)
- 先调用
mcp__<ns>__list_resumes发现简历 ID;ID 是 UUID,绝不是标题或 slug; - 任何编辑前必须先调用
mcp__<ns>__read_resume;“绝不能补丁一个本会话尚未读过的简历”; - 若某次调用因 “not found” 失败,应重跑
list_resumes而不是猜测 ID。
编辑(Editing)
mcp__<ns>__apply_resume_patch接收 RFC 6902 JSON Patch 操作,作用于简历数据文档;- 路径必须从本会话已读到的简历构造,不得凭空猜测。该工具自身的工具描述携带具体的路径形状示例(如
/basics/name、/sections/experience/items/-、/sections/experience/items/0/company、/metadata/template),指南要求模型对齐这些形状; - section 条目是对象数组,每个对象自带 UUID
id。要定位既有条目,需从刚读到的文档中找出其数组索引;永远不要把id当索引; - 优先用一个包含多个操作(operations)的补丁,而不是多个单操作补丁。操作按顺序应用,且整个补丁要么全部成功要么原子失败;
mcp__<ns>__update_resume只改元数据(name、slug、tags、公开可见性),碰不到内容;没有任何工具能整体替换既有简历内容——mcp__<ns>__import_resume是从完整数据文档创建一个全新简历,不是覆盖。
锁定(Locking)
- 被锁定的简历拒绝一切写入。当调用因简历被锁而失败时,调用
mcp__<ns>__unlock_resume解锁、完成修改、再调用mcp__<ns>__lock_resume,把锁恢复到发现时的状态。
边界(Scope)
- 除非用户在本轮对话中明确要求删除某一条,否则不得删除任何简历或求职申请;
- 当用户用自然语言描述改动时,先复述即将执行的具体编辑,再执行。
这些引导项并非凭空而来:按设计文档 2026-08-16-design.md 的说法,它们对应的是 MCP 服务端已编码为错误提示(errorHint)的失败模式——这些提示的存在本身就说明模型经常在这些问题上出错。另外,服务端还发布了 MCP 资源 resume://_meta/schema(schema 资源)与 resume://{id}(单份简历资源模板,见 packages/mcp/src/mcp-server-card.ts),供 resources/read 读取。
六、为什么不能裁剪工具集:restrict() 的作用域限制
README 明确指出:Reactive Resume 发布的每一个工具都会全部暴露,目前无法从插件侧收窄这个集合,因为 Harness 的 ctx.tools.restrict() 要求一个 agent 作用域(agent-scoped)的上下文,而插件自身的 apply(ctx, config) 拿到的不是这种上下文。
这一点在源码注释(src/index.ts 的 inject 声明处)与设计历史中都有印证:
inject只声明["systemPrompt"],刻意不注入tools——插件从不调用restrict(),桥接所需的 tools 服务由@deepseek-ai/dsh-mcp-client自行声明依赖;- 设计文档 2026-08-16-design.md 开头带有“实现后修订说明”:原方案中的
tools配置键和ctx.tools.restrict()调用从 v0.1.0 中被砍掉了。spike 调查(docs/spikes/2026-08-16-restrict-semantics.md)发现restrict()无法触及插件自身ctx.plugin(mcpClient, …)所注册的工具——从无作用域的插件上下文调用它会直接抛错,即使在真正的 agent 作用域内,它也拒绝触碰该作用域自己(而非继承来的)注册项。因此 0.1.0 按 spike 记录的 fallback 策略全量暴露工具。
换言之,从源码结构看,当前版本接受“全量 33 个工具 schema 占用上下文预算”的代价,换来提示词引导带来的正确性收益。
七、工具改名防护:与 packages/mcp 的同仓契约测试
README.md 的 Development 一节说明:本包位于 Reactive Resume monorepo 的 packages/dsh-plugin,紧挨着它桥接的服务端 packages/mcp;src/tool-names.test.ts 会把提示词指南中提到的每个工具名,逐一与 @reactive-resume/mcp/tool-names 做比对,因此在同一个 PR 里给 MCP 工具改名会让这个包同时失败——改名不会静默漂移。
测试的机制很直接:
function toolsReferencedByGuide(): string[] {
const guide = buildPatchGuide("resume");
const matches = guide.matchAll(/mcp__resume__([a-z0-9_]+)/g);
return [...new Set([...matches].map((match) => match[1] as string))];
}
它从指南文本中用正则抽取所有 mcp__resume__<rawName> 形式的原始工具名,再断言每个名字都包含在 packages/mcp/src/mcp-tool-names.ts 导出的 MCP_TOOL_NAME 表中。该表当前覆盖 33 个工具,涵盖简历侧(list_resumes、read_resume、apply_resume_patch、lock_resume/unlock_resume、import_resume、duplicate_resume 等)与申请侧(list_applications、create_application、autofill_application_from_job、tailor_resume_for_application、score_application_match、draft_application_message 等)。测试还专门设了“至少引用一个工具”的用例,防止正则本身被改坏后让下一个断言“空转通过”。
值得注意的是,这是相对设计文档的一次演进:早期方案(独立仓库时期)依赖抓取线上 /.well-known/mcp/server-card.json 生成工具名快照 + 定时 CI 网络比对;迁入 monorepo 后,这种间接的漂移防护被替换成了上面这种直接读服务端源码符号的同步契约测试,生成快照、抓取脚本和定时网络任务均已删除。
八、在仓库内开发该插件
README.md 给出的开发命令:
pnpm --filter dsh-plugin-reactive-resume test
pnpm --filter dsh-plugin-reactive-resume build
结合 package.json 的实际情况补充:
- 构建工具是
tsdown(build脚本),产物为dist/index.js+dist/index.d.ts,发布文件仅包含dist与cordis.patch.yml; @deepseek-ai/cordis、@deepseek-ai/dsh-mcp-client、@deepseek-ai/dsh-system-prompt、@deepseek-ai/schemastery均为peerDependencies,插件绑定宿主 Harness 的版本,避免装第二份运行时;- Node 引擎要求
^22.19.0 || >=24.0.0; - 测试套件还包括 src/config.test.ts(配置默认值与校验)、src/prompt.test.ts(提示词文本断言)与 src/index.test.ts。
设计文档还记录了发布前的手动冒烟流程:在一个真实的 Harness 会话中指向 https://rxresu.me,依次执行列简历、读一份、应用补丁,再到 Web 端确认变更生效。
九、小结
dsh-plugin-reactive-resume 是 Reactive Resume 与 DeepSeek Harness 之间的一个薄桥接层,其工程取舍清晰:工具契约完全复用服务端 packages/mcp(不重复实现、不漂移),插件侧只做两件事——用 @deepseek-ai/dsh-mcp-client 把 /mcp 端点以 x-api-key 鉴权挂载进会话,并注入一段针对简历编辑常见错误(盲改、错用路径、UUID 当索引、忽略锁定)的系统提示词。安装即挂载、缺密钥即静默的 bundle patch 设计保证了安装零风险;而与 packages/mcp 同仓的契约测试则把“提示词引用的工具名”与“服务端真实发布的工具名”锁在同一个 PR 内保持一致。对于希望让自己的 Agent 会话直接操作 Reactive Resume 账户中简历与求职申请的场景,这是当前仓库提供的、可复制配置的完整接入路径。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00