I build Remote Jobs API — a free, keyless endpoint that normalizes live remote-job listings across a dozen boards into one stable schema. The obvious next step was to give it to my AI agent. So I wrapped it in a Model Context Protocol server: remote-jobs-api-mcp.
Now any MCP client — Claude Desktop, Cursor, Claude Code, or a raw harness — can ask for live job data as a tool call. No API key, no account, no scraping, no stale snapshot baked into a prompt. The agent gets the current feed.
The problem with how agents get job data today
The usual options are all a bit sad:
- Scrape a job board from the agent's prompt. Fragile, changes monthly, and your agent has to re-parse HTML every time.
- Paste a snapshot into context. It's stale the moment you write it, and it burns tokens on 400-job dumps.
- A keyed API. Fine for a human who returns tomorrow; wrong for an agent that needs one clean call and shouldn't hold a standing credential that can leak.
What an agent actually wants is: call a tool, get a lean, structured answer, done.
What the MCP server gives the agent
Three tools, all backed by the live keyless API:
-
search_jobs— live jobs by skill keywords and/or a minimum-salary floor. Returns a lean per-job shape (title, company, location, salary, category, source, link) — the long description is dropped so it doesn't pollute the context. -
market_stats— live feed health: which boards are up and how many jobs are live right now. -
salary_band— a live min / median / max salary band for a skill filter, so the agent can answer "what does a senior backend engineer pay, remote?" from real listings, not a cached number.
Here's the lean shape the search tool returns per job — deliberately trimmed for LLM consumption:
def _trim(job):
return {
"title": job.get("title"),
"company": job.get("company"),
"location": job.get("location"),
"salary": job.get("salary"),
"salary_min": job.get("salary_min"),
"salary_max": job.get("salary_max"),
"category": job.get("category"),
"source": job.get("source"),
"url": job.get("url"),
}
The salary band is computed live from the current feed — only jobs that actually disclose a numeric floor are counted, so you never get a "median" from nothing:
def salary_band(skills=None, limit=100):
d = _get("/v1/jobs", params)
floors = sorted(int(j["salary_min"]) for j in d.get("jobs", []) if j.get("salary_min"))
if not floors:
return {"n_with_salary": 0, "note": "no live jobs disclose a numeric salary for this filter"}
return {"n_with_salary": len(floors), "min": floors[0],
"median": floors[len(floors)//2], "max": floors[-1]}
And the tool registration is just the official mcp SDK:
mcp = FastMCP("remote-jobs-api")
@mcp.tool()
def search_jobs_tool(skills: str = "", min_salary: int = 0, limit: int = 10) -> str:
"""Search live normalized remote jobs. skills = comma-separated; min_salary =
floor on parsed annual salary. No API key required."""
return json.dumps(search_jobs(skills or None, min_salary or None, limit))
Install and wire it up
It's a thin, stdlib-only client (urllib) around the public API — the only dependency is the mcp SDK for the transport. Install from the repo:
git clone https://github.com/earnnova-dev/remote-jobs-api-mcp
cd remote-jobs-api-mcp && pip install .
Then point your MCP client at it. For Claude Desktop / Claude Code:
{
"mcpServers": {
"remote-jobs-api": {
"command": "remote-jobs-api-mcp",
"env": { "RJA_BASE_URL": "https://remote-jobs-api.tten.no" }
}
}
}
No env vars required — the base URL defaults to the public keyless instance. It also runs in Docker (docker run -i --rm remote-jobs-api-mcp over stdio).
What I like about this shape
-
The agent gets live data, not a snapshot.
market_statstells you how many jobs are live now — I checked, ~510,000 across 16 sources at the time of writing. -
Graceful degradation. The pure helpers (search, stats, band) are importable and unit-testable without the MCP SDK. If the SDK isn't installed, the server tells you exactly what to
pip installinstead of crashing opaquely. - No credential in the loop. Nothing to leak, nothing to revoke, nothing standing. The agent pays nothing and signs nothing; it just calls a tool.
-
Stdlib client. The HTTP layer is
urllib. If you can runpython3, you can run this.
Honest caveats
- It's the client only — a thin wrapper over a public API. No backend logic, no secrets, no data of its own. The value is the shape and the liveness, not a dataset.
- Salary coverage depends on how many live listings disclose a numeric floor; the band is only as good as what the boards publish. I built it to report "no data" rather than invent a median.
- It's not on PyPI yet — install from the repo for now.
What I'm curious about
- What's the most useful shape of job data to hand an agent — lean like this, or do you want the full description in a separate "details" tool?
- Do you gate a tool behind a check (agent confirms it's worth calling) before it hits the network, or let it fire freely?
- For a salary-band tool, do you trust a single live sample, or would you want a rolling window so it's stable across the day?
It's MIT and open source: github.com/earnnova-dev/remote-jobs-api-mcp. Feedback welcome.
Top comments (0)