Skip to main content
Glama
pnaskardev

Batcave-MCP

by pnaskardev

Batcave — servidor MCP de revisión de currículums

Un servidor MCP que toma dos documentos — un currículum y una descripción del puesto — y los somete a una revisión en tres fases. Cada fase alimenta a la siguiente: no puedes reescribir antes de tener un informe de coincidencia, y no puedes ejecutar la pasada ATS antes de tener una reescritura.

El proceso

Herramienta

Qué hace

start_review

Recepción. Toma el currículum y la descripción del puesto como texto sin formato o como ruta a un archivo .pdf / .docx / .txt / .md, extrae el texto y abre una sesión.

resume_match_report

Fase 1. Reclutador sénior en la empresa objetivo: puntuación de coincidencia sobre 100, las 5 palabras clave que faltan más importantes, 3 señales de alerta que un responsable de contratación detecta en menos de 10 segundos.

rewrite_experience_xyz

Fase 2. Reescribe la sección de experiencia para incorporar las palabras clave de la fase 1 y eliminar sus señales de alerta, cada viñeta con el formato Google XYZ — logrado X, medido por Y, haciendo Z.

ats_scroll_stopper_pass

Fase 3. Pasada del analizador ATS más un responsable de contratación en el currículum n.º 147 de 200: qué secciones se omiten, y luego las reescribe para detener el desplazamiento. Devuelve el currículum final.

session_status

Qué fases están completadas, esperando un resultado o sin iniciar, y qué llamar a continuación.

list_sessions

Sesiones almacenadas, las más recientes primero.

export_dossier

Devuelve la revisión completa — las tres fases más el currículum final — como un único documento markdown.

delete_session

Elimina una sesión y todo lo almacenado en ella. Nada caduca por sí solo.

Related MCP server: ats-resume-writer

Cómo se ejecuta una fase

El servidor no llama a un modelo. Compone el informe, mantiene el estado y hace cumplir el orden; el modelo del cliente conectado hace el razonamiento. Por lo tanto, cada herramienta de fase se llama dos veces:

  1. { session_id } — devuelve el informe de análisis de esa fase, con el currículum, la descripción del puesto y la salida de todas las fases anteriores ya incrustadas.

  2. { session_id, result } — registra la respuesta. result se valida contra el esquema de la fase, por lo que un informe con cuatro palabras clave en lugar de cinco se rechaza en lugar de almacenarse.

La fase 2 lee el informe registrado de la fase 1. La fase 3 lee updated_resume de la fase 2, no el original. Una fase llamada fuera de orden falla con el nombre de la herramienta que debes llamar primero.

Dos reglas integradas en los informes

  • Sin métricas inventadas. Cuando el currículum original no tiene un número, la reescritura emite [QUANTIFY: what to measure] y lo lista en placeholders_needing_user_input.

  • Sin relleno de palabras clave. Una palabra clave solo se incluye donde la experiencia real la respalda; el resto se devuelven en keywords_not_addressed con el motivo.

Transportes

Dos puntos de entrada, mismas herramientas:

Entrada

Transporte

Para

index.ts

stdio

Un cliente en la misma máquina — Claude Code, un IDE

serve.ts

Streamable HTTP en /mcp

Un cliente remoto — esto es lo que se ejecuta en el contenedor

stdio es una tubería entre dos procesos en una misma máquina; no se puede acceder a través de una red. Un contenedor que sirva stdio no aceptaría conexiones, por eso la ruta de EC2 usa serve.ts.

serve.ts requiere dos variables y se niega a arrancar sin cualquiera de ellas:

  • DB_URL — cadena de conexión de Postgres

  • MCP_AUTH_TOKEN — secreto compartido; cada solicitud necesita Authorization: Bearer <token>

GET /healthz es la única ruta sin autenticación. No abre ninguna conexión de base de datos, por lo que un balanceador de carga que la consulte nunca despierta a Postgres.

Almacenamiento

Todo vive en Postgres. El servidor no escribe nada en el disco local — las únicas lecturas locales son los archivos de currículum y descripción del puesto a los que lo apuntas.

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

Dos tablas en lugar de un solo documento, de modo que registrar una fase escribe una fila en lugar de reescribir ambos currículums, y list_sessions nunca selecciona el texto del documento. Las tablas llevan el prefijo del módulo y las migraciones se ejecutan de forma diferida en la primera consulta de ese módulo — iniciar el servidor no despierta la base de datos.

Las migraciones son de solo añadido y se registran en schema_migrations, por lo que cada una se ejecuta exactamente una vez por base de datos. bun run db:migrate aplica lo que está pendiente; el servidor también lo hace de forma diferida en la primera consulta de un módulo como respaldo.

Nada caduca. Las sesiones se acumulan hasta que delete_session las elimina.

Ejecutarlo

bun install
bun run dev        # Postgres + the server, hot reload, nothing to configure

Eso es docker compose -f docker-compose.dev.yml up --build: levanta Postgres, crea las bases de datos de desarrollo y de prueba, ejecuta las migraciones y sirve MCP en http://127.0.0.1:3000/mcp con el token dev-token-not-a-secret. Editar cualquier cosa bajo src/ recarga el servidor en ejecución.

Para ejecutar el servidor directamente en el host en su lugar:

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

Dos comandos de base de datos, ninguno de los cuales necesita que el servidor esté en ejecución:

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 repeatedly

db:check es lo único que abre una conexión sin servir. Ambos puntos de entrada validan DB_URL al arrancar, pero se conectan de forma diferida en la primera consulta, por lo que un arranque limpio no demuestra nada.

Comprobaciones:

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

Las pruebas de extremo a extremo hablan el protocolo de red real contra un Postgres real y eliminan sus tablas al finalizar. Leen TEST_DB_URL, deliberadamente no DB_URL, por lo que apuntar el servidor a una base de datos real no puede activar la limpieza — y el stack de desarrollo incluye una base de datos batcave_test separada para que ejecutar las pruebas nunca moleste a un servidor que tengas en marcha.

.mcp.json en este directorio registra el servidor stdio para Claude Code. Para otro cliente:

{ "command": "bun", "args": ["index.ts"], "cwd": "/path/to/Batcave" }

Ejecutarlo en 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

Migra antes de que el servidor reciba tráfico. Se migrará a sí mismo en la primera llamada a una herramienta si te saltas esto, pero entonces una migración rota aparece como una solicitud de usuario fallida en lugar de un despliegue fallido, y el primer llamador espera por el esquema. Vuelve a ejecutar db:migrate en cada despliegue que incluya una migración nueva; es un no-op cuando no hay nada que aplicar.

Compose se niega a arrancar si alguna de las dos variables no está definida. Guárdalas en el perfil del shell o en un secreto de instancia — no en un archivo de este repositorio.

El puerto publicado es 127.0.0.1:3000, deliberadamente. El endpoint habla HTTP en texto plano y se autentica con un token de portador: a través de internet abierto, ese token es legible para cualquiera en la ruta. Pon TLS delante — un ALB que termine HTTPS y reenvíe a la instancia, o nginx/Caddy en la misma máquina haciendo de proxy a 127.0.0.1:3000. Entonces el grupo de seguridad debería permitir el 443 desde tus clientes y nada más; el puerto 3000 permanece cerrado al mundo.

Rotar el token es export MCP_AUTH_TOKEN=... && docker compose up -d, lo que reinicia el contenedor. Hay un único token para todos — no identifica a nadie, por lo que no puede distinguir tus sesiones de las de un amigo. El acceso por usuario necesita autenticación real y una columna owner en resume_sessions; ninguna de las dos existe todavía.

resume_path se resuelve dentro del contenedor, por lo que un llamador remoto no puede usarlo — las rutas en su portátil no significan nada para el servidor. Por HTTP, pasa resume_text y job_description_text. Monta un volumen si quieres que la forma de ruta funcione para archivos en la máquina.

docker-compose.yml es solo el stack de producción. El desarrollo local usa docker-compose.dev.yml, que trae su propio Postgres y no comparte nada de esta configuración.

Estructura

El servidor es un anfitrión de módulos. Un módulo es una familia autocontenida de herramientas que posee sus propias tablas y su propio vocabulario. La revisión de currículums es la única hoy; un segundo módulo no relacionado es una carpeta bajo src/features/ y una entrada en la lista de 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

Dos reglas sostienen la estructura:

  • src/platform nunca importa desde src/features. Cualquier cosa que un segundo módulo también quisiera pertenece a platform; cualquier cosa que solo la revisión de currículums quiera se queda en la feature.

  • Ningún módulo importa a otro módulo. Dos módulos que necesitan saber el uno del otro son un solo módulo.

stage-tool.ts vive deliberadamente dentro de la feature en lugar de en platform. La forma de informe-y-registro podría resultar reutilizable, pero hoy tiene exactamente un consumidor, y adivinar el caso general antes de que exista un segundo es como se pudre una capa de platform.

Añadir un módulo

// 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]);

Ese es todo el contrato. Las migraciones se aplican una vez cada una, se registran por módulo en schema_migrations y se ejecutan de forma diferida la primera vez que ese módulo toca la base de datos — un módulo sin usar no cuesta ningún viaje de ida y vuelta. tests/modules.test.ts ejercita la unión con un módulo stub que no tiene nada que ver con currículums.

Contribuciones

Consulta CONTRIBUTING.mdbun run dev es toda la configuración. Los problemas de seguridad se gestionan a través de SECURITY.md, no mediante incidencias públicas.

Licencia

MIT.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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