hiring-mcp
Provides S3-compatible object storage for candidate resumes, agent config files, and session logs, with support for presigned PUT URLs and verified uploads.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hiring-mcplist open positions"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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/rejectedA candidate lands on
/jobs/:jobId/applyfrom your careers page (jobId=external_job_idslug or internal uuid). The job survives the SSO round-trip.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).They connect their agent (
claude mcp add --transport http …). The agent readsget_my_profile— which includes the target position + full JD — fills the profile, uploads the resume, then presents a resume↔JD fit comparison.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.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 |
| Profile, resume, readiness + |
| Patch profile fields. Email is identity: settable once, immutable after |
| Resume as markdown (≤25k chars, versioned history) |
| Open roles / full JD |
| Record decision ( |
| Status of the candidate's applications |
| Only when |
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:8787Then 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 insrc/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 headersCSP; candidate URLs scheme-checked at write and render (no
javascript:hrefs).
Surfaces
Path | Who | What |
| candidate's agent | MCP endpoint (Bearer) |
| candidate | portal (cookie) |
| public | apply entry + shareable JD |
| recruiter | console (admin-token login → 12h cookie) |
| ops/scripts | JSON API (Bearer |
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=productionactivates the boot-time secret checks, secure cookies, and the mock-SSO refusal. Put TLS in front;trust proxyis 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.mdfor 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 setLINKEDIN_CLIENT_ID/LINKEDIN_CLIENT_SECRETandLINKEDIN_MOCK=false.
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.
Latest Blog Posts
- 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/meetvaghani12/hiring_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server