job-search-mcp
An MCP server that exposes job-search and application-management capabilities to compatible AI clients, enabling discovery of vacancies, drafting of tailored application materials, and coordinated human-approved submissions.
README
Career Ops Agent
An agentic career-operations system that discovers relevant vacancies, evaluates them against a target profile, drafts tailored application materials, and coordinates human-approved submissions.
The project combines:
- an AI agent and skill layer that defines goals, decision rules, MCP tool usage, and approval boundaries;
- an MCP (Model Context Protocol) server that exposes job-search and application-management capabilities to compatible AI clients;
- a scheduled scanner that monitors company career pages;
- persistent state for deduplication, application tracking, and interrupted session recovery;
- a human approval gate before any application is submitted; and
- ATS automation, an email fallback, and completion notifications for approved applications.
The MCP server is one component of the system, not the entire system. Deterministic work such as scheduled scanning and deduplication runs automatically, while the AI layer interprets results, drafts application materials, and guides the broader workflow. The system is therefore agentic rather than fully autonomous: consequential submissions remain subject to human approval.
I built this because I'm a security engineer and the job search is, structurally, a monitoring-and-response problem: noisy signal sources, a triage step, an enrichment step, and an automated response with a human-approval gate. I built it the way I'd build a detection-and-response pipeline.
Status: Personal project actively used in my own job search. The published version excludes credentials, personal profile data, application history, and the private company seed list. See Configuration.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Windows Task Scheduler (nightly) │
│ └─► Docker readiness check ─► container start │
├─────────────────────────────────────────────────────────┤
│ Scan stage career-page fetchers (per-company) │
│ Filter stage dedupe · already-applied · lateral- │
│ move rules · false-positive URL rules │
│ Draft stage cover-letter generation from profile + │
│ job description │
│ Approval gate letters surface for human confirmation │
│ (MCP session, resume-capable) │
│ Submit stage ATS form automation ─► Gmail fallback │
│ Notify stage Windows toast + email summary │
├─────────────────────────────────────────────────────────┤
│ Shared data volume (single mount, scanner + MCP server)│
│ Seed list: 117+ companies · JSON state · run logs │
└─────────────────────────────────────────────────────────┘
Design decisions worth calling out:
- Human-in-the-loop where it matters, nowhere else. The pipeline pauses at exactly one gate: cover-letter confirmation. Everything upstream (rejecting already-applied companies, lateral moves, product-page false positives) is silent and automatic. Everything downstream (submission) fires immediately on approval. This mirrors how a good SOAR playbook treats analyst approval — one decision point, full context attached, no busywork.
- Self-expanding coverage. A scan that produces zero genuinely new matches after filtering triggers a research step that adds five new target companies to the seed list before the run ends. The pipeline treats "no findings" as a coverage gap, not a success state.
- Session resume. Long-running MCP sessions survive interruption via an
initialize_session/resume: truehandshake, so a nightly run that dies mid-approval can be picked up without re-scanning.
Lessons learned (the fun part)
These are the failures that shaped the current design — the kind of thing a README usually hides and an engineer actually wants to read:
- UTF-8 BOM poisoning. Windows-edited JSON state files grew a byte-order mark that the parser silently choked on. Fix: normalize encoding on every read, never trust the editor.
- Split-brain storage. The nightly script and the MCP server originally mounted different Docker volumes and disagreed about reality. Fix: one shared data directory, one source of truth.
- Scheduler time limits. Windows Task Scheduler's default execution limit killed long runs mid-submission. Fix: extended limits + resume logic, so a kill is an inconvenience instead of data loss.
- Em dash corruption. Encoding mismatches mangled punctuation in generated letters — a cosmetic bug with real cost, since a garbled cover letter reads as carelessness. Fix: end-to-end UTF-8 enforcement and an output lint pass.
- Docker readiness races. The scheduled task fired before the Docker daemon was up. Fix: an explicit readiness probe with backoff before the run starts.
Prerequisites
- Docker — Docker Desktop on Windows (the nightly wrapper auto-starts it),
or any Docker engine if you only want the MCP server. Both halves of the
system — the scheduled scan and the interactive MCP server — run in the
same
job-search-mcp:latestimage and share thedata/volume. - An MCP-capable AI client (Claude Desktop, or anything speaking MCP over
stdio) for the interactive half: reviewing pending matches, approving
letters, triggering submissions. Something like Docker's MCP gateway works
too; the server itself is plain stdio, so any launcher that can run
docker run -ican host it. - Windows for the nightly automation as shipped (PowerShell wrapper, Task Scheduler, toast notifications). The Python core is OS-agnostic — porting the wrapper to cron/systemd is a small exercise.
Registering the MCP server
The server speaks stdio, so the client launches the container directly.
For Claude Desktop, add to claude_desktop_config.json (adjust paths):
{
"mcpServers": {
"job-search": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "C:\\path\\to\\repo\\data:/data",
"-v", "C:\\path\\to\\repo\\config.json:/app/config.json",
"-v", "C:\\path\\to\\your\\cvs:/cvs:ro",
"job-search-mcp:latest"
]
}
}
}
The -i flag and the shared data/ mount are both load-bearing: stdio needs
the former, and the nightly scan and the interactive session only agree about
reality because they read the same /data (see Lessons learned).
Configuration
The repo ships the engine; you bring the fuel. Docker and an MCP client
get the machine running, but it does nothing useful until config.json
contains your identity, your target roles (each mapped to a CV file), and
your seed list of companies worth scanning. That targeting is where the real
work of a job search lives — expect to invest in it, and expect the quality of
your matches to track the quality of your seed list. All of it stays in one
gitignored file; nothing personal ever belongs in the repo itself.
The fastest way in is the interview wizard:
python setup_config.py
It walks you through identity, geography, roles, and seed companies, inherits
the non-personal defaults (careers-page patterns, ATS list) from
config.example.json, and writes config.json as UTF-8 without a BOM —
which matters, because a BOM on this file breaks the JSON parse inside the
container (ask me how I know; see Lessons learned).
Prefer doing it by hand? cp config.example.json config.json and edit. Either
way, then:
- Put your CV PDFs in the directory pointed to by
JOB_SEARCH_CV_DIR(defaults to<repo>\cvs). - Set
JOB_SEARCH_SMTP_USER/JOB_SEARCH_SMTP_PASSas user environment variables (Gmail app password) — seesetup_scheduled_task.ps1header. docker build -t job-search-mcp ., register the MCP server with your client, and runsetup_scheduled_task.ps1once as Administrator to create the nightly task.
The agent's operating rules — triage behavior, the approval gate, the
zero-match seed-expansion rule — live in CLAUDE.md / AGENTS.md.
Roadmap
- Response/funnel tracking: tag every reply, interview, and offer by channel so the pipeline measures conversion, not just throughput
- Pluggable fetchers for common ATS platforms (Comeet, Greenhouse, Lever)
- Structured detection-style rules for match filtering, replacing ad-hoc logic
Why this is on my CV
Everything in this repo — agent orchestration, human-approval gates, silent auto-triage, self-healing scheduled execution, encoding hygiene, state management across container boundaries — is the same engineering that security automation platforms are built from. I just happened to point it at job boards first.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。