@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 +90 -20
- package/build/server.js +1 -1
- package/package.json +1 -1
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
|
-
|
|
19
|
-
|
|
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 =
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
331
|
-
res.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
package/package.json
CHANGED