job-search-mcp
Click on "Install 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., "@job-search-mcpFind AI Engineer roles in Warsaw"
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.
Job Search MCP Server
An MCP (Model Context Protocol) server that lets Claude search justjoin.it job postings and track your applications, so you can ask things like "find me new AI Engineer roles in Warsaw" or "what's the status of the application I sent last week" directly in a chat with Claude Desktop or Claude Code.
Why
Built as a hands-on MCP project for my own AI Engineer job search — real tool, real use, not a toy demo.
Related MCP server: SkillMatch MCP
Tools
Tool | Description |
| Searches justjoin.it listings. Returns title, company, URL, salary, tags, location. |
| Fetches a single job offer page and returns a cleaned-up description plus tech stack tags. |
| Saves/updates an application's status locally. |
| Lists all tracked applications, most recently updated first. |
Application data is stored locally in a JSON file at ~/.job-search-mcp/applications.json —
no database, no external service.
Setup
Requires Node.js 18+.
git clone <this-repo-url>
cd job-search-mcp
npm install
npm run buildThis produces dist/index.js, a stdio MCP server.
Connecting to Claude Desktop
Add the server to your Claude Desktop config
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS), replacing the
path below with the absolute path to dist/index.js in your clone (run pwd inside the
project directory to get it):
{
"mcpServers": {
"job-search": {
"command": "node",
"args": ["/absolute/path/to/job-search-mcp/dist/index.js"]
}
}
}Restart Claude Desktop. The four tools above should appear as available tools (look for the 🔨 tools icon in the chat input).
Connecting to Claude Code
claude mcp add job-search -- node /absolute/path/to/job-search-mcp/dist/index.jsOr add it to a project's .mcp.json:
{
"mcpServers": {
"job-search": {
"command": "node",
"args": ["/absolute/path/to/job-search-mcp/dist/index.js"]
}
}
}Example prompts
"Find AI Engineer roles in Warsaw"
"Get me the full details and tech stack for this listing:
<url>""Mark that job as applied"
"What's the status of all my job applications?"
"Show me everything I've marked as interviewing"
Development
npm run dev # tsc --watch
npm run build # one-off compile
npm start # run the compiled server directly (for manual stdio testing)
npm test # build, then run the test suite (node's built-in test runner)Tests
Tests run with Node's built-in test runner (node:test) — no extra test framework dependency.
npm test compiles first, then runs everything under dist/**/*.test.js.
justjoin.test.ts—buildSearchUrlagainst known/unmapped/diacritic city names, andparseOfferCards/parseJobDetailsagainst trimmed-but-verbatim fixtures of real justjoin.it markup (including the<object>logo wrapper that makes the tag-pill parsing heuristic necessary), so the parsing logic is verified without hitting the live site.store.test.ts—trackApplication/listApplicationsagainst a temp file (via theJOB_SEARCH_STORE_PATHenv var override), so tests never touch your real~/.job-search-mcp/applications.json.
Tricky bits
justjoin.it has no public API. It's a Next.js app that server-renders offer cards, but the CSS classes are build-hashed (e.g.
mui-1cks7or) and change on every deploy, so scraping can't rely on fixed selectors.src/justjoin.tsanchors on stable landmarks instead: theoffer_list_offer_title_linkclass, Lucide icon classes (svg.lucide-map-pin,svg.lucide-building), and — for the tag pills, where even sibling/depth assumptions turned out to be unreliable — a structural heuristic that detects "a<div>whose children are all plain-text, childless<div>s," a shape unique to that row on the card.Job detail pages are easier: they embed proper
schema.org/JobPostingJSON-LD, which is used for title/company/location/salary/employment type. The visible HTML is still used for the description and tech stack, since the JSON-LD description is a single run-on string with no paragraph breaks.City slugs are Polish-only (
warszawa, notwarsaw) —search_jobsmaps common English city names to their Polish slugs. Writing the test for this caught a real bug: Polishł(as inŁódź) isn't a combining-mark diacritic, soString.normalize("NFD")doesn't strip it the way it doesóorź— it needs an explicitł/Ł→l/Lreplacement first.Salary is often undisclosed. Both tools fall back to
"Not specified"rather than erroring when a listing doesn't list a salary.Scraping is inherently brittle. If justjoin.it changes its page structure enough to break these heuristics,
search_jobs/get_job_detailswill return an explicit tool error rather than silently returning garbage.
Roadmap (not yet built)
match_score(job_url)— score a job description against a CV/skills profile via the Anthropic API, with a rationale.A second job board as a data source, so results aren't tied to justjoin.it alone.
Available Tools
2 toolsget_job_detailsGet Job DetailsA
Fetch a single justjoin.it job posting and return a cleaned-up description and tech stack tags.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | The justjoin.it job offer URL, e.g. from search_jobs results |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It describes the tool as fetching and returning cleaned data, implying no side effects. However, it does not explicitly state read-only behavior or disclose any potential limitations like rate limits or authentication needs.
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 that efficiently conveys the action and result with no wasted words.
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 tool with one parameter and no output schema, the description is largely complete. It specifies the return includes a cleaned description and tech stack tags, which is sufficient for a single-purpose fetch tool.
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 coverage is 100% with a single 'url' parameter described. The tool description adds context that the URL comes from search_jobs results, but this adds only marginal value beyond the schema description.
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 fetches a single justjoin.it job posting and returns a cleaned-up description and tech stack tags. It distinguishes itself from the sibling tool track_application by focusing on fetching details rather than tracking 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 description implies the tool is used after searching for jobs, e.g., by providing a URL from search_jobs results. However, it does not explicitly state when not to use or provide alternatives, but the context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
track_applicationTrack ApplicationB
Save or update the application status for a job posting in the local tracker.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | The job offer URL to track | |
| status | Yes | Current application status |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Minimal behavioral info beyond annotations (none provided). 'Save or update' implies upsert, but no details on idempotency, overwrite behavior, authentication, or response.
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?
Single sentence, immediately states purpose. No unnecessary words. Efficient and front-loaded.
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?
Adequate for a simple two-param tool with no output schema. Missing details on success/failure return behavior and effects on existing records.
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 coverage is 100%, so parameters are already well-described. Description adds minimal context ('for a job posting', 'local tracker') but does not enhance parameter meaning beyond 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?
Clearly states verb (save/update) and resource (application status). Identifies context (local tracker). Lacks explicit distinction from sibling 'get_job_details'.
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?
No guidance on when to use this tool vs alternatives. Does not mention that it is for tracking progress or that 'get_job_details' is for viewing details. No exclusions or prerequisites.
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.
2 tool updates
v0.1.0- First observed
get_job_details - First observed
track_application
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: one fetches job details, the other tracks applications. No overlap or confusion.
Both tool names follow a consistent verb_noun pattern: 'get_job_details' and 'track_application'.
With only 2 tools for a 'job-search-mcp', the scope is far too narrow. A job search server typically needs search, list, and filter capabilities.
The tool surface is severely incomplete for a job search server. There is no way to discover or search for jobs, only view details of a known posting and track application status.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Manage job applications — jobs, companies, boards, notes, and profile — from your AI client.
Analyze job listings against your resume, track applications, and generate cover letters.
AI job search MCP — fact-checked jobs, application tracker, alerts. ChatGPT, Claude, Cursor.
AI job search for Claude, ChatGPT, Cursor. 170K+ jobs, 3,800+ companies. OAuth or stdio.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables job search and scraping across multiple job boards (LinkedIn, Indeed, Glassdoor, etc.) with advanced filtering, directly from Claude Desktop or other MCP clients.5MIT
- AlicenseAqualityBmaintenanceClaude-powered job fit analyzer that analyzes job fit based on GitHub portfolio, resume, and preferences, tracks applications locally.202MIT
- AlicenseNot gradedqualityCmaintenanceEnables searching and evaluating job postings from LinkedIn and freehire.me directly through Claude Desktop. Provides tools to search jobs, fetch full posting details, and assess candidate fit using eligibility scans and a scoring rubric.MIT
- FlicenseNot gradedqualityCmaintenanceEnables Claude to parse CVs, search job boards (Remotive, Arbeitnow, Adzuna, Greenhouse/Lever), tailor resumes and cover letters, and prepare application packages with direct apply links—without ever auto-submitting. It runs 100% locally and free, storing jobs and applications as JSON files.-