Skip to main content
Glama
Boljo

Fantastic.jobs MCP Server

by Boljo
README.md
# Fantastic.jobs MCP Server

A small local MCP server that lets Claude search fresh job postings through the [Fantastic.jobs API](https://developer.fantastic.jobs/). Ask Claude something like *"find data engineer jobs in Chicago from the last week"* and it calls the API for you.

## How it works

```
Claude Desktop  ──stdio──▶  job_server.py  ──▶  client_call.py  ──HTTPS──▶  data.fantastic.jobs
```

| File | Role |
|---|---|
| `client_call.py` | Talks to the API. `callout()` sends the request and returns the raw JSON. |
| `job_server.py` | The MCP server. Wraps `callout()` in a `search_jobs` tool and trims the response to a few fields. |
| `main.py` | Manual test script for poking at the API from the terminal. Not used by Claude. |
| `.env` | Holds your API key. Git-ignored. |

Claude Desktop launches `job_server.py` as a background process and talks to it over stdin/stdout. You never start it yourself.

## Requirements

- Python 3.14+
- [uv](https://docs.astral.sh/uv/)
- A Fantastic.jobs API key (from your subscriptions page)
- Claude Desktop (or Claude Code)

## Setup

```bash
git clone <this-repo>
cd linkedin
uv sync
echo 'linkedin_api=YOUR_KEY_HERE' > .env
```

## Test it in the terminal

Call the tool function directly, no MCP involved:

```bash
uv run python -c "from job_server import search_jobs; print(search_jobs('Data Engineer', time_frame='7d'))"
```

A list of jobs means the API call, key, and field trimming all work. An empty list `[]` just means no matches for that search.

To test through the real MCP protocol, use the Inspector (needs Node.js):

```bash
uv run mcp dev job_server.py
```

## Connect to Claude Desktop

Open **Settings → Developer → Edit Config** and add an entry under `mcpServers`. Use full paths: Claude Desktop is launched from the Dock and can't see your shell's `PATH`.

```json
{
  "mcpServers": {
    "fantastic-jobs": {
      "command": "/Users/YOU/.local/bin/uv",
      "args": ["--directory", "/absolute/path/to/linkedin", "run", "job_server.py"]
    }
  }
}
```

Find your uv path with `which uv`. Then fully quit Claude Desktop (Cmd+Q) and reopen it. `search_jobs` should appear in the tools list.

For Claude Code instead:

```bash
claude mcp add fantastic-jobs -- uv --directory /absolute/path/to/linkedin run job_server.py
```

## The tool

```
search_jobs(title, location="", time_frame="24h", limit=10)
```

| Parameter | Notes |
|---|---|
| `title` | Required. Keyword match. Use quotes for an exact phrase, and `OR` to combine: `"Data Engineer" OR "Analytics Engineer"` |
| `location` | Full names only, no abbreviations: `Chicago, Illinois, United States`. Same `OR` syntax. Results are global if omitted. |
| `time_frame` | `1h`, `24h`, or `7d` |
| `limit` | Max results per call. Each result costs API credits. |

Each job comes back trimmed to: `title`, `organization`, `url`, `date_posted`, `locations_derived`, `ai_core_responsibilities`. Edit `FIELDS` in `job_server.py` to change this. The full API response has 50+ fields including AI-extracted skills, salary, and company data.

## Gotchas

- **Never `print()` inside `job_server.py`.** stdout is the channel Claude talks over; a stray print corrupts it. Use `print(..., file=sys.stderr)` for debugging.
- **Restart Claude Desktop after any code or config change.** The server process is spawned at launch and reads the key once at import.
- **`.zshrc` doesn't reach Claude Desktop.** That's why the key lives in `.env`, which `load_dotenv()` picks up regardless of how the server was started.
- **401 Unauthorized** means the key didn't arrive. Check `.env` exists, has the right name (`linkedin_api`), and that no stale `env` block in the Claude config is overriding it.
- **Server shows as failed** in Claude Desktop: Settings → Developer has per-server logs. It's almost always the uv path, the project path, or a missing key.

## Security

`.env` is in `.gitignore`. Confirm before your first commit with `git check-ignore -v .env`. If a key ever leaks, rotate it from your Fantastic.jobs subscriptions page.

TDQS

B3.3/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of confusion or misselection. The tool's purpose is clearly described and unambiguous.

Naming Consistency5/5

The single tool name 'search_jobs' follows the consistent verb_noun snake_case convention. There are no other names to conflict with this pattern.

Tool Count2/5

The server has only one tool, which feels extremely thin for a job search domain. Even a minimal implementation would typically include at least a way to retrieve job details or paginate results.

Completeness2/5

The server only offers a search operation, leaving no way to fetch a specific job, browse companies, or access additional details beyond search results. The surface is too narrow to support a complete job search workflow.

Maintenance

ActivityMaintained
ResponsivenessNo issues