@douyinfe/semi-mcp 1.0.11 → 1.0.13

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 (2) hide show
  1. package/dist/http.js +120 -53
  2. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -1326,23 +1326,25 @@ function createMCPServer() {
1326
1326
  });
1327
1327
  return server;
1328
1328
  }
1329
+ const sessions = new Map();
1330
+ const SESSION_TIMEOUT = 1800000;
1329
1331
  function parseArgs() {
1330
1332
  const args = process.argv.slice(2);
1331
1333
  let port = 3000;
1332
- let host = '0.0.0.0';
1334
+ let hosts = [];
1333
1335
  let stateless = false;
1334
1336
  let timeout = 30;
1335
1337
  for(let i = 0; i < args.length; i++)if ('--port' === args[i] && args[i + 1]) {
1336
1338
  port = parseInt(args[i + 1], 10);
1337
1339
  i++;
1338
1340
  } else if ('--host' === args[i] && args[i + 1]) {
1339
- host = args[i + 1];
1341
+ hosts = args[i + 1].split(',').map((h)=>h.trim());
1340
1342
  i++;
1341
1343
  } else if ('-p' === args[i] && args[i + 1]) {
1342
1344
  port = parseInt(args[i + 1], 10);
1343
1345
  i++;
1344
1346
  } else if ('-h' === args[i] && args[i + 1]) {
1345
- host = args[i + 1];
1347
+ hosts = args[i + 1].split(',').map((h)=>h.trim());
1346
1348
  i++;
1347
1349
  } else if ('--stateless' === args[i]) stateless = true;
1348
1350
  else if ('--timeout' === args[i] && args[i + 1]) {
@@ -1359,7 +1361,12 @@ Usage: semi-mcp-http [options]
1359
1361
 
1360
1362
  Options:
1361
1363
  --port, -p PORT 指定监听端口 (默认: 3000)
1362
- --host, -h HOST 指定监听地址 (默认: 0.0.0.0)
1364
+ --host, -h HOSTS 指定监听地址,多个地址用逗号分隔 (默认: ::)
1365
+ :: 表示 IPv6 任意地址(自动支持 IPv4)
1366
+ 0.0.0.0 表示 IPv4 任意地址
1367
+ ::1 表示 IPv6 本地回环
1368
+ 127.0.0.1 表示 IPv4 本地回环
1369
+ 注意: 如果同时指定 0.0.0.0 和 ::,只使用 ::
1363
1370
  --stateless 无状态模式,不生成 session ID
1364
1371
  --timeout, -t MINUTES 会话超时时间,单位分钟 (默认: 30)
1365
1372
  --help 显示帮助信息
@@ -1373,19 +1380,36 @@ Endpoints:
1373
1380
  }
1374
1381
  return {
1375
1382
  port,
1376
- host,
1383
+ hosts,
1377
1384
  stateless,
1378
1385
  timeout
1379
1386
  };
1380
1387
  }
1388
+ function cleanupSessions() {
1389
+ const now = Date.now();
1390
+ for (const [sessionId, info] of sessions)if (now - info.lastActivity > SESSION_TIMEOUT) {
1391
+ sessions.delete(sessionId);
1392
+ console.log(`[${new Date().toISOString()}] 会话 ${sessionId} 已过期清理`);
1393
+ }
1394
+ }
1395
+ setInterval(cleanupSessions, 60000);
1381
1396
  async function main() {
1382
- const { port, host, stateless, timeout } = parseArgs();
1397
+ const { port, hosts, stateless, timeout } = parseArgs();
1383
1398
  const version = getPackageVersion();
1399
+ let processedHosts = hosts;
1400
+ const hasIPv4All = hosts.includes('0.0.0.0');
1401
+ const hasIPv6All = hosts.includes('::');
1402
+ if (hasIPv4All && hasIPv6All) {
1403
+ processedHosts = hosts.filter((h)=>'0.0.0.0' !== h);
1404
+ console.log(`[${new Date().toISOString()}] 检测到同时监听 IPv4 和 IPv6,使用 :: (IPv6) 统一监听`);
1405
+ }
1406
+ if (0 === processedHosts.length) {
1407
+ processedHosts = [
1408
+ '::'
1409
+ ];
1410
+ console.log(`[${new Date().toISOString()}] 使用默认配置: 监听 IPv6 (::)`);
1411
+ }
1384
1412
  const server = createMCPServer();
1385
- const transport = new StreamableHTTPServerTransport({
1386
- sessionIdGenerator: stateless ? void 0 : ()=>crypto.randomUUID()
1387
- });
1388
- await server.connect(transport);
1389
1413
  console.log(`[${new Date().toISOString()}] MCP 服务器已启动`);
1390
1414
  console.log(`[${new Date().toISOString()}] 模式: ${stateless ? '无状态 (Stateless)' : '有状态 (Stateful)'}`);
1391
1415
  const httpServer = createServer(async (req, res)=>{
@@ -1409,41 +1433,52 @@ async function main() {
1409
1433
  version,
1410
1434
  transport: 'streamable-http',
1411
1435
  stateless,
1412
- sessionTimeout: `${timeout} minutes`
1436
+ sessionTimeout: `${timeout} minutes`,
1437
+ activeSessions: sessions.size
1413
1438
  }));
1414
1439
  return;
1415
1440
  }
