job-tracker
This server lets Claude manage a local job-search workflow through natural language: track applications, discover leads, generate/print resumes, and optionally handle Gmail.
Job tracking: list/filter jobs, get details, add, update status or any field, delete, find stale jobs, and get due follow-ups.
Search & outreach: search across company/position/notes and draft follow-up messages (send nothing).
Spreadsheet customization: add, rename, or delete custom columns with optional default/fill values.
Discovery pipeline: list/add/update/delete leads, promote leads to the tracker, and sync discovery rows against tracked applications.
Search criteria & board sweep: save job-search criteria, run automated ATS board sweeps, and get web-search queries for companies outside the sweep list.
Interview prep: read and append Q&A sections in a markdown prep file.
Resumes & documents: generate tailored resumes from a stable resume master, save/read/list/delete/print documents, and read plain-text files safely.
Resume master management: inspect and update the underlying resume facts JSON.
Gmail integration: search/read Gmail, scan for job updates, draft replies, and send email (only with explicit confirmation).
Setup & readiness: initialize the spreadsheet files and check that everything is configured correctly.
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 "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-tracker Which 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 — no Python or other external interpreter required
Transport: stdio (local process — no networking, no auth)
Data source: your
.xlsxfiles, read/written with SheetJS (xlsx); styling (bold header, freeze/auto-filter, hyperlinks) viaexceljsSchema: dynamic — driven by each sheet's header row (custom columns supported)
Resumes:
.docxbuilt in-process with thedocxpackage;.pdf/.docxtext extraction viapdf-parse+mammothBoard sweep:
run_job_sweepprobes ATS job boards directly over HTTP (nativefetch), in-process — no external scriptPrinting: silent PDF printing —
pdf-to-printer(bundles SumatraPDF) on Windows, CUPS'slpon macOS/Linux;.docxis converted to PDF first using LibreOffice (any OS) or Microsoft Word (Windows only) if installed (optional — see Safety & behavior notes)Client: Claude Desktop or Claude Code (see config below)
Prerequisites
Setting this up on a brand-new Windows 11 machine, with nothing installed yet? WINDOWS_SETUP.md is a complete beginner-friendly walkthrough — Node, the project files, Claude Desktop, and troubleshooting, in order. The steps below are the terse reference version.
Node.js 18 or later — required. Download the LTS installer for your OS from nodejs.org and run it; verify with
node --version.LibreOffice — optional. Only needed if you want
generate_resumeto also produce a.pdf(it always produces the.docxregardless). Works on Windows, macOS, and Linux. On Windows only, Microsoft Word (if already installed) works as an alternative — nothing extra to download for that path.Nothing else. No Python, no other interpreter, no compiler — every other dependency is pure JavaScript and installed by
npm installbelow.
Related MCP server: career-agent
Build
cd mcp-job-tracker
npm install
npm run buildThis produces dist/index.js. npm install pulls everything this server needs —
pdf-to-printer (bundles SumatraPDF for silent Windows printing, no separate
install; macOS/Linux print through CUPS's lp instead, no npm package needed),
exceljs, docx, pdf-parse, mammoth — all pure JS, nothing to compile and no
Python. The one optional external dependency is LibreOffice (any OS) or
Microsoft Word (Windows only), used only to convert a generated resume's
.docx to .pdf; if none is installed, generate_resume still produces the
.docx and says so. The optional Gmail tools need a one-time
npm run gmail:auth (see Gmail integration).
Configure Claude Desktop
Open Claude Desktop's config file:
Windows: %APPDATA%\Claude\claude_desktop_config.json
macOS: ~/Library/Application Support/Claude/claude_desktop_config.jsonAdd a job-tracker server entry (create the file / mcpServers object if needed).
Windows:
{
"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"
}
}
}
}macOS (same shape, forward-slash paths):
{
"mcpServers": {
"job-tracker": {
"command": "node",
"args": ["/Users/developer/mcp-job-tracker/dist/index.js"],
"env": {
"JOB_TRACKER_FILE": "/Users/developer/Documents/Job_Tracking.xlsx",
"JOB_DISCOVERY_FILE": "/Users/developer/Documents/Job_Search_Discovery.xlsx",
"JOB_INTERVIEW_PREP_FILE": "/Users/developer/Documents/Interview_Prep_QA.md",
"JOB_RESUMES_DIR": "/Users/developer/Documents/Resumes"
}
}
}
}node above assumes it's on PATH; if Claude Desktop can't find it, replace
"command": "node" with the absolute path from which node (macOS) / where node (Windows). setup.mjs (below) writes this file for you automatically,
on either OS, so hand-editing it is only needed if you'd rather not use it.
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 — or, for a genuinely fresh start with no existing spreadsheet, skip this step and call theinit_job_tracker_filestool once the server is running; it creates the folder and both blank, formatted workbooks for you.Make sure Node.js 18+ is installed (
node --version) — see Prerequisites above; nothing else is required for the server itself.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
Setup
Tool | What it does |
| Bootstrap a fresh machine/profile: creates the Job Tracking root folder if missing, and creates |
| Read-only readiness report: which files/folders exist, whether search criteria and the resume master are set up, whether Gmail is authorized, and whether a PDF converter (LibreOffice/Word) is available — with a |
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 |
| Housekeeping + append in one call: drops discovery rows whose Company+Position now matches a tracker application, flags anything 60+ days old Stale (60+ days), then appends |
Search criteria & board sweep
Tool | What it does |
| Return the saved search criteria ( |
| Create or update the saved criteria. Every field is optional — only what you pass changes. Backs up the previous file first. |
| Probe ~95 Greenhouse/Ashby/Lever/SmartRecruiters/Workday boards concurrently (in-process, plain HTTP — no external script), filter to engineering-leadership titles whose location plausibly matches the saved criteria, fetch salary/posted-date detail, HTTP-check links, and dedupe against both spreadsheets. Returns only new candidates. Refuses to run until search criteria are complete. Optional |
| Returns ready-to-run WebSearch queries (built from the same saved criteria) aimed at companies outside |
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 |
| One-time setup: creates |
| 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: SheetJS (used for the data read/write itself) doesn't
re-serialize rich cell styling, so every MCP discovery write is followed by a
best-effort exceljs reformat pass (xlsxFormat.ts) that (re)applies bold
header / frozen row / auto-filter / wrapped text / clickable Job Link cells.
Pure JS, nothing external required; if it ever fails for some reason the data
write still succeeds (the tool returns formatted: false).
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 their own resume-generation script or moving bytes through
base64, so it's the single resume path (resumeDocx.ts 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 →
.docxviaresumeDocx.ts(thedocxpackage, in-process; the measured spec: Liberation Serif, 20 pt name, ruled 11 pt headings, 0.63" margins, US Letter) → PDF via LibreOffice or Word, whichever is installed. ~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 — and only on Windows; macOS/Linux have no COM automation to fall back to, so LibreOffice is the sole path there (checked at/Applications/LibreOffice.app/...on macOS, common Linux install paths, then baresofficeon PATH).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) on Windows, or CUPS'slpon macOS/Linux (present by default on macOS, and on any Linux with CUPS installed). No dialog, single copy, default settings either way..docxis converted to a PDF next to it first — using LibreOffice (soffice --headless --convert-to pdf) if installed, otherwise (Windows only) Microsoft Word via COM. The PDF is cached and reused while it's newer than the docx. If no converter 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 testnpm test (via run-tests.mjs) runs two suites:
test-client.mjs— an integration suite. Builds the server, spins it up over stdio against self-contained fixtures in a temp folder, and asserts on every tool (see the file for the current count — runnpm testrather than trusting a hardcoded number here) across the tracker, discovery, promote, interview-prep ambiguity, document, search-criteria/sweep/ discovery-sync, setup, 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.test-unit.mjs— plain unit tests, importingdist/*.jsdirectly (no server process) for the pure logic that lives right next to the network/ OAuth calls above: title/location classification, salary-text mining, per-ATS board-response parsing, Gmail header/MIME/raw-email helpers, and the board-health cache round-trip. This is exactly the logic that would otherwise go completely untested, since it sits inside the same files as the excluded network calls.
Either script exits non-zero if any check fails, and so does npm test.
It does not send a physical print job — print_document is covered only via
its error paths (missing file, unsupported type, unknown printer). Likewise,
run_job_sweep/runSweep() itself is only covered via its refuses-without-
criteria path — it makes live outbound calls to ~95 real ATS endpoints, which
isn't something either suite exercises automatically.
npm run coverageRuns both suites together under c8 and
prints a per-file statement/branch/function breakdown (plus an HTML report
under coverage/, gitignored). sweepEngine.ts/gmailAuth.ts/gmailTools.ts
wrap their genuinely network/OAuth-bound functions in /* c8 ignore */
comments — deliberately narrow: only the functions whose entire body is a live
call are excluded, so pure logic living right next to them (and tested by
test-unit.mjs) still counts. What's left uncovered after that is real,
visible, and mostly runSweep()'s own orchestration plus the Gmail tool
handlers' post-auth bodies — both need either mocking or real
credentials/network to close further.
Available Tools
35 toolsadd_columnAdd columnA
Add a new custom column to the spreadsheet, appended after the last column. Optionally fill every existing job row with a default value. Refuses if a column with that name already exists. Backs the file up before writing. (Note: the built-in job tools only read/write the standard columns; custom columns are managed with these column tools.)
Inputs: name (required), default_value.
add_jobAdd jobA
Append a new job application row. Company and Position are required; all other fields are optional. Status defaults to "Awaiting Response" and Date Applied defaults to today if omitted. Use extra_fields to set any custom columns you've added. Backs the file up to .bak before writing.
Inputs: company (required), position (required), notes, status, +7 more.
append_interview_prepAppend interview prepA
Append markdown to the interview-prep file under a company's section. Creates the file (with a top-level heading) and/or the company section if needed; otherwise adds to the existing section without duplicating its heading. Keep the file's style: 'Q: ...' / 'A: ...' pairs and optional 'Notes: ...' lines. Backs the file up before writing.
Inputs: company (required), content (required).
delete_columnDelete columnA
Permanently remove a custom column and shift the columns to its right left by one (like Excel's Delete Column). Refuses on the built-in columns and on a missing column. Backs the file up to .bak first.
Inputs: name (required).
delete_documentDelete documentA
Delete a saved document from the Resumes folder. Backs it up (to the rotating .backups folder) first so it can be restored, then removes it. Refuses a missing file or any path outside the Resumes folder.
Inputs: filename (required).
delete_jobDelete jobA
Permanently remove a job application row, matched by company + position, shifting the rows below it up (like Excel's Delete Row). Refuses to act on a missing or ambiguous match, backs the file up to .bak first, and returns the full deleted row so it can be re-added if needed.
Inputs: company (required), position (required).
discovery_addAdd discovery leadA
Append a new lead to the discovery sheet. Company and Position are required; set any other columns via fields (e.g. Salary, Job Link, "Match Assessment"). Date Found defaults to today and Status to "Open" if not supplied. Backs the file up first.
Inputs: company (required), position (required), fields.
discovery_deleteDelete discovery leadA
Permanently remove a discovery lead, matched by company + position, shifting rows below it up. Refuses missing/ambiguous matches, backs up first, and returns the deleted row so it can be re-added.
Inputs: company (required), position (required).
discovery_listList discovery leadsA
List job leads from the discovery sheet (Job_Search_Discovery.xlsx). Optional filters: company (case-insensitive substring), status (case-insensitive exact), and query (substring across every column).
Inputs: query, status, company.
discovery_updateUpdate discovery leadA
Edit a discovery lead, matched by company + position. Rename via new_company / new_position; set any columns via fields (empty string clears a value). Refuses missing/ambiguous matches; backs up first.
Inputs: company (required), position (required), fields, new_company, +1 more.
draft_followup_messageDraft follow-up messageA
Draft a short, professional follow-up email for a given application, referencing how long it has been since applying, the role, and the resume version used. Returns draft text only — it does not send anything.
Inputs: company (required), position.
draft_gmail_replyDraft a Gmail reply (not sent)A
Create a real Gmail draft — visible in the Drafts folder — but does NOT send it; a human still has to open it and hit Send. Either replies within an existing thread (pass thread_id, e.g. from search_gmail_for_job or scan_job_updates) or starts a new message (pass to + subject). Requires Gmail to be authorized (see GMAIL_SETUP.md).
Inputs: body (required), cc, to, subject, +1 more.
generate_resumeGenerate tailored resumeA
Generate a tailored resume PDF (or docx) on the host and save it into the Resumes folder — no base64, works from Claude Desktop.
The STABLE facts (employers, titles, dates, education, skills) come from resume_master.json; YOU supply only the per-posting tailoring: a rewritten summary and the key_qualifications bullets aligning the candidate to this specific company + position. The server renders the master + your tailoring into a formatted PDF (python-docx + LibreOffice) matching the existing resumes. Do NOT invent employers, dates, or degrees — those are fixed in the master. Returns the saved path and size, plus a nextStep with a suggested follow-up call (add_job / promote_to_tracker / update_job) — offer it to the user so tailoring, saving, and tracking flow together.
Inputs: company (required), summary (required), position (required), key_qualifications (required), +6 more.
get_due_followupsGet due follow-upsA
List applications whose Next Follow-Up date is due — on or before today (or within the next days days). Skips clearly-closed rows (Declined / Withdrawn / Closed). Sorted most-overdue first; daysUntil is negative when overdue.
Inputs: days.
get_jobGet jobA
Return full details for a single application, matched by company (and position, if the company has more than one row).
Inputs: company (required), position.
get_resume_master_fieldGet resume master fieldA
Read one field from resume_master.json (the stable resume facts) by dot/bracket path, e.g. "skills", "projects[0].description", "experience[2].title". Scoped to that one file. Use list_resume_master_structure first if you're unsure of the path.
Inputs: path (required).
get_stale_jobsGet stale jobsA
List applications still "Awaiting Response" or "Applied" whose Date Applied is more than N days ago (default 14), computed against today.
Inputs: days.
list_documentsList documentsA
List the saved resumes / cover letters in the Resumes folder (name, size, and modified date). Optional filter is a case-insensitive substring on the file name. Newest first.
Inputs: filter.
list_jobsList jobsA
List all tracked job applications, with optional filters. Filter by status (case-insensitive exact match), company (case-insensitive substring), and/or a Date Applied range (inclusive, YYYY-MM-DD).
Inputs: status, company, applied_to, applied_from.
list_resume_master_structureList resume master structureA
Show the SHAPE of resume_master.json (keys, array lengths, scalar types — not the full field values) to help pick a path for get_resume_master_field / update_resume_master_field. Optional path scopes the listing to a sub-section.
Inputs: path.
modify_columnModify columnA
Modify a custom column: rename it (new_name) and/or set the same value into every existing job row (fill_value; pass an empty string to clear the column). At least one of new_name / fill_value is required. Refuses on the built-in columns and on a missing column. Backs the file up first.
Inputs: name (required), new_name, fill_value.
print_documentPrint documentA
Silently print a saved document to a printer. Give a file name in the Resumes folder (or a full path). PDFs print directly; .docx files are converted to PDF first (cached next to the docx). Uses the system default printer unless printer_name is given. Confirms the job was SENT to the printer, not that it physically finished.
Inputs: filename (required), printer_name.
promote_to_trackerPromote lead to trackerA
Turn a discovery lead into a tracked application: copies Company, Position, Job Link, and Location into the tracker with Status "Applied" and Date Applied = today (both overridable). Salary and Match Assessment are carried into Notes unless you supply your own. Optionally removes the lead from the discovery sheet. Backs up both files it writes.
Inputs: company (required), position (required), notes, status, +3 more.
read_documentRead documentA
Extract and return the plain text of a saved resume / cover letter (.pdf or .docx) from the Resumes folder, so it can be read and analyzed in conversation (e.g. "read my Acme Corp resume and check it against this posting"). Read-only. Give a file name in the Resumes folder.
Inputs: filename (required).
read_gmail_messageRead a Gmail message's full bodyA
Read the full body of ONE specific Gmail message — search_gmail_for_job and scan_job_updates only return a short snippet, so use this when you need the actual content (e.g. an interview link or instructions). Pass message_id (the id from a search/scan result), or thread_id to read the most recent message in that thread. Read-only.
Inputs: thread_id, message_id.
read_interview_prepRead interview prepA
Read the interview-prep Q&A markdown file (Interview_Prep_QA.md) and return it as text. Pass an optional company to return just that company's section. Returns a friendly message (not an error) if the file doesn't exist yet or the company has no section.
Inputs: company.
read_text_fileRead text fileA
Read any plain-text file (.md, .txt, .json, .csv, …) inside the Job Tracking folder tree — handoff notes, scratch files, etc. Give a filename or relative path (e.g. "HANDOFF_2026-08-02.md" or "notes/plan.txt"), resolved under the Job Tracking root. Read-only, and strictly scoped to that tree — paths that escape it are refused. For PDFs/docx in the Resumes folder use read_document instead.
Inputs: filename (required).
save_documentSave documentA
Save a generated document (docx or PDF) into the Resumes folder (created if missing).
PREFERRED: pass source_path — the full path to a file already written to disk (e.g. one the docx/pdf skill just generated). The server copies it; this is instant and cannot corrupt the file.
FALLBACK: content_base64, for SMALL files only (~15 KB decoded max). Base64 must be generated token-by-token, so a typical 50 KB resume takes minutes and may exceed the response limit outright — use source_path for anything binary or non-trivial.
Give exactly one of the two. Refuses to overwrite an existing file unless overwrite=true, in which case the old file is backed up first. Returns the saved path and size.
Inputs: filename (required), overwrite, source_path, content_base64.
scan_job_updatesScan Gmail for tracker updatesA
Cross-reference open applications in the tracker against Gmail: for each open job (or one company, if given), searches Gmail for recent related mail and flags simple keyword-based signals (interview, offer, rejection language). Read-only — it only SUGGESTS a status change via suggestedCall; it never updates the tracker itself. Review the emails and call update_job_status yourself if the suggestion looks right.
Inputs: company, days_back, max_per_company.
search_gmail_for_jobSearch Gmail for job-related emailA
Read-only search of the authorized Gmail account for messages related to a company (and optionally position/keywords) — recruiter replies, interview invites, rejections, etc. Does not modify anything. Requires Gmail to be authorized first (see GMAIL_SETUP.md / npm run gmail:auth).
Inputs: company (required), query, position, max_results.
search_jobsSearch jobsA
Case-insensitive substring search across the Company, Position, and Notes columns.
Inputs: query (required).
send_gmail_emailSend an email via GmailA
SENDS a real email immediately through the authorized Gmail account. This cannot be undone — there is no draft step. Only call this after showing the user the exact to/subject/body and getting explicit confirmation; the call must include confirm: true or it is refused. Either sends a reply within an existing thread (thread_id) or a new email (to + subject). Requires Gmail to be authorized (see GMAIL_SETUP.md).
Inputs: body (required), confirm (required), cc, to, +2 more.
update_jobUpdate job (any field)A
Edit any field(s) of an existing application, matched by company + position. Only the fields you supply are changed; pass an empty string to clear a field. Rename with new_company / new_position. Set custom columns via extra_fields. Refuses missing or ambiguous matches, validates dates, and backs the file up before writing.
Inputs: company (required), position (required), notes, status, +9 more.
update_job_statusUpdate job statusA
Update the Status (and optionally the Notes) of an existing application, matched by company + position. Validates the row exists and backs the file up before writing.
Inputs: status (required), company (required), position (required), notes.
update_resume_master_fieldUpdate resume master fieldA
Update one existing field in resume_master.json by dot/bracket path (e.g. a corrected date, an added skill, a new project). Only edits fields that ALREADY exist — it will not create new keys (that's a deliberate schema change to make by hand). Backs the file up first and reports old → new. Scoped to resume_master.json only.
Inputs: path (required), value (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 35 tools
Most tools target distinct resources and actions, but update_job_status overlaps with update_job (which can also set status), and scan_job_updates vs search_gmail_for_job both provide Gmail search functionality. The descriptions help, but a few tools could cause misselection.
The dominant pattern is underscore-separated verb_noun (add_job, delete_document, get_resume_master_field), but discovery_* tools reverse this to noun_verb (discovery_list, discovery_add), and Gmail tools mix patterns (search_gmail_for_job vs read_gmail_message). The inconsistency is noticeable but not chaotic.
With 35 tools this is well above the typical well-scoped range (3-15) and even the heavy range (16-25). The large count reflects the many subdomains covered, but several tools could be consolidated (e.g., update_job_status into update_job, or read_document/read_text_file into one scoped reader).
The tool set covers the full job-search lifecycle: tracker CRUD, discovery pipeline, interview prep, resume generation and document management, plus Gmail integration with a safety-conscious send. Minor gaps exist (e.g., no way to edit or delete interview prep sections, no direct Gmail thread listing) but they don't create dead ends.
Related MCP Connectors
Job search tracker for Claude and ChatGPT: applications, interviews, follow-ups, company watch.
Track job applications on a board: let Claude or ChatGPT read and update jobs, interviews, notes.
Your career companion for ATS checks, resumes, cover letters, job tracking, websites, and more.
Manage job applications — jobs, companies, boards, notes, and profile — from your AI client.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceTurns Claude into a career co-pilot that manages resumes, cover letters, job applications, and interview prep using local YAML files.806 npm7MIT
- FlicenseNot gradedqualityCmaintenanceEnables Claude Desktop to manage a job search end-to-end: find and score job listings, tailor resumes, generate application messages, and track application history, while leaving final external actions to the user.-
- AlicenseAqualityBmaintenanceEnables MCP clients such as Claude Desktop or compatible editors to run local, privacy-first job-search workflows including resume generation and STAR polishing, JD matching, GitHub project recommendations, mock interviews, and application tracking. It exposes these capabilities as callable tools through the Model Context Protocol.71MIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude to read and update the local job-search tracker, adding postings from screenshots, pasted text or links, drafting application emails, syncing Gmail to log sent applications and replies, and answering questions about what's due or which jobs match a criteria. Every change is validated and appears live on the dashboard with a timeline entry.MIT