Skip to main content
Glama
meetvaghani12

hiring-mcp

hiring-mcp

A self-hosted hiring funnel where candidates apply through their own AI agent (Claude Code / Codex CLI) over the Model Context Protocol — plus the recruiter console to review them. Real Postgres, S3-compatible storage, LinkedIn SSO, and a Bearer-authed remote MCP endpoint.

How it works

careers site ──"Apply now"──▶ /jobs/:slug/apply ──▶ LinkedIn SSO ──▶ /mcp (token, once)
                                                                        │
candidate's AI agent ══ Streamable HTTP + Bearer ══▶ POST /mcp/core (7 tools)
                                                                        │
        fills profile ▸ uploads resume ▸ compares resume↔JD ▸ records decision
                                                                        │
recruiter console (/recruiter) ──▶ pipeline: submitted → under_review → interviewing → hired/rejected
  1. A candidate lands on /jobs/:jobId/apply from your careers page (jobId = external_job_id slug or internal uuid). The job survives the SSO round-trip.

  2. They sign in with LinkedIn (OIDC); a candidate account is found-or-created and a personal MCP Bearer token is shown once on /mcp (reissue anytime).

  3. They connect their agent (claude mcp add --transport http …). The agent reads get_my_profile — which includes the target position + full JD — fills the profile, uploads the resume, then presents a resume↔JD fit comparison.

  4. The candidate decides. decision: "apply" → application submitted; decision: "decline" → recorded as declined (the agent must tell the candidate a decline is still visible to the hiring team). One application per role, enforced by a DB constraint. Fit score/summary/gaps are stored as the candidate's self-assessment — labeled that way everywhere they're shown.

  5. Recruiters work the pipeline in /recruiter: filterable application list with legal status transitions, full candidate review (profile, rendered resume, session logs), and positions management (create/close/reopen). Optional webhook (APPLICATION_WEBHOOK_URL) pings on every new application.

Related MCP server: Civify MCP Server

The MCP tools (7, +2 behind a flag)

Tool

Purpose

get_my_profile

Profile, resume, readiness + missing[], and target_position with the full JD

update_my_profile

Patch profile fields. Email is identity: settable once, immutable after

upload_resume

Resume as markdown (≤25k chars, versioned history)

browse_positions / view_position

Open roles / full JD

apply_to_position

Record decision (apply/decline) + self-assessed fit — gated on readiness

my_applications

Status of the candidate's applications

prepare_… / confirm_agent_session_log_upload

Only when REQUIRE_SESSION_LOG=true: presigned PUT to S3, then verify (HEAD + full SHA-256 recompute) and promote

Gating: application_ready = complete profile + resume (+ confirmed session log when the flag is on). transformative_books requires a substantive (≥200 char) answer in the candidate's own voice.

Quick start

cp .env.example .env          # defaults match docker-compose (mock SSO on)
npm install
npm run infra:up              # Postgres + MinIO (+ bucket) via Docker
npm run db:migrate
npm run db:seed               # 3 sample positions
npm run dev                   # http://localhost:8787

Then either open http://localhost:8787/jobs/ai-agent-engineer/apply and walk the candidate flow (mock SSO lets you sign in as any test identity), or run the full end-to-end check from another terminal:

npm run smoke                 # mints a candidate, drives the whole MCP apply flow
npm test                      # unit tests (validation, sessions, SSO linking, pipeline, XSS guards)

Recruiter console: http://localhost:8787/recruiter — sign in with ADMIN_TOKEN.

Connect an AI agent

claude mcp add --transport http hiring https://your-host/mcp/core \
  --header "Authorization: Bearer <candidate-token>"

Architecture

  • Transport: stateless Streamable HTTP at POST /mcp/core. Every request authenticates by Bearer token (SHA-256 hash stored, TTL-enforced), resolves the candidate, and gets a fresh per-candidate server instance — any compute node can serve any request.

  • Web: zero-framework server-rendered HTML (candidate portal + recruiter console share one design system). Sessions are HMAC-signed cookies with server-side expiry.

  • DB: Postgres via Drizzle (src/db/schema.ts). Application statuses: submitted | declined | under_review | interviewing | rejected | hired, with legal transitions enforced in src/services/pipeline.ts.

  • Storage: S3-compatible (MinIO locally). Session-log uploads go direct to S3 via presigned PUT and are verified by re-downloading and recomputing the SHA-256 before promotion.

Security posture

  • Candidate tokens: 24-byte random, hash-at-rest, 90-day TTL, one-time reveal, self-service rotation.

  • Mock SSO (LINKEDIN_MOCK) is an explicit opt-in and refused in production.

  • SSO email-linking only on verified OIDC emails into rows with no existing LinkedIn identity; profile email is immutable once set (prevents email-squatting takeover).

  • Production boot refuses default/weak ADMIN_TOKEN / SESSION_SECRET; admin compare is constant-time; rate limits on every credential-accepting route; security headers

    • CSP; candidate URLs scheme-checked at write and render (no javascript: hrefs).

Surfaces

Path

Who

What

POST /mcp/core

candidate's agent

MCP endpoint (Bearer)

/login, /mcp, /wiki, /apply, /profile

candidate

portal (cookie)

/jobs/:jobId/apply, /positions/:id

public

apply entry + shareable JD

/recruiter/*

recruiter

console (admin-token login → 12h cookie)

/admin/*

ops/scripts

JSON API (Bearer ADMIN_TOKEN)

Project layout

src/
  config.ts            env config + production safety checks
  auth.ts              token hashing, candidate lookup, mint, safeEqual
  validation.ts        resume / books rules, isUuid
  auth/linkedin.ts     OIDC flow + account-linking policy (planLink)
  db/                  schema, connection, migrate, seed
  services/            readiness (apply gate), pipeline (status transitions),
                       storage (S3 presign/verify), notify (webhook)
  mcp/                 per-candidate server + tools (profile, positions, sessionLog)
  web/                 candidate portal: routes, views, session, http hardening
  recruiter/           recruiter console: routes, views, session
  admin/routes.ts      JSON API for ops/scripts
  scripts/             mint-token, smoke (e2e)
test/                  unit tests (vitest)

Production notes

  • NODE_ENV=production activates the boot-time secret checks, secure cookies, and the mock-SSO refusal. Put TLS in front; trust proxy is already set to 1 hop.

  • Container: docker build -t hiring-mcp . — runs migrations separately (node dist/db/migrate.js) before rollout, never at boot.

  • The in-memory rate limiter is single-process by design; move to a shared store if you ever run multiple instances. See DEPLOY.md for the serverless blueprint (deliberately deferred until traffic justifies it).

  • LinkedIn: create an app at developer.linkedin.com with Sign In with LinkedIn using OpenID Connect, redirect URI <PUBLIC_BASE_URL>/auth/linkedin/callback, then set LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET and LINKEDIN_MOCK=false.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search, score, and track job applications from ATS boards via MCP, with explainable matching and an append-only application history.
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Enables AI clients to browse public job listings, save and rank roles into a pipeline, tailor resumes and cover letters, submit applications, and track replies, interviews, and escalations over time. Works anonymously for job browsing or with a scoped API key for full account, apply, and recruiter-outreach capabilities.
    71
    12
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables users to upload a resume to build a profile, discover jobs from company websites and official ATS boards, receive scheduled recommendations, and swipe to manage interest and application status. Runs self-hosted on Cloudflare with private resume storage and no OpenAI API key required.
    MIT