Search a live index of millions of open jobs from employer career sites and 100+ ATS platforms.
One address for any MCP client. You sign in at enterprise.jobo.world once, after adding it.
At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
| Access to | Read | Change |
|---|---|---|
| Jobs |
From the sign-in at enterprise.jobo.world, as the endpoint publishes it. You approve it there.
Odel could not list its tools without signing in.
Remote MCP server exposing Jobo's live job index — millions of listings collected from employer career sites and 150+ applicant tracking systems — to LLM clients.
Full client setup (Claude, ChatGPT, Cursor, Codex CLI) and the tool reference: jobo.world/docs/connectors/mcp.
/mcp endpoint, stateless. Serves MCP spec 2026-07-28
natively and every 2025-era client through the built-in legacy fallback (SDK v2
createMcpHandler, one factory for both eras).jobs:read.The hosted deployment is https://jobs-mcp.jobo.world. To run your own copy instead:
npx jobo-job-search-mcp
Starts the Streamable HTTP server on $PORT (default 3002); point your client at
http://localhost:3002/mcp. This changes where the gateway runs, not its auth model — it's still an
OAuth resource server gated on Jobo account sign-in, since the upstream API validates every request against
the Authorization Server regardless of which copy of the gateway forwarded it. Set MCP_RESOURCE_URL to
match whatever host you actually serve it from — see Configuration below.
Jobo.Enterprise/Jobo.Enterprise.Mcp was deliberately re-scoped to analytics-only in v4, which removed
search_jobs, get_job_details, list_filters, search and fetch. Adding job tools back there would
undo that decision, so this is a second server against the same External API.
The /api/mcp/jobs/* endpoints were never removed — McpController.cs still serves them, and its own
comment notes the GET search is "convenient for the canonical ChatGPT search(query) tool". This server
is a thin OAuth-forwarding gateway in front of endpoints that were built for it.
The immediate payoff: search + fetch restore Deep Research compatibility. Without that canonical
pair a server cannot be used as a ChatGPT Deep Research connector at all.
| Tool | Purpose |
|---|---|
search | Canonical Deep Research contract: {query} → {results: [{id, title, url}]}. |
fetch | Canonical Deep Research contract: {id} → {id, title, text, url, metadata}. |
search_jobs | Structured search — location, work model, employment type, experience level, source, skills, industries, salary, date, facets, paging. |
get_job_details | Full listing for clients not using the Deep Research contract. |
list_filters | Accepted values for every filter, with live counts. |
search/fetch deliberately take the minimum arguments the contract allows. Anything with structure
should go through search_jobs, where filters are real parameters rather than hopeful free text.
fetch returnstext is self-contained prose, because Deep Research reads it and never opens the URL. It is built from
the AI-extracted fields (responsibilities, qualifications, benefits, compensation) in preference to the
raw employer HTML, which is boilerplate-heavy and frequently longer than it is useful. The raw description
is available via get_job_details with include_description: true.
The server is a gateway, not the cryptographic authority. The C# External API validates the JWT with
OpenIddict against the same issuer, audience and jobs:read scope; verifying the signature a second time
here would only let the two validators drift. So this does the minimum a gateway must:
401 with the resource-metadata challenge, starting the OAuth flow.exp, no signature check) so long-lived clients
refresh rather than forwarding a dead token.req.auth, so every tool call forwards the token the client
just sent — never one captured at session-initialize.No session map. That map lived in process memory, so every restart or redeploy stranded clients with
"No active session", and it pinned the deployment to a single replica. Redis cannot back it either: the
value is a live transport object holding open streams. Each POST is served by a fresh server and transport
with no mcp-session-id issued.
| Variable | Default | Notes |
|---|---|---|
JOBO_API_URL | https://connect.jobo.world | Upstream External API. |
MCP_RESOURCE_URL | https://jobs-mcp.jobo.world | OAuth audience. Must differ from the analytics server's mcp.jobo.world. |
OAUTH_AUTH_SERVER_URL | https://enterprise.jobo.world | Authorization Server. |
PORT | 3002 | Analytics server uses 3001. |
npm install && npm run build && npm test
npm run dev
node --test dist/format.test.js covers the mapping logic, including that search and fetch return
exactly the shapes Deep Research requires. For the wire path, point the server at a stub:
JOBO_API_URL=http://localhost:3098 PORT=3097 MCP_RESOURCE_URL=http://localhost:3097 node dist/index.js
Then tools/list and tools/call over HTTP with any JWT-shaped bearer whose exp is in the future —
the gateway forwards it and the stub answers. A real token is only needed against the live API.
Published to the official MCP Registry as world.jobo/job-search (the mcpName in package.json;
server.json in this directory is the registry manifest). Publishing is automated: the mcp-v* tag
workflow publishes npm first, then pushes server.json to the registry under the DNS-TXT-verified
world.jobo/* namespace — see ../RELEASING.md. There is no review queue and aggregators poll roughly
hourly. Note the official registry has no per-server web page by design — it is a metadata API for
aggregators. The downstream surfaces differ: PulseMCP emits a dofollow link, Glama and mcp.so are
nofollow. Manual directory submissions (Claude, ChatGPT, aggregator claims) live in
../MCP-DISTRIBUTION.md.