JobSpy MCP Server
by chinpeerapat
README.md
# 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](https://github.com/speedyapply/JobSpy). Built on [FastMCP 3.x](https://gofastmcp.com) 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`](https://github.com/chinpeerapat/jobspy-mcp-server/releases/download/mcpb-latest/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](https://github.com/chinpeerapat/jobspy-mcp-server/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](PRIVACY.md).
CI packs and publishes the bundle on every push to `main`. To pack locally:
```bash
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](https://docs.astral.sh/uv/).
```bash
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):
```json
{
"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](https://github.com/chinpeerapat/jobspy-mcp-server/pull/1).)
### Cursor / other MCP clients
Any stdio-capable client works with the same command:
```bash
uv run --directory /absolute/path/to/jobspy-mcp-server jobspy-mcp-server
```
### HTTP mode (optional)
```bash
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.
```bash
uv run jobspy-mcp-server --http --stateless --port 8000
```
Notes:
- Requires `--http`. Stdio is unchanged, and `--stateless` without `--http` is an error.
- A single `--http` process 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 quirks
- `jobspy://countries` — valid `country_indeed` values
### Prompt
- `job-search` — a canned multi-board search workflow (`role`, optional `location`)
## 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 `mcp` SDK to standalone [FastMCP 3.x](https://gofastmcp.com), targeting the current MCP spec (2025-11-25, Streamable HTTP transport).
- **Layout**: flat `jobspy_mcp_server/` package replaced by a `src/jobspy_mcp/` layout; locking moved from `requirements.lock` to `uv.lock`.
- **Tool surface**: one focused `search_jobs` tool instead of four. Static catalogs became MCP resources (`jobspy://sites`, `jobspy://countries`) and search tips became the `job-search` prompt — 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-jobspy` is 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
1. **Reinstall.** Pull the new code and run `uv sync` (delete any old `.venv` first if it predates the rewrite). `git` must be on your PATH for the GitHub-pinned `python-jobspy`.
2. **Update client configs.** The console script name is unchanged (`jobspy-mcp-server`), so configs that launch it via `uv run` keep working. Replace any config that used `python -m jobspy_mcp_server` or `mcp run` with the command shown in [Usage](#usage).
3. **Update tool calls / automations.** Call `search_jobs` instead of `scrape_jobs_tool`; parse the structured JSON output instead of formatted text; page with `offset`/`next_offset`. For board and country lists, read the resources instead of calling tools.
4. **Docker users.** The container is now an HTTP server: map port 8000 and connect to `http://host:8000/mcp` (set `JOBSPY_HTTP_TOKEN` for auth). For stdio, run the server directly on the host instead.
## Development
```bash
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)
```bash
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:
```bash
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`](https://github.com/chinpeerapat/jobspy-mcp-server/releases/tag/mcpb-latest) prerelease (stable filename `jobspy-mcp-server.mcpb`).
To cut a numbered GitHub Release:
1. Bump `version` in both `pyproject.toml` and `manifest.json` (they must match).
2. Commit, then tag and push:
```bash
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
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues