hirelayer-mcp 1.0.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/LICENSE +21 -0
- package/README.md +354 -0
- package/dist/client.js +139 -0
- package/dist/index.js +15 -0
- package/dist/server.js +166 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Louis Desclous
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<a href="https://hirelayer.co"><img src="assets/logo.svg" alt="HireLayer logo" width="72" height="72"></a>
|
|
4
|
+
|
|
5
|
+
# HireLayer MCP Server
|
|
6
|
+
|
|
7
|
+
**Resume parsing, candidate matching and candidate ranking for AI agents.**
|
|
8
|
+
|
|
9
|
+
The official [Model Context Protocol](https://modelcontextprotocol.io) server for [HireLayer](https://hirelayer.co). Parse resumes and CVs, turn job descriptions into criteria, then score and rank candidates from Claude, Cursor, VS Code, Codex or any MCP client.
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/hirelayer-mcp)
|
|
12
|
+
[](https://github.com/hirelayer/hirelayer-mcp/actions/workflows/ci.yml)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[](https://modelcontextprotocol.io)
|
|
15
|
+
|
|
16
|
+
[](https://cursor.com/install-mcp?name=hirelayer&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImhpcmVsYXllci1tY3AiXSwiZW52Ijp7IkhJUkVMQVlFUl9BUElfS0VZIjoieW91ci1hcGkta2V5In19)
|
|
17
|
+
[](https://vscode.dev/redirect/mcp/install?name=hirelayer&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22hirelayer_api_key%22%2C%22description%22%3A%22HireLayer%20API%20key%20%28free%20at%20hirelayer.co%29%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22hirelayer-mcp%22%5D%2C%22env%22%3A%7B%22HIRELAYER_API_KEY%22%3A%22%24%7Binput%3Ahirelayer_api_key%7D%22%7D%7D)
|
|
18
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=hirelayer&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22hirelayer_api_key%22%2C%22description%22%3A%22HireLayer%20API%20key%20%28free%20at%20hirelayer.co%29%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22hirelayer-mcp%22%5D%2C%22env%22%3A%7B%22HIRELAYER_API_KEY%22%3A%22%24%7Binput%3Ahirelayer_api_key%7D%22%7D%7D&quality=insiders)
|
|
19
|
+
[](https://lmstudio.ai/install-mcp?name=hirelayer&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImhpcmVsYXllci1tY3AiXSwiZW52Ijp7IkhJUkVMQVlFUl9BUElfS0VZIjoieW91ci1hcGkta2V5In19)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
> "Screen these 3 resumes against the Senior React job and tell me who to interview."
|
|
24
|
+
>
|
|
25
|
+
> Your assistant parses each CV, extracts the job criteria, scores every candidate criterion by criterion and explains the shortlist.
|
|
26
|
+
|
|
27
|
+
## Contents
|
|
28
|
+
|
|
29
|
+
- [What you can do](#what-you-can-do)
|
|
30
|
+
- [Quick start](#quick-start)
|
|
31
|
+
- [Install in your MCP client](#install-in-your-mcp-client)
|
|
32
|
+
- [Tools](#tools)
|
|
33
|
+
- [Example prompts](#example-prompts)
|
|
34
|
+
- [Pricing and credits](#pricing-and-credits)
|
|
35
|
+
- [Data and privacy](#data-and-privacy)
|
|
36
|
+
- [Troubleshooting](#troubleshooting)
|
|
37
|
+
- [FAQ](#faq)
|
|
38
|
+
|
|
39
|
+
## What you can do
|
|
40
|
+
|
|
41
|
+
- **Parse resumes and CVs.** Turn PDF, Word, image and other files into structured JSON: contact details, work experience, education, languages, skills and the full text. Scanned resumes go through OCR, and the resume language is detected.
|
|
42
|
+
- **Turn a job description into criteria.** Get weighted, explained matching criteria, with mandatory requirements flagged.
|
|
43
|
+
- **Match candidates to jobs.** Score a resume against a job from 0 to 1, with an explanation for each criterion.
|
|
44
|
+
- **Rank candidates.** Order up to 10 candidates for the same job in one call, with a score and a rationale for each.
|
|
45
|
+
- **Normalize skills.** Map free-text skills in French or English to a skills taxonomy with stable IDs.
|
|
46
|
+
|
|
47
|
+
Use it to screen applicants in a chat, build a recruiting agent, enrich an ATS, or prototype HR tech features without writing integration code.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
1. **Get a free API key.** [Sign up at hirelayer.co](https://hirelayer.co/auth/signup), with no card required, and copy your key from **Dashboard → API keys**. The free plan includes 50 credits a month.
|
|
52
|
+
2. **Add the server to your client.** Click a one-click install button above, or copy a config from [the next section](#install-in-your-mcp-client).
|
|
53
|
+
3. **Ask your assistant.** For example: *"Parse ~/Downloads/resume.pdf and summarize the candidate."*
|
|
54
|
+
|
|
55
|
+
To try it without your own data, use the sample job and resumes in [`examples/`](examples).
|
|
56
|
+
|
|
57
|
+
Requires Node.js 20 or later, because the server runs with `npx`.
|
|
58
|
+
|
|
59
|
+
## Install in your MCP client
|
|
60
|
+
|
|
61
|
+
Replace `your-api-key` with your HireLayer API key in each config below.
|
|
62
|
+
|
|
63
|
+
<details open>
|
|
64
|
+
<summary><b>Claude Code</b></summary>
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
claude mcp add hirelayer --env HIRELAYER_API_KEY=your-api-key -- npx -y hirelayer-mcp
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary><b>Claude Desktop</b></summary>
|
|
74
|
+
|
|
75
|
+
Open **Settings → Developer → Edit Config** and add the server to `claude_desktop_config.json`:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"hirelayer": {
|
|
81
|
+
"command": "npx",
|
|
82
|
+
"args": ["-y", "hirelayer-mcp"],
|
|
83
|
+
"env": { "HIRELAYER_API_KEY": "your-api-key" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Restart Claude Desktop.
|
|
90
|
+
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
<details>
|
|
94
|
+
<summary><b>Cursor</b></summary>
|
|
95
|
+
|
|
96
|
+
Click **Install in Cursor** above, or add this to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project):
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"hirelayer": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["-y", "hirelayer-mcp"],
|
|
104
|
+
"env": { "HIRELAYER_API_KEY": "your-api-key" }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
</details>
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary><b>VS Code (GitHub Copilot)</b></summary>
|
|
114
|
+
|
|
115
|
+
Click **Install in VS Code** above. VS Code asks for your API key and stores it securely. To configure it by hand, add this to `.vscode/mcp.json`:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"inputs": [
|
|
120
|
+
{ "type": "promptString", "id": "hirelayer_api_key", "description": "HireLayer API key", "password": true }
|
|
121
|
+
],
|
|
122
|
+
"servers": {
|
|
123
|
+
"hirelayer": {
|
|
124
|
+
"type": "stdio",
|
|
125
|
+
"command": "npx",
|
|
126
|
+
"args": ["-y", "hirelayer-mcp"],
|
|
127
|
+
"env": { "HIRELAYER_API_KEY": "${input:hirelayer_api_key}" }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
<details>
|
|
136
|
+
<summary><b>Windsurf</b></summary>
|
|
137
|
+
|
|
138
|
+
Add this to `~/.codeium/windsurf/mcp_config.json`:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"mcpServers": {
|
|
143
|
+
"hirelayer": {
|
|
144
|
+
"command": "npx",
|
|
145
|
+
"args": ["-y", "hirelayer-mcp"],
|
|
146
|
+
"env": { "HIRELAYER_API_KEY": "your-api-key" }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
</details>
|
|
153
|
+
|
|
154
|
+
<details>
|
|
155
|
+
<summary><b>OpenAI Codex CLI</b></summary>
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
codex mcp add hirelayer --env HIRELAYER_API_KEY=your-api-key -- npx -y hirelayer-mcp
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
</details>
|
|
162
|
+
|
|
163
|
+
<details>
|
|
164
|
+
<summary><b>Gemini CLI</b></summary>
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
gemini mcp add -e HIRELAYER_API_KEY=your-api-key hirelayer npx -y hirelayer-mcp
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
</details>
|
|
171
|
+
|
|
172
|
+
<details>
|
|
173
|
+
<summary><b>Cline, Roo Code, Zed, LM Studio and other clients</b></summary>
|
|
174
|
+
|
|
175
|
+
Any client that runs stdio MCP servers works with this command and environment variable:
|
|
176
|
+
|
|
177
|
+
- Command: `npx -y hirelayer-mcp`
|
|
178
|
+
- Environment: `HIRELAYER_API_KEY=your-api-key`
|
|
179
|
+
|
|
180
|
+
Cline users can also ask Cline to install the server: [`llms-install.md`](llms-install.md) has the steps.
|
|
181
|
+
|
|
182
|
+
</details>
|
|
183
|
+
|
|
184
|
+
<details>
|
|
185
|
+
<summary><b>Docker</b></summary>
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
docker build -t hirelayer-mcp .
|
|
189
|
+
docker run -i --rm -e HIRELAYER_API_KEY=your-api-key hirelayer-mcp
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
In Docker, `parse_resume` only reads files that you mount into the container. Otherwise, pass `file_url`.
|
|
193
|
+
|
|
194
|
+
</details>
|
|
195
|
+
|
|
196
|
+
## Tools
|
|
197
|
+
|
|
198
|
+
| Tool | What it does | Typical input |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `parse_resume` | Parses a resume or CV file into structured JSON: contact details, experience, education, languages, skills and the full text | A local `file_path` or a public `file_url`. Accepts PDF, DOC, DOCX, ODT, RTF, TXT, PPT, PPTX, ODP, XLS, JPG, PNG or BMP files under 4.5 MB. |
|
|
201
|
+
| `extract_job_criteria` | Turns a job description into weighted criteria: a weight from 1 to 3, a mandatory flag and a rationale for each | Job description text |
|
|
202
|
+
| `match_candidate` | Scores one candidate against a job from 0 to 1, with a summary and a status and explanation for each criterion | Job text, resume text and criteria |
|
|
203
|
+
| `rank_candidates` | Ranks up to 10 candidates for one job, with a score and a rationale for each | Job text and up to 10 resume texts |
|
|
204
|
+
| `resolve_skills` | Maps free-text skills in French or English to taxonomy skills, with their families and domains | Free text, from one skill to a whole skills section |
|
|
205
|
+
|
|
206
|
+
All tools only read and analyse data. They never change anything in your systems.
|
|
207
|
+
|
|
208
|
+
The server also ships **prompts** that clients show as ready-made commands:
|
|
209
|
+
|
|
210
|
+
| Prompt | What it does |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `screen_candidates` | Runs the full screening workflow (criteria, parsing, matching) and writes a shortlist |
|
|
213
|
+
| `summarize_resume` | Parses one resume and writes a recruiter summary |
|
|
214
|
+
| `normalize_skills` | Normalizes a skills section and groups it by domain |
|
|
215
|
+
|
|
216
|
+
### How screening works
|
|
217
|
+
|
|
218
|
+
```mermaid
|
|
219
|
+
flowchart LR
|
|
220
|
+
J[Job description] --> C[extract_job_criteria]
|
|
221
|
+
R[Resume files] --> P[parse_resume]
|
|
222
|
+
C --> M[match_candidate]
|
|
223
|
+
P --> M
|
|
224
|
+
P --> K[rank_candidates]
|
|
225
|
+
J --> K
|
|
226
|
+
M --> S[Shortlist with explanations]
|
|
227
|
+
K --> S
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
<details>
|
|
231
|
+
<summary><b>Example output from <code>match_candidate</code></b></summary>
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"score": 0.89,
|
|
236
|
+
"summary": "Profil très aligné : React, TypeScript et l’expérience demandée sont démontrés. Le niveau d’anglais reste à confirmer.",
|
|
237
|
+
"evaluated_criteria": [
|
|
238
|
+
{
|
|
239
|
+
"id": "crit_1",
|
|
240
|
+
"label": "Maîtrise de React",
|
|
241
|
+
"weight": 3,
|
|
242
|
+
"is_mandatory": true,
|
|
243
|
+
"match_status": "ideal",
|
|
244
|
+
"match_explanation": "Le CV décrit une équipe React dirigée depuis 2022 sur une plateforme en production."
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Criteria labels, rationales, summaries and explanations are written in French. Your assistant translates them when it answers you in another language.
|
|
251
|
+
|
|
252
|
+
</details>
|
|
253
|
+
|
|
254
|
+
## Example prompts
|
|
255
|
+
|
|
256
|
+
**Recruiters and hiring managers**
|
|
257
|
+
|
|
258
|
+
- "Parse `~/Downloads/jane-doe.pdf` and summarize her experience in five bullet points."
|
|
259
|
+
- "Here is our job description for a Senior Data Engineer. Extract the criteria, then tell me which ones are must-haves."
|
|
260
|
+
- "Score the resumes in `~/candidates/` against this job and give me a shortlist table with scores and main gaps."
|
|
261
|
+
- "Rank these 8 candidates for the Account Executive role and explain why the top 3 stand out."
|
|
262
|
+
- "Does this candidate meet every mandatory criterion? If not, which ones are missing?"
|
|
263
|
+
|
|
264
|
+
**Developers and HR tech teams**
|
|
265
|
+
|
|
266
|
+
- "Parse this resume and map the result to our ATS candidate schema: `{ name, email, current_title, skills[] }`."
|
|
267
|
+
- "Normalize this skills section: Pack Office (Word, Excel), React.js, anglais courant, gestion de projet."
|
|
268
|
+
- "Write a TypeScript function that sends a resume to the HireLayer API, using the JSON this tool returned as the expected type."
|
|
269
|
+
|
|
270
|
+
**Try it now with the sample files**
|
|
271
|
+
|
|
272
|
+
- "Screen the resumes in `examples/` against `examples/job-senior-react-developer.md`."
|
|
273
|
+
|
|
274
|
+
## Pricing and credits
|
|
275
|
+
|
|
276
|
+
Each successful tool call costs **1 HireLayer credit**. A `rank_candidates` call costs 1 credit whatever the number of candidates. Failed calls are not charged.
|
|
277
|
+
|
|
278
|
+
| Plan | Credits | Price |
|
|
279
|
+
|---|---|---|
|
|
280
|
+
| Free | 50 a month | Free, no card required |
|
|
281
|
+
| Paid plans | More credits and higher limits | See [hirelayer.co/#pricing](https://hirelayer.co/#pricing) |
|
|
282
|
+
|
|
283
|
+
## Data and privacy
|
|
284
|
+
|
|
285
|
+
- The server runs **on your machine** and calls the HireLayer API over HTTPS with your API key. It has no telemetry.
|
|
286
|
+
- `parse_resume` reads only the file you name. By default HireLayer stores the original file and returns a link to it in `info_resume.url`. Set `do_not_store_data: true` in a call so the file is not stored.
|
|
287
|
+
- See the [privacy policy](https://hirelayer.co/privacy-policy) and the [security policy](SECURITY.md).
|
|
288
|
+
|
|
289
|
+
Resumes contain personal data. Use the tools in line with your hiring process and the rules that apply to you, such as GDPR. Scores support human decisions; they don't replace them.
|
|
290
|
+
|
|
291
|
+
## Configuration
|
|
292
|
+
|
|
293
|
+
| Variable | Required | Default | Description |
|
|
294
|
+
|---|---|---|---|
|
|
295
|
+
| `HIRELAYER_API_KEY` | Yes | | Your HireLayer API key |
|
|
296
|
+
| `HIRELAYER_BASE_URL` | No | `https://hirelayer.co` | API base URL, for testing |
|
|
297
|
+
|
|
298
|
+
## Troubleshooting
|
|
299
|
+
|
|
300
|
+
| Symptom | Fix |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `HIRELAYER_API_KEY is not set` | Add the `env` block with your key to the client config, then restart the client. |
|
|
303
|
+
| `HireLayer API returned 401` | The key is wrong or revoked. Copy it again from **Dashboard → API keys**. |
|
|
304
|
+
| `HireLayer API returned 403` | You have used your monthly credits. Wait for the reset or upgrade your plan. |
|
|
305
|
+
| Parsing seems slow | Parsing usually takes about 35 seconds, and longer for scans that need OCR. The server sends progress updates so clients don't time out. |
|
|
306
|
+
| `npx` not found or an old Node.js | Install Node.js 20 or later from [nodejs.org](https://nodejs.org). |
|
|
307
|
+
| The file is not found | Use an absolute path, for example `/Users/me/Downloads/cv.pdf` rather than `~/Downloads/cv.pdf`. |
|
|
308
|
+
|
|
309
|
+
To debug, run the server in the MCP Inspector:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
HIRELAYER_API_KEY=your-api-key npx @modelcontextprotocol/inspector npx -y hirelayer-mcp
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## FAQ
|
|
316
|
+
|
|
317
|
+
**Is HireLayer an ATS?**
|
|
318
|
+
No. HireLayer provides the AI building blocks of recruiting software: resume parsing, matching, ranking and skills. Use them on their own through MCP, or plug them into your ATS or HR tech product through the [REST API](https://hirelayer.co/api-docs).
|
|
319
|
+
|
|
320
|
+
**Which language are the results in?**
|
|
321
|
+
The text that HireLayer writes (criteria labels and rationales, match summaries and explanations, ranking rationales) is in French; your assistant translates it when it answers in another language. Skills resolution returns French or English labels.
|
|
322
|
+
|
|
323
|
+
**Which resume languages are supported?**
|
|
324
|
+
The parser detects the main language of each resume and returns it in `info_resume.language`.
|
|
325
|
+
|
|
326
|
+
**Can I use the REST API directly?**
|
|
327
|
+
Yes. See the [API reference](https://hirelayer.co/api-docs), the [OpenAPI spec](https://hirelayer.co/openapi.json) and [`llms.txt`](https://hirelayer.co/llms.txt) for agents.
|
|
328
|
+
|
|
329
|
+
**Is there a hosted remote server?**
|
|
330
|
+
A hosted server with one-click sign-in (OAuth) is on the way. For now, the server runs locally with `npx`.
|
|
331
|
+
|
|
332
|
+
## Development
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
git clone https://github.com/hirelayer/hirelayer-mcp.git
|
|
336
|
+
cd hirelayer-mcp
|
|
337
|
+
npm install
|
|
338
|
+
npm test
|
|
339
|
+
HIRELAYER_API_KEY=your-api-key npx @modelcontextprotocol/inspector node dist/index.js
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Report bugs in [GitHub issues](https://github.com/hirelayer/hirelayer-mcp/issues) and vulnerabilities as described in [SECURITY.md](SECURITY.md).
|
|
343
|
+
|
|
344
|
+
## Links
|
|
345
|
+
|
|
346
|
+
- Website: [hirelayer.co](https://hirelayer.co)
|
|
347
|
+
- API reference: [hirelayer.co/api-docs](https://hirelayer.co/api-docs)
|
|
348
|
+
- OpenAPI: [hirelayer.co/openapi.json](https://hirelayer.co/openapi.json)
|
|
349
|
+
- Docs for agents: [hirelayer.co/llms.txt](https://hirelayer.co/llms.txt)
|
|
350
|
+
- Contact: [contact@hirelayer.co](mailto:contact@hirelayer.co)
|
|
351
|
+
|
|
352
|
+
## License
|
|
353
|
+
|
|
354
|
+
[MIT](LICENSE)
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
export const VERSION = createRequire(import.meta.url)('../package.json').version;
|
|
5
|
+
const DEFAULT_BASE_URL = 'https://hirelayer.co';
|
|
6
|
+
// The API rejects resumes above 4.5 MB.
|
|
7
|
+
export const MAX_RESUME_BYTES = 4.5 * 1024 * 1024;
|
|
8
|
+
// 502 and 503 are transient and never charged, so one retry is safe.
|
|
9
|
+
const RETRYABLE_STATUSES = new Set([502, 503]);
|
|
10
|
+
const MAX_RETRY_DELAY_MS = 10_000;
|
|
11
|
+
export class HireLayerError extends Error {
|
|
12
|
+
status;
|
|
13
|
+
constructor(message, status) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.name = 'HireLayerError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class HireLayerClient {
|
|
20
|
+
apiKey;
|
|
21
|
+
baseUrl;
|
|
22
|
+
fetch;
|
|
23
|
+
retryDelayMs;
|
|
24
|
+
constructor({ apiKey, baseUrl, fetch: fetchImpl, retryDelayMs }) {
|
|
25
|
+
this.apiKey = apiKey;
|
|
26
|
+
this.baseUrl = (baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
27
|
+
this.fetch = fetchImpl ?? globalThis.fetch;
|
|
28
|
+
this.retryDelayMs = retryDelayMs ?? 2_000;
|
|
29
|
+
}
|
|
30
|
+
async parseResume(source, options = {}) {
|
|
31
|
+
const { bytes, filename } = await this.loadResume(source, options.signal);
|
|
32
|
+
const form = new FormData();
|
|
33
|
+
form.append('file', new Blob([bytes]), filename);
|
|
34
|
+
if (options.applicationId)
|
|
35
|
+
form.append('application_id', options.applicationId);
|
|
36
|
+
if (options.doNotStoreData !== undefined) {
|
|
37
|
+
form.append('do_not_store_data', String(options.doNotStoreData));
|
|
38
|
+
}
|
|
39
|
+
return this.request('/api/v3/parser', { method: 'POST', body: form, signal: options.signal });
|
|
40
|
+
}
|
|
41
|
+
extractJobCriteria(jobText, options = {}) {
|
|
42
|
+
return this.postJson('/api/v1/jobs/extract-criteria', { job_text: jobText }, options);
|
|
43
|
+
}
|
|
44
|
+
matchCandidate(body, options = {}) {
|
|
45
|
+
return this.postJson('/api/v1/matching/job-candidate', body, options);
|
|
46
|
+
}
|
|
47
|
+
rankCandidates(body, options = {}) {
|
|
48
|
+
return this.postJson('/api/v1/matching/job-candidates/rank', body, options);
|
|
49
|
+
}
|
|
50
|
+
resolveSkills(text, language, options = {}) {
|
|
51
|
+
return this.postJson('/api/v1/skills/resolve', language ? { text, language } : { text }, options);
|
|
52
|
+
}
|
|
53
|
+
postJson(path, body, { signal }) {
|
|
54
|
+
return this.request(path, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/json' },
|
|
57
|
+
body: JSON.stringify(body),
|
|
58
|
+
signal,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async request(path, init, attempt = 1) {
|
|
62
|
+
const response = await this.fetch(`${this.baseUrl}${path}`, {
|
|
63
|
+
...init,
|
|
64
|
+
headers: {
|
|
65
|
+
...init.headers,
|
|
66
|
+
'X-API-Key': this.apiKey,
|
|
67
|
+
'User-Agent': `hirelayer-mcp/${VERSION}`,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
if (attempt === 1 && RETRYABLE_STATUSES.has(response.status) && !init.signal?.aborted) {
|
|
71
|
+
await response.body?.cancel();
|
|
72
|
+
await sleep(this.retryDelay(response.headers.get('retry-after')), init.signal);
|
|
73
|
+
return this.request(path, init, attempt + 1);
|
|
74
|
+
}
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
let payload = text;
|
|
77
|
+
try {
|
|
78
|
+
payload = JSON.parse(text);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Keep the raw text: gateways can answer with HTML or plain text.
|
|
82
|
+
}
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const detail = typeof payload === 'string' ? payload.slice(0, 500) : JSON.stringify(payload);
|
|
85
|
+
throw new HireLayerError(`HireLayer API returned ${response.status}: ${detail}${errorHint(response.status, detail)}`, response.status);
|
|
86
|
+
}
|
|
87
|
+
return payload;
|
|
88
|
+
}
|
|
89
|
+
retryDelay(retryAfter) {
|
|
90
|
+
const seconds = Number(retryAfter);
|
|
91
|
+
if (retryAfter && Number.isFinite(seconds) && seconds >= 0) {
|
|
92
|
+
return Math.min(seconds * 1000, MAX_RETRY_DELAY_MS);
|
|
93
|
+
}
|
|
94
|
+
return this.retryDelayMs;
|
|
95
|
+
}
|
|
96
|
+
async loadResume({ filePath, fileUrl }, signal) {
|
|
97
|
+
if (Boolean(filePath) === Boolean(fileUrl)) {
|
|
98
|
+
throw new HireLayerError('Provide exactly one of file_path or file_url.');
|
|
99
|
+
}
|
|
100
|
+
let bytes;
|
|
101
|
+
let filename;
|
|
102
|
+
if (filePath) {
|
|
103
|
+
bytes = new Uint8Array(await readFile(filePath));
|
|
104
|
+
filename = basename(filePath);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const response = await this.fetch(fileUrl, { signal });
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new HireLayerError(`Could not download ${fileUrl}: HTTP ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
112
|
+
filename = basename(new URL(fileUrl).pathname) || 'resume';
|
|
113
|
+
}
|
|
114
|
+
if (bytes.byteLength > MAX_RESUME_BYTES) {
|
|
115
|
+
throw new HireLayerError(`The resume is ${(bytes.byteLength / 1024 / 1024).toFixed(1)} MB; HireLayer accepts files under 4.5 MB.`);
|
|
116
|
+
}
|
|
117
|
+
return { bytes, filename };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function errorHint(status, detail) {
|
|
121
|
+
if (status === 401) {
|
|
122
|
+
return ' Check HIRELAYER_API_KEY: copy your key from https://hirelayer.co/dashboard/api-keys.';
|
|
123
|
+
}
|
|
124
|
+
if (status === 403 && /credit/i.test(detail)) {
|
|
125
|
+
return ' Credits reset every month; see https://hirelayer.co/#pricing for more.';
|
|
126
|
+
}
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
function sleep(ms, signal) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
if (signal?.aborted)
|
|
132
|
+
return reject(signal.reason);
|
|
133
|
+
const timer = setTimeout(resolve, ms);
|
|
134
|
+
signal?.addEventListener('abort', () => {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
reject(signal.reason);
|
|
137
|
+
}, { once: true });
|
|
138
|
+
});
|
|
139
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { HireLayerClient, VERSION } from './client.js';
|
|
4
|
+
import { createServer } from './server.js';
|
|
5
|
+
if (process.argv.includes('--version')) {
|
|
6
|
+
console.log(VERSION);
|
|
7
|
+
process.exit(0);
|
|
8
|
+
}
|
|
9
|
+
const apiKey = process.env.HIRELAYER_API_KEY;
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
console.error('HIRELAYER_API_KEY is not set. Create a free key (50 credits a month) at https://hirelayer.co and pass it in the server env.');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const server = createServer(new HireLayerClient({ apiKey, baseUrl: process.env.HIRELAYER_BASE_URL }));
|
|
15
|
+
await server.connect(new StdioServerTransport());
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { VERSION } from './client.js';
|
|
4
|
+
// Clients drop calls that stay silent too long (often after 60 s); parsing takes about
|
|
5
|
+
// 35 s and scans needing OCR take longer, so long calls report progress at this interval.
|
|
6
|
+
const PROGRESS_INTERVAL_MS = 10_000;
|
|
7
|
+
const INSTRUCTIONS = `HireLayer gives you recruiting tools: resume (CV) parsing, job criteria extraction, candidate matching, candidate ranking and skills normalization.
|
|
8
|
+
|
|
9
|
+
Typical workflows:
|
|
10
|
+
- Screen candidates for a job: extract_job_criteria on the job description, parse_resume on each resume, then match_candidate for each one (pass the criteria and info_resume.text). Use rank_candidates to order up to 10 candidates in one call.
|
|
11
|
+
- Read a resume: parse_resume returns contact details, experience, education, languages, skills and the full text.
|
|
12
|
+
- Normalize skills: resolve_skills maps free text in French or English to taxonomy skills.
|
|
13
|
+
|
|
14
|
+
Notes:
|
|
15
|
+
- The text HireLayer writes (criteria labels and rationales, match summaries and explanations, ranking rationales) is in French: translate it when you answer the user in another language.
|
|
16
|
+
- info_resume.text can reach 100,000 characters; match_candidate and rank_candidates accept 50,000 characters per resume, so truncate longer texts.
|
|
17
|
+
- Pass do_not_store_data: true to parse_resume when the user does not want HireLayer to keep the file.
|
|
18
|
+
- Every successful tool call costs 1 HireLayer credit. Resume parsing usually takes about 35 seconds.`;
|
|
19
|
+
const criterion = z.object({
|
|
20
|
+
id: z.string().min(1).describe('Criterion ID, e.g. crit_1 from extract_job_criteria.'),
|
|
21
|
+
label: z.string().min(1).describe('What is evaluated, in a short phrase.'),
|
|
22
|
+
weight: z.number().int().min(1).max(3).describe('3 essential, 2 important, 1 nice to have.'),
|
|
23
|
+
is_mandatory: z.boolean().describe('Whether the job states it as a hard requirement.'),
|
|
24
|
+
rationale: z.string().min(1).describe('Why the criterion matters for the job.'),
|
|
25
|
+
});
|
|
26
|
+
// The tools only read and analyse data; they never change anything in the user's systems.
|
|
27
|
+
const annotations = { readOnlyHint: true, destructiveHint: false, openWorldHint: true };
|
|
28
|
+
export function createServer(client) {
|
|
29
|
+
const server = new McpServer({
|
|
30
|
+
name: 'hirelayer',
|
|
31
|
+
title: 'HireLayer',
|
|
32
|
+
version: VERSION,
|
|
33
|
+
websiteUrl: 'https://hirelayer.co',
|
|
34
|
+
icons: [{ src: 'https://hirelayer.co/favicon/android-chrome-512x512.png', mimeType: 'image/png', sizes: ['512x512'] }],
|
|
35
|
+
}, { instructions: INSTRUCTIONS });
|
|
36
|
+
const run = async (extra, call) => {
|
|
37
|
+
const stopProgress = reportProgress(extra);
|
|
38
|
+
try {
|
|
39
|
+
const result = await call(extra.signal);
|
|
40
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
return { content: [{ type: 'text', text: message }], isError: true };
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
stopProgress();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
server.registerTool('parse_resume', {
|
|
51
|
+
title: 'Parse a resume',
|
|
52
|
+
description: 'Parse a resume / CV (PDF, DOC, DOCX, ODT, PPT, PPTX, ODP, XLS, RTF, TXT, JPG, PNG or BMP, under 4.5 MB) into structured JSON: contact details, work experience, education, languages, skills and the full resume text in info_resume.text. Pass a local file_path or a public file_url. Takes about 35 seconds. Costs 1 HireLayer credit.',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
file_path: z.string().optional().describe('Absolute path of a local resume file.'),
|
|
55
|
+
file_url: z.string().url().optional().describe('Public URL of a resume file to download.'),
|
|
56
|
+
application_id: z.string().optional().describe('Your own reference, echoed back in the result.'),
|
|
57
|
+
do_not_store_data: z
|
|
58
|
+
.boolean()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('true: HireLayer does not keep the file after parsing.'),
|
|
61
|
+
},
|
|
62
|
+
annotations,
|
|
63
|
+
}, ({ file_path, file_url, application_id, do_not_store_data }, extra) => run(extra, (signal) => client.parseResume({ filePath: file_path, fileUrl: file_url }, { applicationId: application_id, doNotStoreData: do_not_store_data, signal })));
|
|
64
|
+
server.registerTool('extract_job_criteria', {
|
|
65
|
+
title: 'Extract job criteria',
|
|
66
|
+
description: 'Turn a job description (any language) into weighted matching criteria (matching_criteria[]), each with a weight from 1 to 3, a mandatory flag and a rationale (labels and rationales are written in French). Feed the result to match_candidate. Costs 1 HireLayer credit.',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
job_text: z.string().min(1).max(50000).describe('Full job description.'),
|
|
69
|
+
},
|
|
70
|
+
annotations,
|
|
71
|
+
}, ({ job_text }, extra) => run(extra, (signal) => client.extractJobCriteria(job_text, { signal })));
|
|
72
|
+
server.registerTool('match_candidate', {
|
|
73
|
+
title: 'Match a candidate to a job',
|
|
74
|
+
description: 'Score one candidate against a job: returns a score between 0 and 1, a summary and an explained evaluation of each criterion (written in French). Use the criteria from extract_job_criteria and the resume text from parse_resume (info_resume.text). Costs 1 HireLayer credit.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
job_text: z.string().min(1).max(50000).describe('Job description.'),
|
|
77
|
+
candidate_text: z.string().min(1).max(50000).describe('Resume as plain text.'),
|
|
78
|
+
matching_criteria: z
|
|
79
|
+
.array(criterion)
|
|
80
|
+
.default([])
|
|
81
|
+
.describe('Criteria to evaluate, usually from extract_job_criteria.'),
|
|
82
|
+
},
|
|
83
|
+
annotations,
|
|
84
|
+
}, (args, extra) => run(extra, (signal) => client.matchCandidate(args, { signal })));
|
|
85
|
+
server.registerTool('rank_candidates', {
|
|
86
|
+
title: 'Rank candidates for a job',
|
|
87
|
+
description: 'Rank up to 10 candidates against the same job description, from their resume texts: returns each candidate with a rank, a score between 0 and 1 and a rationale (written in French). Costs 1 HireLayer credit per call, whatever the number of candidates.',
|
|
88
|
+
inputSchema: {
|
|
89
|
+
job_text: z.string().min(1).max(50000).describe('Job description.'),
|
|
90
|
+
candidates: z
|
|
91
|
+
.array(z.object({
|
|
92
|
+
id: z.string().min(1).describe('Your candidate ID, unique in the request.'),
|
|
93
|
+
candidate_text: z.string().min(1).max(50000).describe('Resume as plain text.'),
|
|
94
|
+
}))
|
|
95
|
+
.min(1)
|
|
96
|
+
.max(10),
|
|
97
|
+
},
|
|
98
|
+
annotations,
|
|
99
|
+
}, (args, extra) => run(extra, (signal) => client.rankCandidates(args, { signal })));
|
|
100
|
+
server.registerTool('resolve_skills', {
|
|
101
|
+
title: 'Resolve skills',
|
|
102
|
+
description: 'Map free-text skills in French or English (one skill, a compound string or a whole skills section) to skills of the HireLayer taxonomy, with their IDs, families and domains. Unmatched parts are listed in unresolved. Costs 1 HireLayer credit.',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
text: z.string().min(1).max(5000).describe('Free text containing one or more skills.'),
|
|
105
|
+
language: z.enum(['fr', 'en']).optional().describe('Language of the labels returned: fr (default) or en.'),
|
|
106
|
+
},
|
|
107
|
+
annotations,
|
|
108
|
+
}, ({ text, language }, extra) => run(extra, (signal) => client.resolveSkills(text, language, { signal })));
|
|
109
|
+
server.registerPrompt('screen_candidates', {
|
|
110
|
+
title: 'Screen candidates for a job',
|
|
111
|
+
description: 'Parse resumes, score each candidate against a job description and build a shortlist.',
|
|
112
|
+
argsSchema: {
|
|
113
|
+
job_description: z.string().describe('The job description, or the path of a file that contains it.'),
|
|
114
|
+
resumes: z.string().describe('Resume file paths or URLs, one per line, or a folder that holds them.'),
|
|
115
|
+
},
|
|
116
|
+
}, ({ job_description, resumes }) => userPrompt(`Screen these candidates for the job below with the HireLayer tools.
|
|
117
|
+
|
|
118
|
+
1. Call extract_job_criteria on the job description.
|
|
119
|
+
2. Call parse_resume on each resume.
|
|
120
|
+
3. Call match_candidate for each candidate with the criteria and info_resume.text.
|
|
121
|
+
4. Present a shortlist table sorted by score: candidate, score, mandatory criteria met, main gaps. Then recommend who to interview and why.
|
|
122
|
+
|
|
123
|
+
Job description:
|
|
124
|
+
${job_description}
|
|
125
|
+
|
|
126
|
+
Resumes:
|
|
127
|
+
${resumes}`));
|
|
128
|
+
server.registerPrompt('summarize_resume', {
|
|
129
|
+
title: 'Summarize a resume',
|
|
130
|
+
description: 'Parse one resume and write a short recruiter-style candidate summary.',
|
|
131
|
+
argsSchema: {
|
|
132
|
+
resume: z.string().describe('Path or URL of the resume file.'),
|
|
133
|
+
},
|
|
134
|
+
}, ({ resume }) => userPrompt(`Parse this resume with parse_resume, then write a recruiter summary: current role, years of experience, key skills, education, languages, location and availability. Flag missing contact details.
|
|
135
|
+
|
|
136
|
+
Resume: ${resume}`));
|
|
137
|
+
server.registerPrompt('normalize_skills', {
|
|
138
|
+
title: 'Normalize a skills list',
|
|
139
|
+
description: 'Map a free-text skills section to standard taxonomy skills.',
|
|
140
|
+
argsSchema: {
|
|
141
|
+
skills: z.string().describe('Free-text skills, e.g. a resume skills section.'),
|
|
142
|
+
},
|
|
143
|
+
}, ({ skills }) => userPrompt(`Call resolve_skills on the text below (language en), then list the normalized skills grouped by domain and the parts that could not be resolved.
|
|
144
|
+
|
|
145
|
+
${skills}`));
|
|
146
|
+
return server;
|
|
147
|
+
}
|
|
148
|
+
function userPrompt(text) {
|
|
149
|
+
return { messages: [{ role: 'user', content: { type: 'text', text } }] };
|
|
150
|
+
}
|
|
151
|
+
function reportProgress(extra) {
|
|
152
|
+
const progressToken = extra._meta?.progressToken;
|
|
153
|
+
if (progressToken === undefined)
|
|
154
|
+
return () => { };
|
|
155
|
+
let progress = 0;
|
|
156
|
+
const timer = setInterval(() => {
|
|
157
|
+
progress += 1;
|
|
158
|
+
extra
|
|
159
|
+
.sendNotification({
|
|
160
|
+
method: 'notifications/progress',
|
|
161
|
+
params: { progressToken, progress, message: 'Waiting for HireLayer…' },
|
|
162
|
+
})
|
|
163
|
+
.catch(() => { });
|
|
164
|
+
}, PROGRESS_INTERVAL_MS);
|
|
165
|
+
return () => clearInterval(timer);
|
|
166
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hirelayer-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"mcpName": "co.hirelayer/hirelayer",
|
|
5
|
+
"description": "Official HireLayer MCP server: resume and CV parsing, job criteria extraction, candidate matching and ranking, and skills normalization for AI agents in Claude, Cursor, VS Code and other MCP clients.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hirelayer-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
19
|
+
"inspect": "npm run build && npx @modelcontextprotocol/inspector node dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"mcp-server",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"hirelayer",
|
|
26
|
+
"resume-parser",
|
|
27
|
+
"cv-parser",
|
|
28
|
+
"resume-parsing",
|
|
29
|
+
"candidate-matching",
|
|
30
|
+
"candidate-ranking",
|
|
31
|
+
"job-matching",
|
|
32
|
+
"resume-screening",
|
|
33
|
+
"recruiting",
|
|
34
|
+
"recruitment",
|
|
35
|
+
"ats",
|
|
36
|
+
"hr-tech",
|
|
37
|
+
"hiring",
|
|
38
|
+
"talent-acquisition",
|
|
39
|
+
"ai-agents",
|
|
40
|
+
"claude",
|
|
41
|
+
"cursor"
|
|
42
|
+
],
|
|
43
|
+
"homepage": "https://github.com/hirelayer/hirelayer-mcp#readme",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/hirelayer/hirelayer-mcp.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/hirelayer/hirelayer-mcp/issues"
|
|
50
|
+
},
|
|
51
|
+
"author": "HireLayer <contact@hirelayer.co> (https://hirelayer.co)",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
61
|
+
"zod": "^4.6.5"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^22.20.5",
|
|
65
|
+
"typescript": "^5.9.3"
|
|
66
|
+
}
|
|
67
|
+
}
|