seek-mcp
seek-mcp
Australian job-search MCP server. Zero dependencies — the JSON-RPC 2.0 stdio transport is ~80 lines of hand-rolled Bun, no SDK.
Why not actually SEEK?
There is no SEEK job-search API. developer.seek.com is the hirer/ATS side — posting
ads, managing candidates, screening — gated behind a partner agreement and a seven-stage
onboarding. There is no read endpoint for job seekers, and no community SEEK MCP that isn't
a scraper sitting behind Cloudflare waiting to break.
So this reads Adzuna, which aggregates Australian job boards (including SEEK-sourced ads) and has a free, instant, documented API. You get listings with apply-through links, plus the part SEEK never gives you: aggregate salary and employer data.
Why no SDK?
Because the protocol is small and the failure modes matter. Rolling the transport by hand
means every error path is explicit: a tool failure returns isError: true content the
model can recover from, a missing credential is a first-class message with the fix in it,
and an unknown method gets a proper JSON-RPC error instead of a crash. When something goes
wrong at 2am, there's no abstraction between the symptom and the cause.
Setup
Free credentials at https://developer.adzuna.com (instant, ~1000 calls/month).
Register:
claude mcp add seek --scope user \
--env ADZUNA_APP_ID=your_id \
--env ADZUNA_APP_KEY=your_key \
-- bun run /path/to/seek-mcp/src/server.tsRestart Claude Code,
/mcpto confirm.
Tools
Tool | What it does |
| Keyword + location search with salary, contract-type, recency and radius filters. Returns apply links. |
| Salary histogram, top hiring employers, month-by-month salary trend. |
| Just the match count — a cheap demand signal for comparing roles or suburbs. |
Sample — count_jobs {"what": "registered nurse", "where": "Perth WA", "max_days_old": 7}:
91 ads matching "registered nurse" — in Perth WA, posted in the last 7 days.Testing philosophy
./test.sh runs the handshake, tool listing, and — deliberately first — the failure paths:
credentials unset, unknown tool name. A checker that cannot fail is not a checker, so the
suite proves the red paths fire before any green is believed.
CI runs without credentials on purpose: it exercises exactly the negative half. The
live-query half runs locally with ADZUNA_APP_ID/ADZUNA_APP_KEY exported, and the suite
tells you it is NOT fully green until that has happened at least once:
negative path (credentials deliberately unset):
PASS initialize responds
PASS lists all three tools
PASS missing creds is an error
PASS unknown tool is an error
positive path (live Adzuna query):
PASS live count returns adsBoth failure-detection claims were themselves verified by breaking the server (wrong endpoint, wrong assertion) and confirming the suite goes red — see the commit history.
Design notes
stdio framing: newline-delimited JSON-RPC; partial reads buffered until a full line.
Notifications (no
id) never get responses, per spec — including unknown ones.Tool errors are
result.isError, not protocol errors, so the calling model can read the message and retry with different arguments.No
Date.now()in output paths — responses are deterministic given the API data, which keeps the test assertions honest.
MIT.