job-resume-agent
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., "@job-resume-agentTailor my resume for the job description currently in my browser."
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.
job-resume-agent
An evidence-grounded resume tailoring system. Given a job description detected in the browser and a structured experience bank the user has curated, it produces an ATS-readable LaTeX/PDF resume where every bullet is traceable to a piece of user-provided evidence. Any wording the model inferred, generalized, or reworded is visibly flagged and must be approved by the user before export.
The product refuses to fabricate. If a required skill or achievement is not backed by evidence, it says so instead of inventing one.
Status: working proof of concept. The repository includes an MCP server, evidence linker, safe LaTeX renderer, CLI, and 65 tests. It does not yet include the Chrome extension.
How it works: the model judges, the server enforces
There is no keyword extractor in this project, by design. An MCP server is
called by a language model, so the reasoning should happen in the model —
which can tell that "Redis, Kafka, DynamoDB, low-latency" means distributed
systems, and that "barista-made espresso" is a perk, not a requirement. A
token matcher can do neither, and an earlier version of this repo proved it:
it read a Verkada posting as having 40 "requirements", 17 of which were
hyphenated boilerplate like well-being and team-building. Coverage
numbers computed against that denominator were meaningless.
So the split is:
The calling LLM decides | The server enforces |
Which requirements the posting actually states | That cited |
Which experience speaks to them | That coverage claims have surviving citations |
What order content appears in | That rephrasings introduce no new numbers or proper nouns |
How a bullet is worded for this posting | That the PDF really is one page (by compiling it) |
Which skills to emphasise first | That Education and every bank skill are always present |
That a half-empty page is reported, not shipped silently |
The model gets full latitude over selection and wording, and zero latitude over facts. That is what makes "let the model polish it" safe.
Related MCP server: mcp-overleaf-server
MCP tool workflow
load_bank(path)— returns the full bank catalog: every entry, every bullet, everybullet_id. The agent reads this; it is the only source of resume content.set_job_description(raw_text, ...)— accepts raw scraped HTML and returns clean plain text plus ajob_id. It deliberately does not extract requirements — that is the agent's job.analyze_fit(requirements, job_id)— the agent submits the requirements it found and thebullet_ids/skills backing each verdict. The server validates: unresolvable citations are stripped, and anycovered/partialverdict left with no evidence is downgraded to a gap and reported incorrections. Returns flat coverage, must-have-weighted coverage, and a recommendation.tailor_resume(selection, job_id)— the agent picks entries, bullets, and order. Optionalrewritten_textper bullet is classified by the evidence linker; new numbers or new proper nouns make itunsupportedand block export. Unknown ids are errors, not silent skips.get_draft(draft_id)— retrieve a draft.export_draft(draft_id, output_dir)— renders.texand compiles withpdflatex, counting real pages. On overflow the least-important trailing bullets are dropped (never reworded) and it recompiles. Returnspage_count,pdf_path,dropped_bullet_ids, andfill_ratio.
One page is a ceiling, not a target
Enforcing "at most one page" alone let thin drafts ship as half-empty pages, which reads to a recruiter as a thin candidate rather than a focused one. Three of the five test resumes were using only ~57% of the page. Two changes fixed it:
Skills are additive. The agent's
skillsselection now sets emphasis order, not membership — every bank skill is listed regardless. All of them are evidence-backed, and a posting that doesn't mention Julia is not a posting that penalises knowing Julia.Sparseness is reported.
tailor_resumereturnsbank_usageand warns when the selection uses under 60% of available bullets;export_draftmeasures the compiled PDF's real ink extent and returnsfill_ratio, warning below 0.75. The agent can then add content and re-tailor.
Result across the five test postings: 57–83% fill → 78–85%, still one page, with no trimming required.
What the guarantees look like in practice
Validated against five real 2027 internship postings (GlossGenius, Verkada ×2, Compeer, BTI360) using a real experience bank:
All five export to genuine one-page PDFs with Education and Skills present.
Verkada Mobile correctly reports 3 must-have gaps (Swift, Kotlin, mobile architecture) and refuses to claim them — the bank has no iOS evidence.
An agent asserting
verdict: "covered"for Swift with no citation gets coverage0.0and an explicit correction.An agent rewriting a bullet to "Shipped a SwiftUI iOS app to 50,000 users" gets
unsupported, andexport_draftrefuses.Inflating a real metric (
50+→500+) is caught the same way.accept_inferred: truecannot launder anunsupportedbullet.
Quickstart
Requires Python 3.11+ and a TeX distribution with pdflatex on PATH
(MiKTeX, TeX Live) for the one-page gate. Without pdflatex, exports still
work but skip page-count enforcement (a warning is returned).
python -m pip install -e ".[dev]"
# Inspect the bank the way an agent sees it (bullet_ids and all)
resume-agent catalog --bank docs/example-experience-bank.yaml
# Apply a selection JSON (normally authored by the agent) and compile
resume-agent tailor --bank docs/example-experience-bank.yaml \
--jd path/to/job.txt --selection selection.json --export --out ./outRun the MCP server over stdio:
resume-agent serveRegister it with Claude Code:
claude mcp add resume-agent -- resume-agent serveSet RESUME_AGENT_BANK_PATH to a local bank with your own information. Keep
your private bank and outputs under .private/ — that directory is
git-ignored. The committed example bank uses placeholder contact details and
is safe to publish.
Why this exists
Most "AI resume tailoring" tools optimize for keyword match and paragraph fluency at the cost of factual integrity. They will happily add "led a team of 10" or "improved latency by 40%" when the user never claimed either. That is a career risk for the user and a trust problem for the recipient.
This project takes the opposite stance: the model is a selector and rephraser over the user's own evidence, not a generator of achievements. Tailoring means selecting, reordering, and rephrasing — never inventing. Crucially, that constraint is enforced mechanically by the server rather than requested politely in a prompt. See docs/product-requirements.md for the acceptance criteria that operationalize this.
Product shape
Three components, loosely coupled:
Chrome extension. Detects when the active tab is a job description on a supported site (LinkedIn, Greenhouse, Lever, Ashby, Workday, plain text). Offers a Generate Tailored Resume action. Sends the JD to the local backend; opens a review UI when the draft is ready.
Local backend + web app. Owns the experience bank, the tailoring pipeline, the evidence matrix, and the LaTeX/PDF renderer. Runs on
localhostby default. Ships a review UI: side-by-side diff vs. base resume, evidence links per bullet, keyword coverage heatmap, unsupported-claim warnings, approve/reject/edit per bullet.Provider adapters (BYO account). The user connects their own OpenAI, Anthropic, or local (Ollama) account. We do not proxy through our servers and, in local-first mode, we do not store provider keys in a hosted DB — see
docs/security-and-privacy.md.
The public reference LaTeX template lives at templates/resume.template.tex and is used as the visual/structural target for the renderer. Personal identifiers (name, email, links, phone, citizenship) are declared as macros in the template so they can be overridden by a private overlay. The maintainer's own resume source, which contains real PII, lives at .private/alan-resume-source.tex and is git-ignored — treat it as the canonical worked example of what a filled-in template looks like, not as a file that ships with the repository. A minimal experience bank derived from that private source is at docs/example-experience-bank.yaml.
Non-goals (v1)
No fabrication assist. No "suggest a bullet you might have done." No "fill in a plausible metric."
No hosted multi-tenant SaaS. MVP is local-first. Cloud sync is deferred (see roadmap).
No auto-apply. We do not submit applications, message recruiters, or write cover letters. Out of scope.
No scraping of gated job boards. The extension reads the currently open page in the user's own browser session; it does not crawl.
No storage of provider API keys server-side in v1. Keys live in OS keychain or extension local storage.
No résumé "score." Numeric ATS scores are pseudoscience without the ATS in the loop. We show keyword coverage and evidence coverage, not a grade.
No LinkedIn scraping to seed the experience bank. Import is manual or from user-uploaded PDF/LaTeX only.
Documentation map
File | Purpose |
User stories, acceptance criteria, non-goals, UX principles | |
System diagram, components, provider adapter interface, LaTeX validation | |
Experience bank schema, evidence links, tailored-resume artifact schema | |
Threat model, extension permissions rationale, key storage, data flows | |
Phased build plan from M0 (skeleton) through M5 (multi-user cloud) | |
Foundational architecture decision record | |
Sample bank derived only from the maintainer's private | |
Development principles, coding standards, review posture |
Repository layout
job-resume-agent/
├── src/resume_agent/
│ ├── mcp_server.py # the six MCP tools; agent-facing contract
│ ├── catalog.py # renders the bank for the agent to read
│ ├── jd_clean.py # HTML -> clean posting text
│ ├── fit.py # validates the agent's coverage claims
│ ├── tailor.py # assembles a draft from the agent's selection
│ ├── evidence_linker.py # classifies rephrasings against evidence
│ ├── latex_renderer.py # safe LaTeX rendering (escaping, sections)
│ ├── export.py # export gate + real pdflatex one-page loop
│ ├── models.py # Pydantic schemas
│ ├── state.py # process-local session store
│ └── cli.py # serve / catalog / tailor
├── templates/resume.template.tex # public, PII-free reference template
├── tests/ # 65 tests, incl. adversarial fabrication tests
├── docs/
└── .private/ # git-ignored: real bank, resumes, outputsThe Chrome extension is not built yet; the MCP tool boundary is designed so it can call the same tools the agent does.
Assumptions on record
These are the reasonable assumptions this design makes without asking the user. Change them by opening an ADR.
Local-first, single-user MVP. Cloud/multi-user is a v2 concern. See
docs/adr/001-local-first-provider-agnostic.md.BYO provider account. We do not front an LLM as a service. The user picks OpenAI, Anthropic, or Ollama; we call it with their credentials.
LaTeX is the source of truth for the tailored artifact. PDF is a build product. The user's base template (
templates/resume.template.tex, optionally overlaid by a private file such as.private/alan-resume-source.tex) defines the layout envelope.Python backend, TypeScript extension + web UI. Matches the ML/LaTeX ecosystem on the backend and the browser platform on the frontend.
Chrome first. Firefox/Safari can follow via MV3 compatibility once the surface is stable.
The bank is user-owned. The user can export the full bank as YAML at any time and delete the local DB with one command.
Every generated bullet carries provenance. No bullet reaches the PDF without at least one
evidence_idreference in the tailored-resume artifact.
Open questions
Tracked in docs/roadmap.md#open-questions.
This server cannot be deployed
Maintenance
Related MCP Connectors
Resume builder with native MCP — create and edit resumes from your AI assistant.
Tailor resumes, generate cover letters, render CVs as PDF, and browse 22+ templates.
Tailor a resume and write a cover letter as real Word .docx files from facts you stored once.
Tailored, graded job applications: a CV, cover letter and form answers built per vacancy.
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
- 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
- AlicenseNot gradedqualityCmaintenanceEnables users to tailor a one-page LaTeX CV to a job posting by fetching relevant facts from a YAML file, rendering a LaTeX document from id-based selections, and compiling it to PDF, without wasting tokens on repetitive CV reads or compiler logs.MIT
- AlicenseNot gradedqualityCmaintenanceEnables drafting, validating, and reviewing tailored CVs and cover letters from private local career inputs, with reproducible PDF builds and application tracking support.MIT