@sargonpiraev/hh-mcp-server 1.10.0 → 1.12.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 +31 -99
- package/build/index.js +282 -177
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,133 +1,65 @@
|
|
|
1
1
|
# HeadHunter MCP Server
|
|
2
2
|
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-
|
|
6
3
|
MCP Server for HeadHunter API - job search and vacancy management
|
|
7
4
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
- 🔍 User Profile Management - View and manage your HeadHunter profile
|
|
11
|
-
- 📄 Resume Management - View detailed resume information
|
|
12
|
-
- 💼 Job Application Management - Manage negotiations and communications with employers
|
|
13
|
-
- ⭐ Vacancy Search & Favorites - Search for jobs and manage your favorites
|
|
14
|
-
|
|
15
|
-
## Get Your Credentials
|
|
16
|
-
|
|
17
|
-
Before installation, you'll need to obtain your API credentials:
|
|
18
|
-
|
|
19
|
-
1. **HH_USER_AGENT**: User-Agent header for HeadHunter API requests (required by HH API)
|
|
20
|
-
|
|
21
|
-
Save these credentials for the installation steps below.
|
|
5
|
+
## Quick Start
|
|
22
6
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<details>
|
|
26
|
-
<summary><b>Installing via Smithery</b></summary>
|
|
27
|
-
|
|
28
|
-
To install HeadHunter MCP Server for any client automatically via [Smithery](https://smithery.ai):
|
|
7
|
+
1. Install dependencies:
|
|
29
8
|
|
|
30
9
|
```bash
|
|
31
|
-
|
|
10
|
+
npm install
|
|
32
11
|
```
|
|
33
12
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<details>
|
|
37
|
-
<summary><b>Install in Cursor</b></summary>
|
|
38
|
-
|
|
39
|
-
#### Cursor One-Click Installation
|
|
40
|
-
|
|
41
|
-
[](https://cursor.com/install-mcp?name=@sargonpiraev/hh-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzYXJnb25waXJhZXYvaGFiaXRpZnktbWNwLXNlcnZlciJdLCJlbnYiOnsiSEFCSVRJRllfQVBJX0tFWSI6InlvdXItaGFiaXRpZnktYXBpLWtleSJ9fQo=)
|
|
13
|
+
2. Set up environment:
|
|
42
14
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```json
|
|
48
|
-
{
|
|
49
|
-
"mcpServers": {
|
|
50
|
-
"hh-mcp-server": {
|
|
51
|
-
"command": "npx",
|
|
52
|
-
"args": ["-y", "@sargonpiraev/hh-mcp-server"],
|
|
53
|
-
"env": {
|
|
54
|
-
"HH_USER_AGENT": "your-HH_USER_AGENT-here"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
15
|
+
```bash
|
|
16
|
+
cp .env.example .env
|
|
17
|
+
# Edit .env with your API credentials
|
|
59
18
|
```
|
|
60
19
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<details>
|
|
64
|
-
<summary><b>Install in VS Code</b></summary>
|
|
65
|
-
|
|
66
|
-
[](vscode:mcp/install?%7B%22name%22%3A%22hh-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22@sargonpiraev/hh-mcp-server%22%5D%7D)
|
|
67
|
-
|
|
68
|
-
Or add manually to your VS Code settings:
|
|
20
|
+
3. Start the server:
|
|
69
21
|
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
"servers": {
|
|
73
|
-
"hh-mcp-server": {
|
|
74
|
-
"type": "stdio",
|
|
75
|
-
"command": "npx",
|
|
76
|
-
"args": ["-y", "@sargonpiraev/hh-mcp-server"],
|
|
77
|
-
"env": {
|
|
78
|
-
"HH_USER_AGENT": "your-HH_USER_AGENT-here"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
22
|
+
```bash
|
|
23
|
+
npm start
|
|
83
24
|
```
|
|
84
25
|
|
|
85
|
-
|
|
26
|
+
## Configuration
|
|
86
27
|
|
|
87
|
-
|
|
88
|
-
<summary><b>Install in Claude Desktop</b></summary>
|
|
89
|
-
|
|
90
|
-
Add to your `claude_desktop_config.json`:
|
|
28
|
+
Add to your MCP client config:
|
|
91
29
|
|
|
92
30
|
```json
|
|
93
31
|
{
|
|
94
32
|
"mcpServers": {
|
|
95
33
|
"hh-mcp-server": {
|
|
96
|
-
"command": "
|
|
97
|
-
"args": ["
|
|
34
|
+
"command": "node",
|
|
35
|
+
"args": ["/dist/index.js"],
|
|
98
36
|
"env": {
|
|
99
|
-
"HH_USER_AGENT": "
|
|
37
|
+
"HH_USER_AGENT": "your_hh_user_agent"
|
|
100
38
|
}
|
|
101
39
|
}
|
|
102
40
|
}
|
|
103
41
|
}
|
|
104
42
|
```
|
|
105
43
|
|
|
106
|
-
</details>
|
|
107
|
-
|
|
108
44
|
## Available Tools
|
|
109
45
|
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
46
|
+
- **get-vacancies**: Search for vacancies
|
|
47
|
+
- **get-dictionaries**: Directories of fields
|
|
48
|
+
- **get-areas**: Tree view of all regions
|
|
49
|
+
- **get-languages**: The list of all languages
|
|
50
|
+
- **get-industries**: Industries
|
|
51
|
+
- **get-metro**: The list of metro stations in all cities
|
|
52
|
+
- **get-professional-roles**: Professional role directory
|
|
53
|
+
- **get-employers**: Employer search
|
|
118
54
|
|
|
119
|
-
|
|
55
|
+
## Development
|
|
120
56
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
[](https://boosty.to/sargonpiraev)
|
|
57
|
+
```bash
|
|
58
|
+
npm run build # Build TypeScript
|
|
59
|
+
npm run test # Run tests
|
|
60
|
+
npm run lint # Check code style
|
|
61
|
+
```
|
|
128
62
|
|
|
129
|
-
##
|
|
63
|
+
## License
|
|
130
64
|
|
|
131
|
-
|
|
132
|
-
- 📧 Email: [sargonpiraev@gmail.com](mailto:sargonpiraev@gmail.com)
|
|
133
|
-
- 💬 Join [Discord](https://discord.gg/ZsWGxRGj)
|
|
65
|
+
MIT
|
package/build/index.js
CHANGED
|
@@ -4,216 +4,321 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import axios from 'axios';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
|
-
// Load environment variables
|
|
8
7
|
dotenv.config();
|
|
9
|
-
// Environment configuration schema
|
|
10
8
|
const envSchema = z.object({
|
|
11
|
-
|
|
12
|
-
// TOOL_GLOB_PATTERNS: z.string().optional(),
|
|
13
|
-
// LOG_LEVEL: z.enum(['debug', 'info', 'notice', 'warning', 'error']).default('info').optional(),
|
|
9
|
+
HH_API_TOKEN: z.string(),
|
|
14
10
|
});
|
|
15
|
-
// Parse and validate environment variables
|
|
16
11
|
const env = envSchema.parse(process.env);
|
|
17
12
|
const mcpServer = new McpServer({
|
|
18
13
|
name: '@sargonpiraev/hh-mcp-server',
|
|
19
14
|
version: '',
|
|
20
15
|
}, {
|
|
16
|
+
instructions: `In English | [По-русски](https://api.hh.ru/openapi/redoc)
|
|
17
|
+
|
|
18
|
+
Documentation [in GitHub](https://github.com/hhru/api/blob/master/docs_eng/README.md).
|
|
19
|
+
|
|
20
|
+
You can use Ctrl+F to search through the documentation.
|
|
21
|
+
|
|
22
|
+
# General information
|
|
23
|
+
|
|
24
|
+
* The whole API works using the HTTPS protocol.
|
|
25
|
+
* Authorization is performed via the OAuth2 protocol.
|
|
26
|
+
* All data is available only in the JSON format.
|
|
27
|
+
* The basic URL is `https://api.hh.ru/`
|
|
28
|
+
* Requests for [any site of HeadHunter Groups of Companies](#section/General-information/Host-selection)
|
|
29
|
+
are available
|
|
30
|
+
* <a name="date-format"></a> Dates are formatted according to
|
|
31
|
+
[ISO 8601](http://en.wikipedia.org/wiki/ISO_8601): `YYYY-MM-DDThh:mm:ss±hhmm`.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Request requirements
|
|
35
|
+
|
|
36
|
+
Your request should send the `User-Agent` header, but if your
|
|
37
|
+
HTTP client does not allow it, you can send an `HH-User-Agent` header. If no header is sent,
|
|
38
|
+
you will receive the `400 Bad Request` as a response.
|
|
39
|
+
By specifying the name of the application and the developer's contact email in the header,
|
|
40
|
+
you will help us to contact you promptly if required.
|
|
41
|
+
The `User-Agent` and `HH-User-Agent` headers are interchangeable. If you send both headers,
|
|
42
|
+
only `HH-User-Agent` is processed.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
User-Agent: MyApp/1.0 (my-app-feedback@example.com)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
More detail on [errors in the User-Agent title](https://github.com/hhru/api/blob/master/docs_eng/errors.md#user-agent).
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
### Request body format when sending JSON
|
|
52
|
+
|
|
53
|
+
Data transferred in the request body must meet the requirements:
|
|
54
|
+
|
|
55
|
+
* Valid JSON (it is acceptable to transfer both minified and
|
|
56
|
+
pretty-printed variants with extra whitespace and line breaks).
|
|
57
|
+
|
|
58
|
+
* It is recommended to use the UTF-8 encoding without extra escaping
|
|
59
|
+
(`{"name": "Иванов Иван"}`).
|
|
60
|
+
|
|
61
|
+
* It is also possible to use the ascii encoding with escaping
|
|
62
|
+
(`{"name": "\u0418\u0432\u0430\u043d\u043e\u0432 \u0418\u0432\u0430\u043d"}`).
|
|
63
|
+
|
|
64
|
+
* Data types in certain fields are supplemented with additional conditions
|
|
65
|
+
described in each specific method. In JSON, data types are `string`, `number`,
|
|
66
|
+
`boolean`, `null`, `object`, `array`.
|
|
67
|
+
|
|
68
|
+
### Response
|
|
69
|
+
A response that exceeds a certain length will be compressed using gzip.
|
|
70
|
+
|
|
71
|
+
### Errors and response codes
|
|
72
|
+
|
|
73
|
+
API extensively uses informing through response codes.
|
|
74
|
+
The application must process them correctly.
|
|
75
|
+
|
|
76
|
+
In case of failure or breakdown, responses with the `503` and `500` code may be
|
|
77
|
+
returned.
|
|
78
|
+
|
|
79
|
+
When an error occurs, the response body, besides the response code,
|
|
80
|
+
may have additional information allowing the developer
|
|
81
|
+
to learn the cause of a particular response.
|
|
82
|
+
|
|
83
|
+
[More detail on possible errors](https://github.com/hhru/api/blob/master/docs_eng/errors.md).
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
## Undocumented fields and request parameters
|
|
87
|
+
|
|
88
|
+
In responses and API parameters you can find keys that are not described in the
|
|
89
|
+
documentation. It usually means that they are left for compatibility with older
|
|
90
|
+
versions. It is not recommended to use them. If your application already uses
|
|
91
|
+
such keys, switch to using desirable keys described in the documentation.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Pagination
|
|
95
|
+
|
|
96
|
+
For every request that presupposes the return of object list you can indicate
|
|
97
|
+
`page=N&per_page=M` in the parameters. Numeration starts from zero; the first
|
|
98
|
+
(zero) page is returned by default with 20 objects on it. In all responses where
|
|
99
|
+
pagination is available, the uniform root object:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"found": 1,
|
|
104
|
+
"per_page": 1,
|
|
105
|
+
"pages": 1,
|
|
106
|
+
"page": 0,
|
|
107
|
+
"items": [{}]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Host selection
|
|
112
|
+
|
|
113
|
+
HeadHunter API allows you to obtain data from all of the HeadHunter group of
|
|
114
|
+
companies websites.
|
|
115
|
+
|
|
116
|
+
In particular:
|
|
117
|
+
|
|
118
|
+
* hh.ru
|
|
119
|
+
* rabota.by
|
|
120
|
+
* hh1.az
|
|
121
|
+
* hh.uz
|
|
122
|
+
* hh.kz
|
|
123
|
+
* headhunter.ge
|
|
124
|
+
* headhunter.kg
|
|
125
|
+
|
|
126
|
+
Requests for data on all the websites should be made to `https://api.hh.ru/`.
|
|
127
|
+
|
|
128
|
+
If you need to track the specific websites, you can add `?host=` parameter to
|
|
129
|
+
your request. Default is `hh.ru`.
|
|
130
|
+
|
|
131
|
+
Example: to obtain [localizations](https://api.hh.ru/openapi/en/redoc#tag/Public-directories/operation/get-locales) available on hh.kz, you should
|
|
132
|
+
make a GET-request to `https://api.hh.ru/locales?host=hh.kz`.
|
|
133
|
+
|
|
134
|
+
## CORS (Cross-Origin Resource Sharing)
|
|
135
|
+
|
|
136
|
+
API supports the CORS technology for requesting browser data from any domain.
|
|
137
|
+
This method is preferable to JSONP. It is not restricted to the GET method. To
|
|
138
|
+
debug CORS, a [special method](https://github.com/hhru/api/blob/master/docs_eng/cors.md) is available. To use JSONP, transfer the
|
|
139
|
+
`?callback=callback_name` parameter.
|
|
140
|
+
|
|
141
|
+
* [CORS specification on w3.org](http://www.w3.org/TR/cors/)
|
|
142
|
+
* [HTML5Rocks CORS Tutorial](http://www.html5rocks.com/en/tutorials/cors/)
|
|
143
|
+
* [CORS on dev.opera.com](http://dev.opera.com/articles/view/dom-access-control-using-cross-origin-resource-sharing/)
|
|
144
|
+
* [CORS on caniuse.com](http://caniuse.com/#feat=cors)
|
|
145
|
+
* [CORS on en.wikipedia.org](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
## External links to articles and standards
|
|
149
|
+
|
|
150
|
+
* [HTTP/1.1](http://tools.ietf.org/html/rfc2616)
|
|
151
|
+
* [JSON](http://json.org/)
|
|
152
|
+
* [URI Template](http://tools.ietf.org/html/rfc6570)
|
|
153
|
+
* [OAuth 2.0](http://tools.ietf.org/html/rfc6749)
|
|
154
|
+
* [REST](http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)
|
|
155
|
+
* [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601)
|
|
156
|
+
|
|
157
|
+
# Authorization
|
|
158
|
+
|
|
159
|
+
The API supports the following authorization levels:
|
|
160
|
+
- [application authorization](#tag/Application-authorization)
|
|
161
|
+
- [user authorization](#section/Authorization/User-authorization)
|
|
162
|
+
|
|
163
|
+
* [Obtaining user authorization](#section/Authorization/User-authorization)
|
|
164
|
+
* [Rules for generating special `redirect_uri`](#section/Authorization/Rules-for-generating-special-redirect_uri)
|
|
165
|
+
* [Authorization process](#section/Authorization/Authorization-process)
|
|
166
|
+
* [Successful obtaining temporary `authorization_code`](#get-authorization_code)
|
|
167
|
+
* [Obtaining access and refresh tokens](#section/Authorization/Obtaining-access-and-refresh-tokens)
|
|
168
|
+
* [Updating a pair of access and refresh tokens](#section/Authorization/Updating-a-pair-of-access-and-refresh-tokens)
|
|
169
|
+
* [Authorization request under another user](#section/Authorization/Authorization-request-under-another-user)
|
|
170
|
+
* [Authorization under different working account](#section/Authorization/Authorization-under-different-working-account)
|
|
171
|
+
* [Obtaining application authorization](#tag/Application-authorization)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## User authorization
|
|
175
|
+
To make requests as a user you need a user token.
|
|
176
|
+
|
|
177
|
+
Assign a user to the application (open the page) via the request:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
https://hh.ru/oauth/authorize?
|
|
181
|
+
response_type=code&
|
|
182
|
+
client_id={client_id}&
|
|
183
|
+
state={state}&
|
|
184
|
+
redirect_uri={redirect_uri}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Required parameters:
|
|
188
|
+
|
|
189
|
+
* `response_type=code` — specifies authorization method using the `authorization code`
|
|
190
|
+
* `client_id` — ID obtained when creating the application
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
Optional parameters:
|
|
194
|
+
|
|
195
|
+
* `state` — if specified, it will be added in the redirect link of the response.
|
|
196
|
+
|
|
197
|
+
It allows to prevent cross-site request forgery. More info see in [RFC 6749. Section 10.12](http://tools.ietf.org/html/rfc6749#section-10.12)
|
|
198
|
+
* `redirect_uri` — a URI for redirecting user after authorization.
|
|
199
|
+
|
|
200
|
+
If not specified, the value from the application settings is used. If the value is specified, its validation is carried out. Probably, you will need to make `urlencode` of the value.
|
|
201
|
+
|
|
202
|
+
## Rules for generating special redirect_uri
|
|
203
|
+
|
|
204
|
+
For example, if `http://example.com/oauth` is saved in the settings, it is allowed to specify:
|
|
205
|
+
|
|
206
|
+
* `http://www.example.com/oauth` — a subdomain;
|
|
207
|
+
* `http://www.example.com/oauth/sub/path` — a detailed path;
|
|
208
|
+
* `http://example.com/oauth?lang=RU` — an additional parameter;
|
|
209
|
+
* `http://www.example.com/oauth/sub/path?lang=RU` — all the mentioned.
|
|
210
|
+
|
|
211
|
+
Forbidden:
|
|
212
|
+
|
|
213
|
+
* `https://example.com/oauth` — another protocols;
|
|
214
|
+
* `http://wwwexample.com/oauth` — another domains;
|
|
215
|
+
* `http://wwwexample.com/` — another paths;
|
|
216
|
+
* `http://example.com/oauths` — another paths;
|
|
217
|
+
* `http://example.com:80/oauths` — originally missing port.
|
|
218
|
+
|
|
219
|
+
## Authorization process
|
|
220
|
+
|
|
221
|
+
If the user is not authorized, the site login form appears. When the authorization is done, the form with subject access request is shown.
|
|
222
|
+
|
|
223
|
+
If the user rejects the access request, it triggers the redirection on the specified `redirect_uri` with `?error=access_denied` and
|
|
224
|
+
`state={state}`, if any.
|
|
225
|
+
|
|
226
|
+
<a name="get-authorization_code"></a>
|
|
227
|
+
### Successful obtaining temporary `authorization_code`
|
|
228
|
+
|
|
229
|
+
If the user accepts the access request, the redirect link contains a temporary `authorization_code`:
|
|
230
|
+
|
|
231
|
+
```http
|
|
232
|
+
HTTP/1.1 302 FOUND
|
|
233
|
+
Location: {redirect_uri}?code={authorization_code}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
If the user is already authorized, and the access to the application was granted earlier, then the login and subject access request are omitted, and the above redirect link returns as a response.
|
|
237
|
+
|
|
238
|
+
## Obtaining access and refresh tokens
|
|
239
|
+
|
|
240
|
+
After obtaining the `authorization_code` the application makes a server-server request `POST https://api.hh.ru/token` to exchange the `authorization_code` to an `access_token` (old `POST https://hh.ru/oauth/token` request should consider as deprecated).
|
|
241
|
+
|
|
242
|
+
The request body should contain [additional parameters](#required_parameters).
|
|
243
|
+
|
|
244
|
+
The request body should be sent in standard `application/x-www-form-urlencoded` format with the corresponding `Content-Type` header.
|
|
245
|
+
|
|
246
|
+
The `authorization_code` has a short lifetime. When it expires, request another `authorization_code`.
|
|
247
|
+
|
|
248
|
+
## Updating a pair of access and refresh tokens
|
|
249
|
+
The `access_token` also has a lifetime (`expires_in` parameter, in seconds). When it expires, the application should make a request with the `refresh_token` to obtain a new one.
|
|
250
|
+
|
|
251
|
+
The request should be done in `application/x-www-form-urlencoded` format.
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
POST https://api.hh.ru/token
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
(old `POST https://hh.ru/oauth/token` request should consider as deprecated)
|
|
258
|
+
|
|
259
|
+
The request body should contain [additional parameters](#required_parameters).
|
|
260
|
+
|
|
261
|
+
You can use the `refresh_token` only once and only after expiration of the `access_token`.
|
|
262
|
+
|
|
263
|
+
A new pair of access and refresh tokens should be used for further API requests and requests for token prolongation.
|
|
264
|
+
|
|
265
|
+
## Authorization request under another user
|
|
266
|
+
|
|
267
|
+
The following case is possible:
|
|
268
|
+
|
|
269
|
+
1. Application redirects a user to the site with the authorization request.
|
|
270
|
+
2. The user is already authorized on site and application already has an access granted.
|
|
271
|
+
3. The user will be proposed to continue the work under current account or login to another one.
|
|
272
|
+
|
|
273
|
+
If you need to replace step 3 by instant redirection with temporary token, you should add the parameter `skip_choose_account=true` to `/oauth/authorize...` request. Then the authorized user gets access immediately.
|
|
274
|
+
|
|
275
|
+
If you need to show the authorization form every time, you should add the parameter `force_login=true` to `/oauth/authorize...` request. In this case the authorization form appears even if the user is already authorized.
|
|
276
|
+
|
|
277
|
+
This feature may be useful for applications that provide services for applicants only. If an employer user tries to log in, the application may propose them to get access under another user.
|
|
278
|
+
|
|
279
|
+
Also, after the authorization the application can show a message:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
You logged in as %FirstName_LastName%. Is it not you?
|
|
283
|
+
```
|
|
284
|
+
and propose a link with `force_login=true` for authorization under another login.
|
|
285
|
+
|
|
286
|
+
## Authorization under different working account
|
|
287
|
+
|
|
288
|
+
To obtain information about getting a list of manager working accounts and working under different working accounts, see the [manager working account](#tag/Employer-managers/operation/get-manager-accounts) section.
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
<!-- ReDoc-Inject: <security-definitions> -->
|
|
292
|
+
`,
|
|
21
293
|
capabilities: {
|
|
22
294
|
tools: {},
|
|
23
295
|
logging: {},
|
|
24
296
|
},
|
|
25
|
-
instructions: `MCP Server for HeadHunter API - job search and vacancy management`,
|
|
26
297
|
});
|
|
27
298
|
const logger = {
|
|
28
299
|
log: (...message) => mcpServer.server.sendLoggingMessage({ level: 'info', data: message.join(' ') }),
|
|
29
300
|
error: (...message) => mcpServer.server.sendLoggingMessage({ level: 'error', data: message.join(' ') }),
|
|
30
301
|
debug: (...message) => mcpServer.server.sendLoggingMessage({ level: 'debug', data: message.join(' ') }),
|
|
31
302
|
};
|
|
32
|
-
// Axios client setup
|
|
33
303
|
const apiClient = axios.create({
|
|
34
|
-
baseURL: '
|
|
304
|
+
baseURL: '',
|
|
35
305
|
headers: {
|
|
36
306
|
Accept: 'application/json',
|
|
37
307
|
},
|
|
38
308
|
timeout: 30000,
|
|
39
309
|
});
|
|
40
|
-
// Add request interceptor for environment variables
|
|
41
310
|
apiClient.interceptors.request.use((config) => {
|
|
42
|
-
if (env.
|
|
43
|
-
config.headers['
|
|
311
|
+
if (env.HH_API_TOKEN) {
|
|
312
|
+
config.headers['Authorization'] = env.HH_API_TOKEN;
|
|
44
313
|
}
|
|
45
314
|
return config;
|
|
46
315
|
}, (error) => {
|
|
47
316
|
return Promise.reject(error);
|
|
48
317
|
});
|
|
49
|
-
function handleResult(data) {
|
|
50
|
-
return {
|
|
51
|
-
content: [
|
|
52
|
-
{
|
|
53
|
-
type: 'text',
|
|
54
|
-
text: JSON.stringify(data, null, 2),
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function handleError(error) {
|
|
60
|
-
console.error(error);
|
|
61
|
-
logger.error('Error occurred:', JSON.stringify(error));
|
|
62
|
-
if (axios.isAxiosError(error)) {
|
|
63
|
-
const message = error.response?.data?.description || error.message;
|
|
64
|
-
return {
|
|
65
|
-
isError: true,
|
|
66
|
-
content: [{ type: 'text', text: `API Error: ${message}` }],
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
isError: true,
|
|
71
|
-
content: [{ type: 'text', text: `Error: ${error}` }],
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
// Tools
|
|
75
|
-
mcpServer.tool('get-vacancies', `Search for vacancies`, {
|
|
76
|
-
page: z.number().optional(),
|
|
77
|
-
per_page: z.number().optional(),
|
|
78
|
-
text: z.string().optional(),
|
|
79
|
-
search_field: z.string().optional(),
|
|
80
|
-
experience: z.string().optional(),
|
|
81
|
-
employment: z.string().optional(),
|
|
82
|
-
schedule: z.string().optional(),
|
|
83
|
-
area: z.string().optional(),
|
|
84
|
-
metro: z.string().optional(),
|
|
85
|
-
professional_role: z.string().optional(),
|
|
86
|
-
industry: z.string().optional(),
|
|
87
|
-
employer_id: z.string().optional(),
|
|
88
|
-
currency: z.string().optional(),
|
|
89
|
-
salary: z.number().optional(),
|
|
90
|
-
label: z.string().optional(),
|
|
91
|
-
only_with_salary: z.boolean().optional(),
|
|
92
|
-
period: z.number().optional(),
|
|
93
|
-
date_from: z.string().optional(),
|
|
94
|
-
date_to: z.string().optional(),
|
|
95
|
-
top_lat: z.number().optional(),
|
|
96
|
-
bottom_lat: z.number().optional(),
|
|
97
|
-
left_lng: z.number().optional(),
|
|
98
|
-
right_lng: z.number().optional(),
|
|
99
|
-
order_by: z.string().optional(),
|
|
100
|
-
sort_point_lat: z.number().optional(),
|
|
101
|
-
sort_point_lng: z.number().optional(),
|
|
102
|
-
clusters: z.boolean().optional(),
|
|
103
|
-
describe_arguments: z.boolean().optional(),
|
|
104
|
-
no_magic: z.boolean().optional(),
|
|
105
|
-
premium: z.boolean().optional(),
|
|
106
|
-
responses_count_enabled: z.boolean().optional(),
|
|
107
|
-
part_time: z.string().optional(),
|
|
108
|
-
accept_temporary: z.boolean().optional(),
|
|
109
|
-
employment_form: z.string().optional(),
|
|
110
|
-
work_schedule_by_days: z.string().optional(),
|
|
111
|
-
working_hours: z.string().optional(),
|
|
112
|
-
work_format: z.string().optional(),
|
|
113
|
-
excluded_text: z.string().optional(),
|
|
114
|
-
education: z.string().optional(),
|
|
115
|
-
}, async (args) => {
|
|
116
|
-
try {
|
|
117
|
-
const response = await apiClient.get('/vacancies', {
|
|
118
|
-
params: args,
|
|
119
|
-
});
|
|
120
|
-
return handleResult(response.data);
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
return handleError(error);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
mcpServer.tool('get-dictionaries', `Directories of fields`, {}, async (args) => {
|
|
127
|
-
try {
|
|
128
|
-
const response = await apiClient.get('/dictionaries', {
|
|
129
|
-
params: args,
|
|
130
|
-
});
|
|
131
|
-
return handleResult(response.data);
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
return handleError(error);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
mcpServer.tool('get-areas', `Tree view of all regions`, {
|
|
138
|
-
additional_case: z.string().optional(),
|
|
139
|
-
}, async (args) => {
|
|
140
|
-
try {
|
|
141
|
-
const response = await apiClient.get('/areas', {
|
|
142
|
-
params: args,
|
|
143
|
-
});
|
|
144
|
-
return handleResult(response.data);
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
return handleError(error);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
mcpServer.tool('get-languages', `The list of all languages`, {}, async (args) => {
|
|
151
|
-
try {
|
|
152
|
-
const response = await apiClient.get('/languages', {
|
|
153
|
-
params: args,
|
|
154
|
-
});
|
|
155
|
-
return handleResult(response.data);
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
return handleError(error);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
mcpServer.tool('get-industries', `Industries`, {}, async (args) => {
|
|
162
|
-
try {
|
|
163
|
-
const response = await apiClient.get('/industries', {
|
|
164
|
-
params: args,
|
|
165
|
-
});
|
|
166
|
-
return handleResult(response.data);
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
return handleError(error);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
mcpServer.tool('get-metro', `The list of metro stations in all cities`, {}, async (args) => {
|
|
173
|
-
try {
|
|
174
|
-
const response = await apiClient.get('/metro', {
|
|
175
|
-
params: args,
|
|
176
|
-
});
|
|
177
|
-
return handleResult(response.data);
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
return handleError(error);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
mcpServer.tool('get-professional-roles', `Professional role directory`, {}, async (args) => {
|
|
184
|
-
try {
|
|
185
|
-
const response = await apiClient.get('/professional_roles', {
|
|
186
|
-
params: args,
|
|
187
|
-
});
|
|
188
|
-
return handleResult(response.data);
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
return handleError(error);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
mcpServer.tool('get-employers', `Employer search`, {
|
|
195
|
-
text: z.string().optional(),
|
|
196
|
-
area: z.string().optional(),
|
|
197
|
-
type: z.string().optional(),
|
|
198
|
-
only_with_vacancies: z.boolean().optional(),
|
|
199
|
-
sort_by: z.enum(['by_name', 'by_vacancies_open']).optional(),
|
|
200
|
-
page: z.number().optional(),
|
|
201
|
-
per_page: z.number().optional(),
|
|
202
|
-
}, async (args) => {
|
|
203
|
-
try {
|
|
204
|
-
const response = await apiClient.get('/employers', {
|
|
205
|
-
params: args,
|
|
206
|
-
});
|
|
207
|
-
return handleResult(response.data);
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
return handleError(error);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
318
|
async function main() {
|
|
214
319
|
const transport = new StdioServerTransport();
|
|
215
320
|
await mcpServer.server.connect(transport);
|
|
216
|
-
logger.log('HeadHunter MCP Server started');
|
|
321
|
+
logger.log('HeadHunter API MCP Server started');
|
|
217
322
|
}
|
|
218
323
|
main().catch((error) => {
|
|
219
324
|
console.error('Server error:', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sargonpiraev/hh-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "MCP Server for HeadHunter API - job search and vacancy management",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -8,17 +8,20 @@
|
|
|
8
8
|
"ai",
|
|
9
9
|
"assistant"
|
|
10
10
|
],
|
|
11
|
-
"main": "./build/
|
|
12
|
-
"types": "./build/
|
|
11
|
+
"main": "./build/index.js",
|
|
12
|
+
"types": "./build/index.d.ts",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"files": [
|
|
15
15
|
"build/",
|
|
16
16
|
"README.md",
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"hh-mcp-server": "./build/index.js"
|
|
21
|
+
},
|
|
19
22
|
"scripts": {
|
|
20
23
|
"test": "jest --passWithNoTests --coverage",
|
|
21
|
-
"dev": "tsx watch ./src/
|
|
24
|
+
"dev": "tsx watch ./src/index.ts",
|
|
22
25
|
"build": "tsc -p tsconfig.json",
|
|
23
26
|
"lint": "eslint ./src --ext .ts",
|
|
24
27
|
"format": "prettier --write .",
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
"commitlint": "commitlint --edit",
|
|
28
31
|
"typecheck": "tsc --noEmit",
|
|
29
32
|
"audit": "npm audit --omit=dev",
|
|
30
|
-
"inspect": "npx @modelcontextprotocol/inspector node ./build/
|
|
33
|
+
"inspect": "npx @modelcontextprotocol/inspector node ./build/index.js"
|
|
31
34
|
},
|
|
32
35
|
"engines": {
|
|
33
36
|
"node": ">=18.0.0"
|