Skip to main content
Glama
Gowthamch6s

JobMatch MCP

by Gowthamch6s

JobMatch MCP

An MCP server that exposes an active job search as agent-callable tools: search live postings, score a resume against a specific posting, and track applications through their pipeline. Point Claude Desktop, Cursor, or any other MCP host at it and it can search jobs, judge fit, and log outcomes for you, without a dedicated frontend.

Built as a companion to AI Career Copilot (an agentic RAG job-matching system) and AgentFlow Studio (a self-correcting LangGraph agent) — this project's job is specifically to prove out MCP server authorship: schema-driven tool design, server-side auth, structured error handling, and observability, as a distinct engineering skill from agent orchestration or RAG.

Why this exists

Job search tools live inside someone else's chat window or someone else's webapp. This puts job search, resume-fit scoring, and application tracking behind three narrow tools any MCP-compatible agent can call directly — including inside your own coding assistant, where you're already spending your day.

Related MCP server: jobwise

Tools

Tool

Purpose

Key inputs

search_jobs

Live job postings by title/keyword + location

query (required), location, max_results (1-20)

score_resume_match

0-100 fit score of a resume against a posting

resume_text, job_description (both required)

track_application

Create or update a tracked application

job_title, company, status enum, optional application_id to update

list_applications

List tracked applications, optionally by status

status (optional filter)

Every tool's full input/output contract — including every field, type, and default — lives in its docstring in src/jobmatch_mcp/server.py, which is also what the MCP host shows the calling model. Nothing here is undocumented.

