Batcave-MCP
Batcave — MCP-сервер для проверки резюме
MCP-сервер, который принимает два документа — резюме и описание вакансии — и прогоняет их через трехэтапную проверку. Каждый этап служит входом для следующего: нельзя переписывать, пока нет отчета о соответствии, и нельзя запускать проход ATS, пока нет переписанной версии.
Конвейер
Инструмент | Что делает |
| Прием. Принимает резюме и описание вакансии в виде обычного текста или пути к файлу |
| Этап 1. Старший рекрутер в целевой компании: оценка соответствия из 100, топ-5 недостающих ключевых слов, 3 красных флага, которые менеджер по найму замечает менее чем за 10 секунд. |
| Этап 2. Переписывает раздел об опыте, чтобы включить ключевые слова этапа 1 и убрать его красные флаги, каждый пункт в форме Google XYZ — достиг X, измеряемый Y, путем выполнения Z. |
| Этап 3. Проход парсера ATS плюс менеджер по найму на резюме №147 из 200: какие разделы пропускаются, затем переписывает их, чтобы остановить прокрутку. Возвращает итоговое резюме. |
| Какие этапы завершены, ожидают результата или не начаты, и что вызывать дальше. |
| Сохраненные сессии, сначала самые недавно обновленные. |
| Возвращает всю проверку — все три этапа плюс итоговое резюме — в виде одного markdown-документа. |
| Удаляет сессию и все, что с ней связано. Ничто не истекает само по себе. |
Related MCP server: ats-resume-writer
Как работает этап
Сервер не вызывает модель. Он составляет бриф, хранит состояние и обеспечивает порядок; модель подключенного клиента выполняет рассуждения. Поэтому каждый инструмент этапа вызывается дважды:
{ session_id }— возвращает аналитический бриф для этого этапа, с уже встроенными резюме, описанием вакансии и результатами всех предыдущих этапов.{ session_id, result }— записывает ответ.resultпроверяется по схеме этапа, поэтому отчет с четырьмя ключевыми словами вместо пяти отклоняется, а не сохраняется.
Этап 2 читает записанный отчет этапа 1. Этап 3 читает updated_resume из этапа 2, а не оригинал. Этап, вызванный не по порядку, завершается ошибкой с именем инструмента, который нужно вызвать первым.
Два правила, заложенные в брифы
Никаких выдуманных метрик. Если в исходном резюме нет числа, переписанная версия выдает
[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— строка подключения к PostgresMCP_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 вообще не выбирает текст документа. Таблицы имеют префикс модуля, а миграции выполняются лениво при первом запросе этого модуля — запуск сервера не будит базу данных.
Миграции являются append-only и записываются в 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, выполняет миграции и обслуживает MCP на http://127.0.0.1:3000/mcp с токеном 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 в этом каталоге регистрирует stdio-сервер для Claude Code. Для другого клиента:
{ "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 при каждом деплое, который включает новую миграцию; это no-op, когда применять нечего.
Compose отказывается запускаться, если какая-либо переменная не задана. Храните их в профиле оболочки или в секрете инстанса — не в файле в этом репозитории.
Опубликованный порт — 127.0.0.1:3000, намеренно. Конечная точка говорит по открытому HTTP и аутентифицируется с помощью bearer-токена: в открытом интернете этот токен может прочитать любой на пути. Поставьте перед ним TLS — ALB, завершающий HTTPS и пересылающий на инстанс, или nginx/Caddy на той же машине, проксирующий на 127.0.0.1:3000. Тогда группа безопасности должна разрешать 443 от ваших клиентов и ничего больше; порт 3000 остается закрытым для мира.
Ротация токена — это export MCP_AUTH_TOKEN=... && docker compose up -d, что перезапускает контейнер. Токен один для всех — он никого не идентифицирует, поэтому не может отличить ваши сессии от сессий друга. Доступ для каждого пользователя требует настоящей аутентификации и колонки владельца в resume_sessions; ни того, ни другого пока нет.
resume_path разрешается внутри контейнера, поэтому удаленный вызывающий не может его использовать — пути на его ноутбуке ничего не значат для сервера. По HTTP передавайте resume_text и job_description_text. Смонтируйте том, если хотите, чтобы форма пути работала для файлов на машине.
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/features. Все, что может понадобиться второму модулю, принадлежит платформе; все, что нужно только проверке резюме, остается в функциональности.Ни один модуль не импортирует другой модуль. Два модуля, которым нужно знать друг о друге, — это один модуль.
stage-tool.ts намеренно живет внутри функциональности, а не в платформе. Форма «сначала бриф, затем запись» может оказаться переиспользуемой, но сегодня у нее ровно один потребитель, а угадывание общего случая до появления второго — это то, как гниет слой платформы.
Добавление модуля
// 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, а не через публичные issues.
Лицензия
MIT.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAnalyzes resumes against job descriptions to identify missing skills, keywords, and improvement opportunities using AI. Provides structured feedback including gap analysis, ATS optimization suggestions, and actionable recommendations to improve job application success.
- AlicenseAqualityCmaintenanceRewrites resumes to beat ATS screening (Workday, Greenhouse, iCIMS, Taleo) against a specific job description, with strict truthfulness guardrails — never invents dates, metrics, titles, or seniority. Pay-what-you-want access codes ($0 works).2MIT
- FlicenseNot gradedqualityDmaintenanceAutomates ATS resume scanning via Jobscan, enabling AI to iteratively scan, analyze gaps, optimize, and rescan resumes against job descriptions to improve match rates.3
- FlicenseAqualityCmaintenanceEnables tailoring resumes to job descriptions by scraping JDs, applying rules, and generating optimized DOCX resumes.11
Related MCP Connectors
Tailor resumes, generate cover letters, render CVs as PDF, and browse 22+ templates.
Search 6.3M+ live jobs from companies' own career pages, plus resume tailoring & cover letters.
Search AI-native jobs, inspect application forms, and fetch free interview-prep resources.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/pnaskardev/Batcave-MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server