jd-intel-mcp 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # jd-intel-mcp
2
2
 
3
- MCP server for [jd-intel](https://github.com/prPMDev/jd-intel). Lets any AI assistant (Claude Desktop, Cursor, Windsurf) search open job listings across Greenhouse, Lever, and Ashby through natural conversation.
3
+ MCP server for [jd-intel](https://github.com/prPMDev/jd-intel). Lets any AI assistant (Claude Desktop, Cursor, Windsurf) search open job listings across Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Recruitee, and Workday through natural conversation.
4
4
 
5
5
  > **Stop pasting job descriptions into AI assistants. Let your AI fetch them directly.**
6
6
 
@@ -9,7 +9,7 @@ MCP server for [jd-intel](https://github.com/prPMDev/jd-intel). Lets any AI assi
9
9
  ## What you can ask
10
10
 
11
11
  - "Is Stripe hiring PMs in the US?"
12
- - "Find remote engineering roles at fintech companies, posted in the last two weeks, then draft a cover letter for the best match."
12
+ - "Find remote engineering roles at fintech companies, posted in the last two weeks, then rank them by fit for a senior backend profile."
13
13
  - "What companies in your index are in the developer tools space?"
14
14
  - "Does Figma use Greenhouse or Lever?"
15
15
 
package/descriptions.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * the AI's behavior changes immediately when descriptions change.
9
9
  */
10
10
 
11
- export const FETCH_JOBS = `Fetch open job postings from a specific company's ATS (Greenhouse, Lever, or Ashby).
11
+ export const FETCH_JOBS = `Fetch open job postings from a specific company's ATS (Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Recruitee, Workday).
12
12
 
13
13
  USE WHEN: the user asks about roles at a known company ("Is Stripe hiring?", "What's open at Figma?").
14
14
 
@@ -32,12 +32,14 @@ location_excludes: array. Drop jobs whose location contains any keyword. Use as
32
32
 
33
33
  limit: default 100. Reduce for high-volume companies.
34
34
 
35
+ 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.
36
+
35
37
  RESPONSE: { status, data: [jobs], metadata: { attempted, succeeded, failed, notes } }. Check status first. "partial" means some adapters failed. Tell the user results may be incomplete.
36
38
 
37
39
  ERROR CODES:
38
40
  - company_not_found: slug not in registry, not detected
39
- - ats_unreachable: known ATS failed
40
- - invalid_args: missing/malformed args
41
+ - ats_unreachable: known ATS failed, or a supplied workday {tenant,env,site} was rejected by Workday
42
+ - invalid_args: missing/malformed args, including an incomplete workday triple
41
43
  - rate_limited: upstream 429`;
42
44
 
43
45
  export const SEARCH_REGISTRY = `Find companies in the indexed registry by name or sector.
@@ -60,7 +62,7 @@ RESPONSE: { status, data: [{ slug, name, sector, ats }], metadata }. Each result
60
62
  ERROR CODES:
61
63
  - invalid_args: both query and sector missing`;
62
64
 
63
- export const DETECT_ATS = `Detect which ATS platform (Greenhouse, Lever, or Ashby) a company uses.
65
+ export const DETECT_ATS = `Detect which ATS platform (Greenhouse, Lever, Ashby, SmartRecruiters, Teamtailor, Recruitee) a company uses by probing. Workday is registry-only and never returned here; find Workday-hosted companies via search_registry or fetch_jobs (which auto-detects from the registry).
64
66
 
65
67
  USE WHEN: user asks about the ATS platform explicitly ("What ATS does Stripe use?") or for debugging.
66
68
 
@@ -70,7 +72,7 @@ ARGUMENT GUIDE:
70
72
 
71
73
  company: company name or slug. Hyphens and spaces stripped automatically ("Cockroach Labs" → "cockroachlabs").
72
74
 
73
- RESPONSE: { status, data: "greenhouse" | "lever" | "ashby" | null, metadata }. data === null means no supported ATS hosts this company. "partial" status means some probes failed. Result may be incomplete.
75
+ RESPONSE: { status, data: "greenhouse" | "lever" | "ashby" | "smartrecruiters" | "teamtailor" | "recruitee" | null, metadata }. data === null means none of the probeable ATS host this company (Workday is registry-only and never detected here). "partial" status means some probes failed. Result may be incomplete.
74
76
 
75
77
  ERROR CODES:
76
78
  - invalid_args: company arg missing
@@ -80,4 +82,4 @@ export const REGISTRY_RESOURCE = `The full jd-intel company registry, grouped by
80
82
 
81
83
  Use for broad surveys ("what fintech companies are indexed?", "tell me about the catalog"). Fetched once per session, then cached. Cheaper than repeated search_registry calls for multi-query reasoning.
82
84
 
83
- Shape: { greenhouse: [{slug, name, sector}], lever: [...], ashby: [...] }.`;
85
+ Shape: { greenhouse: [{slug, name, sector}], lever: [...], ashby: [...], smartrecruiters: [...], teamtailor: [...], recruitee: [...], workday: [...] }.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jd-intel-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for jd-intel. Your AI assistant fetches and reasons over full job descriptions, no copy-paste.",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -22,11 +22,12 @@
22
22
  "start": "node server.js",
23
23
  "dev": "node --watch server.js",
24
24
  "install:local": "node cli.js install",
25
- "uninstall:local": "node cli.js uninstall"
25
+ "uninstall:local": "node cli.js uninstall",
26
+ "test": "node --test test/*.test.js"
26
27
  },
27
28
  "dependencies": {
28
29
  "@modelcontextprotocol/sdk": "^1.0.0",
29
- "jd-intel": "^0.1.0",
30
+ "jd-intel": "^0.6.0",
30
31
  "zod": "^3.23.0"
31
32
  },
32
33
  "keywords": [
package/tools.js CHANGED
@@ -21,7 +21,10 @@ import {
21
21
  DETECT_ATS,
22
22
  } from './descriptions.js';
23
23
 
24
- export function registerTools(server) {
24
+ export function registerTools(server, deps = {}) {
25
+ const _fetchJobs = deps.fetchJobs || fetchJobs;
26
+ const _findAtsBySlug = deps.findAtsBySlug || findAtsBySlug;
27
+
25
28
  server.registerTool(
26
29
  'fetch_jobs',
27
30
  {
@@ -35,12 +38,37 @@ export function registerTools(server) {
35
38
  location_includes: z.array(z.string()).optional().describe('Keep jobs whose location contains any keyword'),
36
39
  location_excludes: z.array(z.string()).optional().describe('Drop jobs whose location contains any keyword'),
37
40
  limit: z.number().int().positive().optional().describe('Cap results (default 100)'),
41
+ workday: z
42
+ .object({
43
+ tenant: z.string().trim().min(1).describe('Workday tenant, the first URL label, e.g. "expedia"'),
44
+ env: z.string().trim().min(1).describe('Workday env/datacenter, e.g. "wd108", "wd5"'),
45
+ site: z.string().trim().min(1).describe('Workday career-site path, e.g. "search", "Cisco_Careers"'),
46
+ })
47
+ .strict()
48
+ .optional()
49
+ .describe('Override the registry for a Workday board not indexed. Derive all three from the careers URL https://{tenant}.{env}.myworkdayjobs.com/{site}. Never guess these.'),
38
50
  },
39
51
  },
40
52
  async (args) => {
53
+ let ats;
54
+ let config;
55
+ if (args.workday) {
56
+ const { tenant, env, site } = args.workday;
57
+ if (!tenant?.trim() || !env?.trim() || !site?.trim()) {
58
+ return error(
59
+ ERROR_CODES.INVALID_ARGS,
60
+ 'workday requires all three of {tenant, env, site}. Read them from the careers URL https://{tenant}.{env}.myworkdayjobs.com/{site}.'
61
+ );
62
+ }
63
+ ats = 'workday';
64
+ config = { tenant, env, site };
65
+ }
66
+
41
67
  try {
42
- const jobs = await fetchJobs({
68
+ const jobs = await _fetchJobs({
43
69
  company: args.company,
70
+ ats,
71
+ config,
44
72
  titleFilter: args.title_filter,
45
73
  filter: args.filter,
46
74
  postedWithinDays: args.posted_within_days,
@@ -50,15 +78,23 @@ export function registerTools(server) {
50
78
  });
51
79
 
52
80
  const normalizedSlug = args.company.toLowerCase().replace(/[^a-z0-9]/g, '');
53
- const registryAts = await findAtsBySlug(normalizedSlug);
81
+ const registryAts = await _findAtsBySlug(normalizedSlug);
54
82
 
55
83
  return success(jobs, {
56
84
  count: jobs.length,
57
85
  registry_hit: registryAts !== null,
58
- ats: registryAts,
86
+ ats: config ? 'workday' : registryAts,
87
+ workday_override: Boolean(config),
59
88
  });
60
89
  } catch (err) {
61
- return error(ERROR_CODES.INVALID_ARGS, err.message || 'Unknown error');
90
+ const msg = err.message || 'Unknown error';
91
+ if (config && /Workday API error/.test(msg)) {
92
+ return error(
93
+ ERROR_CODES.ATS_UNREACHABLE,
94
+ `Workday rejected ${config.tenant}/${config.env}/${config.site}: ${msg}. Verify the triple against the careers URL https://{tenant}.{env}.myworkdayjobs.com/{site}.`
95
+ );
96
+ }
97
+ return error(ERROR_CODES.INVALID_ARGS, msg);
62
98
  }
63
99
  }
64
100
  );