Job Opportunities API

Check the data. Then trust it.

start

MCP server

A remote MCP server at api.jobopportunitiesapi.org/mcp — Streamable HTTP — so an AI agent in Claude, Cursor, Windsurf, VS Code or Cline can search live job postings with the same key, metering and plan limits as the REST API.

Last verified 2026-10-01 · Assumes: Nothing about JOA. Some familiarity with the Model Context Protocol helps but is not required. · Markdown copy

1What this is

A single remote endpoint speaking the Model Context Protocol over Streamable HTTP — the same auth, metering and data as the REST API, reached a second way.

In depthWhy it exists, what it is not, what people get wrong

POST https://api.jobopportunitiesapi.org/mcp carries every JSON-RPC 2.0 message: initialize, notifications/initialized, ping, tools/list, tools/call. Every response is a single application/json object — never Server-Sent Events, because nothing here pushes unprompted. GET on the same path answers 405, which is spec-legal for a server with nothing to stream. No session id is issued: the server is stateless, and every call carries its own Authorization header.

Six tools wrap six parts of the REST API, and each tool call replays through the exact same request path a direct curl of the wrapped endpoint would use — the same handler, the same auth check, the same metering. There is no second implementation of any of this to drift out of sync with the API you already know.

Exact contractTypes, defaults, ranges, errors, edge cases
Endpoint
https://api.jobopportunitiesapi.org/mcp — the API host, not the website host.
Transport
Streamable HTTP, a single endpoint for both verbs, per the MCP specification.
Protocol version
2025-06-18 — the classic initialize handshake every current MCP client (Claude, Cursor, Windsurf, VS Code, Cline) actually implements.
Session state
None. Stateless by construction — no Mcp-Session-Id, no server-held conversation.
Registry name
org.jobopportunitiesapi/mcp — reverse-DNS of jobopportunitiesapi.org, for the MCP registry.

2The six tools

Two need no key at all — market_signals and coverage. The other four need a free API key, same as the REST routes they wrap.

In depthWhy it exists, what it is not, what people get wrong
ToolWrapsKey?
search_jobsGET /v1/jobsNeeded.
get_jobGET /v1/jobs/{id}Needed.
company_hiringGET /v1/companies/{slug} plus the keyless open-roles-history trendHybrid — the 30/90/365-day trend is keyless; the full roster needs one.
market_signalsGET /public/market-signals (and its /history)Never. Keyless statistics, like the REST route.
coverageGET /public/coverage plus /public/statsNever.
changes_sinceGET /v1/changesNeeded, and only on the Growth plan or above — same gate as the REST route.

A row-serving tool called with no Authorization header never reaches the database: it answers a structured refusal naming the free-key page, the same shape every tool's own description tells the calling model to expect — never a bare 401 the model has to interpret.

Exact contractTypes, defaults, ranges, errors, edge cases
search_jobs
Filters: country, city, US state, category/exclude_category, seniority, remote, remote_confirmed, employment_type, source_type, provider, company/company_domain, title, q, description_contains, has_salary, min_salary/max_salary, posted_after, verified_after, status, quality, include_poster_type, require_fields, include_description, limit (1-200, default 25), cursor.
get_job
id (required — uuid or slug), include_closed, quality, include_poster_type.
company_hiring
slug (required), history_days (30, 90 or 365), include_open_jobs, jobs_limit (1-50, default 10).
market_signals
country, role_family, seniority, include_history, history_days (30, 90 or 365) — every filter optional; omit all for the whole-dataset aggregate.
coverage
include_countries, include_employers — both optional booleans.
changes_since
since (required — RFC3339 timestamp or a previous next_since cursor), limit (1-5000, default 500), event_type (created/updated/withdrawn/delisted, filtered client-side after the call so it does not change what is metered).

Every tool's inputSchema is full JSON Schema, served live by tools/list itself — the table above is for reading, not for hand-typing into a client; a conformant client reads the schema from the server.

3Client configuration

The same JSON block works in Claude Desktop, Claude.ai, Cursor, Windsurf, VS Code and Cline — only the file it goes in changes.

In depthWhy it exists, what it is not, what people get wrong

Claude Desktop and Claude.ai use the streamableHttp shape below, either pasted into Settings → Connectors → Add custom connector, or written directly into claude_desktop_config.json.

Claude Desktop / Claude.ai
{
  "mcpServers": {
    "joa": {
      "type": "streamableHttp",
      "url": "https://api.jobopportunitiesapi.org/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}

Cursor, VS Code and Cline all read the plain shape below — Cursor's project or global .cursor/mcp.json, VS Code's .vscode/mcp.json (or Command Palette → "MCP: Add Server" → HTTP), and Cline's MCP Servers → Configure MCP Servers, which reads the identical mcpServers block.

Cursor / VS Code / Cline
{
  "mcpServers": {
    "joa": {
      "url": "https://api.jobopportunitiesapi.org/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}

Windsurf: Cascade → Plugins → MCP Servers → add a custom server with the same URL and header, or edit ~/.codeium/windsurf/mcp_config.json directly — same JSON shape as Cursor's above.

Exact contractTypes, defaults, ranges, errors, edge cases

ChatGPT: OpenAI's unified plugin directory needs a ZIP submission with an identity-verified developer account and is not yet submitted. Until then, any MCP-capable ChatGPT developer-mode client that accepts a raw Streamable HTTP URL with a static header can use the Cursor/VS Code configuration above unchanged.

4Raw JSON-RPC, if your client speaks it directly

A handshake and a tool call, as curl — useful for testing a connection or building a client that is not on the list above.

In depthWhy it exists, what it is not, what people get wrong

initialize first, then a tool call. The handshake needs no key; a keyed tool call does.

Exact contractTypes, defaults, ranges, errors, edge cases
Handshake
curl -s https://api.jobopportunitiesapi.org/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18","capabilities":{},
                 "clientInfo":{"name":"curl","version":"1.0"}}}'
A tool call
curl -s https://api.jobopportunitiesapi.org/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"search_jobs","arguments":{"country":["DE"],"category":["Engineering"]}}}'

A keyless tool call (market_signals, coverage) is identical but without the Authorization header.

5Auth, metering and limits

Every tool call is metered exactly like the REST endpoint it wraps — same plan, same monthly allowance, same rate limit. There is no separate MCP quota.

In depthWhy it exists, what it is not, what people get wrong

A tool call replays through the same request path the matching REST endpoint uses, so a search_jobs call and a direct curl of /v1/jobs with the identical filter and the same key charge the identical number of records — there is one metering implementation, reached two ways, not two that could drift apart.

changes_since on a plan without the delta-feed entitlement gets the same 403 a direct curl of /v1/changes would get. A free Explore key (1,000 records a month, no card) is enough to try every keyed tool — get one here.

Exact contractTypes, defaults, ranges, errors, edge cases
ThingBehaviour
No Authorization header, keyed toolisError: true, text naming /login?ref=mcp — no internal call made, no record charged.
changes_since, no delta_feed entitlementSame 403 as GET /v1/changes on that plan.
Record allowanceShared with the REST API — one meter per key, see the record meter.
Rate limitsSame per-plan request ceilings as the REST API — see rate limits.

This page was rendered 3 October 2026, 01:05 UTC. Every figure on it comes from the endpoint named beside it, and every published request is re-sent against the live API before this site is allowed to build. If something here is wrong, the thumbs-down above reaches a person.