job-search-mcp

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.

Category
Visit Server

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: true handshake, 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:latest image and share the data/ 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 -i can 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:

  1. Put your CV PDFs in the directory pointed to by JOB_SEARCH_CV_DIR (defaults to <repo>\cvs).
  2. Set JOB_SEARCH_SMTP_USER / JOB_SEARCH_SMTP_PASS as user environment variables (Gmail app password) — see setup_scheduled_task.ps1 header.
  3. docker build -t job-search-mcp ., register the MCP server with your client, and run setup_scheduled_task.ps1 once 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.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured