hrmcp-server
MCP server for candidate scoring. Post a resume and job description, get back structured scores across skills match, experience, industry background, and education — plus strengths and gaps grounded in the actual text. Hosted API at recruit api .app or self-host on Railway.
README
hrmcp-server
A source-available MCP server for HR and recruiting workflows. Scores candidates against job descriptions using Claude, returns structured dimension scores, strengths, and gaps. Any agent framework that speaks MCP can call it natively.
Hosted API — use it without running anything: recruitapi.app Self-host — deploy to Railway in under 15 minutes (see below)
Endpoints
POST /score-candidate
Score a candidate's resume against a job description.
Request
{
"resume_text": "...",
"job_description": "...",
"weights": {
"skills_match": 0.40,
"experience": 0.30,
"industry_background": 0.20,
"education": 0.10
},
"recency_window_years": 10
}
| Field | Type | Required | Default |
|---|---|---|---|
resume_text |
string | yes | — |
job_description |
string | yes | — |
weights |
object | no | {skills_match: 0.40, experience: 0.30, industry_background: 0.20, education: 0.10} |
recency_window_years |
integer > 0 | no | 10 |
Weights must sum to 1.0. Each value must be between 0.0 and 1.0.
Both text fields max out at 15,000 characters. Resume must be at least 50 words.
Response — 200
{
"overall_score": 82,
"dimension_scores": {
"skills_match": 88,
"experience": 85,
"industry_background": 74,
"education": 70
},
"strengths": [
"Five years of hands-on Python in production ML pipelines",
"Led cross-functional team during platform migration"
],
"gaps": [
"No experience with Kubernetes",
"MBA preferred; candidate holds a BS"
],
"recency_window_used": 10,
"model": "claude-sonnet-4-20250514",
"warnings": []
}
All scores are integers 0–100. strengths and gaps are 2–4 strings each, grounded in the resume and job description — not generic observations.
Idempotency
Pass an Idempotency-Key header to cache the response for 24 hours. A retry with the same key returns the cached response without calling the model or deducting a credit.
Idempotency-Key: req_01J8XYZ
GET /health
{ "status": "ok", "model": "claude-sonnet-4-20250514" }
Authentication
Every request to /score-candidate requires an API key. Pass it either way:
X-API-Key: hrmcp_sk_...
Authorization: Bearer hrmcp_sk_...
Keys are issued after a credit purchase. Get one at hrmcp-server-production.up.railway.app/billing.
Rate limits
Limits apply per API key on a rolling window — not a fixed clock boundary.
| Limit | Default |
|---|---|
| Per minute | 30 requests |
| Per day | 500 requests |
| Concurrent | 5 in-flight |
Every response includes current limit state:
X-RateLimit-Limit-Minute: 30
X-RateLimit-Remaining-Minute: 28
X-RateLimit-Reset-Minute: 1712000060
X-RateLimit-Limit-Day: 500
X-RateLimit-Remaining-Day: 497
X-RateLimit-Reset-Day: 1712041234
Self-hosted deployments can disable rate limiting entirely with RATE_LIMIT_ENABLED=false.
Credits
Each successful call to /score-candidate deducts one credit. Credits are purchased in bundles of 100 for $5 and expire after 180 days.
| Status | Code | Meaning |
|---|---|---|
| 402 | credits_exhausted |
Balance is zero |
| 402 | credits_expired |
Credits exist but the bundle has expired |
Error format
All errors use the same envelope:
{
"error": {
"code": "missing_required_field",
"message": "One or more required fields are missing.",
"detail": { "missing_fields": ["resume_text"] }
}
}
| Code | Status | Description |
|---|---|---|
unauthorized |
401 | Missing, invalid, or revoked API key |
rate_limit_exceeded |
429 | Rolling window limit hit; see Retry-After header |
credits_exhausted |
402 | No credits remaining |
credits_expired |
402 | Credits have passed their expiry date |
missing_required_field |
400 | resume_text or job_description absent |
resume_too_short |
400 | Resume under 50 words |
payload_too_large |
400 | Input exceeds 15,000 characters |
weights_invalid_sum |
400 | Weights do not sum to 1.0 |
weights_missing_keys |
400 | One or more weight keys absent |
weights_negative_value |
400 | Weight value outside 0.0–1.0 |
recency_window_invalid |
400 | recency_window_years is not a positive integer |
idempotency_key_invalid |
400 | Idempotency-Key header value is invalid |
model_unavailable |
503 | Anthropic API timeout or outage |
response_parse_failure |
500 | Model returned unparseable output |
Warnings are non-fatal and appear alongside a 200 response:
| Code | Trigger |
|---|---|
non_english_detected |
Non-Latin characters in resume or JD |
job_description_thin |
JD under 30 words |
Self-hosting
Deploy to Railway
- Click the button above — Railway clones the repo and provisions a Postgres service
- Add the five required environment variables (see below)
- Register
POST /webhooks/stripein your Stripe dashboard; paste the signing secret intoSTRIPE_WEBHOOK_SECRET - Watch the deploy log for
db_migratedandserver_started - Hit
/healthto confirm
Run locally
git clone https://github.com/Spaceghost99/hrmcp-server.git
cd hrmcp-server
npm install
cp .env.example .env
# Fill in ANTHROPIC_API_KEY at minimum
npm run dev
Scoring works without DATABASE_URL. Auth and billing require Postgres.
Environment variables
Five variables are required and will crash the server on startup if missing.
| Variable | Required | Default | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
yes | — | Anthropic API key |
DATABASE_URL |
prod only | — | PostgreSQL connection string (Railway provides this) |
STRIPE_SECRET_KEY |
prod only | — | Stripe secret key (sk_test_... or sk_live_...) |
STRIPE_WEBHOOK_SECRET |
prod only | — | Stripe webhook signing secret (whsec_...) |
PORT |
no | 3000 |
Railway sets this automatically |
NODE_ENV |
no | development |
Set to production on Railway |
ANTHROPIC_MODEL |
no | claude-sonnet-4-20250514 |
Model for scoring |
ANTHROPIC_TIMEOUT_MS |
no | 25000 |
Anthropic API timeout in ms |
BCRYPT_ROUNDS |
no | 10 |
bcrypt cost factor for key hashing |
RATE_LIMIT_ENABLED |
no | true |
Set false to disable rate limiting |
RATE_LIMIT_PER_MINUTE |
no | 30 |
Rolling per-minute limit per key |
RATE_LIMIT_PER_DAY |
no | 500 |
Rolling per-day limit per key |
RATE_LIMIT_CONCURRENT |
no | 5 |
Max in-flight requests per key |
MAX_RESUME_CHARS |
no | 15000 |
Resume character limit |
MAX_JD_CHARS |
no | 15000 |
Job description character limit |
STRIPE_PRICE_LOOKUP_KEY |
no | credits_100 |
Stripe price lookup key |
CREDITS_BUNDLE_SIZE |
no | 100 |
Credits per purchase |
CREDITS_EXPIRY_DAYS |
no | 180 |
Days before credits expire |
LOG_LEVEL |
no | info |
info or error |
IDEMPOTENCY_TTL_SECONDS |
no | 86400 |
Idempotency cache TTL in seconds |
APP_URL |
no | http://localhost:3000 |
Public base URL; used in billing links |
See .env.example for descriptions of every variable.
Architecture
src/
index.ts — HTTP server, request routing, middleware wiring
config.ts — Environment variable loading
middleware/
auth.ts — API key extraction and validation
rateLimit.ts — Rolling window rate limiter (in-memory)
logger.ts — Structured JSON logging
billing/
keys.ts — Key generation, hashing, verification (pure functions)
db.ts — PostgreSQL pool, schema migration, all queries
stripe.ts — Checkout sessions, webhook handler, credit lifecycle
tools/
score-candidate/
handler.ts — Input validation, orchestration
scorer.ts — Anthropic API call, response parsing
schema.ts — Zod schemas and default weights
prompt.ts — System prompt and user prompt builder
errors/
codes.ts — Error code constants
envelope.ts — createError / createWarning helpers
Request flow for POST /score-candidate:
- Auth — extract key, SHA-256 lookup, bcrypt verify
- Rate limit — check rolling minute/day/concurrent windows
- Parse + validate body
- Idempotency — return cached response if key matches
- Deduct credit — atomic
SELECT FOR UPDATEin Postgres - Score — call Anthropic, parse and validate response
- Cache response if idempotency key present
- Apply
X-RateLimit-*headers, send response - Release concurrent slot, write request log line
Alternatives
Other hosted candidate-scoring options exist. hrmcp-server is source-available, self-hostable, and speaks MCP natively — no wrapper required for agent frameworks that support the protocol.
License
Licensed under the Elastic License 2.0. Free to use, self-host, and modify. You may not offer the software to third parties as a hosted or managed service. For commercial hosting inquiries contact aaron@recruitapi.app.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.