job-tracker
Optional Gmail integration that searches the user's inbox for recruiter emails, scans for updates on tracked job applications, and drafts/sends replies through five dedicated tools.
Click on "Install 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-trackerWhich applications are still waiting for a response?"
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 Search Assistant — MCP Server
A local Model Context Protocol server that lets Claude read and update your job-search spreadsheets through natural conversation — "what's still awaiting a response after 2 weeks?", "add this posting to my tracker", "mark the Amazon role as interviewing", "add a Salary Range column and set it to 180-210k for the Amazon role", "what remote leads did I find this week?", "promote the Acme Corp lead to my tracker".
It manages a funnel — discover → apply → track — plus an interview-prep note:
File | Role | Env var |
| Applications you've submitted (the tracker) |
|
| Leads you've found but not yet applied to (discovery) |
|
| Rolling interview Q&A reference (prep) |
|
| Folder for generated resumes / cover letters, saved and printed from |
|
| Stable resume facts that |
|
promote_to_tracker turns a discovery lead into a tracked application; save_document
and print_document write generated docs to the Resumes\ folder and print them. An
optional Gmail integration (five tools) searches your inbox for recruiter mail,
scans for updates on tracked roles, and drafts/sends replies — see Gmail integration.
Runtime: Node.js + TypeScript
Transport: stdio (local process — no networking, no auth)
Data source: your
.xlsxfiles, read/written with SheetJS (xlsx)Schema: dynamic — driven by each sheet's header row (custom columns supported)
Printing: silent PDF printing via
pdf-to-printer(bundles SumatraPDF);.docxis converted to PDF first using LibreOffice or Microsoft WordClient: Claude Desktop or Claude Code (see config below)
Build
cd mcp-job-tracker
npm install
npm run buildThis produces dist/index.js. npm install also pulls pdf-to-printer, which
bundles SumatraPDF for silent PDF printing (no separate install). Printing
.docx additionally requires LibreOffice or Microsoft Word for the
docx→PDF conversion step; PDFs need neither. Keeping the discovery sheet's
formatting on MCP writes uses Python + openpyxl (scripts/format_discovery.py);
optional — writes still succeed without it. generate_resume needs Python +
python-docx (scripts/gen_resume.py) plus LibreOffice (matches the
existing resumes' look) for its PDF step. read_document uses Python +
pypdf (PDFs) and python-docx (docx) via scripts/extract_text.py. The
optional Gmail tools need a one-time npm run gmail:auth (see Gmail integration).
Related MCP server: quantjobs
Configure Claude Desktop
Open Claude Desktop's config file (Windows):
%APPDATA%\Claude\claude_desktop_config.jsonAdd a job-tracker server entry (create the file / mcpServers object if needed):
{
"mcpServers": {
"job-tracker": {
"command": "node",
"args": ["C:\\Users\\developer\\mcp-job-tracker\\dist\\index.js"],
"env": {
"JOB_TRACKER_FILE": "C:\\Users\\developer\\Documents\\Job_Tracking.xlsx",
"JOB_DISCOVERY_FILE": "C:\\Users\\developer\\Documents\\Job_Search_Discovery.xlsx",
"JOB_INTERVIEW_PREP_FILE": "C:\\Users\\developer\\Documents\\Interview_Prep_QA.md",
"JOB_RESUMES_DIR": "C:\\Users\\developer\\Documents\\Resumes"
}
}
}
}Restart Claude Desktop. You should see the job-tracker tools appear. If
JOB_TRACKER_FILE is omitted, the server defaults to
<home>\Documents\Job_Tracking.xlsx. If JOB_DISCOVERY_FILE,
JOB_INTERVIEW_PREP_FILE, or JOB_RESUMES_DIR are omitted, they default to
Job_Search_Discovery.xlsx, Interview_Prep_QA.md, and a Resumes\ folder
sitting **next to** the tracker file. **Claude Code** uses the same entry under
mcpServers in ~/.claude.json.
Moving to another machine
The server is portable (plain Node stdio — Windows or macOS). To set it up on a new machine:
Copy this project folder over (you can skip
node_modulesanddist).Copy your
Job_Tracking.xlsxto the new machine.Make sure Node.js 18+ is installed (
node --version).Run the installer, pointing it at your spreadsheet:
node setup.mjs "C:\\path\\to\\Job_Tracking.xlsx" # Windows node setup.mjs "/Users/you/Documents/Job_Tracking.xlsx" # macOSIt installs deps, builds, backs up the existing Claude Desktop config, and wires in the
job-trackerentry with paths correct for that machine. Omit the path to default to<home>/Documents/Job_Tracking.xlsx. Flags:--no-build(reuse a copieddist/),--bare-node(usenodefrom PATH instead of an absolute node path).Fully quit and restart Claude Desktop.
Tools
Tool | What it does |
| List all rows. Optional filters: |
| Full details for one application, matched by |
| Append a new row. |
| Update |
| Edit any field(s) of a row matched by |
| Permanently remove a row matched by |
| Rows still Awaiting Response or Applied whose Date Applied is more than |
| Rows whose Next Follow-Up date is due — on/before today, or within the next |
| Case-insensitive substring search across Company, Position, and Notes. |
| (stretch) Drafts a short, professional follow-up email for a role, referencing time since applying and the resume version used. Draft text only — it does not send anything. |
| Add a new custom column at the end of the sheet. Optional |
| Rename a custom column ( |
| Remove a custom column, shifting the columns to its right left (like Excel's Delete Column). Refuses on the base columns. |
Discovery-sheet tools (Job_Search_Discovery.xlsx)
Tool | What it does |
| List leads. Optional filters: |
| Append a lead. |
| Edit a lead matched by |
| Remove a lead matched by |
| Copy a lead into the tracker as an application — maps Company/Position/Job Link/Location, sets Status Applied and Date Applied today (both overridable), and folds Salary + Match Assessment into Notes. Refuses to create a duplicate tracker row. Optional |
Interview-prep tools (Interview_Prep_QA.md)
Tool | What it does |
| Return the prep file as text, or just one company's section with the optional |
| Append markdown under a |
Document tools (Resumes\)
Tool | What it does |
| **Generate a tailored resume PDF (or docx) host-side and save it to |
| Save a |
| Silently print a saved file (name in |
| Extract the plain text of a saved |
| Read any plain-text file ( |
| List saved resumes / cover letters in the |
| Delete a document from the |
| Show the shape of |
| Read one field from |
| Update one existing field in |
Gmail tools (optional — require one-time auth)
Tool | What it does |
| Search your Gmail for job-related mail by |
| Read one message's full body by its |
| Scan recent Gmail for updates relevant to your tracked applications — optional |
| Save a Gmail draft — reply within a |
| Sends immediately and cannot be recalled. Refuses unless |
Until Gmail is authorized (see Gmail integration below), all five return a clear "authorize first" error.
Spreadsheet schema
Both workbooks use a dynamic schema — the columns are driven by each sheet's actual header row, and the read/write engine is schema-agnostic (a sheet is just a spec: file path, required columns, which columns are dates, and the match keys).
Tracker (Job_Tracking.xlsx)
The ten base columns below must always be present (the tools match, filter, and compute dates on them) and are protected from rename/delete:
Company | Position | Job Link | Location | Resume Version | Contact/Referral | Date Applied | Status | Next Follow-Up | NotesAny custom columns you add beyond these are first-class:
They appear in
list_jobs/get_joboutput automatically.You set them per job with the
extra_fieldsargument ofadd_jobandupdate_job, e.g.extra_fields: { "Salary Range": "180-210k" }(an empty string clears the value).You add / rename / remove them with
add_column,modify_column, anddelete_column.
Custom columns are stored as text. The two base date columns (Date Applied, Next Follow-Up) are the ones stored as Excel date serials.
Managing columns
// add a column, pre-filled on every existing row
add_column { "name": "Salary Range", "default_value": "TBD" }
// rename it and/or overwrite every row's value
modify_column { "name": "Salary Range", "new_name": "Comp", "fill_value": "Confidential" }
// remove it (shifts later columns left, like Excel)
delete_column { "name": "Comp" }Conventional Status values: Applied, Awaiting Response,
Informal - Referral Sent, Interviewing, Offer, Declined, Withdrawn,
Closed - No Longer Available. (The field is free text — these are just the
values suggested to Claude.)
Discovery (Job_Search_Discovery.xlsx)
The base columns, in order:
Date Found | Company | Position | Location (Remote/Hybrid) | Salary | Salary Confidence (Confirmed/Estimated) | Job Link | Source (Company Careers Page/LinkedIn/Aggregator) | Posted Date / Days Since Posted | Known Gap Flag | Match Assessment | StatusAll discovery values are stored as text (including Date Found, kept as an
ISO YYYY-MM-DD string — no date-serial conversion). Rows are matched by
Company + Position. Set columns through the fields argument of
discovery_add / discovery_update; Company and Position have their own
parameters. Status on new leads defaults to Open.
The column-management tools (add_column etc.) operate on the tracker; the
discovery sheet is managed through its own row tools.
Formatting: the discovery sheet is also written by the daily 11 AM task
(Python + openpyxl, with bold header / frozen row / auto-filter / clickable
links). Because SheetJS (used by the MCP tools) doesn't re-serialize that rich
styling, every MCP discovery write is followed by a best-effort openpyxl
reformat pass (scripts/format_discovery.py) that re-applies the standard
formatting — so MCP edits and the daily task keep the file looking the same.
Requires Python + openpyxl; if absent, the write still succeeds (the tool
returns formatted: false) and the next daily run restores styling.
Interview prep (Interview_Prep_QA.md)
A plain-markdown reference — not a spreadsheet — organised as:
# Interview Prep — Q&A Reference
## Company — Role
**Q: ...?**
A: ...
*Notes: ...*
---
## Next Company — Role
...append_interview_prep matches an existing ## Company section on the company
portion of the heading (the part before — Role), so appending "Acme Corp" lands
under ## Acme Corp — Engineering Manager. New sections are separated with ---.
Keep to the **Q: …** / A: … / optional *Notes: …* pattern when adding content.
If a bare company name matches more than one section (e.g. ## Acme Corp — Billing
and ## Acme Corp — Clinical), both read/append refuse and list the matches — pass
the full heading to disambiguate. An exact full-title match always wins over a
bare prefix, so a specific heading is never ambiguous.
Generating a tailored resume (generate_resume)
The recommended way to produce a resume — especially from Claude Desktop,
which can't write to arbitrary host paths. The model never emits the binary;
it emits tailored text, and the server renders the PDF host-side. The server's
MCP instructions tell connected clients to use this tool for resumes rather
than hand-rolling a python-docx/LibreOffice script or moving bytes through
base64, so it's the single resume path (scripts/gen_resume.py is the one
generator).
generate_resume {
"company": "Acme Corp",
"position": "Engineering Manager",
"summary": "…tailored SUMMARY paragraph for this posting…",
"key_qualifications": [
"…bullet tying real experience to this role…",
"…another alignment bullet…"
]
// optional: skills[], include_projects, format ("pdf"|"docx"), filename, overwrite
}Facts stay fixed. Employers, titles, dates, education, certs, and the base skills live in
resume_master.json; the model can't invent or drift them — it only supplies thesummaryandkey_qualificationsfor thiscompany+position. Edit your real facts inresume_master.json(a sibling of the tracker; override withJOB_RESUME_MASTER_FILE).Rendering merges master + tailoring →
.docxviascripts/gen_resume.py(python-docx, the measured spec: Liberation Serif, 20 pt name, ruled 11 pt headings, 0.63" margins, US Letter) → PDF via LibreOffice. ~15 s per resume.Output lands in
Resumes\(e.g.Dale-Magrath-Resume-Acme-Corp-Engineering-Manager.pdf), ready forprint_document. No base64, no download-then-move.Wired to tracking. The result carries a
nextStepwith the exact follow-up call, chosen from the sheets: brand-new company →add_job(as Applied, Resume Version set to the file), an existing discovery lead →promote_to_tracker, already tracked →update_jobto set the Resume Version. So tailor → save → apply → track is one Desktop flow — the model just offers the suggested step.
Why this beats base64: the tailored text is ~1 k tokens; the 55 KB PDF binary would be ~22 k. The model does the writing; the host does the rendering.
Rendering notes (why it's built this way)
Hard-won details worth keeping — changing them tends to reintroduce old bugs:
Role/date lines are a 2-column borderless table, not a tab stop. A tab with a right tab-stop spanning a bold title and italic dates in one paragraph triggered a LibreOffice PDF-export bug (ToUnicode CMap on subset fonts) that made the title extract as garbled characters in
pdftotext,pypdf, andpdfminer.six— even though it looked fine on screen (and could trip ATS parsers). The table renders identically and extracts cleanly in all three. Keep it a table.LibreOffice does the docx→PDF, not Word. LibreOffice subsets fonts → ~55 KB with Liberation Serif, matching the existing resumes. Word barely subsets (embedded ~1.1 MB of Calibri → a 258 KB PDF), so
convertDocxToPdfprefers LibreOffice and falls back to Word only if it's absent.2-page fit is by layout, never by truncation. A full experience history + 6-bullet Key Qualifications + Projects + Education + Skills lands on 2 pages with Honors & Awards off (opt in with
include_honors). If it ever runs long, the lever is tightening spacing (heading 12/5 pt, body 8 pt) — content is never silently trimmed to hit a page count.base64 is a last resort. The only resume ever saved through the base64 path came out corrupt (valid
%PDF/%%EOFbut a brokenstartxref). The leading-bytes check catches gross truncation, not subtle corruption — so prefergenerate_resume, orsave_documentwithsource_path, for anything binary.
Saving an already-generated document (Resumes\)
If a file already exists on disk (e.g. produced by the docx/pdf skill),
save_document puts it into Resumes\, and print_document sends it to a printer:
// Preferred: hand over a path to a file already written to disk.
save_document { "filename": "Dale-Resume-Acme-Corp.docx", "source_path": "C:\\Temp\\out.docx" }
print_document { "filename": "Dale-Resume-Acme-Corp.docx" } // default printer
print_document { "filename": "sample.pdf", "printer_name": "Office EPSON ET-3850 Series" }Always prefer
source_pathfor real documents.content_base64makes the model emit the whole file as base64 token-by-token — roughly 3 characters per token for binary formats, so a 53 KB resume is ~22 000 output tokens (several minutes), and a 134 KB PDF exceeds the response limit and can never finish.source_pathcosts ~30 tokens at any file size and can't corrupt the bytes. Inline base64 is therefore capped at 20 000 characters and fails immediately with a pointer tosource_path.
PDFs print directly via SumatraPDF (bundled with
pdf-to-printer); no dialog, single copy, default settings..docxis converted to a PDF next to it first — using LibreOffice (soffice --headless --convert-to pdf) if installed, otherwise Microsoft Word via COM. The PDF is cached and reused while it's newer than the docx. If neither LibreOffice nor Word is available, printing a.docxreturns a clear error suggesting you save it as a PDF instead.print_documentconfirms the job was sent to the printer — not that it physically finished. Base64 transport is meant for small files (resumes, well under 1 MB), not large documents.
Gmail integration (optional)
The five Gmail tools let Claude search your inbox for recruiter mail, scan for updates on tracked applications, and draft/send replies — all against your own account. They're off until you authorize them; unauthorized calls return a clear error telling you what to run.
One-time setup (full walkthrough in GMAIL_SETUP.md):
Create a Google Cloud project, enable the Gmail API, and create a Desktop app OAuth client; download its JSON as
credentials.jsonin the project root (next topackage.json). This step is manual — it can't be scripted.Run the one-time consent flow:
npm run gmail:authIt opens Google's consent screen and saves the token to
gmail_token.json. The server refreshes it automatically afterward.Restart Claude Desktop so the tools load.
Config / files (both .gitignored):
File | Purpose | Env override |
| OAuth client (manual download) |
|
| Stored access/refresh token from consent |
|
Requested scopes are read-only + compose + send (gmail.readonly,
gmail.compose, gmail.send) — intentionally no mailbox modify/delete
access. npm install pulls the one dependency (google-auth-library).
Safety: search_gmail_for_job / read_gmail_message / scan_job_updates
are read-only; draft_gmail_reply only ever creates a draft; send_gmail_email
sends immediately and can't be undone, so it refuses without confirm: true
and should only be called after you've reviewed the exact to/subject/body.
Safety & behavior notes
Backups (rotating): every write first copies the file into a sibling
.backups\folder with a timestamped name (<file>.<YYYYMMDD-HHmmss-SSS>.bak), keeping the **last 10** per file. This replaces the old single-slot.bak, so repeated writes (manual edits or the daily task) can't clobber your last-good copy. Applies to the sheets, the prep file, anddelete_document;promote_to_trackerbacks up each file it writes. (Nothing is backed up when a file is created fresh.)Fresh reads: the workbook is re-read on every call, so edits you make directly in Excel are always reflected.
Validation: row-editing tools verify the row exists (and is unambiguous) before changing anything; bad dates are rejected up front.
promote_to_trackerrefuses to create a duplicate tracker row.Dates (tracker only) are stored as Excel date serials formatted
d-mmm-yy. New rows are written the same way, so2026-06-01shows as1-Jun-26— matching the existing rows. Reads/writes use timezone-free calendar-date math. Discovery dates are plain ISO text.Formatting preservation: new rows copy the number format (and, where the writer supports it, the cell style) of the last existing row, and column widths are preserved — the way a human copy-pasting a row would leave things.
Note: the open-source SheetJS build faithfully preserves number formats and column widths (what this workbook uses). Rich per-cell styling (custom fonts/fills/borders) is not re-serialized by the community build; if you later add heavy cell styling, preserving it would require SheetJS Pro.
File locked: if a file is open in another program (Excel for the sheets, Word/an editor for the prep
.md), writes fail with a clear "please close it" message rather than a cryptic error.
Testing
npm testtest-client.mjs builds the server, then spins it up over stdio against
self-contained fixtures in a temp folder and asserts on every tool (66
checks across the tracker, discovery, promote, interview-prep ambiguity,
document, and backup paths). Your real files are never touched — the suite
creates its own throwaway tracker/discovery/prep/Resumes in the OS temp dir and
deletes them afterward. It exits non-zero if any check fails.
It does not send a physical print job — print_document is covered only via
its error paths (missing file, unsupported type, unknown printer).
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceEnables job search and scraping across multiple job boards (LinkedIn, Indeed, Glassdoor, etc.) with advanced filtering, directly from Claude Desktop or other MCP clients.5MIT
- FlicenseAqualityCmaintenanceEnables conversational management of quant job search, tailored CV generation, and skill gap analysis through MCP tools. Allows Claude to search, ingest, score jobs, and build/iterate LaTeX CVs.32
- Flicense-qualityCmaintenanceEnables running a job search with Claude Code: parses CV, discovers roles, fetches exact application fields, drafts non-trivial applications (positioning, not autofill), and renders an offline dashboard for review.
- FlicenseBqualityCmaintenanceEnables searching justjoin.it for job listings and tracking applications locally with status updates, all via natural language in Claude.4
Related MCP Connectors
MCP server for AI job search — find jobs, track applications, get alerts. Claude, ChatGPT, Cursor.
Job search and interview prep MCP. 11 tools, OAuth 2.1, cross-LLM. four-leaf.ai.
Search live startup jobs from Claude, Cursor, or ChatGPT via MCP. Free, no account needed.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/DaleMagrath/mcp-job-tracker'
If you have feedback or need assistance with the MCP directory API, please join our Discord server