JobMatch MCP
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., "@JobMatch MCPSearch for senior Python developer jobs in Berlin and score my resume against the top result."
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.
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 |
| Live job postings by title/keyword + location |
|
| 0-100 fit score of a resume against a posting |
|
| Create or update a tracked application |
|
| List tracked applications, optionally by status |
|
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 |
| No | Caller-side input failed validation (empty string, out-of-range value, bad enum) |
| No | Search/lookup succeeded but matched nothing |
| Yes | Upstream (Adzuna) rejected the request for rate limiting |
| Yes | Upstream unreachable or returned a 5xx |
| No |
|
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 redactionSetup
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-mcpRun the test suite and eval harness:
pytest -v
python evals/run_evals.pyBuild 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-mcpConnecting 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 ~1sEvals
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 samescore_match(resume_text, job_description)signatureCover letter drafting tool, grounded in a tracked application's
notes+ the matched job description
License
MIT
Available Tools
4 toolslist_applicationsList ApplicationsA
List tracked applications, optionally filtered by pipeline status.
| Name | Required | Description | Default |
|---|---|---|---|
| status | No | If given, only return applications in this status. Omit for all. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| resume_text | Yes | Plain-text resume content (or a relevant excerpt). | |
| job_description | Yes | Plain-text job posting description to match against. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Job title or keyword, e.g. "agentic AI engineer". Required, non-empty. | |
| location | No | City/region to filter by, e.g. "Tampa, FL". Optional; omit for all locations. | |
| max_results | No | Number of listings to return, 1-20. Defaults to 5. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| notes | No | Free-text notes (e.g. recruiter name, follow-up date). Optional. | |
| status | No | Pipeline stage. One of saved, applied, interviewing, offer, rejected, withdrawn. Defaults to "saved". | saved |
| company | Yes | Hiring company name. Required. | |
| job_url | No | Link to the posting. Optional. | |
| job_title | Yes | Title of the role, e.g. "Agentic AI Engineer". Required. | |
| match_score | No | 0-100 score from score_resume_match, if computed. Optional. | |
| application_id | No | If provided, updates that existing application instead of creating a new one. Omit when creating a new entry. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
4 tool updates
v0.1.0- First observed
list_applications - First observed
score_resume_match - First observed
search_jobs - First observed
track_application
TDQS
Scored across 4 tools
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.
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.
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.
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
Related MCP Connectors
7 recruiting tools over one MCP endpoint: ATS boards, LinkedIn jobs, profiles, companies, Naukri.
Agent-driven search: build, import, tune, search, and score result quality — all over MCP.
Public MCP server for discovering open jobs. Search, filter, and get application links.
Search recruiting CRM records, manage candidates, jobs, pipelines, notes and tasks with OAuth.
CRM1
Related MCP Servers
- FlicenseAqualityCmaintenanceEnables searching job listings, tracking applications, managing resumes, and tailoring resumes to job posts, all locally via MCP.620-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to search, score, and track job applications from ATS boards via MCP, with explainable matching and an append-only application history.MIT
- AlicenseNot gradedqualityBmaintenanceEnables 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
- AlicenseNot gradedqualityCmaintenanceEnables 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