@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.
Files changed (3) hide show
  1. package/README.md +31 -99
  2. package/build/index.js +282 -177
  3. package/package.json +8 -5
package/README.md CHANGED
@@ -1,133 +1,65 @@
1
1
  # HeadHunter MCP Server
2
2
 
3
- ![npm version](https://img.shields.io/npm/v/@sargonpiraev/hh-mcp-server)
4
- ![smithery badge](https://smithery.ai/badge/@sargonpiraev/hh-mcp-server)
5
-
6
3
  MCP Server for HeadHunter API - job search and vacancy management
7
4
 
8
- ## Features
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
- ## Installation
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
- npx -y @smithery/cli@latest install @sargonpiraev/hh-mcp-server --client <CLIENT_NAME>
10
+ npm install
32
11
  ```
33
12
 
34
- </details>
35
-
36
- <details>
37
- <summary><b>Install in Cursor</b></summary>
38
-
39
- #### Cursor One-Click Installation
40
-
41
- [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=@sargonpiraev/hh-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzYXJnb25waXJhZXYvaGFiaXRpZnktbWNwLXNlcnZlciJdLCJlbnYiOnsiSEFCSVRJRllfQVBJX0tFWSI6InlvdXItaGFiaXRpZnktYXBpLWtleSJ9fQo=)
13
+ 2. Set up environment:
42
14
 
43
- #### Manual Configuration
44
-
45
- Add to your Cursor `~/.cursor/mcp.json` file:
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
- </details>
62
-
63
- <details>
64
- <summary><b>Install in VS Code</b></summary>
65
-
66
- [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP-0098FF)](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
- ```json
71
- "mcp": {
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
- </details>
26
+ ## Configuration
86
27
 
87
- <details>
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": "npx",
97
- "args": ["-y", "@sargonpiraev/hh-mcp-server"],
34
+ "command": "node",
35
+ "args": ["/dist/index.js"],
98
36
  "env": {
99
- "HH_USER_AGENT": "your-HH_USER_AGENT-here"
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
- - **`get-vacancies`**: Search for vacancies
111
- - **`get-dictionaries`**: Directories of fields
112
- - **`get-areas`**: Tree view of all regions
113
- - **`get-languages`**: The list of all languages
114
- - **`get-industries`**: Industries
115
- - **`get-metro`**: The list of metro stations in all cities
116
- - **`get-professional-roles`**: Professional role directory
117
- - **`get-employers`**: Employer search
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
- **Total: 8 tools available** 🎯
55
+ ## Development
120
56
 
121
- ## Support This Project
122
-
123
- Hi! I'm Sargon, a software engineer passionate about AI tools and automation. I create open-source MCP servers to help developers integrate AI assistants with their favorite services.
124
-
125
- Your support helps me continue developing and maintaining these tools, and motivates me to create new integrations that make AI assistants even more powerful! 🚀
126
-
127
- [![Support on Boosty](https://img.shields.io/badge/Support-Boosty-orange?logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCA5TDEzLjA5IDE1Ljc0TDEyIDIyTDEwLjkxIDE1Ljc0TDQgOUwxMC45MSA4LjI2TDEyIDJaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)](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
- ## Connect with Author
63
+ ## License
130
64
 
131
- - 🌐 Visit [sargonpiraev.com](https://sargonpiraev.com)
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
- HH_USER_AGENT: z.string().min(1),
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 &#x60;https://api.hh.ru/&#x60;
28
+ * Requests for [any site of HeadHunter Groups of Companies](#section/General-information/Host-selection)
29
+ are available
30
+ * &lt;a name&#x3D;&quot;date-format&quot;&gt;&lt;/a&gt; Dates are formatted according to
31
+ [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601): &#x60;YYYY-MM-DDThh:mm:ss±hhmm&#x60;.
32
+
33
+
34
+ ## Request requirements
35
+
36
+ Your request should send the &#x60;User-Agent&#x60; header, but if your
37
+ HTTP client does not allow it, you can send an &#x60;HH-User-Agent&#x60; header. If no header is sent,
38
+ you will receive the &#x60;400 Bad Request&#x60; as a response.
39
+ By specifying the name of the application and the developer&#x27;s contact email in the header,
40
+ you will help us to contact you promptly if required.
41
+ The &#x60;User-Agent&#x60; and &#x60;HH-User-Agent&#x60; headers are interchangeable. If you send both headers,
42
+ only &#x60;HH-User-Agent&#x60; is processed.
43
+
44
+ &#x60;&#x60;&#x60;
45
+ User-Agent: MyApp/1.0 (my-app-feedback@example.com)
46
+ &#x60;&#x60;&#x60;
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
+ (&#x60;{&quot;name&quot;: &quot;Иванов Иван&quot;}&#x60;).
60
+
61
+ * It is also possible to use the ascii encoding with escaping
62
+ (&#x60;{&quot;name&quot;: &quot;\u0418\u0432\u0430\u043d\u043e\u0432 \u0418\u0432\u0430\u043d&quot;}&#x60;).
63
+
64
+ * Data types in certain fields are supplemented with additional conditions
65
+ described in each specific method. In JSON, data types are &#x60;string&#x60;, &#x60;number&#x60;,
66
+ &#x60;boolean&#x60;, &#x60;null&#x60;, &#x60;object&#x60;, &#x60;array&#x60;.
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 &#x60;503&#x60; and &#x60;500&#x60; 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
+ &#x60;page&#x3D;N&amp;per_page&#x3D;M&#x60; 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
+ &#x60;&#x60;&#x60;json
102
+ {
103
+ &quot;found&quot;: 1,
104
+ &quot;per_page&quot;: 1,
105
+ &quot;pages&quot;: 1,
106
+ &quot;page&quot;: 0,
107
+ &quot;items&quot;: [{}]
108
+ }
109
+ &#x60;&#x60;&#x60;
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 &#x60;https://api.hh.ru/&#x60;.
127
+
128
+ If you need to track the specific websites, you can add &#x60;?host&#x3D;&#x60; parameter to
129
+ your request. Default is &#x60;hh.ru&#x60;.
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 &#x60;https://api.hh.ru/locales?host&#x3D;hh.kz&#x60;.
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
+ &#x60;?callback&#x3D;callback_name&#x60; 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&#x3D;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 &#x60;redirect_uri&#x60;](#section/Authorization/Rules-for-generating-special-redirect_uri)
165
+ * [Authorization process](#section/Authorization/Authorization-process)
166
+ * [Successful obtaining temporary &#x60;authorization_code&#x60;](#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
+ &#x60;&#x60;&#x60;
180
+ https://hh.ru/oauth/authorize?
181
+ response_type&#x3D;code&amp;
182
+ client_id&#x3D;{client_id}&amp;
183
+ state&#x3D;{state}&amp;
184
+ redirect_uri&#x3D;{redirect_uri}
185
+ &#x60;&#x60;&#x60;
186
+
187
+ Required parameters:
188
+
189
+ * &#x60;response_type&#x3D;code&#x60; — specifies authorization method using the &#x60;authorization code&#x60;
190
+ * &#x60;client_id&#x60; — ID obtained when creating the application
191
+
192
+
193
+ Optional parameters:
194
+
195
+ * &#x60;state&#x60; — 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
+ * &#x60;redirect_uri&#x60; — 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 &#x60;urlencode&#x60; of the value.
201
+
202
+ ## Rules for generating special redirect_uri
203
+
204
+ For example, if &#x60;http://example.com/oauth&#x60; is saved in the settings, it is allowed to specify:
205
+
206
+ * &#x60;http://www.example.com/oauth&#x60; — a subdomain;
207
+ * &#x60;http://www.example.com/oauth/sub/path&#x60; — a detailed path;
208
+ * &#x60;http://example.com/oauth?lang&#x3D;RU&#x60; — an additional parameter;
209
+ * &#x60;http://www.example.com/oauth/sub/path?lang&#x3D;RU&#x60; — all the mentioned.
210
+
211
+ Forbidden:
212
+
213
+ * &#x60;https://example.com/oauth&#x60; — another protocols;
214
+ * &#x60;http://wwwexample.com/oauth&#x60; — another domains;
215
+ * &#x60;http://wwwexample.com/&#x60; — another paths;
216
+ * &#x60;http://example.com/oauths&#x60; — another paths;
217
+ * &#x60;http://example.com:80/oauths&#x60; — 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 &#x60;redirect_uri&#x60; with &#x60;?error&#x3D;access_denied&#x60; and
224
+ &#x60;state&#x3D;{state}&#x60;, if any.
225
+
226
+ &lt;a name&#x3D;&quot;get-authorization_code&quot;&gt;&lt;/a&gt;
227
+ ### Successful obtaining temporary &#x60;authorization_code&#x60;
228
+
229
+ If the user accepts the access request, the redirect link contains a temporary &#x60;authorization_code&#x60;:
230
+
231
+ &#x60;&#x60;&#x60;http
232
+ HTTP/1.1 302 FOUND
233
+ Location: {redirect_uri}?code&#x3D;{authorization_code}
234
+ &#x60;&#x60;&#x60;
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 &#x60;authorization_code&#x60; the application makes a server-server request &#x60;POST https://api.hh.ru/token&#x60; to exchange the &#x60;authorization_code&#x60; to an &#x60;access_token&#x60; (old &#x60;POST https://hh.ru/oauth/token&#x60; 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 &#x60;application/x-www-form-urlencoded&#x60; format with the corresponding &#x60;Content-Type&#x60; header.
245
+
246
+ The &#x60;authorization_code&#x60; has a short lifetime. When it expires, request another &#x60;authorization_code&#x60;.
247
+
248
+ ## Updating a pair of access and refresh tokens
249
+ The &#x60;access_token&#x60; also has a lifetime (&#x60;expires_in&#x60; parameter, in seconds). When it expires, the application should make a request with the &#x60;refresh_token&#x60; to obtain a new one.
250
+
251
+ The request should be done in &#x60;application/x-www-form-urlencoded&#x60; format.
252
+
253
+ &#x60;&#x60;&#x60;
254
+ POST https://api.hh.ru/token
255
+ &#x60;&#x60;&#x60;
256
+
257
+ (old &#x60;POST https://hh.ru/oauth/token&#x60; request should consider as deprecated)
258
+
259
+ The request body should contain [additional parameters](#required_parameters).
260
+
261
+ You can use the &#x60;refresh_token&#x60; only once and only after expiration of the &#x60;access_token&#x60;.
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 &#x60;skip_choose_account&#x3D;true&#x60; to &#x60;/oauth/authorize...&#x60; 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 &#x60;force_login&#x3D;true&#x60; to &#x60;/oauth/authorize...&#x60; 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
+ &#x60;&#x60;&#x60;
282
+ You logged in as %FirstName_LastName%. Is it not you?
283
+ &#x60;&#x60;&#x60;
284
+ and propose a link with &#x60;force_login&#x3D;true&#x60; 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
+ &lt;!-- ReDoc-Inject: &lt;security-definitions&gt; --&gt;
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: 'https://api.hh.ru',
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.HH_USER_AGENT) {
43
- config.headers['User-Agent'] = env.HH_USER_AGENT;
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.10.0",
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/main.js",
12
- "types": "./build/main.d.ts",
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/main.ts",
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/main.js"
33
+ "inspect": "npx @modelcontextprotocol/inspector node ./build/index.js"
31
34
  },
32
35
  "engines": {
33
36
  "node": ">=18.0.0"