使用说明
JobSpy MCP Server
An MCP (Model Context Protocol) server that scrapes live job postings from LinkedIn, Indeed, Glassdoor, ZipRecruiter, Google Jobs, Bayt, Naukri, and BDJobs using python-jobspy. Built on FastMCP 3.x against the current MCP spec (2025-11-25).
Why local?
Job boards aggressively block datacenter IPs, and JobSpy depends on tls-client (a native binary). Running the server on your own machine over stdio gives the best scrape success rate. Streamable HTTP mode is available behind a flag if you want to host it (bring your own proxies).
Installation
Claude Desktop (MCPB, one-click)
Download jobspy-mcp-server.mcpb (rolling build from main) and open it with Claude Desktop, or drag the file onto the app. Versioned files live on Releases. The host's UV runtime installs Python and dependencies on first launch — you do not need a local Python toolchain.
Optional install-time settings: proxies and a CA certificate bundle (for proxied HTTPS). See PRIVACY.md.
CI packs and publishes the bundle on every push to main. To pack locally:
bash scripts/build-mcpb.sh
# → dist/jobspy-mcp-server-<version>.mcpb
# → dist/jobspy-mcp-server.mcpb
From source (stdio)
Requires Python 3.10+ and uv.
git clone <this-repo> && cd jobspy-mcp-server
uv sync
Note: python-jobspy is pinned to a GitHub commit ahead of the PyPI release (which is missing LinkedIn/Naukri fixes), so git must be on your PATH during install.
Usage
Claude Desktop (manual stdio)
If you are not using the MCPB, add this to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%/Claude/claude_desktop_config.json (Windows):
{
"mcpServers": {
"jobspy": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/jobspy-mcp-server", "jobspy-mcp-server"]
}
}
}
Tip: Claude Desktop launches with a limited PATH that usually excludes ~/.local/bin, so you may need the absolute path to uv (e.g. /Users/you/.local/bin/uv) in command. (Thanks to @VennieSo in #1.)
Cursor / other MCP clients
Any stdio-capable client works with the same command:
uv run --directory /absolute/path/to/jobspy-mcp-server jobspy-mcp-server
HTTP mode (optional)
uv run jobspy-mcp-server --http --port 8000
# MCP endpoint: http://127.0.0.1:8000/mcp
Set JOBSPY_HTTP_TOKEN to require a bearer token; without it the endpoint is unauthenticated and should stay on localhost.
--stateless (HTTP only, opt-in)
By default HTTP mode keeps a short-lived session per client in memory. That is the right default for one process.
If you run more than one process — several workers, or a load balancer in front of several containers — those sessions live on whichever process handled the first request. The next request can land on a different process and fail.
--stateless turns sessions off. Each request is handled on its own, so any process can take the next call. Job searches already send everything they need in the request (page with offset), so this server does not depend on sessions.
uv run jobspy-mcp-server --http --stateless --port 8000
Notes:
- Requires
--http. Stdio is unchanged, and--statelesswithout--httpis an error. - A single
--httpprocess does not need it. - Progress heartbeats during a scrape still work; they go out on that same request, not via a stored session.
- Do not use this as a reason to run the scraper serverless. Job boards block datacenter IPs, and JobSpy needs a native TLS library.
Environment variables
| Variable | Purpose |
|---|---|
JOBSPY_PROXIES |
Comma-separated proxy list passed to JobSpy (useful for hosted mode or heavy scraping) |
JOBSPY_CA_CERT |
Path to a CA certificate bundle for proxied HTTPS |
JOBSPY_HTTP_TOKEN |
Bearer token required by HTTP mode when set |
What it exposes
Tool: search_jobs
Read-only search across the supported boards. Key parameters:
| Parameter | Notes |
|---|---|
search_term |
Required keywords |
sites |
Boards to scrape; default ["indeed"] (most reliable) |
location, distance, is_remote |
Where |
results_wanted |
Per site, capped at 50 |
job_type |
fulltime / parttime / contract / temporary / internship |
hours_old |
Only postings from the last N hours |
offset |
Pagination |
country_indeed |
Country for Indeed/Glassdoor (see jobspy://countries) |
google_search_term |
Required when scraping Google Jobs |
include_description |
Truncated descriptions, off by default (keeps responses small) |
Returns compact JSON records (title, company, location, salary, posted date, URL) as both text and structured content.
Resources
jobspy://sites— per-board capabilities and quirksjobspy://countries— validcountry_indeedvalues
Prompt
job-search— a canned multi-board search workflow (role, optionallocation)
What changed in 2.0
Version 2.0 is a ground-up rewrite of the 1.x server.
Changes
- Framework: migrated from the legacy FastMCP 1.0 bundled in the
mcpSDK to standalone FastMCP 3.x, targeting the current MCP spec (2025-11-25, Streamable HTTP transport). - Layout: flat
jobspy_mcp_server/package replaced by asrc/jobspy_mcp/layout; locking moved fromrequirements.locktouv.lock. - Tool surface: one focused
search_jobstool instead of four. Static catalogs became MCP resources (jobspy://sites,jobspy://countries) and search tips became thejob-searchprompt — this keeps the per-turn token cost of tool schemas low. - Output: structured content with a declared output schema (
count,offset,results_per_site,sites_queried,truncated,next_offset,note,jobs[]) alongside the JSON text block. Records are compact; descriptions are opt-in and truncated at 1,500 chars. - Robustness: input validation with actionable errors (blank terms, empty site lists, unknown countries), a 60-job total response cap, a 120s scrape timeout, progress heartbeats every 5s, and proper MCP tool errors instead of transport crashes.
- Ops: proxy/CA-cert configuration via environment variables, optional bearer-token auth for HTTP mode, and Directory-ready tool annotations (
title,readOnlyHint,destructiveHint). - Dependency:
python-jobspyis now installed from GitHub main (the PyPI release lags behind and is missing LinkedIn/Naukri fixes).
Breaking changes
| 1.x | 2.0 |
|---|---|
Tool scrape_jobs_tool |
Renamed to search_jobs; results are JSON records, not formatted prose |
Tools get_supported_sites / get_supported_countries |
Removed — read resources jobspy://sites / jobspy://countries |
Tool get_job_search_tips |
Removed — use the job-search prompt |
python -m jobspy_mcp_server |
Use python -m jobspy_mcp (or the jobspy-mcp-server script) |
uv run mcp dev/run -m jobspy_mcp_server |
Removed — use uv run jobspy-mcp-server or the MCP Inspector |
pip install -r requirements.lock |
Removed — use uv sync |
| Docker image spoke stdio | Docker image now serves Streamable HTTP on port 8000 |
results_wanted up to 100+, uncapped output |
Capped at 50 per site and 60 jobs total per response |
Migrating from 1.x
- Reinstall. Pull the new code and run
uv sync(delete any old.venvfirst if it predates the rewrite).gitmust be on your PATH for the GitHub-pinnedpython-jobspy. - Update client configs. The console script name is unchanged (
jobspy-mcp-server), so configs that launch it viauv runkeep working. Replace any config that usedpython -m jobspy_mcp_serverormcp runwith the command shown in Usage. - Update tool calls / automations. Call
search_jobsinstead ofscrape_jobs_tool; parse the structured JSON output instead of formatted text; page withoffset/next_offset. For board and country lists, read the resources instead of calling tools. - Docker users. The container is now an HTTP server: map port 8000 and connect to
http://host:8000/mcp(setJOBSPY_HTTP_TOKENfor auth). For stdio, run the server directly on the host instead.
Development
uv sync --extra dev
uv run pytest # in-memory tests, scraping mocked
npx @modelcontextprotocol/inspector uv run jobspy-mcp-server # interactive testing
bash scripts/build-mcpb.sh # pack Claude Desktop bundle → dist/*.mcpb
Docker (HTTP mode)
docker build -t jobspy-mcp-server .
docker run --rm -p 8000:8000 jobspy-mcp-server
The image serves Streamable HTTP on port 8000, with sessions on (same as --http without --stateless). To turn sessions off:
docker run --rm -p 8000:8000 jobspy-mcp-server \
jobspy-mcp-server --http --stateless --host 0.0.0.0 --port 8000
Scraping from cloud IPs is likely to get blocked; pass proxies (JOBSPY_PROXIES) or prefer running locally.
Distribution status
The supported distribution path is the MCPB bundle (Claude Desktop one-click install). Clone + uv sync is still the right workflow for development. Publishing to PyPI is currently blocked because python-jobspy is pinned to a git commit (PyPI rejects direct URL dependencies) — switch to a released version once upstream tags one. Hosted streamable-HTTP is possible if you bring your own proxies; job boards block datacenter IPs.
Releasing
Every push to main runs tests, packs the MCPB, and updates the mcpb-latest prerelease (stable filename jobspy-mcp-server.mcpb).
To cut a numbered GitHub Release:
- Bump
versionin bothpyproject.tomlandmanifest.json(they must match). - Commit, then tag and push:
git tag v0.2.1
git push origin v0.2.1
The tag must be v + the package version. CI attaches jobspy-mcp-server-<version>.mcpb to that release. Do not commit dist/*.mcpb — GitHub Releases is the publish location.
License
MIT