Metadata-Version: 2.4
Name: cv-tailor-mcp
Version: 0.3.0
Summary: MCP server that tailors a LaTeX one-page CV to a job posting without burning tokens on boilerplate or compile-log babysitting.
Author: Aarón Ugalde Téllez
License-Expression: MIT
Project-URL: Homepage, https://github.com/AaronUgalde/cv-tailor-mcp
Project-URL: Repository, https://github.com/AaronUgalde/cv-tailor-mcp
Project-URL: Issues, https://github.com/AaronUgalde/cv-tailor-mcp/issues
Keywords: mcp,cv,resume,latex,cursor
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp>=1.0
Requires-Dist: jinja2>=3.1
Requires-Dist: pydantic>=2.13
Requires-Dist: pymupdf>=1.28
Requires-Dist: pyyaml>=6.0
Dynamic: license-file

# 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`](cv_tailor_mcp/examples/cv_facts.example.yaml) for a
fictional but complete example, and [`docs/SCHEMA.md`](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.

```bash
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:

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

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

Useful setup options:

```bash
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`](docs/SCHEMA.md) -- see
[`cv_tailor_mcp/templates/default_cv_template.tex.j2`](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](LICENSE).
