# jd-intel-mcp [Health: Active]

**Category:** 🔎 Search & Data Extraction  
**Repository:** https://github.com/prPMDev/jd-intel  
**GitHub Stars:** 1  
**npm Downloads (last month):** 670  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/jd-intel-mcp

## Description
Your AI fetches and reads full job descriptions across seven ATS platforms, no copy-paste.

## Tools
Capabilities this server exposes over MCP:

- **fetch_jobs** — Fetch open job postings from one company's ATS board (Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Recruitee, Personio, Workable, Workday).

USE WHEN: the user asks about roles at a known company ("Is Stripe hiring?", "What's open at Figma?").

DON'T USE WHEN:
- User doesn't know the company → read the registry Resource or call search_registry
- User only asks which ATS a company uses → call detect_ats

ARGUMENT GUIDE:

company: lowercase slug, no spaces (e.g. "stripe", "cockroachlabs"). Hyphens and spaces auto-stripped. A registry slug costs one board fetch; any other slug is probed on every probeable ATS.

title_filter: JavaScript-compatible regex matched against TITLE ONLY. Case-insensitive by default. Do NOT use inline flags like (?i) (not supported by V8). Use for role identity ("product manager", "staff engineer"). Does NOT match description text. That's the distinction from filter.

filter: JavaScript-compatible regex matched across title + department + description. Case-insensitive by default. Do NOT use inline flags like (?i). Use for topic/scope ("integrations", "growth"). AND'd with title_filter.

posted_within_days: number. "recent" or "new" → 30. "this week" → 7. "today" → 1.

location_includes: array of keywords. Case-insensitive substring match; short codes (US, UK) use word-boundary matching automatically. Checked against every entry of a job's locations array: a job open in Berlin and New York matches ["New York"]. For US queries prefer ["United States", "US", "Remote - US"]. Avoid bare "Remote". It matches Remote-EMEA, Remote-LatAm.

location_excludes: array. Drops a job only when every one of its locations matches a keyword: the Berlin and New York job survives ["US"] because it is still open in Berlin, and drops under ["Germany", "US"]. Use as refinement on top of includes.

limit: max jobs per page, default 100. max_tokens usually stops output first, so on a large board a default call returns a few complete postings. total_matched and truncated say what was left out.

max_tokens: response budget, default 12000, any integer from 1, counted as characters/4 of the text block. Whole postings only: output stops before the job that would pass the budget, never mid-description, and at least one job is always returned even when it alone exceeds the budget. A larger value returns more complete postings per call; a smaller one is a quick scan. The payload is sent twice (text and structuredContent), so the wire carries about double.

offset: matches to skip, default 0. next_offset from the previous response is the next page. Every page fetches the whole board again.

order: "newest" (default) sorts by postedAt, undated last, ties by id. "board" keeps the ATS's own order. Sorting happens before limit, offset and the budget, so a cut drops the oldest matches first. On a capped board (counts_exact false) the sort covers only the rows read so far, so "newest" pages can repeat or skip rows; "board" pages a capped board in a stable order.

workday: optional { tenant, env, site }. Use ONLY for a Workday company not in the registry when the user gives a careers URL. Derive from https://{tenant}.{env}.myworkdayjobs.com/{site}: tenant is the first label, env is the part like wd108, site is the path segment. Overrides the registry for that fetch. Never guess or fabricate these values; use only what the user supplied or what is literally in the careers URL. Omit this argument entirely if you do not have a real URL.

An argument that fails the input schema (wrong type, out of range, unknown key, a blank workday field) returns isError with plain text naming the field and no envelope; fix that argument and call again.

RESPONSE: { status, data: [jobs] | null, metadata: { count, registry_hit, ats, workday_override, version, registry_source, total_matched, total_before_filters, content_missing, match, company, boards, failed, counts_exact, truncated, est_tokens, offset, next_offset, order } }.

count: jobs in data. total_matched: jobs that passed the filters, before offset, limit and the budget. total_before_filters: rows the boards listed before any filter; count 0 with total_before_filters above 0 is a board with openings where none passed the filters.

match: how the company was resolved. "registry": the slug is a registry row and that board was fetched. "probe": the slug is not in the registry and every probeable ATS was asked; a board that answered belongs to whoever owns that slug on that ATS, and the registry does not vouch that it is the company asked about. "workday_override": the workday argument named the board. registry_hit is match === "registry". company: { key, name } from the registry row on a registry match, else null.

boards: one entry per board that answered, [{ ats, slug, name, site, board_url, org_name, org_url, jobs_found, matched, selected, scan }]. name: the registry row's name, null on a probe or an override. site: the Workday career site, else null. org_name, org_url: the organization name and careers host the board itself states, null where its platform exposes none (Lever and Ashby expose neither; org_url is null when the only host is the ATS's own). jobs_found: the board's list before filters. matched: its rows after filters. selected: true. scan: { listed, prefiltered, hydrated, capped } on Workday and SmartRecruiters, else null. ats: the one ATS every board shares, null when they differ. failed: adapters a probe could not check, [{ ats, slug, name, code, message }]; a failed adapter is neither a match nor a miss. On an error, metadata.failed is present only when a probe found no board; a registry or override board's own failure carries no metadata.

counts_exact: false when a board's scan.capped is true. Workday and SmartRecruiters read at most 100 postings per call, so total_matched and total_before_filters are then floors. truncated: null, or { reason: "limit" | "size" | "scan_cap", not_returned }: "limit" when more matched than the page holds, "size" when max_tokens stopped output, "scan_cap" when nothing cut the page but a board's scan was capped. not_returned is the count left after this page, null when counts_exact is false. next_offset: offset for the next page, null when nothing is left. With counts_exact false it is also set whenever the page filled, since a capped board holds rows the scan did not read; the page it names comes back with count 0 and truncated scan_cap once the cap is reached. est_tokens: characters/4 of the text block. order: the order in effect. workday_override: the workday argument was used. version: server version. registry_source: "network", "disk-fallback" (the bundled copy) or "mixed".

Each job carries location (the primary), locations (every place it is open in, primary first) and workplace { type, source }. type is remote, hybrid, onsite or unknown; source is ats when the platform stated it, text when read from the location string, null when unknown. locationType repeats workplace.type.

Each job also carries content { status, reason }. status "complete": the posting was read. status "missing": Workday or SmartRecruiters listed the job but its detail request failed (reason "http_503", "http_429", "network_error"), so description is empty and salary is null because they are unknown, not because the posting has none; on Workday location, workplace and postedAt then come from the list row, and on SmartRecruiters url is empty. Tell the user the posting could not be read and give the link or the board. content_missing counts these jobs before the filters: a filter cannot match a description that never arrived, so such a job may be missing from data, and count 0 with content_missing above 0 is not "no matching roles". Calling again usually reads them.

AGE: results are newest first unless order says otherwise. A posting older than about 90 days is usually dead or evergreen. fetch_jobs still returns them; posted_within_days: 90 leaves them out.

STATUSES:
- success: every board asked answered (failed is empty) and every posting was read (content_missing is 0). data may be []. A registry board or a workday override that the ATS answers with 404 (no board at that slug, a site Workday does not know) is a board with total_before_filters 0, not an error.
- partial: at least one board answered and something was not checked: an adapter is in failed, or content_missing is above 0. data holds what the answering boards returned.
- error: no board answered. data is null and error.code says why.

ERROR CODES:
- company_not_found: the slug is not in the registry, no probeable ATS listed a posting under it, and every check completed. An unregistered board with zero postings reads the same way.
- ats_unreachable: the board's ATS failed (5xx, a 4xx other than 404 or 429, a network error, a timeout), a supplied workday triple failed the same way (a 404 is a board with zero rows, see success), or a probe found no board and a check failed with no 429. On a probe, metadata.failed lists the failed checks.
- rate_limited: the board's ATS returned 429, or a probe found no board and a check failed with a 429. On a probe, metadata.failed lists the failed checks.
- invalid_args: a filter regex that does not compile, or an empty company.
- internal_error: an exception inside the server, not an argument problem.
- **search_registry** — Find companies in the indexed registry by name or sector.

USE WHEN: a company name or a sector word is in hand ("Is Stripe in your index?", "Show me fintech companies").

DON'T USE WHEN:
- User wants the whole catalog, across every sector → read the registry://jd-intel/all Resource instead (one large read)
- User asks about a specific company's jobs → call fetch_jobs directly

ARGUMENT GUIDE:

query: optional. Case-insensitive substring match against company name or sector.
sector: optional. Case-insensitive substring match against sector only ("fintech", "developer tools").
limit: optional. Max rows returned, default 50, max 200.

At least one is required. Passing both narrows: a row must match query (on name or sector) and sector (AND). An argument that fails the input schema (wrong type, unknown key) returns isError with plain text naming the field and no envelope; fix that argument and call again.

RESPONSE: { status, data: [{ slug, name, sector, ats }] | null, metadata: { count, total, truncated, query, sector, version, registry_source } }. Each row is one board; ats is the platform it is on. Rows are ranked: an exact name or slug, then names starting with the query, then names containing it, then sector-only matches. count is rows in data. total is every row that matched. truncated: null, or { reason: "limit", not_returned } when more matched than limit; narrow the term or combine query with sector before raising limit. query and sector echo the arguments, null when not passed. registry_source is "network", "disk-fallback" (the bundled copy) or "mixed".

STATUSES:
- success: the registry was searched. data may be [].
- error: data is null and error.code says why.

ERROR CODES:
- invalid_args: both query and sector missing.
- internal_error: an exception inside the server, not an argument problem.
- **detect_ats** — List the ATS platforms a company answers on: registry rows first (all nine platforms, Workday included, no request made), then a live probe of every probeable ATS (Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Recruitee, Personio, Workable) the registry did not list. Workday cannot be probed, so a Workday board appears only from the registry.

USE WHEN: user asks about the ATS platform explicitly ("What ATS does Stripe use?") or for debugging.

DON'T USE WHEN: you want to fetch jobs. Call fetch_jobs directly (it auto-detects internally).

ARGUMENT GUIDE:

company: company name or slug. Hyphens and spaces stripped automatically ("Cockroach Labs" → "cockroachlabs"). A missing or non-string company or an unknown key fails the input schema and returns isError with plain text naming the field and no envelope; fix that argument and call again.

RESPONSE: { status, data: "greenhouse" | "lever" | "ashby" | "smartrecruiters" | "teamtailor" | "recruitee" | "personio" | "workable" | "workday" | null, metadata: { attempted, succeeded, boards, failed, notes } }.

boards: every board known for this slug, [{ ats, slug, source }], in platform order. source "registry": a registry row, not probed. source "probe": the ATS answered to this slug live; the registry does not vouch that it is the company asked about. A listed board exists and may hold zero postings. data: the ats of the first board, null when boards is empty. succeeded: the ats of each board. attempted: the ATS probed live (every probeable ATS the registry did not list). failed: probes that could not be checked, [{ ats, slug, code, message }]; a failed probe is neither a match nor a miss. notes: present only when boards spans several platforms, stating that data is the first in platform order, not a ranking, and that every board is in metadata.boards.

STATUSES:
- success: every probe completed (failed is empty). data null with boards empty means no registry row and no probeable ATS answered.
- partial: at least one board is known and at least one probe is in failed.
- error: no board is known and at least one probe failed. data is null.

ERROR CODES:
- rate_limited: no board is known and a failed probe was a 429. metadata.failed lists them.
- ats_unreachable: no board is known and every failed probe was a 5xx, another 4xx, a network error or a timeout. metadata.failed lists them.
- internal_error: an exception inside the server, not an argument problem.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "jd-intel-mcp": {
    "command": "npx",
    "args": ["-y","jd-intel-mcp"]
  }
}
```

## Documentation

## What jd-intel-mcp does

The jd-intel-mcp MCP server connects an MCP-capable AI client to public job-board APIs used by supported applicant tracking systems. It helps an assistant find companies, determine which ATS hosts a company’s careers page, and retrieve open roles with their full job-description content and structured fields.

The server is intended for job research, resume tailoring, role comparison, and hiring-market scans. Instead of pasting descriptions into a chat, an assistant can request listings for a company or search across companies in a sector. Filters can narrow results by role type, topic, location, and posting age.

## How it works

The server runs locally and communicates with the client over MCP. Its requests use public APIs rather than browser automation or authenticated access. Results are normalized across platforms so an assistant can work with a consistent job shape instead of parsing unrelated HTML layouts.

Company resolution can use a registry of known organizations or probe supported ATS platforms when a company is not in that registry. A detected board includes its ATS and slug. When a company is found through probing rather than the registry, the returned organization details should be checked because a slug match does not confirm the company’s identity.

Job results are ordered newest first by posting date, with undated roles last by default. Calls can return pagination and matching information, including the number of jobs found before filters and the boards that responded. Rate limits and unreachable ATS platforms are reported separately from an empty result.

## Setup and configuration

For Claude Desktop, the repository provides an installable `.mcpb` extension. Claude Code, Cursor, Windsurf, and VS Code can run the server with `npx -y jd-intel-mcp` using their MCP configuration interfaces.

The server has no documented API-key or environment-variable configuration. It runs on the local machine, so it is intended for desktop MCP clients rather than mobile applications. After installation, start a new client conversation and query the company registry or request a job search to verify that the tools are available.

## Tools and capabilities

The jd-intel-mcp MCP server exposes three tools:

- `fetch_jobs` retrieves open roles for a company. Supported filters include role type, topic, location, and recency.
- `search_registry` finds companies by name or sector.
- `detect_ats` identifies the ATS platform associated with a company.

The underlying package also documents detailed result data, including board status, selected matches, failed adapter checks, organization URLs where supplied by the ATS, and pagination totals. Invalid company arguments, unknown ATS values, and invalid regular-expression filters produce an argument error before a request is sent.

## Limitations and notes

Coverage depends on the supported public ATS endpoints and whether a board responds. A failed adapter is not treated as proof that a company has no jobs. Workday and SmartRecruiters may report the number of listed rows before fetching job details, so that count can be a lower bound when a scan is capped.

The README names Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Recruitee, Personio, Workable, and Workday as supported platforms. The project does not provide access to private or authenticated job data.

_Full upstream README: https://allmcps.com/mcp/jd-intel-mcp/readme_

