Batcave-MCP
Batcave — 简历审阅 MCP 服务器
一个 MCP 服务器,接收两份文档——一份 简历 和一份 职位描述——并让它们经过三阶段审阅。每个阶段都送入下一个阶段:未拿到匹配报告之前不能重写,未完成重写之前不能运行 ATS 检查。
管道
工具 | 作用 |
| 开始输入。接收简历和职位描述,可以以原始文本形式,也可以作为 |
| 阶段1。 扮演目标公司的资深招聘人员:给出以 100 为满分的匹配分数、最缺失的 5 个关键词,以及招聘经理在 10 秒内就会注意到的 3 个危险信号。 |
| 阶段2。 重写经历部分,以吸收阶段1的关键词并消除危险信号,每条经历都遵循 Google XYZ 格式——通过做 Z,完成了 X,以 Y 作为衡量。 |
| 阶段3。 模拟 ATS 解析器,同时扮演一位看到 200 份简历中第 147 份的招聘经理:识别哪些部分会被跳过,然后重写它们以阻止滚动。返回最终简历。 |
| 显示哪些阶段已完成、正在等待结果、尚未开始,以及下一步该调用什么工具。 |
| 显示已存储的会话,最先更新优先。 |
| 将整个审阅——三个阶段加上最终简历——作为一份 Markdown 文档返回。 |
| 删除一个会话及其存储的所有数据。没有任何数据会自己过期。 |
Related MCP server: resume-fit-scanner
阶段如何运行
服务器本身不会调用模型。它负责整合简报、保存状态并强制执行的顺序;实际推理由所连接客户端的模型完成。因此每个阶段工具会被调用两次:
返回
{ session_id } { result }—— 返回该阶段的分析简报,其中已经嵌入了简历、职位描述以及之前所有阶段的输出。{ session_id, result }—— 记录答案。result会按该阶段的模式校验,因此一个只有 4 个关键词而不是 5 个的报告会被拒绝而非存储。
阶段2 读取已记录阶段1的报告。阶段3 读取阶段2 中的 updated_resume,而不是原始简历。乱序调用阶段时,会以报错方式返回你应当先调用的工具名。
简报中内置的两条规则
不能编造指标。 如果原始简历中没有数字,重写时输出
[QUANTIFY: what to measure],并将其列入placeholders_needing_user_input。不要堆砌关键词。 只有在真实经历能够支撑某个关键词时,才将其加入;其余关键词连同原因放入
keywords_not_addressed中返回。
传输方式
两个入口点,同一套工具:
入口 | 传输方式 | 适用场景 |
| stdio | 同一台机器上的客户端: Claude Code, IDE |
| Streamable HTTP 上的 | 远程客户端——这就是容器中运行的方式 |
stdio 是同一台机器上两个进程之间的通道;它不能通过网络访问。只提供 stdio 的容器将无法接受任何连接,这就是 EC2 路径使用 serve.ts 的原因。
serve.ts 要求两个变量,缺少其中任意一个都不会启动:
DB_URL— Postgres 连接字符串MCP_AUTH_TOKEN— 共享密钥;每个请求都需要Authorization: Bearer <token>
GET /healthz 是唯一一个无需认证的路由。它不会打开数据库连接,因此轮询它的负载均衡器永远不会唤醒 Postgres。
存储
一切都保存在 Postgres 中。服务器不会向本地磁盘写任何内容——它唯一进行的本地读取只是你指向的简历和职位描述文件。
resume_sessions(id, created_at, updated_at, company, role,
resume jsonb, job_description jsonb)
resume_stages(session_id -> resume_sessions.id on delete cascade, stage, status,
issued_at, completed_at, result jsonb, primary key (session_id, stage))
schema_migrations(module, id, applied_at) -- shared, owned by src/platform/db.ts采用两个表而不是单一个文档,这样记录一个阶段时只需写入一行,而不需要重写既有的简历,而且 list_sessions 根本不会去查询文档文本。表按模块加前缀,迁移会在该模块第一次查询时惰性运行——启动服务器并不会唤醒数据库。
迁移是只追加的,并记入 schema_migrations,因此每个迁移在每个数据库中正好执行一次。bun run db:migrate 应用所有尚未执行的迁移;服务器也会在该模块首次查询时,作为备选方案惰性执行。
没有任何数据会自动过期。会话积累到 delete_session 将其删除。
运行
bun install
bun run dev # Postgres + the server, hot reload, nothing to configure这就是 docker compose -f docker-compose.dev.yml up --build:它会启动 Postgres,创建 dev 和 test 数据库,运行迁移,并在 http://127.0.0.1:3000/mcp 上提供 MCP 服务,使用 token dev-token-not-a-secret。编辑 src/ 下任何内容都会重新加载正在运行的服务器。
或者,要在宿主机直接运行服务器:
export DB_URL='postgres://postgres:postgres@localhost:55432/batcave'
bun start # stdio, for a client on this machine
bun run serve # HTTP on :3000, also needs MCP_AUTH_TOKEN有两个数据库命令,都不需要服务器运行:
bun run db:check # can this machine reach DB_URL, and what is in it?
bun run db:migrate # create or update the tables; safe to run repeatedlydb:check 是唯一以非服务形式打开连接的代码。两个入口点时会在启动时校验 DB_URL 参数,但只会在首条查询上惰性连接,因此顺利启动并不证明这没毛病。
检查:
bun run check # Biome format + lint (check:fix to apply)
bun run typecheck
bun test # unit tests; no database needed
TEST_DB_URL='postgres://postgres:postgres@localhost:55432/batcave_test' bun test端到端测试使用真实的有线协议,连接真实 Postgres,并在收尾时删除自己的表。它们读取 TEST_DB_URL,特意不是 DB_URL,这既能防止服务器连到真实数据库时会触发清理;同时 dev 堆栈自带一个独立的 batcave_test 数据库,所以测试不会干扰你正在运行的服务器。
本目录的 .mcp.json 为 Claude Code 注册了 stdio 服务器。若使用其他客户端:
{ "command": "bun", "args": ["index.ts"], "cwd": "/path/to/Batcave" }在 EC2 上运行
export DB_URL='postgres://user:pass@host/db?sslmode=require'
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
bun run db:check # confirm the instance is reachable from this box
docker compose run --rm mcp bun scripts/migrate.ts # create the tables
docker compose up -d --build
docker compose logs -f mcp迁移操作必须在服务器接收流量之前执行。 如果跳过这一步,服务器会在第一次工具调用时自行迁移,但那时迁移失败会表现为用户请求失败,而不是部署失败,而且第一个用户会等待数据库模式。每次发布包含新迁移时都再次运行 db:migrate,若没有待应用的迁移,就会什么都做。
Compose 会在任一变量未设置时拒绝启动。请把它们放在 shell profile 或 instance secret 中,不要放在本仓库里的文件中。
对外公布的端口是 127.0.0.1:3000,这是设计如此。 该端点采用明文 HTTP 并用 bearer token 认证;在开放的互联网上,任何能够看到数据包的人都能读取这个 token。请在其前配置 TLS——可以用终止 HTTPS 并转至实例的 ALB,或用 nginx/Caddy 在本地代理到 127.0.0.1:3000。然后,安全组只允许来自客户端的 443 端口,其余端口全网关闭;3000 端口保持不外暴露。
轮换 token 的做法是执行 export MCP_AUTH_TOKEN=... && docker compose up -d,这会重启容器。目前所有使用同一个 token——它不能标识用户,因此无法区分你的会话和别人的会话。基于用户的访问权限需要真正的 auth 以及 resume_sessions 表上的 0 字段;这两者目前都不存在。
resume_path 是在容器内部解析的,因此远程调用者无法使用它——他们笔记本电脑上的路径对服务器毫无意义。通过 HTTP 传递时,请提供 resume_text 和 job_description_text。如果你的文件位于容器中,并想让路径形式也可用,请挂载一个 volume。
docker-compose.yml 是纯生产环境的配置。本地开发应使用 docker-compose.dev.yml,它会启动自己的 Postgres,与此处生产配置没有任何共享。
布局
服务器是模块的宿主机。一个模块是自包含的工具全家,拥有自己的表和自己的词汇。目前唯一的模块是简历审阅;未来如果再出现与它无关的第二个模块,就是一个 src/features/ 下的文件夹,再在 index.ts 的列表中加入一个条目。
index.ts stdio entrypoint
serve.ts HTTP entrypoint (the container runs this)
src/modules.ts the one list of mounted modules, shared by both entries
src/module.ts the ToolModule contract every feature implements
src/server.ts mounts modules onto an McpServer
src/http.ts Streamable HTTP handler, bearer auth, /healthz
src/platform/ feature-agnostic; knows nothing about resumes
db.ts lazy Postgres pool + per-module migration runner
documents.ts text / pdf / docx extraction
stored-document.ts what an extracted document looks like
tool-result.ts keeps `content` and `structuredContent` in step
src/features/resume-review/
index.ts the ToolModule: name, migrations, register()
migrations.ts this module's tables
sessions.ts repository, domain types, stage gating
briefs.ts the three briefs
schemas.ts zod schema per stage result
stage-tool.ts the brief-then-record tool shape
dossier.ts markdown rendering
tools/ one file per group of registered tools
intake.ts, stages.ts, dossier.ts, session-admin.ts支撑这一结构的两条规则如下:
src/platform绝不从src/featuresimport。 任何第二个模块同样需要的东西放入 platform;只有简历审阅需要的东西留在 feature 内。模块之间不得互相 import。 相互需要了解对方的两个模块,其实就应该是一个模块。
stage-tool.ts 有意放在 feature 里,而不是平台层中。这种“先 briefing,再记录”的形态可能将来会变为可复用,但它今天只有唯一的消费者;在第二个消费者出现前就猜测通用形式,正是平台层糜烂的原因。
添加模块
// src/features/interview-prep/index.ts
export const interviewPrep: ToolModule = {
name: "interview-prep",
migrations, // its own tables, namespaced in schema_migrations
register(server) {
registerWhateverTools(server);
},
};// index.ts
const server = createServer([resumeReview, interviewPrep]);这就是全部契约。迁移只执行一次,按模块记录在 schema_migrations 中,并在该模块第一次访问数据库时惰性运行——一个未使用的模块不会带来任何必须连接到数据库的消耗。tests/modules.test.ts 使用一个与简历毫无关系的桩模块来验证这个接缝。
参与贡献
参见 CONTRIBUTING.md。只需 bun run dev 即可完成完整环境配置。安全方面问题请通过 SECURITY.md 提交,不要在公开的 issue 里报告。
YouTube 许可
MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
Tailor a CV to a job posting: score the match, propose a reviewable rewrite, export PDF or Word.
- PopCVsOAuthcom.popcvs
ATS resume checker and AI resume builder: score, tailor to a job, cover letters, PDF export.
Free ATS resume score and job-match checker: scores a resume against a job description.
Score and tailor your CV/resume against a job posting — for AI agents and humans, no-login trial.
Related MCP Servers
- FlicenseAqualityDmaintenanceEnables tailoring resumes to job descriptions by scraping JDs, applying rules, and generating optimized DOCX resumes.11-
- AlicenseNot gradedqualityDmaintenanceAnalyzes resume text against job descriptions to provide ATS fit scores, missing keywords, and suggestions.MIT
- AlicenseNot gradedqualityBmaintenanceEnables JD-aware resume matching through MCP tools, providing deterministic scoring, gap analysis, bullet rewrites, and tailored cover letter generation.13 npmMIT
- AlicenseAqualityDmaintenanceEnables MCP clients to analyze resumes for ATS compatibility, parse job descriptions, get optimization and roast-style critiques, and generate tailored resumes with shareable preview links.60MIT