Skip to main content

cv-tailor-mcp

An MCP server that tailors a one-page LaTeX CV to a specific job posting without burning tokens on repetitive work: re-reading your whole CV source, writing the same LaTeX boilerplate over and over, or pasting pdflatex compiler logs into the chat.

It works identically from Claude Code and Cursor (or any other MCP-compliant client) -- MCP is an open protocol, so the same server binary is just registered in each client's own config file.

This repo contains no personal data. You keep your own cv_facts.yaml (your real experience/projects/skills) in your own private repo, and point this server at it via environment variables. See cv_tailor_mcp/examples/cv_facts.example.yaml for a fictional but complete example, and docs/SCHEMA.md for the full schema.

What it does

Instead of:

  1. Reading your entire CV source file into the model's context every time,
  2. reading a previous tailored variant as a style reference,
  3. having the model write ~150-250 lines of near-identical LaTeX by hand, and
  4. pasting compiler output into the chat 3-5 times while manually tightening spacing to fit one page...

...you get a small set of tool calls:

Tool Purpose
list_tags Discover the tags in your cv_facts.yaml
get_facts(tags=[...]) Get only the relevant slice of your experience/projects/skills
list_variants See CVs you've already generated (tagline, tags) without re-reading each file
render_cv(request) Generate cv_<variant>.tex from typed selections and temporary overrides
compile_cv(variant) Run pdflatex, get back {success, pages, first_error}, auto-cleans .aux/.log/etc.
render_and_compile(request) Render, auto-fit to one page, compile, and validate the PDF in one call

render_and_compile tries default, compact, and tight spacing when spacing_profile is auto, then returns paths, page count, the selected profile, and structured diagnostics.

Install

Requires Python 3.10+. A LaTeX distribution is optional during setup: without pdflatex, the MCP can still generate .tex files and reports a clear error if compile_cv is called.

git clone https://github.com/AaronUgalde/cv-tailor-mcp.git
cd cv-tailor-mcp
./setup.sh

The setup script:

  1. Installs the command in a project-local virtual environment.
  2. Creates ~/.cv-tailor/cv_facts.yaml with fictional starter data.
  3. Creates ~/.cv-tailor/generated/.
  4. Safely adds cv-tailor to ~/.cursor/mcp.json, preserving other servers.

It never replaces an existing facts file or cv-tailor MCP entry unless you explicitly choose the corresponding --force-facts or --force-config flag.

Next, replace the fictional information in ~/.cv-tailor/cv_facts.yaml, restart Cursor, and ask it to call list_tags.

Install as a user command

Install the published PyPI package as an isolated command with:

uv tool install cv-tailor-mcp
cv-tailor-mcp init

pipx install cv-tailor-mcp works as an alternative to uv.

Useful setup options:

cv-tailor-mcp init --no-cursor
cv-tailor-mcp init --facts-path ~/private-cv/facts.yaml --output-dir ~/CVs
cv-tailor-mcp init --force-config       # never overwrites facts
cv-tailor-mcp init --force-facts        # explicit facts reset
cv-tailor-mcp configure --client all --facts-path ~/private-cv/facts.yaml --output-dir ~/CVs
cv-tailor-mcp configure --client all --project-dir ~/my-cv --force-config
cv-tailor-mcp doctor

configure updates or migrates Cursor and Claude MCP entries without creating or changing CV facts. Existing configuration files are backed up before a write, and unrelated MCP servers and custom environment values are preserved. Use --cursor-config or --claude-config for non-default locations.

cv-tailor-mcp serve starts the stdio server and is normally invoked by Cursor rather than run manually.

Configuration

Environment variables

Variable Required Default
CV_FACTS_PATH yes --
CV_OUTPUT_DIR yes --
CV_TEMPLATE_PATH no bundled cv_tailor_mcp/templates/default_cv_template.tex.j2
PDFLATEX_PATH no resolved via PATH (shutil.which("pdflatex"))

The server fails fast if the facts or output paths are missing. A missing pdflatex only disables compile_cv; all other tools remain available.

Other MCP clients can use the command and environment variables generated in Cursor's mcp.json. The transport is standard MCP over stdio.

Example session

> list_tags
{"tags": [{"name": "backend", "experience": 1, "projects": 1, ...}, ...]}

> get_facts(tags=["backend", "cloud"])
{... only the entries/bullets tagged backend or cloud ...}

> render_and_compile({
    "variant": "acme",
    "tagline": "Backend Engineering Intern Candidate & Cloud",
    "summary": "Plain text is escaped safely by default.",
    "education": [{
      "id": "edu_utaustin",
      "header_override": {"graduation": "Expected Graduation: December 2027"}
    }],
    "skill_group_ids": ["languages", "backend_cloud"],
    "experience": [{
      "id": "acme_backend_intern",
      "bullet_ids": ["acme_api_bullet", "acme_pipeline_bullet"],
      "bullet_overrides": {
        "acme_api_bullet": "Tailored plain-text wording with 25% improvement."
      },
      "extra_bullets": [{"latex": "Explicit raw \\textbf{LaTeX} opt-in."}]
    }],
    "projects": [{
      "id": "proj_taskflow",
      "bullet_ids": ["taskflow_realtime_bullet"],
      "header_override": {"subtitle": "Distributed Systems Project"}
    }],
    "spacing_profile": "auto"
  })
{"success": true, "tex_path": ".../cv_acme.tex", "pdf_path": ".../cv_acme.pdf",
 "pages": 1, "spacing_profile": "default", "diagnostics": []}

Bring your own template

If the bundled one-page style doesn't match yours, write your own .tex.j2 (Jinja2, using << >> for variables and <% %> for blocks/loops instead of the default {{ }}/{% %}, since LaTeX already uses {/}) and point CV_TEMPLATE_PATH at it. It must accept the same context documented in docs/SCHEMA.md -- see cv_tailor_mcp/templates/default_cv_template.tex.j2 for a working reference.

Safety model

  • cv_facts.yaml remains immutable from the MCP's perspective. Education, experience, project, and bullet overrides exist only in the generated CV.
  • Existing YAML text remains trusted LaTeX for backward compatibility. New request text is plain and escaped by default; raw LaTeX requires {"latex": "..."}.
  • Output validation reports malformed commands, placeholders, missing sections, page-count problems, and suspicious PDF whitespace. Always review the final PDF before submitting it.

License

MIT, see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cv_tailor_mcp-0.3.0.tar.gz (30.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cv_tailor_mcp-0.3.0-py3-none-any.whl (25.6 kB view details)

Uploaded Python 3

File details

Details for the file cv_tailor_mcp-0.3.0.tar.gz.

File metadata

  • Download URL: cv_tailor_mcp-0.3.0.tar.gz
  • Upload date:
  • Size: 30.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for cv_tailor_mcp-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9a03224b11be9110d279f85e339a798c3aaa51c706a8c612ef04e0c60d25a13a
MD5 4714932344d5f7030768372b4337e4df
BLAKE2b-256 35cdb1f66c26ee505929d486c44c7877b345b7a735f04ae5d60f847966351e29

See more details on using hashes here.

File details

Details for the file cv_tailor_mcp-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: cv_tailor_mcp-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 25.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for cv_tailor_mcp-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8ba6085f7531c95883b43d4e0bd43b0c715d42b9ee11704081ac4a76ec8a0ed5
MD5 90f87ffd55c4935b9d17faa7d7479c8c
BLAKE2b-256 bdda013111ca13f889e481206aff29c12c3d8c68d532200b7b153ccbb2e580c2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page