@sargonpiraev/hh-mcp-server 1.16.0 → 1.18.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/build/main.js CHANGED
@@ -4,6 +4,8 @@ import cors from 'cors';
4
4
  import crypto from 'crypto';
5
5
  import { z } from 'zod';
6
6
  import { mcpServer, envSchema as baseEnvSchema } from './server.js';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
7
9
  const envSchema = baseEnvSchema.extend({
8
10
  PORT: z.string().optional().default('3000'),
9
11
  HOST: z.string().optional().default('localhost'),
@@ -15,8 +17,12 @@ const envSchema = baseEnvSchema.extend({
15
17
  const env = envSchema.parse(process.env);
16
18
  const app = express();
17
19
  const sessions = new Map();
18
- // Enable CORS for all routes
19
- app.use(cors());
20
+ const transports = {};
21
+ // Enable CORS for all routes and expose MCP session header
22
+ app.use(cors({
23
+ origin: '*', // Allow all origins
24
+ exposedHeaders: ['Mcp-Session-Id'],
25
+ }));
20
26
  app.use(express.json({ limit: '10mb' }));
21
27
  const logger = {
22
28
  log: (...message) => {
@@ -319,24 +325,64 @@ async function requireAuth(req, res, next) {
319
325
  // MCP endpoint - POST for JSON-RPC requests
320
326
  app.post('/mcp', requireAuth, async (req, res) => {
321
327
  try {
322
- const sessionId = getOrCreateSession(req);
323
- res.set('Mcp-Session-Id', sessionId);
324
- res.set('Content-Type', 'application/json');
325
- // Handle JSON-RPC request through MCP server
326
- const result = await mcpServer.server.request(req.body, z.any());
327
- res.json(result);
328
+ const sessionId = req.get('Mcp-Session-Id');
329
+ logger.log(sessionId ? `MCP request for session: ${sessionId}` : 'New MCP request');
330
+ let transport;
331
+ if (sessionId && transports[sessionId]) {
332
+ // Reuse existing transport
333
+ transport = transports[sessionId];
334
+ }
335
+ else if (!sessionId && isInitializeRequest(req.body)) {
336
+ // New initialization request
337
+ transport = new StreamableHTTPServerTransport({
338
+ sessionIdGenerator: () => crypto.randomUUID(),
339
+ enableJsonResponse: true, // Enable JSON response mode for simplicity
340
+ onsessioninitialized: (sessionId) => {
341
+ logger.log(`Session initialized with ID: ${sessionId}`);
342
+ transports[sessionId] = transport;
343
+ },
344
+ });
345
+ // Set up cleanup on close
346
+ transport.onclose = () => {
347
+ const sid = transport.sessionId;
348
+ if (sid && transports[sid]) {
349
+ logger.log(`Transport closed for session ${sid}`);
350
+ delete transports[sid];
351
+ }
352
+ };
353
+ // Connect transport to MCP server BEFORE handling request
354
+ await mcpServer.connect(transport);
355
+ await transport.handleRequest(req, res, req.body);
356
+ return; // Already handled
357
+ }
358
+ else {
359
+ // Invalid request - no session ID or not initialization request
360
+ res.status(400).json({
361
+ jsonrpc: '2.0',
362
+ error: {
363
+ code: -32000,
364
+ message: 'Bad Request: No valid session ID provided',
365
+ },
366
+ id: null,
367
+ });
368
+ return;
369
+ }
370
+ // Handle the request with existing transport
371
+ await transport.handleRequest(req, res, req.body);
328
372
  }
329
373
  catch (error) {
330
- console.error('MCP request error:', error);
331
- res.status(500).json({
332
- jsonrpc: '2.0',
333
- error: {
334
- code: -32603,
335
- message: 'Internal error',
336
- data: error instanceof Error ? error.message : String(error),
337
- },
338
- id: req.body?.id || null,
339
- });
374
+ logger.error('MCP request error:', error instanceof Error ? error.message : String(error));
375
+ if (!res.headersSent) {
376
+ res.status(500).json({
377
+ jsonrpc: '2.0',
378
+ error: {
379
+ code: -32603,
380
+ message: 'Internal error',
381
+ data: error instanceof Error ? error.message : String(error),
382
+ },
383
+ id: req.body?.id || null,
384
+ });
385
+ }
340
386
  }
341
387
  });
342
388
  // MCP endpoint - GET for SSE streams
@@ -407,12 +453,36 @@ async function main() {
407
453
  }
408
454
  }
409
455
  // Graceful shutdown
410
- process.on('SIGINT', () => {
456
+ process.on('SIGINT', async () => {
411
457
  logger.log('Shutting down MCP Server...');
458
+ // Close all active transports
459
+ for (const sessionId in transports) {
460
+ try {
461
+ logger.log(`Closing transport for session ${sessionId}`);
462
+ await transports[sessionId].close();
463
+ delete transports[sessionId];
464
+ }
465
+ catch (error) {
466
+ logger.error(`Error closing transport for session ${sessionId}:`, error instanceof Error ? error.message : String(error));
467
+ }
468
+ }
469
+ logger.log('Server shutdown complete');
412
470
  process.exit(0);
413
471
  });
414
- process.on('SIGTERM', () => {
472
+ process.on('SIGTERM', async () => {
415
473
  logger.log('Shutting down MCP Server...');
474
+ // Close all active transports
475
+ for (const sessionId in transports) {
476
+ try {
477
+ logger.log(`Closing transport for session ${sessionId}`);
478
+ await transports[sessionId].close();
479
+ delete transports[sessionId];
480
+ }
481
+ catch (error) {
482
+ logger.error(`Error closing transport for session ${sessionId}:`, error instanceof Error ? error.message : String(error));
483
+ }
484
+ }
485
+ logger.log('Server shutdown complete');
416
486
  process.exit(0);
417
487
  });
418
488
  main().catch((error) => {
package/build/server.js CHANGED
@@ -21,7 +21,7 @@ export const mcpServer = new McpServer({
21
21
  });
22
22
  export const env = envSchema.parse(process.env);
23
23
  export const apiClient = axios.create({
24
- baseURL: '',
24
+ baseURL: 'https://api.hh.ru',
25
25
  headers: {
26
26
  Accept: 'application/json',
27
27
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sargonpiraev/hh-mcp-server",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "HeadHunter API MCP Server provides seamless integration between AI tools and HeadHunter's comprehensive job search platform.",
5
5
  "keywords": [
6
6
  "mcp",