Job Application MCP
by firyomaefx
README.md
<div align="center">
# Job Application MCP
**A local-first, open-source job application assistant built on the
[Model Context Protocol](https://modelcontextprotocol.io).**
Analyse jobs · Match against your CV · Tailor documents · Preview forms · Track applications —
**all on your machine, nothing uploaded, submission stays in your hands.**
[](./LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org/)
[](#status)
</div>
---
## Table of contents
- [Why](#why)
- [What it does (free core)](#what-it-does-free-core)
- [What it deliberately does NOT do](#what-it-deliberately-does-not-do)
- [How it works](#how-it-works)
- [Quick start](#quick-start)
- [Connect an MCP client](#connect-an-mcp-client)
- [Tools reference](#tools-reference)
- [A full example flow](#a-full-example-flow)
- [Configuration](#configuration)
- [Project layout](#project-layout)
- [Companion apps](#companion-apps)
- [Develop](#develop)
- [How matching works](#how-matching-works)
- [Privacy & data](#privacy--data)
- [Roadmap / Pro upgrade](#roadmap--pro-upgrade)
- [Limitations](#limitations)
- [License](#license)
- [Contributing](#contributing)
---
## Why
Job hunting is repetitive: read a posting, check it against your CV, tweak a cover
letter, fill the same fields on yet another form, and remember where you applied.
**Job Application MCP** turns that into a set of tools any MCP-compatible AI client
(Claude Desktop, Claude Code, others) can drive for you — **while keeping you in
control**:
- Your CV and applications never leave your machine in the free core.
- The assistant prepares and previews; **you** review and submit.
- It's open source under AGPL-3.0, so you can audit exactly what it does.
> ℹ️ This repository is the **free community core** described in
> [`BUSINESS_PROPOSAL.md`](./BUSINESS_PROPOSAL.md). Paid AI/cloud/business services
> are intentionally out of scope here.
---
## What it does (free core)
| Capability | Description |
| --- | --- |
| 🧩 **Local MCP server** | A stdio server you connect from any MCP client. |
| 👤 **Candidate profile** | One local profile: skills, headline, contact, summary. |
| 📄 **CV parsing** | Parse **PDF / DOCX / TXT** locally — no upload, no network. |
| 🔍 **Job analysis** | Paste a job description; get extracted key skills + a short summary. |
| 🎯 **Match scoring** | Heuristic **0–100** score with matched / missing skills. |
| ✂️ **Tailoring suggestions** | Structural advice on which skills to surface or add. |
| ✍️ **Answer drafting** | A template starter for screening questions (you rewrite it). |
| 📝 **Form autofill preview** | Maps your profile onto form fields — **preview only, never submits**. |
| 📊 **Application tracking** | Local SQLite store with a status pipeline (draft → submitted → offer…). |
## What it deliberately does NOT do
- ❌ **No automated submission.** You submit manually on the employer's site.
- ❌ **No automation of LinkedIn or Indeed** — their Terms of Service prohibit it. Paste the description and complete those by hand.
- ❌ **No CAPTCHA solving, 2FA bypass, or login recovery** — it pauses for manual action.
- ❌ **No AI rewriting, cloud sync, or premium adapters** — those are future Pro/cloud services.
These aren't missing features; they're the design.
---
## How it works
```text
┌─────────────────┐ stdio (JSON-RPC) ┌──────────────────────┐
│ MCP client │ ◀────────────────────────▶│ job-application-mcp │
│ (Claude, etc.) │ │ (this server) │
└─────────────────┘ └──────────┬───────────┘
│ reads / writes
▼
┌──────────────────────┐
│ local SQLite DB │
│ + parsed CV text │
│ (JOB_MCP_DATA_DIR) │
└──────────────────────┘
```
You (or an AI client) call **tools**. Each tool does a small, auditable thing and
returns structured JSON. Tools that touch external sites do not exist; everything
operates on data you provide and store locally.
---
## Quick start
### Requirements
- **Node.js ≥ 22** (uses the built-in `node:sqlite` — no native compilation needed)
- npm
### Install & build
```bash
git clone https://github.com/<your-user>/job-application-mcp.git
cd job-application-mcp
npm install
npm run build # -> dist/
```
### Verify it works
```bash
npm test # full unit/integration test suite
node dist/cli/cli.js --help
```
---
## Connect an MCP client
Build the project, then add the server to your MCP client's config.
### Claude Desktop / Claude Code
```jsonc
{
"mcpServers": {
"job-application-mcp": {
"command": "node",
"args": ["/ABSOLUTE/path/to/job-application-mcp/dist/src/index.js"]
}
}
}
```
> ⚠️ Use an **absolute** path — MCP clients launch the server from their own
> working directory, not yours.
### Run directly (no client)
```bash
node dist/cli/cli.js serve # equivalent to: node dist/src/index.js
```
The server speaks JSON-RPC over stdio. See
[A full example flow](#a-full-example-flow) for raw request examples.
---
## Tools reference
All 23 tools are local and synchronous-ish (CV parsing and AI calls are async).
Arguments are validated with zod; the MCP client receives a JSON Schema for each.
The **13 free-core tools** (profile → autofill) are the everyday workflow. The
**credits/Pro** and **admin/business** tools are wire-ready seams for the hosted
Pro service (see [Business model](#business-model)); they run locally today and
do not require any subscription.
### Profile
| Tool | Args | Returns |
| --- | --- | --- |
| `get_profile` | — | The local candidate profile (auto-created on first call). |
| `update_profile` | `full_name?, email?, phone?, location?, headline?, summary?, skills?, experience_years?` | Updated profile. `skills` replaces the list. |
### CVs
| Tool | Args | Returns |
| --- | --- | --- |
| `parse_cv` | `file_path?` **or** `text`, `label?` | Parsed CV stored locally; detected skills + a short preview. |
| `list_cvs` | — | All stored CVs. |
`parse_cv` supports `.pdf` (pdf-parse), `.docx` (mammoth), and plain text. If you
pass `text`, it's stored as-is. `file_path` must be inside the data dir (or a
folder listed in `JOB_MCP_CV_DIRS`) — paths outside the allow-list are rejected,
so an MCP client can't read arbitrary files.
### Jobs
| Tool | Args | Returns |
| --- | --- | --- |
| `analyze_job` | `description` (≥20 chars), `title?, company?, url?, store?=true` | Extracted keywords + summary; stored as a job (unless `store: false`). |
| `get_job` | `job_id` | A stored job with its keywords. |
### Matching & prep
| Tool | Args | Returns |
| --- | --- | --- |
| `match_cv` | `job_id`, `cv_id`, `save?=false` | 0–100 score, matched/missing/extra skills; optionally creates a draft application. |
| `tailor_cv` | `job_id`, `cv_id` | Tailored CV draft. With your own AI API key (`AI_API_KEY`): AI-rewritten prose; without: heuristic local draft. |
| `cover_letter` | `job_id`, `cv_id` | Cover-letter draft (AI with your own key, else heuristic). |
| `draft_answer` | `question`, `cv_id?, job_id?` | Screening-answer draft (AI with your own key, else heuristic template). Always review. |
> **AI tools and your own key.** `tailor_cv`, `cover_letter`, and `draft_answer`
> use a real AI provider when you set `AI_PROVIDER` (`openai` or `anthropic`) and
> `AI_API_KEY` — on the Free **or** Pro plan. Without a key they fall back to a
> local heuristic. The Pro *hosted* path additionally debits an AI credit; using
> your own key is never debited. See [Business model](#business-model).
### Applications & forms
| Tool | Args | Returns |
| --- | --- | --- |
| `save_application` | `job_id`, `cv_id?, match_score?, tailored_cv_text?, cover_letter?, answers?, notes?` | A **draft** application record. Does not submit. |
| `list_applications` | `status?` | Application records (optionally filtered by status). |
| `update_application_status` | `application_id`, `status`, `notes?` | Updates status; sets `submitted_at` when status is `submitted`. |
| `autofill_form` | `application_id`, `form_fields?` | A **preview** mapping of profile data onto form fields. Nothing is submitted. |
**Status pipeline:** `draft → ready → submitted → interview → offer → rejected → closed`.
### Analytics & status
| Tool | Args | Returns |
| --- | --- | --- |
| `application_analytics` | — | Counts by status, average match score, recent activity. Pro entitlement adds an advanced breakdown. |
| `status` | — | Server/plan/credits snapshot (plan label, AI credit balance, active features). |
### Credits & Pro (wire-ready seams)
| Tool | Args | Returns |
| --- | --- | --- |
| `credits` | — | Current AI credit balance + recent ledger entries. |
| `topup_credits` | `code`, `amount` | Apply an AI-credit top-up code (idempotent per code). Admin/test. |
| `grant_monthly_credits` | `amount?` | Grant the monthly AI allowance (idempotent per calendar month). Admin/test. |
### Admin / business (wire-ready seams)
| Tool | Args | Returns |
| --- | --- | --- |
| `admin_create_account` | `email`, `name`, `plan?` | Create a business account. |
| `admin_add_candidate` | `account_id`, `profile_id`, `role?` | Add a candidate/coach to an account. |
| `admin_list_team` | `account_id` | List an account's team members. |
| `admin_usage_report` | `account_id` | Usage summary for an account. |
---
## A full example flow
Here's a complete session using the stdio JSON-RPC interface directly. (An MCP
client like Claude does this for you in natural language.)
```bash
node dist/src/index.js
```
Send these lines (one JSON-RPC object per line):
```jsonc
// 1. handshake
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"demo","version":"0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
// 2. set up your profile
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"update_profile","arguments":{"full_name":"Ada Lovelace","headline":"Backend Engineer","skills":["python","sql","aws","docker"]}}}
// 3. analyse + store a job (paste the description text)
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"analyze_job","arguments":{"title":"Backend Engineer","company":"Acme Co","description":"We need a backend engineer with Python, SQL, and AWS to build data pipelines. PostgreSQL and Docker are a plus."}}}
// 4. parse a CV (file path or pasted text)
{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"parse_cv","arguments":{"label":"main-cv","text":"Ada Lovelace — Backend Engineer. Python, SQL, AWS, PostgreSQL. Built ETL pipelines..."}}}
// 5. score the CV against the job (save=true creates a draft application)
{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"match_cv","arguments":{"job_id":1,"cv_id":1,"save":true}}}
// 6. preview autofill for a form (no submission)
{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"autofill_form","arguments":{"application_id":1,"form_fields":[{"name":"name","label":"Full name"},{"name":"email","label":"Email"},{"name":"salary","label":"Salary expectation"}]}}}
// 7. after you submit on the employer's site, mark it submitted
{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"update_application_status","arguments":{"application_id":1,"status":"submitted"}}}
```
Step 6 returns a mapping where the `salary` field is flagged
`requires_user_review: true` — sensitive fields always need your eyes.
---
## Configuration
All config is via environment variables. **The free core needs none of them.**
| Variable | Default | Purpose |
| --- | --- | --- |
| `JOB_MCP_DATA_DIR` | `./data` | Where the SQLite DB and parsed data live. |
| `JOB_MCP_CV_DIRS` | — | Extra folders `parse_cv` may read from (OS path separator: `;` on Windows, `:` elsewhere). |
| `JOB_MCP_HTTP_PORT` | `8787` | Port for the local HTTP bridge (loopback only). |
| `JOB_MCP_HTTP_TOKEN` | — | Optional bearer token the bridge requires on `/call`. |
The following enable **your own AI key** (works on Free **or** Pro) and are
placeholders for future hosted Pro/cloud services (see `.env.example`):
| Variable | Purpose |
| --- | --- |
| `AI_PROVIDER` / `AI_API_KEY` | `openai` or `anthropic` + your own key → real AI drafts for `tailor_cv` / `cover_letter` / `draft_answer`. Keys entered in the desktop UI are session-only; use the environment for restart persistence. |
| `AI_MODEL` / `AI_BASE_URL` | Optional model + base URL override (e.g. an OpenAI-compatible endpoint). |
| `JOB_MCP_LICENCE_SERVER` | Pro licence activation endpoint (hosted service). |
| `SUPABASE_URL` / `SUPABASE_ANON_KEY` | Cloud sync & auth (Pro, hosted). |
---
## Project layout
```text
job-application-mcp/
├── src/
│ ├── index.ts # stdio MCP server entrypoint
│ ├── http.ts # local HTTP bridge entrypoint (127.0.0.1)
│ ├── server.ts # server factory + tool registry
│ ├── tools/ # task and compatibility MCP tools
│ │ ├── types.ts # ToolDef / AnyTool / result helpers
│ │ ├── profile.ts # get/update_profile
│ │ ├── cv.ts # parse_cv, list_cvs
│ │ ├── job.ts # analyze_job, get_job
│ │ ├── matching.ts # match_cv, tailor_cv, cover_letter, draft_answer
│ │ ├── application.ts # save/list/update_application, autofill_form
│ │ ├── analytics.ts # application_analytics, status
│ │ ├── pro.ts # credits, topup_credits, grant_monthly_credits
│ │ └── admin.ts # admin_create_account / add_candidate / list_team / usage_report
│ ├── store/ # SQLite layer (node:sqlite)
│ │ ├── db.ts # open / migrate / close / reset
│ │ ├── profile.ts # profile CRUD
│ │ └── applications.ts# cv / job / application CRUD
│ ├── cv/parser.ts # PDF / DOCX / TXT parsing (path allow-listed)
│ ├── ai/ # provider abstraction (mock / openai / anthropic)
│ ├── licence/ # entitlement tokens + credits + referrals
│ ├── payments/ # payment-webhook seam (server-side, not in clients)
│ ├── sync/ # cloud-sync seam (local no-op until Pro)
│ ├── features.ts # feature gating (plan + credits + AI mode)
│ └── lib/
│ ├── types.ts # domain types
│ ├── crypto.ts # HMAC-SHA256 + constant-time compare
│ ├── entitlement.ts # Entitlement value type + plan limits
│ └── scoring.ts # keyword extraction + match scoring (pure)
├── cli/cli.ts # human CLI: job-mcp serve | serve:http | --help
├── extension/ # Chrome MV3 extension (form capture → preview)
├── desktop/ # Electron wrapper UI (launches the bridge)
├── .github/workflows/ci.yml # build + typecheck + test + stdio smoke
├── .github/workflows/daily-effectiveness.yml # scheduled regression gate
├── evals/ # synthetic effectiveness fixtures + thresholds
├── tests/ # node:test suite
├── BUSINESS_PROPOSAL.md # open-core + paid-service model
├── CLAUDE.md # guide for Claude Code working in this repo
└── LICENSE # AGPL-3.0-or-later
```
---
## Companion apps
The free core is the stdio MCP server above. Two optional companions live in this
repo and talk to a **local HTTP bridge** (same tools, loopback only):
### HTTP bridge
```bash
npm run serve:http # 127.0.0.1:8787 by default
```
Endpoints: `GET /health`, `POST /call` with `{"name","arguments"}`. Optional
bearer auth via `JOB_MCP_HTTP_TOKEN`. Source: [`src/http.ts`](./src/http.ts).
### Chrome extension — `extension/`
Captures form fields on a career page and asks the bridge to **preview** an
autofill mapping. Never submits. Load it unpacked from `extension/`. See
[`extension/README.md`](./extension/README.md).
### Desktop app — `desktop/`
An Electron wrapper that launches the bridge and shows a dashboard (status,
profile, applications). Run with `cd desktop && npm install && npm start`. See
[`desktop/README.md`](./desktop/README.md).
> Both companions are **read/preview only** and reach only `127.0.0.1`. They
> inherit the project's no-submission, no-LinkedIn/Indeed-automation rules.
---
## Develop
```bash
npm run build # tsc -> dist/
npm run typecheck # tsc --noEmit
npm test # node --test (scoring + store)
npm run eval:effectiveness # synthetic safety/effectiveness report
npm run dev # tsc --watch
```
GitHub also runs the deterministic effectiveness gate daily at 01:17 UTC
(09:17 Malaysia time). It uses synthetic fixtures, makes no model calls, and
never changes code or user data automatically. See
[`docs/EFFECTIVENESS.md`](./docs/EFFECTIVENESS.md) for metrics and limits.
### Adding a tool
1. Create `src/tools/<name>.ts` exporting a `ToolDef<z.ZodObject<…>>`.
2. Register it in `src/tools/index.ts` (add to the `tools` array).
3. Add a test in `tests/` if it has pure logic worth covering.
Tools receive parsed, typed input and return a `ToolResult` (`{ summary, data?, notes? }`).
The server handles JSON Schema generation, validation, and error wrapping for you.
---
## How matching works
Matching is a **pure, heuristic** function in [`src/lib/scoring.ts`](./src/lib/scoring.ts) — no AI, no network.
1. **Extract keywords** from the job description: tokenize, drop stopwords and
filler verbs, weight tech hints and frequent terms.
2. **Score**: for each job keyword, check the candidate's curated profile skills
**plus** skills detected in the CV text. Stem-ish matches count (e.g.
`react` ≈ `reactjs`).
3. **Result**: `score` (0–100), `matched`, `missing`, `extra`, and `notes`
(e.g. "Below 50% — consider tailoring or skipping").
Formula: `score = matched/total × 100` plus a small, capped breadth bonus for
extra skills (only when there are job keywords). It's intentionally simple and
auditable — a baseline you can replace with the Pro AI scorer later.
---
## Privacy & data
- **Local-first by default.** CVs, profile, and applications live in
`JOB_MCP_DATA_DIR` (default `./data`), which is gitignored. Ollama and the
heuristic stay local; configuring OpenAI, Anthropic, an update feed, or a
licence endpoint enables the corresponding outbound request.
- **Provider keys are not stored in SQLite.** Desktop-entered keys last only for
the bridge process; environment variables can provide restart persistence.
- **No PII in logs.** CV text and personal fields aren't logged at info level.
- **Sensitive fields are flagged.** `autofill_form` marks salary, authorization,
gender, disability, and consent fields as `requires_user_review: true`.
## Windows alpha installer
GitHub prereleases provide a standalone x64 one-click Setup executable. The
installer bundles the local bridge, so a separate Node.js installation is not
required. Version 0.4.2 is intentionally **unsigned alpha software**: Windows
SmartScreen may display a warning. Verify the downloaded file against
`SHA256SUMS.txt` from the same GitHub prerelease before running it. This is not
a production-ready hosted or paid release.
- **You can wipe everything** by deleting the data directory.
---
## Roadmap / Pro upgrade
The free core is the foundation. [`BUSINESS_PROPOSAL.md`](./BUSINESS_PROPOSAL.md)
describes the open-core model. Likely next steps:
- **Free core:** `match_cv` + `tailor_cv` integration test through the MCP layer; richer keyword extraction (skill taxonomy).
- **Distribution:** Windows installer via GitHub Releases; Chrome Web Store extension for form-field capture.
- **Pro (separate hosted services):** AI CV tailoring & cover letters, encrypted cloud backup, multi-device sync, maintained site adapters, application analytics.
- **Business:** multi-candidate accounts, team dashboards, white-label.
Tool handlers are written so a Pro upgrade can slot in without changing the public
tool surface — e.g. `tailor_cv` returns heuristic suggestions today and could call
an AI service tomorrow.
---
## Limitations
- **Keyword extraction is heuristic** and will miss or mis-rank some skills. Confirm
against the source posting before relying on a match score.
- **No browser automation.** Form fields must be supplied to `autofill_form`
(manually or, later, via the extension). The tool previews; it doesn't fill.
- **LinkedIn / Indeed:** analysis-only, paste the description, submit manually.
- **Alpha status:** APIs and stored schemas may change before 1.0.
---
## License
Copyright © 2026 Job Application MCP contributors.
This program is free software: you can redistribute it and/or modify it under the
terms of the **GNU Affero General Public License** as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later version.
See [`LICENSE`](./LICENSE).
A separate **commercial licence** is available for embedding without AGPL
obligations — see [`BUSINESS_PROPOSAL.md`](./BUSINESS_PROPOSAL.md) §8.
---
## Contributing
Issues and PRs welcome. By contributing you agree your contributions will be
licensed under the same AGPL-3.0-or-later terms.
Please keep the free core **fully local and free** — no feature in this repo may
gate a free-core capability behind a paid/network service. See
[`CLAUDE.md`](./CLAUDE.md) for the conventions used in this codebase.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues