jd-intel-mcp 0.6.0 → 0.8.1
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 +25 -10
- package/errors.js +4 -11
- package/package.json +5 -3
- package/server.js +14 -2
- package/tools.js +34 -9
- package/version.js +12 -0
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, Ashby, SmartRecruiters, Teamtailor, Recruitee, and Workday through natural conversation.
|
|
3
|
+
MCP server for [jd-intel](https://github.com/prPMDev/jd-intel). Lets any AI assistant (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code) 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
|
|
|
@@ -17,9 +17,31 @@ The AI handles the phrasing. The MCP server handles the calls, filters, and norm
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
## Install
|
|
20
|
+
## Install
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
### Claude Desktop (one-file install, no terminal)
|
|
23
|
+
|
|
24
|
+
Download [jd-intel.mcpb](https://github.com/prPMDev/jd-intel/releases/latest/download/jd-intel.mcpb), then in Claude Desktop open **Settings**, then **Extensions**, then **Advanced settings**, and click **Install Extension**. Pick the file, review the access summary, click **Install**, and start a new chat. No Node.js needed (Claude Desktop runs it on its own bundled runtime). It's open source and unsigned, so choose **Install Anyway** if prompted.
|
|
25
|
+
|
|
26
|
+
Prefer the terminal? Install [Node.js 18+](https://nodejs.org/), then run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx jd-intel-mcp install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This locates the Claude Desktop config, adds the entry alongside any existing servers, and writes back valid JSON. Quit and reopen Claude Desktop.
|
|
33
|
+
|
|
34
|
+
### Other clients (Claude Code, Cursor, Windsurf, VS Code)
|
|
35
|
+
|
|
36
|
+
The same server runs via `npx` (needs Node.js 18+):
|
|
37
|
+
|
|
38
|
+
- **Claude Code:** `claude mcp add jd-intel -- npx -y jd-intel-mcp`
|
|
39
|
+
- **Cursor / Windsurf:** add under `mcpServers` (`command: "npx"`, `args: ["-y", "jd-intel-mcp"]`) in the client's MCP config.
|
|
40
|
+
- **VS Code (Copilot agent):** add under `servers` with `"type": "stdio"` in `.vscode/mcp.json`.
|
|
41
|
+
|
|
42
|
+
### Manual config (fallback)
|
|
43
|
+
|
|
44
|
+
Edit Claude Desktop's config file directly:
|
|
23
45
|
|
|
24
46
|
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
25
47
|
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
@@ -37,13 +59,6 @@ Add this to your Claude Desktop config file:
|
|
|
37
59
|
|
|
38
60
|
Restart Claude Desktop. The tools appear automatically.
|
|
39
61
|
|
|
40
|
-
**One-command install (avoids hand-editing the config):**
|
|
41
|
-
```bash
|
|
42
|
-
npx jd-intel-mcp install
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
This detects your OS, locates the Claude Desktop config, adds the entry alongside any existing servers, and writes back valid JSON. Prevents the "paste-a-snippet-into-existing-config" hand-editing error.
|
|
46
|
-
|
|
47
62
|
---
|
|
48
63
|
|
|
49
64
|
## Tools exposed
|
package/errors.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error code taxonomy for all MCP tools.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The canonical codes now live in the jd-intel library (src/errors.js), so the
|
|
5
|
+
* library (which throws AtsError with a .code) and the MCP layer share one
|
|
6
|
+
* source of truth. Re-exported here to keep the existing import path stable.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
COMPANY_NOT_FOUND: 'company_not_found', // Slug not in registry and not detected
|
|
11
|
-
ATS_UNREACHABLE: 'ats_unreachable', // Known ATS failed (500, timeout)
|
|
12
|
-
PARTIAL_FAILURE: 'partial_failure', // Discovery mode; some adapters failed
|
|
13
|
-
INVALID_ARGS: 'invalid_args', // Missing required, wrong type, bad pattern
|
|
14
|
-
NO_RESULTS: 'no_results', // Query succeeded, filters returned nothing
|
|
15
|
-
RATE_LIMITED: 'rate_limited', // Upstream returned 429
|
|
16
|
-
};
|
|
9
|
+
export { ERROR_CODES } from 'jd-intel';
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jd-intel-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.8.1",
|
|
4
|
+
"mcpName": "io.github.prPMDev/jd-intel-mcp",
|
|
5
|
+
"description": "MCP server for jd-intel. Your AI assistant fetches and reads full job descriptions across seven ATS platforms, no copy-paste.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "server.js",
|
|
7
8
|
"bin": {
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
"errors.js",
|
|
17
18
|
"descriptions.js",
|
|
18
19
|
"install.js",
|
|
20
|
+
"version.js",
|
|
19
21
|
"README.md"
|
|
20
22
|
],
|
|
21
23
|
"scripts": {
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
31
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
|
-
"jd-intel": "^0.
|
|
32
|
+
"jd-intel": "^0.8.0",
|
|
31
33
|
"zod": "^3.23.0"
|
|
32
34
|
},
|
|
33
35
|
"keywords": [
|
package/server.js
CHANGED
|
@@ -23,11 +23,23 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
23
23
|
|
|
24
24
|
import { registerTools } from './tools.js';
|
|
25
25
|
import { registerResources } from './resources.js';
|
|
26
|
+
import { VERSION } from './version.js';
|
|
26
27
|
|
|
27
28
|
async function main() {
|
|
29
|
+
// Claude Desktop runs this on its OWN bundled Node, not the user's system
|
|
30
|
+
// Node. Log which version that is (read it in Claude Desktop's MCP logs),
|
|
31
|
+
// and fail loudly with a human message instead of a cryptic SDK crash if
|
|
32
|
+
// the runtime is too old.
|
|
33
|
+
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
34
|
+
console.error(`[jd-intel] runtime check: Node ${process.version} on ${process.platform}/${process.arch}`);
|
|
35
|
+
if (nodeMajor < 18) {
|
|
36
|
+
console.error(`[jd-intel] Needs Node 18 or newer, but this runtime is ${process.version}. If you installed via Claude Desktop, update Claude Desktop to the latest version and try again.`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
const server = new McpServer({
|
|
29
41
|
name: 'jd-intel',
|
|
30
|
-
version:
|
|
42
|
+
version: VERSION,
|
|
31
43
|
});
|
|
32
44
|
|
|
33
45
|
registerTools(server);
|
|
@@ -37,7 +49,7 @@ async function main() {
|
|
|
37
49
|
await server.connect(transport);
|
|
38
50
|
|
|
39
51
|
// Log to stderr so stdout stays clean for MCP protocol traffic
|
|
40
|
-
console.error(
|
|
52
|
+
console.error(`jd-intel MCP server ${VERSION} running on stdio`);
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
main().catch((err) => {
|
package/tools.js
CHANGED
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
-
import { fetchJobs, detectAts as libDetectAts, registry } from 'jd-intel';
|
|
13
|
+
import { fetchJobs, detectAts as libDetectAts, registry, ATS_NAMES, AtsError } from 'jd-intel';
|
|
14
14
|
|
|
15
15
|
const { search: searchRegistry, findAtsBySlug } = registry;
|
|
16
|
+
// Tolerate an older jd-intel that predates getSource. The bundle always
|
|
17
|
+
// vendors a matching version; this only guards a skewed local/global install.
|
|
18
|
+
const getRegistrySource = registry.getSource || (() => 'unknown');
|
|
16
19
|
import { success, partial, error } from './envelope.js';
|
|
17
20
|
import { ERROR_CODES } from './errors.js';
|
|
21
|
+
import { VERSION } from './version.js';
|
|
18
22
|
import {
|
|
19
23
|
FETCH_JOBS,
|
|
20
24
|
SEARCH_REGISTRY,
|
|
@@ -80,20 +84,39 @@ export function registerTools(server, deps = {}) {
|
|
|
80
84
|
const normalizedSlug = args.company.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
81
85
|
const registryAts = await _findAtsBySlug(normalizedSlug);
|
|
82
86
|
|
|
87
|
+
// Discovery miss: not in the registry and no board returned anything.
|
|
88
|
+
// Guard on !config so a valid Workday override that returns 0 jobs is not
|
|
89
|
+
// mislabeled; a registry hit with 0 open roles stays a success([]).
|
|
90
|
+
if (!config && registryAts === null && jobs.length === 0) {
|
|
91
|
+
return error(
|
|
92
|
+
ERROR_CODES.COMPANY_NOT_FOUND,
|
|
93
|
+
`No board found for "${args.company}" on any supported ATS. Check the slug, or pass an explicit workday {tenant,env,site} for a Workday board.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
return success(jobs, {
|
|
84
98
|
count: jobs.length,
|
|
85
99
|
registry_hit: registryAts !== null,
|
|
86
100
|
ats: config ? 'workday' : registryAts,
|
|
87
101
|
workday_override: Boolean(config),
|
|
102
|
+
version: VERSION,
|
|
103
|
+
registry_source: getRegistrySource(),
|
|
88
104
|
});
|
|
89
105
|
} catch (err) {
|
|
90
106
|
const msg = err.message || 'Unknown error';
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
// AtsError carries a stable .code from the adapter (ats_unreachable /
|
|
108
|
+
// rate_limited), so we map by code, not by parsing the message.
|
|
109
|
+
if (err instanceof AtsError) {
|
|
110
|
+
if (config && err.code === ERROR_CODES.ATS_UNREACHABLE) {
|
|
111
|
+
// Keep the Workday triple-repair hint.
|
|
112
|
+
return error(
|
|
113
|
+
ERROR_CODES.ATS_UNREACHABLE,
|
|
114
|
+
`Workday rejected ${config.tenant}/${config.env}/${config.site}: ${msg}. Verify the triple against the careers URL https://{tenant}.{env}.myworkdayjobs.com/{site}.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return error(err.code, msg);
|
|
96
118
|
}
|
|
119
|
+
// Anything else is an arg-validation error from the library.
|
|
97
120
|
return error(ERROR_CODES.INVALID_ARGS, msg);
|
|
98
121
|
}
|
|
99
122
|
}
|
|
@@ -128,6 +151,8 @@ export function registerTools(server, deps = {}) {
|
|
|
128
151
|
count: filtered.length,
|
|
129
152
|
query: args.query || null,
|
|
130
153
|
sector: args.sector || null,
|
|
154
|
+
version: VERSION,
|
|
155
|
+
registry_source: getRegistrySource(),
|
|
131
156
|
});
|
|
132
157
|
}
|
|
133
158
|
);
|
|
@@ -145,12 +170,12 @@ export function registerTools(server, deps = {}) {
|
|
|
145
170
|
const results = await libDetectAts(args.company);
|
|
146
171
|
|
|
147
172
|
if (results.length === 0) {
|
|
148
|
-
return success(null, { attempted:
|
|
173
|
+
return success(null, { attempted: ATS_NAMES, succeeded: [] });
|
|
149
174
|
}
|
|
150
175
|
|
|
151
176
|
if (results.length === 1) {
|
|
152
177
|
return success(results[0].ats, {
|
|
153
|
-
attempted:
|
|
178
|
+
attempted: ATS_NAMES,
|
|
154
179
|
succeeded: [results[0].ats],
|
|
155
180
|
});
|
|
156
181
|
}
|
|
@@ -159,7 +184,7 @@ export function registerTools(server, deps = {}) {
|
|
|
159
184
|
return partial(
|
|
160
185
|
results[0].ats,
|
|
161
186
|
{
|
|
162
|
-
attempted:
|
|
187
|
+
attempted: ATS_NAMES,
|
|
163
188
|
succeeded: results.map((r) => r.ats),
|
|
164
189
|
notes: [`Company found on multiple platforms: ${results.map((r) => r.ats).join(', ')}. Returning first match.`],
|
|
165
190
|
}
|
package/version.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of the server version.
|
|
3
|
+
*
|
|
4
|
+
* Read from package.json via createRequire — works on every Node 18+ with no
|
|
5
|
+
* import-attribute version concerns (Claude Desktop's bundled Node version is
|
|
6
|
+
* not known ahead of time). server.js reports this to the MCP host; tools.js
|
|
7
|
+
* surfaces it in response metadata so the AI can tell the user what's running.
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
export const VERSION = require('./package.json').version;
|