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: Shortlist 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.

F
license - not found
-
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.

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/meetvaghani12/hiring_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server