1416
1441
  if ('/mcp' === url.pathname) {
1417
- let body = '';
1418
- req.on('data', (chunk)=>{
1419
- body += chunk.toString();
1420
- });
1421
- await new Promise((resolve)=>{
1422
- req.on('end', async ()=>{
1423
- try {
1424
- const parsedBody = body ? JSON.parse(body) : void 0;
1425
- await transport.handleRequest(req, res, parsedBody);
1426
- console.log(`[${new Date().toISOString()}] ${req.method} ${url.pathname} - ${res.statusCode}`);
1427
- } catch (error) {
1428
- const errorMessage = error instanceof Error ? error.message : String(error);
1429
- console.error(`[${new Date().toISOString()}] 请求处理错误:`, errorMessage);
1430
- if (!res.headersSent) {
1431
- res.writeHead(500, {
1432
- 'Content-Type': 'application/json'
1433
- });
1434
- res.end(JSON.stringify({
1435
- jsonrpc: '2.0',
1436
- error: {
1437
- code: -32000,
1438
- message: errorMessage
1439
- },
1440
- id: null
1441
- }));
1442
- }
1443
- }
1444
- resolve();
1445
- });
1446
- });
1442
+ const sessionId = req.headers['mcp-session-id'];
1443
+ try {
1444
+ let transport;
1445
+ if (stateless) {
1446
+ transport = new StreamableHTTPServerTransport({
1447
+ sessionIdGenerator: void 0
1448
+ });
1449
+ await server.connect(transport);
1450
+ } else if (sessionId && sessions.has(sessionId)) {
1451
+ transport = sessions.get(sessionId).transport;
1452
+ sessions.get(sessionId).lastActivity = Date.now();
1453
+ } else {
1454
+ const newSessionId = crypto.randomUUID();
1455
+ transport = new StreamableHTTPServerTransport({
1456
+ sessionIdGenerator: ()=>newSessionId
1457
+ });
1458
+ await server.connect(transport);
1459
+ sessions.set(newSessionId, {
1460
+ transport,
1461
+ lastActivity: Date.now()
1462
+ });
1463
+ }
1464
+ await transport.handleRequest(req, res);
1465
+ } catch (error) {
1466
+ const errorMessage = error instanceof Error ? error.message : String(error);
1467
+ console.error(`[${new Date().toISOString()}] 请求处理错误:`, errorMessage);
1468
+ if (!res.headersSent) {
1469
+ res.writeHead(500, {
1470
+ 'Content-Type': 'application/json'
1471
+ });
1472
+ res.end(JSON.stringify({
1473
+ jsonrpc: '2.0',
1474
+ error: {
1475
+ code: -32000,
1476
+ message: errorMessage
1477
+ },
1478
+ id: null
1479
+ }));
1480
+ }
1481
+ }
1447
1482
  return;
1448
1483
  }
1449
1484
  if ('/' === url.pathname && 'GET' === req.method) {
@@ -1482,33 +1517,65 @@ async function main() {
1482
1517
  error: '未知的端点'
1483
1518
  }));
