Skip to main content
Glama
edefice

job-search-mcp

by edefice

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

search_jobs(keyword, location?)

Searches justjoin.it listings. Returns title, company, URL, salary, tags, location. location accepts a city name ("Warsaw", "Krakow", ...) or "remote".

get_job_details(url)

Fetches a single job offer page and returns a cleaned-up description plus tech stack tags.

track_application(url, status)

Saves/updates an application's status locally. status is one of saved, applied, interviewing, rejected, offer.

list_applications()

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 build

This 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.js

Or 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.tsbuildSearchUrl against known/unmapped/diacritic city names, and parseOfferCards/parseJobDetails against 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.tstrackApplication/listApplications against a temp file (via the JOB_SEARCH_STORE_PATH env 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.ts anchors on stable landmarks instead: the offer_list_offer_title_link class, 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/JobPosting JSON-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, not warsaw) — search_jobs maps 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, so String.normalize("NFD") doesn't strip it the way it does ó or ź — it needs an explicit ł/Łl/L replacement 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_details will 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 tools
get_job_detailsGet Job DetailsA

Fetch a single justjoin.it job posting and return a cleaned-up description and tech stack tags.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesThe justjoin.it job offer URL, e.g. from search_jobs results

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. 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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesThe job offer URL to track
statusYesCurrent application status

TDQS

B3.1/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

  1. 2 tool updatesv0.1.0
    • First observedget_job_details
    • First observedtrack_application

TDQS

B3.4/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct purposes: one fetches job details, the other tracks applications. No overlap or confusion.

Naming Consistency5/5

Both tool names follow a consistent verb_noun pattern: 'get_job_details' and 'track_application'.

Tool Count2/5

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.

Completeness1/5

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

ActivitySlowing
ResponsivenessNo issues

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

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables job search and scraping across multiple job boards (LinkedIn, Indeed, Glassdoor, etc.) with advanced filtering, directly from Claude Desktop or other MCP clients.
    5
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables 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.
    -