resume-pdf-mcp
# resume-pdf-mcp
Production-grade **Model Context Protocol (MCP)** server that compiles LaTeX resumes into PDFs.
**GitHub:** [https://github.com/shashimehta03/mcp-resume](https://github.com/shashimehta03/mcp-resume)
Works with **Claude Desktop**, **Claude Code**, **Cursor**, VS Code, and any MCP-compatible client.
---
## What it does
Exposes MCP tools so your AI client can:
| Tool | Purpose |
|---|---|
| `compile_resume` | Compile LaTeX → PDF (XeLaTeX or pdfLaTeX) |
| `validate_resume` | Check LaTeX before compile (structure + safety) |
| `preview_resume` | Page count, PDF size, compile duration |
| `export_tex` | Save `.tex` into the output folder |
| `list_templates` | List built-in resume templates |
| `compile_template` | Fill a template with JSON variables → PDF |
| `version` | Server / engine / build info |
Security defaults: filename sanitization, path isolation, no shell interpolation, `-no-shell-escape`, size/timeout limits, stderr-only logging (LaTeX source is never logged in full).
---
## Complete setup checklist (follow in order)
Do every step once after cloning. Details for each step are in the sections below.
1. **Install Node.js 20+** and a LaTeX engine (`pdflatex` and/or `xelatex`) — see [Requirements](#requirements)
2. **Clone the repo** and run `npm install` then `npm run build` — see [Clone and install](#clone-and-install-do-this-first)
3. **Confirm build output exists:** `dist/index.js` and `dist/templates/` must be present
4. **Note your absolute paths:** Node binary + clone folder — see [What you must change after cloning](#what-you-must-change-after-cloning)
5. **Configure your MCP host** (pick one):
- Claude Desktop → [Configure Claude Desktop](#configure-claude-desktop)
- Cursor → [Configure Cursor](#configure-cursor)
- Claude Code → [Configure Claude Code](#configure-claude-code)
6. **Replace every example path** in the config with *your* paths (Windows: use `\\` in JSON)
7. **Do not leave `npm start` running** — Claude/Cursor start the server themselves
8. **Fully quit and reopen** the host app (Claude: tray → Quit, not just close the window)
9. **Verify the server is running** (Claude: Settings → Developer → `resume-pdf` = **running**)
10. **Use Chat mode** (Claude Desktop) — Cowork/Code may ignore this MCP server
11. **Smoke-test in chat:** ask Claude/Cursor to call the `version` tool
12. **Optional:** run `npm run test:tools` locally to confirm TeX works without an MCP host
After you change code later: `npm run build` → quit/reopen the MCP host again.
---
## Requirements
1. **Node.js 20+** (22 LTS recommended)
2. **A LaTeX engine on PATH**
- Windows: [MiKTeX](https://miktex.org/) or TeX Live (`pdflatex` / `xelatex`)
- macOS: `brew install --cask mactex-no-gui` (or BasicTeX)
- Linux: `texlive-xetex` / `texlive-latex-recommended`
Verify:
```bash
node -v
pdflatex --version
# and/or
xelatex --version
```
---
## Clone and install (do this first)
```bash
git clone https://github.com/shashimehta03/mcp-resume.git
cd mcp-resume
npm install
npm run build
```
Confirm the build:
```bash
# Windows PowerShell
Test-Path .\dist\index.js
Test-Path .\dist\templates\index.json
# macOS / Linux
ls dist/index.js dist/templates/index.json
```
Optional local env file (MCP hosts do **not** auto-load `.env` — still set `env` in their JSON):
```bash
cp .env.example .env
```
You do **not** need to keep `npm start` running for Claude/Cursor. Those apps **spawn** the server themselves. Only run `npm start` / `npm run test:tools` for local smoke tests.
---
## What you must change after cloning
Repo examples keep sample paths on purpose. **After clone, change them to your machine.**
| What | Default in repo / examples | Change to |
|---|---|---|
| Project folder | `E:\\mcp_resume` / `/Users/YOU/mcp-resume` | Where you cloned the repo |
| `args` entrypoint | `.../dist/index.js` | `<your-clone>/dist/index.js` (after `npm run build`) |
| `OUTPUT_DIR` | `./output` or example absolute path | `<your-clone>/output` (use absolute in MCP hosts) |
| `TEMP_DIR` | `./temp` | `<your-clone>/temp` |
| `TEMPLATES_DIR` | `dist/templates` | `<your-clone>/dist/templates` (or leave unset) |
| `command` (Node) | `node` or `C:\\Program Files\\nodejs\\node.exe` | Full path to `node` (Claude Desktop usually needs the full path) |
| `DEFAULT_ENGINE` | `xelatex` in `.env.example`, `pdflatex` in some examples | Whichever engine you installed |
| `cwd` (optional) | project root | Same as your clone path |
More notes: [`examples/README.md`](examples/README.md).
### Find your paths
**Windows (PowerShell):**
```powershell
# Node
(Get-Command node).Source
# Project (run from inside the clone)
(Get-Location).Path
# Real Claude config path on Microsoft Store / MSIX installs
$pkg = (Get-AppxPackage -Name "*Claude*").PackageFamilyName
Join-Path $env:LOCALAPPDATA "Packages\$pkg\LocalCache\Roaming\Claude\claude_desktop_config.json"
```
**macOS / Linux:**
```bash
which node
pwd # from inside the clone
# Config is usually:
# ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
```
**Windows JSON tip:** every backslash in a path must be doubled: `E:\\mcp_resume\\dist\\index.js`.
---
## Configure Claude Desktop
### 1) Build the project
```bash
npm install
npm run build
```
### 2) Open the config Claude actually reads
In Claude Desktop:
**Settings → Desktop app → Developer → Edit Config**
That opens `claude_desktop_config.json`.
#### Windows Microsoft Store / MSIX builds (important)
“Edit Config” may open:
`%APPDATA%\Claude\claude_desktop_config.json`
…but the app often **reads**:
`%LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude\claude_desktop_config.json`
Edit the **LocalCache** file (or copy your finished config into **both**).
If you only edit the `%APPDATA%` file, Claude may ignore your server with no error.
Details: [MSIX config path issue](https://github.com/anthropics/claude-code/issues/26073).
### 3) Add the server (merge into existing JSON)
If the file already has `preferences` / `coworkUserFilesPath`, **keep them**. Only add or update the top-level `mcpServers` key (do **not** paste a second `{ ... }` object).
Valid shape (copy, then replace paths):
```json
{
"mcpServers": {
"resume-pdf": {
"command": "C:\\Program Files\\nodejs\\node.exe",
"args": ["E:\\mcp_resume\\dist\\index.js"],
"cwd": "E:\\mcp_resume",
"env": {
"OUTPUT_DIR": "E:\\mcp_resume\\output",
"TEMP_DIR": "E:\\mcp_resume\\temp",
"TEMPLATES_DIR": "E:\\mcp_resume\\dist\\templates",
"DEFAULT_ENGINE": "pdflatex",
"LOG_LEVEL": "info",
"NODE_ENV": "production"
}
}
}
}
```
What to change in that block (defaults left above on purpose):
| Key | Change? |
|---|---|
| `command` | **Yes** → your `node.exe` / `node` absolute path |
| `args[0]` | **Yes** → your clone’s `dist/index.js` |
| `cwd` | **Yes** → your clone folder |
| `OUTPUT_DIR` / `TEMP_DIR` / `TEMPLATES_DIR` | **Yes** → folders under your clone |
| `DEFAULT_ENGINE` | If needed → `pdflatex` or `xelatex` |
| `LOG_LEVEL` / `NODE_ENV` | Optional |
**macOS / Linux example** (same keys — only paths change):
```json
{
"mcpServers": {
"resume-pdf": {
"command": "/usr/local/bin/node",
"args": ["/Users/YOU/mcp-resume/dist/index.js"],
"cwd": "/Users/YOU/mcp-resume",
"env": {
"OUTPUT_DIR": "/Users/YOU/mcp-resume/output",
"TEMP_DIR": "/Users/YOU/mcp-resume/temp",
"TEMPLATES_DIR": "/Users/YOU/mcp-resume/dist/templates",
"DEFAULT_ENGINE": "xelatex",
"LOG_LEVEL": "info"
}
}
}
}
```
Repo template: [`examples/claude-desktop-config.json`](examples/claude-desktop-config.json).
### 4) Fully quit and reopen Claude
Closing the window is not enough. Use the tray / menu bar → **Quit**, confirm no `Claude` process is left, then start Claude again.
MCP servers are loaded **only at startup**.
### 5) Verify
1. **Settings → Developer → Local MCP servers**
2. Select `resume-pdf`
3. Status should be **running** (not failed / disconnected)
4. If failed: open **View Logs** on that server
5. Start a **new Chat** (not Cowork, not Code)
6. Ask:
```text
Call the resume-pdf version tool and show the result.
```
To steer Claude every time:
```text
Always use resume-pdf MCP tools (compile_resume, validate_resume, compile_template)
for any resume/LaTeX/PDF work. Do not compile LaTeX yourself in the sandbox.
```
PDFs land in your `OUTPUT_DIR` (for example `<clone>/output`).
### Optional: install as a Claude Desktop Extension
If the JSON `mcpServers` entry does not stay connected on your Claude build:
```bash
npm run build
npm run install:claude
```
Then fully quit/reopen Claude and check **Settings → Developer / Extensions** for **Resume PDF** (enabled).
Before relying on that helper on another machine, point these at **your** clone (repo defaults are examples — leave them until you change them):
- `mcpb/manifest.json` → `user_config.*.default`
- `mcpb/server/index.js` → fallback `RESUME_PDF_MCP_ROOT`
---
## Configure Cursor
1. Open Cursor MCP settings (or project `.cursor/mcp.json` / global MCP config, depending on your Cursor version).
2. Add a server entry like:
```jsonc
{
"mcpServers": {
"resume-pdf": {
// CHANGE: "node" is usually fine in Cursor if Node is on PATH
"command": "node",
// CHANGE: absolute path to YOUR clone
"args": ["E:/mcp_resume/dist/index.js"],
"env": {
// CHANGE these to YOUR clone paths
"OUTPUT_DIR": "E:/mcp_resume/output",
"TEMP_DIR": "E:/mcp_resume/temp",
"DEFAULT_ENGINE": "pdflatex"
}
}
}
}
```
See also [`examples/cursor-mcp.json`](examples/cursor-mcp.json).
Restart Cursor / reload MCP servers after saving.
---
## Configure Claude Code
Add to your Claude Code MCP config (see [`examples/claude-code-mcp.json`](examples/claude-code-mcp.json)):
```jsonc
{
"mcpServers": {
"resume-pdf": {
"command": "node",
// CHANGE: YOUR clone's dist entry
"args": ["./dist/index.js"],
// CHANGE: YOUR clone directory
"cwd": "/absolute/path/to/mcp-resume",
"env": {
"DEFAULT_ENGINE": "xelatex",
"OUTPUT_DIR": "./output",
"TEMP_DIR": "./temp"
}
}
}
}
```
Or from the project folder:
```bash
claude mcp add resume-pdf -- node ./dist/index.js
```
(Adjust to your Claude Code version’s exact `mcp add` syntax.)
---
## Environment variables
Copy [`.env.example`](.env.example). Values below are defaults — change only if you need to.
| Variable | Default | Notes |
|---|---|---|
| `OUTPUT_DIR` | `./output` | Prefer absolute path in MCP host `env` |
| `TEMP_DIR` | `./temp` | Per-request workspaces |
| `DEFAULT_ENGINE` | `xelatex` | Use `pdflatex` if XeLaTeX isn’t installed |
| `MAX_LATEX_SIZE` | `512000` | Max LaTeX source bytes |
| `COMPILE_TIMEOUT` | `60000` | Compile timeout (ms) |
| `MAX_MEMORY_MB` | `1024` | Soft memory guidance |
| `LOG_LEVEL` | `info` | `silent` \| `error` \| `warn` \| `info` \| `debug` |
| `TEMPLATES_DIR` | `dist/templates` (runtime) | Override if you move templates |
| `KEEP_TEMP_ON_FAILURE` | `false` | Set `true` to debug failed compiles |
MCP hosts pass these via the `env` block in their JSON config (they do not automatically load `.env`).
---
## How to use (from the AI chat)
Once the server is **running** in the client:
### Compile raw LaTeX
```text
Use compile_resume with engine pdflatex and this LaTeX:
\documentclass{article}
\begin{document}
Hello from resume-pdf-mcp
\end{document}
```
### Validate first
```text
Use validate_resume on this LaTeX before compiling: ...
```
### Use a template
```text
Use list_templates, then compile_template with template_name "modern-simple"
and variables for name, email, summary, skills, experience, education.
```
Example payload shape: [`examples/sample-template-request.json`](examples/sample-template-request.json).
### Expected success response
```json
{
"success": true,
"pdf_path": ".../output/resume.pdf",
"tex_path": ".../output/resume.tex",
"compilation_logs": "...",
"execution_time": 0.82
}
```
---
## Local testing (without Claude)
```bash
npm test # unit + integration
npm run test:coverage
npm run test:tools # hits all tools; watch USAGE logs on stderr
npm run test:smoke # compile minimal-ats template once
```
---
## Docker
```bash
docker compose build
docker compose run --rm resume-pdf-mcp
```
Image includes TeX Live. See [docs/docker.md](docs/docker.md).
For Claude Desktop via Docker, point `command` at `docker` and pass your compose/run args (stdio must stay attached).
---
## Project layout
```
src/ TypeScript source (tools, services, latex, config)
dist/ Build output (what MCP hosts should run)
mcpb/ Claude Desktop extension manifest + launcher
examples/ Sample MCP configs and requests
docs/ Installation, usage, architecture, contributing
tests/ Vitest suite
output/ Generated PDFs/TeX (gitignored contents)
temp/ Compile workspaces (gitignored contents)
```
Architecture:
```
Tool → ResumeService → LatexCompiler → FileSystemService → XeLaTeX/pdfLaTeX
```
---
## Troubleshooting
| Problem | Fix |
|---|---|
| Tool not in Claude | Use **Chat** mode; fully Quit/reopen; confirm Developer shows **running** |
| Config saved but ignored (Windows) | Edit the MSIX `LocalCache\Roaming\Claude\claude_desktop_config.json` path |
| `ENGINE_NOT_FOUND` | Install MiKTeX/TeX Live; put `pdflatex`/`xelatex` on PATH; set `DEFAULT_ENGINE` |
| `Server disconnected` | Rebuild (`npm run build`); use full `node` path; set `cwd`; Quit Claude and reopen |
| Claude compiles in sandbox instead | Server not connected — fix Developer status; instruct Claude to use `compile_resume` |
| Wrong output folder | Set absolute `OUTPUT_DIR` in the MCP `env` block |
Logs (Windows MSIX):
`%LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude\logs\mcp-server-resume-pdf.log`
---
## Documentation
- [Installation](docs/installation.md)
- [Usage](docs/usage.md)
- [Docker](docs/docker.md)
- [Development](docs/development.md)
- [Architecture](docs/architecture.md)
- [Contributing](docs/contributing.md)
---
## Development scripts
```bash
npm run dev # tsx stdio server
npm run build
npm start
npm run lint
npm run typecheck
npm run install:claude # install local Claude Desktop extension helper
```
---
## License
MIT — see [LICENSE](LICENSE).
---
## Links
- Repository: [https://github.com/shashimehta03/mcp-resume](https://github.com/shashimehta03/mcp-resume)
- Issues: [https://github.com/shashimehta03/mcp-resume/issues](https://github.com/shashimehta03/mcp-resume/issues)
TDQS
Scored across 7 tools
Most tools are clearly distinct: compilation, validation, preview, export, templates, and version all target different operations. The main confusion is between compile_template and compile_resume—one renders a named template while the other compiles raw LaTeX source, but the distinction (template vs source) is reasonably clear from names.
Tools consistently use a verb_noun pattern throughout (compile_template, compile_resume, preview_resume, export_tex, list_templates, validate_resume). The pattern is mostly regular, though having both compile_template and compile_resume with the same 'compile' verb is a minor deviation from strict one-verb-per-action.
Seven tools is well within the ideal 3-15 range for a resume/pdf generation server. Each tool has a distinct role in the workflow: template management, compilation, validation, preview, export, and metadata.
Core workflows are covered: templates can be listed and compiled, source can be validated, compiled, previewed, and exported. However, there's no obvious update/delete operation for templates, and no tool to manage or inspect output artifacts beyond preview metadata, leaving some minor lifecycle gaps.