Design choices (and why they're the point of this project)

Schema-driven tools, not one mega-tool. Each tool has one responsibility, a Literal enum for status rather than a free-text string, explicit required/optional fields, and worked examples in its docstring. This is what a JSON-Schema-first tool-use hiring bar is actually checking for — see the docstrings, not just the type hints.

Auth never touches the model. ADZUNA_APP_ID / ADZUNA_APP_KEY are read once, server-side, in config.py. They never appear in a tool's input schema, output, or log line (see redact_params in logging_utils.py). A calling model can request a search; it can never see, forward, or leak a credential.

Structured errors, not stack traces. Every expected failure mode is a documented JobMatchError subclass returned as {"ok": false, "error": {"code", "message", "retryable"}} instead of a raw exception — see the error catalog below. A calling agent can branch on code and retryable without burning tokens parsing a traceback. Genuinely unexpected exceptions are left to propagate to FastMCP's own error handling, because those are bugs, not domain outcomes.

Runs with zero credentials. If ADZUNA_APP_ID/ADZUNA_APP_KEY aren't set, search_jobs automatically falls back to a deterministic mock dataset (source: "mock" on every listing) instead of failing. The server, its test suite, and its eval harness are all fully runnable — including in CI, with no secrets configured.

Read-before-write. track_application only ever inserts a brand-new row or updates a row the caller identified by application_id. There's no bulk/implicit mutation path.

Observable by default. Every tool call emits one structured JSON log line — tool name, latency in ms, outcome, redacted params — via log_tool_call. No log line ever contains a credential or full resume text.

Error catalog

Code

Retryable

Meaning

INVALID_QUERY

No

Caller-side input failed validation (empty string, out-of-range value, bad enum)

NO_RESULTS_FOUND

No

Search/lookup succeeded but matched nothing

RATE_LIMITED

Yes

Upstream (Adzuna) rejected the request for rate limiting

UPSTREAM_UNAVAILABLE

Yes

Upstream unreachable or returned a 5xx

APPLICATION_NOT_FOUND

No

track_application referenced an application_id that doesn't exist

Architecture

MCP host (Claude Desktop / Cursor / etc.)
        │  stdio (JSON-RPC)
        ▼
  server.py  ── registers 4 tools on a FastMCP instance
        │
        ├─ adzuna_client.py  ── live Adzuna call, or deterministic mock fallback
        ├─ matching.py        ── dependency-light TF-IDF-style resume/job scorer
        ├─ storage.py          ── SQLite-backed application tracker (read-before-write)
        ├─ errors.py            ── JobMatchError catalog → structured {ok, error} shape
        └─ logging_utils.py      ── one structured JSON log line per call, with redaction

Setup

git clone <this repo>
cd jobmatch-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

cp .env.example .env   # optional -- server runs fine with these blank (mock mode)

Run it directly over stdio:

jobmatch-mcp

Run the test suite and eval harness:

pytest -v
python evals/run_evals.py

Build and run in Docker:

docker build -t jobmatch-mcp .
docker run -i --rm -e ADZUNA_APP_ID=... -e ADZUNA_APP_KEY=... -v jobmatch-data:/data jobmatch-mcp

Connecting to Claude Desktop / Cursor

Add to your MCP client's config (e.g. claude_desktop_config.json):

{
  "mcpServers": {
    "jobmatch": {
      "command": "jobmatch-mcp",
      "env": {
        "ADZUNA_APP_ID": "your-app-id",
        "ADZUNA_APP_KEY": "your-app-key",
        "JOBMATCH_DB_PATH": "/absolute/path/to/jobmatch.db"
      }
    }
  }
}

Omit the ADZUNA_* keys entirely to run in mock mode.

Testing

21 tests across four modules (tests/), covering every tool's happy path and every documented error code — including the mock-mode fallback, invalid input, not-found, and status-round-trip cases. All tests use an isolated throwaway SQLite file per test and run fully offline.

21 passed in ~1s

Evals

evals/run_evals.py scores a fixed set of resume/job-description pairs against expected score bounds and expected relative rankings (e.g. a resume listing the exact stack in the posting must outscore an unrelated resume) — the same reproducible-eval pattern used in AI Career Copilot, applied here to the matching function specifically.

6/6 passed (100.0%)

Roadmap / what a v2 would add

  • Streamable HTTP transport alongside stdio, for multi-host use beyond a single local MCP client

  • Swap matching.py's TF-IDF scorer for an embedding-based one behind the same score_match(resume_text, job_description) signature

  • Cover letter drafting tool, grounded in a tracked application's notes + the matched job description

License

MIT

Available Tools

4 tools
list_applicationsList ApplicationsA

List tracked applications, optionally filtered by pipeline status.

ParametersJSON Schema
NameRequiredDescriptionDefault
statusNoIf given, only return applications in this status. Omit for all.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. 'List' accurately implies a read-only operation with no side effects, and the optional status filter is disclosed. It does not add extra details like pagination or ordering, but for a simple list tool the core behavior is sufficiently transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no wasted words. It states the main purpose and the optional filter efficiently.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter list tool with a complete input schema and an output schema present, the description covers everything an agent needs to select and invoke it correctly. No critical information is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents the single status parameter. The description merely reiterates that filtering is optional, adding no additional semantic meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('List') and resource ('tracked applications'), including the optional filter by pipeline status. This clearly distinguishes it from siblings like track_application (a write operation) and search_jobs (searching for new jobs).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended usage is clear: use this tool to retrieve tracked applications, optionally filtering by status. It does not explicitly name alternatives or exclusions, but the context is unambiguous given the sibling tool names and the read-only nature of the action.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

score_resume_matchScore Resume MatchB

Score how well a resume matches a specific job description.

ParametersJSON Schema
NameRequiredDescriptionDefault
resume_textYesPlain-text resume content (or a relevant excerpt).
job_descriptionYesPlain-text job posting description to match against.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden of behavioral disclosure. It states the core behavior (scoring match) but doesn't disclose what the score means, how it's calculated, whether it's a percentage, a grade, or what the output schema contains. Since there is an output schema, some return details are covered, but the description itself adds little behavioral context beyond the basic action.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, clear sentence that front-loads the action and the two inputs. It is concise and free of fluff. It could add a bit more context about the output, but it earns its place as a minimal, clear statement.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple with two well-documented parameters and an output schema. The description is adequate for an agent to know what the tool does, but it doesn't explain what the score represents or how to interpret it. Given the output schema exists, the agent can see the return shape, but the description could still clarify the scoring scale or criteria. It's a minimum viable description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters clearly. The description adds no additional meaning beyond what the schema provides. Baseline 3 is appropriate since the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: scoring how well a resume matches a job description. It uses a specific verb ('score') and identifies the two key resources (resume and job description). It doesn't explicitly differentiate from siblings, but the siblings are clearly different actions (search, track, list), so the purpose is distinct enough.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the use case: when you have a resume and a job description and want a match score. It doesn't explicitly state when not to use it or mention alternatives, but the sibling tools are clearly different functions, so an agent can infer when this is appropriate. No explicit exclusions or alternative routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_jobsSearch JobsA

Search live job postings by title/keyword and optional location.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYesJob title or keyword, e.g. "agentic AI engineer". Required, non-empty.
locationNoCity/region to filter by, e.g. "Tampa, FL". Optional; omit for all locations.
max_resultsNoNumber of listings to return, 1-20. Defaults to 5.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden of behavioral disclosure. It accurately conveys a read-only search behavior and mentions 'live' postings, but it does not mention result ordering, pagination, or any system-level constraints. These gaps are minor for a search tool but still present.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no wasted words. It names the action, resource, and main filters immediately, making it easy for an agent to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple search tool with full schema coverage and an output schema, the description is sufficient for selecting and invoking the tool. The main omission is explicit guidance on when to use this tool over its siblings, but those siblings are distinct enough that this is a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the parameters are already fully documented in the schema with examples and constraints. The description adds little beyond restating that query and location are the relevant filters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Search'), a resource ('live job postings'), and the key filters (title/keyword, optional location). It clearly distinguishes itself from sibling tools that score resume matches, track applications, or list applications.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The use case is clear from the description: find job postings by keyword and optionally filter by location. It does not explicitly name alternatives or exclusions, but the sibling tools perform such different functions that confusion is unlikely.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

track_applicationTrack ApplicationA

Create a new tracked application, or update an existing one by id.

ParametersJSON Schema
NameRequiredDescriptionDefault
notesNoFree-text notes (e.g. recruiter name, follow-up date). Optional.
statusNoPipeline stage. One of saved, applied, interviewing, offer, rejected, withdrawn. Defaults to "saved".saved
companyYesHiring company name. Required.
job_urlNoLink to the posting. Optional.
job_titleYesTitle of the role, e.g. "Agentic AI Engineer". Required.
match_scoreNo0-100 score from score_resume_match, if computed. Optional.
application_idNoIf provided, updates that existing application instead of creating a new one. Omit when creating a new entry.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are present, so the description carries the full burden of disclosing the operation. It clearly indicates a mutating, upsert-like behavior, but it doesn't mention the required job_title/company fields, default status, or what happens on invalid application_id. The description is accurate but sparse.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, front-loaded sentence that conveys both modes of the tool without waste. It earns maximum points for efficiency.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The output schema and 100% schema coverage carry the parameter detail, so the brief description suffices for the common case. However, it omits guidance on how to obtain an application_id for updates, leaving a minor gap in the overall tool context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description itself adds no parameter-specific meaning beyond what's in the schema. With 100% schema description coverage, the parameter semantics are fully handled by the input schema, so this is a baseline 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb+resource: 'Create a new tracked application, or update an existing one by id.' It distinctively covers the upsert operation, clearly differentiating from siblings like search_jobs, score_resume_match, and list_applications, which have different purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a clear operational context (create new vs. update by id), but it never explicitly says when to prefer this tool over alternatives or how to obtain an application_id (e.g., via list_applications). No exclusions or alternative references are provided, so the usage is mostly implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedlist_applications
    • First observedscore_resume_match
    • First observedsearch_jobs
    • First observedtrack_application

TDQS

A4/5.0

Scored across 4 tools

Disambiguation5/5

Each tool targets a distinct function: searching jobs, scoring resume-job match, tracking/updating applications, and listing applications. There is no overlap or ambiguity between their purposes.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern (search_jobs, score_resume_match, track_application, list_applications). The style is uniform and predictable.

Tool Count5/5

With only 4 tools, the server is tightly scoped to its job-match niche. Each tool is essential and covers a distinct step in the user's workflow without redundancy.

Completeness4/5

The server covers the core workflow of searching jobs, matching resumes, and managing applications. Minor gaps exist (e.g., no delete application or detailed single-application view), but agents can safely navigate the current surface.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    C
    maintenance
    Enables searching job listings, tracking applications, managing resumes, and tailoring resumes to job posts, all locally via MCP.
    6
    20
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search, score, and track job applications from ATS boards via MCP, with explainable matching and an append-only application history.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI clients to serve as a personal career analyst by searching, matching, and explaining job recommendations, managing job applications, and syncing public job boards through standardized MCP tools.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables natural language management of job applications through MCP-compatible clients, including searching real job offers, creating and deleting applications, generating CVs or cover letters, and retrieving application metrics.
    ISC