1484
1519
  });
1485
- httpServer.listen(port, host, ()=>{
1486
- console.log(`
1520
+ const servers = [];
1521
+ let startedCount = 0;
1522
+ console.log(`
1487
1523
  ╔══════════════════════════════════════════════════════════════╗
1488
1524
  ║ Semi MCP Server (Streamable HTTP) v${version.padEnd(10)} ║
1489
1525
  ╠══════════════════════════════════════════════════════════════╣
1490
- ║ 服务地址: http://${('0.0.0.0' === host ? 'localhost' : host).padEnd(15)}:${String(port).padEnd(5)} ║
1491
1526
  ║ 模式: ${stateless ? '无状态 (Stateless)' : '有状态 (Stateful) '} ║
1492
1527
  ║ 会话超时: ${String(timeout).padEnd(3)} 分钟 ║
1493
-
1494
- ║ 端点: ║
1528
+ ║`);
1529
+ const formatHost = (h)=>{
1530
+ if ('::' === h) return ':: (所有 IPv6)';
1531
+ if ('0.0.0.0' === h) return '0.0.0.0 (所有 IPv4)';
1532
+ if ('::1' === h) return '::1 (IPv6 本地)';
1533
+ if ('127.0.0.1' === h) return '127.0.0.1 (IPv4 本地)';
1534
+ return h;
1535
+ };
1536
+ processedHosts.forEach((host, index)=>{
1537
+ httpServer.on('error', (err)=>{
1538
+ const displayHost = formatHost(host);
1539
+ console.log(`║ ✗ 端点 ${index + 1}: http://${displayHost}:${port}`);
1540
+ console.error(`[${new Date().toISOString()}] 启动失败 [${host}]:`, err.message);
1541
+ });
1542
+ httpServer.listen(port, host, ()=>{
1543
+ startedCount++;
1544
+ const displayHost = formatHost(host);
1545
+ console.log(`║ ✓ 端点 ${index + 1}: http://${displayHost}:${port}`);
1546
+ if (startedCount === processedHosts.length) {
1547
+ console.log(`║ ║
1548
+ ║ 可用端点: ║
1495
1549
  ║ POST /mcp 发送 MCP 请求 ║
1496
1550
  ║ GET /mcp SSE 流 (服务器推送) ║
1497
1551
  ║ GET /health 健康检查 ║
1498
1552
  ╚══════════════════════════════════════════════════════════════╝
1499
1553
  `);
1554
+ console.log(`[${new Date().toISOString()}] 所有服务器已启动,监听 ${processedHosts.length} 个地址`);
1555
+ console.log(`[${new Date().toISOString()}] 总计监听: ${processedHosts.join(', ')}`);
1556
+ }
1557
+ });
1558
+ servers.push(httpServer);
1500
1559
  });
1501
1560
  const shutdown = async ()=>{
1502
1561
  console.log('\n正在关闭服务器...');
1503
- try {
1504
- await transport.close();
1505
- console.log('Transport 已关闭');
1562
+ for (const [sessionId, info] of sessions)try {
1563
+ await info.transport.close();
1564
+ console.log(`[${new Date().toISOString()}] 会话 ${sessionId} 已关闭`);
1506
1565
  } catch (error) {
1507
- console.error('关闭 transport 时出错:', error);
1566
+ console.error(`关闭会话 ${sessionId} 时出错:`, error);
1508
1567
  }
1509
- httpServer.close(()=>{
1510
- console.log('服务器已关闭');
1511
- process.exit(0);
1568
+ sessions.clear();
1569
+ let closedCount = 0;
1570
+ servers.forEach((server, index)=>{
1571
+ server.close(()=>{
1572
+ closedCount++;
1573
+ console.log(`[${new Date().toISOString()}] 服务器 ${index + 1}/${servers.length} 已关闭`);
1574
+ if (closedCount === servers.length) {
1575
+ console.log('所有服务器已关闭');
1576
+ process.exit(0);
1577
+ }
1578
+ });
1512
1579
  });
1513
1580
  };
1514
1581
  process.on('SIGINT', shutdown);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-mcp",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Semi Design MCP Server - Model Context Protocol server for Semi Design components and documentation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",