Resume Tailor MCP Server
Click on "Deploy 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., "@Resume Tailor MCP ServerTailor my resume for this senior software engineer JD and list gaps."
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.
Resume Tailor MCP Server
A local-first Model Context Protocol (MCP) server that tailors your resume or CV to any job description, with enforced provenance, evidence-gated tailoring, structured validation, and a release gate before any PDF is marked production-ready.
Core invariant: Claude proposes; Python decides. The server enforces what is allowed, what is true, how it is formatted, and whether it can be released.
Table of Contents
Related MCP server: ResumeMCP
What It Does
Workspace isolation and identity: masters and all personal data live in
~/.resume-tailor/(or$RESUME_TAILOR_HOME), never in this repository. Two masters:resumeandcv, kept separate. Identity comes only from the localconfig.yamlbinding -- never from Claude memory, chat history, a previous tailored version, or another workspace (see Workspaces and Identity).Evidence-gated tailoring: before rewriting any bullet, the server asks Claude to record what the candidate actually said about each required skill. Tailoring is only allowed with confirmed evidence; invented metrics, technologies, or scope expansions are rejected structurally.
Structured patches: Claude sends
[{operation, target, new_content}]; the server loads the master itself. Passing a full resume body is not accepted.Provenance validation: 8 rules checked on every patch (placement by evidence category, claim strength, unsupported tech/metrics/verbs, scope expansion, internship-as-professional). All-or-nothing: partial saves don't exist.
Template contract: only
classic-minimalistissupportedwith a LaTeX renderer. Other templates areexperimentalmetadata and cannot be released. No silent fallback.Release gate:
validate_version+release_resumecompile the PDF, run all validators (content, structure, format_tex, pdf), check page cap againstcareer_stage, verify provenance replay, and check the master hasn't changed since tailoring. All critical failures block release.Immutability: released versions are frozen; further changes require a new version.
Audit logs: append-only JSONL with an
ALLOWED_FIELDSwhitelist -- structurally prevents PII in logs.
Architecture
Personal data lives under one or more workspaces, each an isolation boundary for one person on one machine (never inferred from the Claude account -- see "Workspaces and identity" below):
~/.resume-tailor/ # App root (RESUME_TAILOR_HOME)
├── config.yaml # active_workspace_id, schema_version, workspaces{}
├── .config.lock
└── workspaces/
└── RT-XXXXXXXX/ # One workspace = one person on one machine
├── .lock
├── master/
│ ├── resume.yaml # Canonical master resume
│ ├── cv.yaml # Canonical master CV (separate)
│ ├── backups/ # Timestamped backups on every save
│ ├── legacy/ # Verbatim snapshots from migrate_legacy_data
│ └── history.yaml # Every master write, incl. import provenance
├── data/
│ ├── versions/ # Tailored versions (write-once)
│ ├── jd_history/ # Saved job descriptions
│ ├── exports/ # Released PDFs + LaTeX source
│ ├── evidence/ # Per-workflow evidence records
│ ├── tailoring_sessions/ # Workflow state
│ └── releases/ # Release reports (no resume content)
└── monitoring/
├── audit.jsonl # Event log (IDs/hashes/counts only)
├── .audit.lock
├── errors.jsonl # Error log
└── metrics.json # Rebuilt from audit.jsonl
resume-tailor-mcp/ # This repository
├── server.py # FastMCP entrypoint
├── lib/
│ ├── workspace.py # Workspace management, path safety
│ ├── resolve.py # Workspace/master state resolution (never raises)
│ ├── discovery.py # Folder-based master discovery/import
│ ├── locking.py # fcntl-based locking, atomic writes
│ ├── storage.py # Workspace-backed load/save
│ ├── schemas.py # Pydantic v2 models (single field vocab)
│ ├── ids.py # Block IDs, high-water mark, index
│ ├── patches.py # Patch validation and application
│ ├── evidence.py # Evidence workflow
│ ├── tailoring.py # Master-only tailoring
│ ├── workflows.py # Workflow sessions
│ ├── release.py # Validation + release gate
│ ├── audit.py # Append-only audit logging
│ ├── metrics.py # Metrics rebuilt from audit history
│ ├── keywords.py # JD keyword extraction
│ ├── rules.py # Etiquette rules loader
│ ├── templates.py # Template whitelist, no fallback
│ ├── latex.py # classic-minimalist LaTeX renderer
│ ├── export.py # compile_tex, export formats
│ ├── migration.py # One-time legacy migration
│ └── validators/
│ ├── content.py # ATS / etiquette checks
│ ├── structure.py # Section order, provenance completeness
│ ├── format_tex.py # Pre-compile inferred properties
│ ├── provenance.py # 8 provenance rules
│ └── pdf.py # Post-compile measured properties
├── resources/
│ ├── resume_etiquette.yaml # Content + formatting rules
│ └── templates/templates.yaml # Template registry with contracts
└── bin/tectonic # Bundled TeX engineSubsystem responsibilities
Module | What it enforces |
| Path safety ( |
| Workspace/master state resolution -- never raises, never guesses |
| One folder, one level, never auto-selects a candidate |
| Reentrant |
| Header fields not patchable; all-or-nothing rejection |
| 8 provenance rules; internship ≠ professional |
|
|
| Release requires PDF backend, supported template, passing page cap |
|
|
Workspaces and identity
The server never infers who you are from Claude memory, earlier
conversations, a previous tailored version, or another workspace. Identity
comes only from the local binding in config.yaml, and the same Claude
account on two different machines gets two unrelated workspaces -- the
account is not the identity boundary, the workspace is.
Call get_workspace_status() first, in any session. It never raises, and
reports one of four states:
NO_WORKSPACE-- nothing on this machine yet. Pointdiscover_mastersat the folder holding your resume/CV, or build one from scratch with thecreate-master-fileskill.WORKSPACE_FOUND-- bound and has at least one usable master.WORKSPACE_NEEDS_SETUP-- bound, but no valid master of the kind you need yet.WORKSPACE_INVALID-- the binding is missing, broken, or ambiguous (several workspaces exist and none is active).list_workspaces()shows what's on this machine;select_workspace(id)binds to one you name. Nothing is ever picked automatically.
Folder-based discovery, not a live filesystem source. discover_masters(folder)
scans exactly the one folder you name, one level deep -- no recursion, no
searching elsewhere on the machine. It ranks candidates with a resume-vs-CV
guess and a reason for each, and never selects one itself, even when there
is only one candidate. import_master_from_folder(folder, filename, kind)
then copies that one named file into the workspace (the folder is never
read again afterward) through the same preview/confirm protocol as
set_master_resume, and records where it came from
(get_master_history(kind) reads that provenance back -- filename, source
folder name/hash, file hash, and when each write happened).
A workspace with no valid master of the requested kind blocks tailoring
outright (MASTER_NOT_FOUND); the server never substitutes memory,
conversation history or another workspace's data.
Installation & Quick Start
Prerequisites
Python 3.10+
macOS, Linux, or Windows (WSL)
1. Clone and install
git clone https://github.com/priyanshu-arya/Resume-Tailor-MCP.git
cd Resume-Tailor-MCP
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt2. Download Tectonic
PDF export requires Tectonic -- a self-contained TeX engine with no system TeX Live dependency.
# macOS Apple Silicon
curl -L -o /tmp/tec.tar.gz "https://github.com/tectonic-typesetting/tectonic/releases/download/tectonic%400.17.0/tectonic-0.17.0-aarch64-apple-darwin.tar.gz"
mkdir -p bin && tar -xzf /tmp/tec.tar.gz -C bin && chmod +x bin/tectonic
# macOS Intel
curl -L -o /tmp/tec.tar.gz "https://github.com/tectonic-typesetting/tectonic/releases/download/tectonic%400.17.0/tectonic-0.17.0-x86_64-apple-darwin.tar.gz"
mkdir -p bin && tar -xzf /tmp/tec.tar.gz -C bin && chmod +x bin/tectonic
# Linux x86_64
curl -L -o /tmp/tec.tar.gz "https://github.com/tectonic-typesetting/tectonic/releases/download/tectonic%400.17.0/tectonic-0.17.0-x86_64-unknown-linux-musl.tar.gz"
mkdir -p bin && tar -xzf /tmp/tec.tar.gz -C bin && chmod +x bin/tectonicThe first PDF export downloads LaTeX packages (requires internet once). Subsequent runs are fully offline.
3. Initialize workspace and add your master
Ask your AI assistant:
"Initialize my resume workspace."
This creates ~/.resume-tailor/ with all required subdirectories.
If you have an existing master in resources/master_resume.yaml (v1 install):
"Migrate my legacy master resume to the workspace."
If you're starting fresh, use the create-master-file skill:
"Set up my master resume."
Client Integration
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"resume-tailor": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["/absolute/path/to/resume-tailor-mcp/server.py"]
}
}
}Restart Claude Desktop fully after editing.
Claude Code (CLI)
claude mcp add resume-tailor -- /absolute/path/to/.venv/bin/python /absolute/path/to/resume-tailor-mcp/server.pyOr add to .mcp.json:
{
"mcpServers": {
"resume-tailor": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["/absolute/path/to/resume-tailor-mcp/server.py"]
}
}
}Cursor / Windsurf / Cline
Add the same mcpServers block to your IDE's MCP config file
(~/.cursor/mcp.json, ~/.codeium/windsurf/mcp_config.json, or Cline's settings).
Workflow Guide
Full tailoring (recommended: tailor_resume_workflow prompt)
Use the guided tailor_resume_workflow MCP prompt. It walks through:
analyze_tailoring_requirements(jd_text, source_kind)— extracts keywords, identifies gaps, creates a workflow ID.For each
evidence_promptsquestion, ask the candidate in plain words. Only record what they explicitly confirm.save_tailoring_evidence(workflow_id, term, category, evidence_text, confirmed=True)— one call per term, orcategory="none"if they don't have it.get_master_resume(kind)— to read citable block IDs.tailor_resume(save_as, patches, workflow_id, evidence_ids=[...])— structured patches only.validate_version(version_id, workflow_id)— runs all validators.release_resume(version_id, workflow_id)— blocks if any critical failure.export_resume(version_id, workflow_id)— returns the released PDF + LaTeX source.
The task is not complete until export_resume delivers both the PDF and the LaTeX source in the reply.
Quick ATS check (no rewriting)
Use the quick_ats_check prompt: match_resume_to_jd + score_ats without saving or releasing.
Checking workflow status
get_workflow_status(workflow_id) → evidence IDs, version IDs, release status
get_workflow_status() → global metrics (workflow_count, release_count, …)MCP Surface Reference
Tools
Tool | Purpose |
| Create workspace at |
| Return workspace ID and directory layout |
| Report |
| Every workspace on this machine and which is active |
| Bind to an existing workspace the user named; logged on both sides |
| Rank importable resume/CV files in one named folder, one level deep -- never selects one |
| Copy one named file in as the master, with import provenance recorded |
| Read back a master's write history, including import provenance |
| Copy legacy |
| Bytes and file counts per subdirectory |
| Return master doc + hash + citable block index |
| Two-step preview/confirm import or update of a master |
| Extract JD keywords, create workflow, return evidence prompts |
| Record confirmed candidate evidence for one JD term |
| Apply structured patches against the workspace master |
| Run all validators + compile, return |
| Gate check → immutable release if all validators pass |
| Return released PDF + LaTeX (or draft with DRAFT label) |
| Per-workflow state or global metrics rebuilt from audit log |
| Read-only keyword gap analysis (no workflow required) |
| Read-only ATS / etiquette check |
| Side-by-side diff of two versions |
| List saved version IDs and metadata |
| List registered templates with status and contract |
| Suggest the best supported template for a JD |
Resources (URI-addressable)
URI | Content |
| Master resume (alias for |
| Master of given kind ( |
| One section of the master |
| A saved tailored version |
| Template registry |
| Etiquette + ATS rules (single source of truth) |
Prompts
Prompt | Purpose |
| Full guided flow: analyze → evidence → tailor → validate → release → export |
| ATS + keyword gap check without rewriting |
Templates
ID | Status | Notes |
| supported | The only template with a LaTeX renderer. Letter, 11pt, Computer Modern. Single column. |
| experimental | Metadata only; no renderer; cannot be released. |
| experimental | Metadata only; no renderer; cannot be released. |
| experimental | Metadata only; no renderer; cannot be released. |
| experimental | Metadata only; no renderer; cannot be released. |
| experimental | Metadata only; no renderer; cannot be released. |
| experimental | Metadata only; no renderer; cannot be released. |
| experimental | Metadata only; no renderer; cannot be released. |
Requesting an experimental template in tailor_resume is accepted for saving (the template ID is recorded). release_resume will block with template.releasable as a critical failure. export_resume in draft mode returns TEMPLATE_NO_RENDERER. There is no silent fallback to classic-minimalist.
PDF Validation
The release gate runs two classes of checks:
Inferred (pre-compile, from LaTeX source) — lib/validators/format_tex.py:
Paper size and font size from
\documentclass[...]Margin adjustments from
\addtolengthForbidden environments (
multicols, contentminipage,\includegraphics,textblock)
Measured (post-compile, from PDF) — lib/validators/pdf.py using pypdf + pdfplumber (poppler optional):
Page count ≤ cap (career_stage × template max_pages)
Page size matches contract ±1pt
Embedded font family matches contract
Modal body character size in 10–12pt
Text extractable (ATS-parseable)
All section headings present in extracted text
Name and employer names appear in text
Left text edge ≥ 0.5 in (margin check)
No empty pages
If neither pypdf nor pdfplumber is available, detect_pdf_backend() returns available: False and production release is blocked.
Known Limitations (v2)
New entries are Projects-only.
add_project_entrycan create a brand-new Projects entry (needs at least oneprofessional/internship/personal_project/academicevidence ref covering the entry, and 1-4 sourced bullets); there is still no operation that adds a job, employer, role or degree -- a new Experience/Education entry requires editing the master and re-tailoring.The evidence-placement matrix can't say "the project must itself be academic." For an existing master project bullet, academic-only evidence can still back it even if the project isn't flagged
academic: true(the table only knows section, not per-entry academic-ness). This is enforced only where the system makes the structural claim itself -- a brand-new entry viaadd_project_entryrequires theacademicflag whenever academic evidence backs it (provenance.project_academic_context). Retrofitting this onto every existing project bullet would be stricter than the shipped matrix, since academic work legitimately appears under a plain Projects heading.The metric pool is the union of every ref cited together, not per-ref. A number from one cited source can back a claim framed around a different cited source's subject, as long as both are cited on the same patch (
test_metric_pool_shared_across_refs_is_a_known_looseness). A currency sign may be dropped from new text (never added or changed) -- a deliberate asymmetry, not a bug.No deterministic truncation. If a version is too long, repair it by dropping bullets (
drop_block) or reordering sections -- the server will not automatically cut bullets (doing so could drop the metric or claim that makes a bullet true).Font check is by family name. The validator checks that the PDF's embedded font names include the contract's family string (e.g. "Computer Modern"). It does not verify exact variant names.
Unknown-requirement detection is heuristic. JD terms that aren't in the known-skills vocabulary are flagged by pattern (CamelCase, ALLCAPS 2–6 letters, words with
./+/#) -- this catches most technologies but will have false positives and false negatives.Repair limit is 3 per workflow. After 3 repair attempts,
tailor_resume(repair_of=...)is blocked; start a new workflow.classic-minimalistonly.max_pages: 2applies to every career stage today; the 2–3 page director/VP cap in CLAUDE.md becomes enforceable when a supported template raises itsmax_pageslimit. When the template cap is the one actually binding,release.career_stagereports a non-blockingwarningrather than a silentpass, so a director/academic release doesn't look like its full career-stage cap applied.Substring
kw in blobmatching replaced by word-boundary regex. Short ambiguous terms (go,r,rest,c) use case-sensitive or contextual matching; aliases (k8s→kubernetes,js→javascript, etc.) are normalized.Skill item IDs are content-derived, not counter-backed. Every other block ID family (
exp-,proj-,edu-,skg-,cert-) uses a persistent high-water-mark counter, so a deleted ID is never reissued. Skill items are the one exception: their ID is a slug of the skill's name (skill-python). Deleting and re-adding a skill with the same name reissues the same ID (harmless); renaming a skill changes its ID, so an older tailored version'ssource_refsciting the old ID will no longer resolve. This is intentional -- making skill IDs sequential would renumber every existing master and invalidate every existing version's refs -- not a bug to be fixed casually.
Privacy & Zero API Keys
All data stays on your machine in
~/.resume-tailor/.No analytics, telemetry, or third-party services.
Audit logs contain only IDs, hashes, counts, error codes and timings -- never names, email, phone, full resume text, or JD text. This is structurally enforced by
ALLOWED_FIELDSinlib/audit.py.The only outbound request is Tectonic's one-time LaTeX package download (fully offline after that).
Contributing
Issues and PRs welcome at github.com/priyanshu-arya/Resume-Tailor-MCP.
For development: pip install -r requirements-dev.txt && pytest (728 tests). PDF/release tests are skipped when tectonic is absent.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Build an ATS-friendly resume and check it against a job description, fully offline.
Resume builder with native MCP — create and edit resumes from your AI assistant.
Generate tailored, ATS-optimized resume PDFs and cover letters from a job description, over MCP.
Build, version and render resumes as PDFs from Claude or any MCP client.
Related MCP Servers
- AlicenseAqualityCmaintenanceGenerates professional PDF resumes using LaTeX templates through natural language descriptions. Supports 9 professional templates, AI-powered resume tailoring, and organized folder management for job applications.423MIT
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to fetch a master resume, tailor it to a job description, and generate a polished PDF resume using headless Chromium.-
- FlicenseAqualityDmaintenanceEnables tailoring resumes to job descriptions by scraping JDs, applying rules, and generating optimized DOCX resumes.11-
- AlicenseAqualityBmaintenanceTailors LaTeX résumés, CVs, and cover letters to job descriptions by injecting truthfully-selected content from a master CV, compiling PDFs, and logging applications.111MIT