@douyinfe/semi-mcp 1.0.13 → 1.0.14
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/dist/http.js +94 -73
- package/package.json +2 -2
package/dist/http.js
CHANGED
|
@@ -1387,28 +1387,37 @@ Endpoints:
|
|
|
1387
1387
|
}
|
|
1388
1388
|
function cleanupSessions() {
|
|
1389
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
|
-
}
|
|
1390
|
+
for (const [sessionId, info] of sessions)if (now - info.lastActivity > SESSION_TIMEOUT) sessions.delete(sessionId);
|
|
1394
1391
|
}
|
|
1395
1392
|
setInterval(cleanupSessions, 60000);
|
|
1393
|
+
async function processSessionRequest(sessionId, handler) {
|
|
1394
|
+
const session = sessions.get(sessionId);
|
|
1395
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
1396
|
+
const requestPromise = handler();
|
|
1397
|
+
session.requestQueue.push(()=>requestPromise);
|
|
1398
|
+
if (!session.isProcessing) {
|
|
1399
|
+
session.isProcessing = true;
|
|
1400
|
+
try {
|
|
1401
|
+
while(session.requestQueue.length > 0){
|
|
1402
|
+
const nextHandler = session.requestQueue.shift();
|
|
1403
|
+
await nextHandler();
|
|
1404
|
+
}
|
|
1405
|
+
} finally{
|
|
1406
|
+
session.isProcessing = false;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
return requestPromise;
|
|
1410
|
+
}
|
|
1396
1411
|
async function main() {
|
|
1397
1412
|
const { port, hosts, stateless, timeout } = parseArgs();
|
|
1398
1413
|
const version = getPackageVersion();
|
|
1399
1414
|
let processedHosts = hosts;
|
|
1400
1415
|
const hasIPv4All = hosts.includes('0.0.0.0');
|
|
1401
1416
|
const hasIPv6All = hosts.includes('::');
|
|
1402
|
-
if (hasIPv4All && hasIPv6All)
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
if (0 === processedHosts.length) {
|
|
1407
|
-
processedHosts = [
|
|
1408
|
-
'::'
|
|
1409
|
-
];
|
|
1410
|
-
console.log(`[${new Date().toISOString()}] 使用默认配置: 监听 IPv6 (::)`);
|
|
1411
|
-
}
|
|
1417
|
+
if (hasIPv4All && hasIPv6All) processedHosts = hosts.filter((h)=>'0.0.0.0' !== h);
|
|
1418
|
+
if (0 === processedHosts.length) processedHosts = [
|
|
1419
|
+
'::'
|
|
1420
|
+
];
|
|
1412
1421
|
const server = createMCPServer();
|
|
1413
1422
|
console.log(`[${new Date().toISOString()}] MCP 服务器已启动`);
|
|
1414
1423
|
console.log(`[${new Date().toISOString()}] 模式: ${stateless ? '无状态 (Stateless)' : '有状态 (Stateful)'}`);
|
|
@@ -1440,45 +1449,69 @@ async function main() {
|
|
|
1440
1449
|
}
|
|
1441
1450
|
if ('/mcp' === url.pathname) {
|
|
1442
1451
|
const sessionId = req.headers['mcp-session-id'];
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1452
|
+
let body = '';
|
|
1453
|
+
req.on('data', (chunk)=>{
|
|
1454
|
+
body += chunk.toString();
|
|
1455
|
+
});
|
|
1456
|
+
await new Promise((resolve)=>{
|
|
1457
|
+
req.on('end', async ()=>{
|
|
1458
|
+
try {
|
|
1459
|
+
let transport;
|
|
1460
|
+
if (stateless) {
|
|
1461
|
+
transport = new StreamableHTTPServerTransport({
|
|
1462
|
+
sessionIdGenerator: void 0
|
|
1463
|
+
});
|
|
1464
|
+
await server.connect(transport);
|
|
1465
|
+
} else if (sessionId && sessions.has(sessionId)) {
|
|
1466
|
+
transport = sessions.get(sessionId).transport;
|
|
1467
|
+
sessions.get(sessionId).lastActivity = Date.now();
|
|
1468
|
+
} else {
|
|
1469
|
+
const newSessionId = sessionId || crypto.randomUUID();
|
|
1470
|
+
transport = new StreamableHTTPServerTransport({
|
|
1471
|
+
sessionIdGenerator: ()=>newSessionId
|
|
1472
|
+
});
|
|
1473
|
+
await server.connect(transport);
|
|
1474
|
+
sessions.set(newSessionId, {
|
|
1475
|
+
transport,
|
|
1476
|
+
lastActivity: Date.now(),
|
|
1477
|
+
requestQueue: [],
|
|
1478
|
+
isProcessing: false
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
if ('GET' === req.method) {
|
|
1482
|
+
transport.handleRequest(req, res).catch(()=>{});
|
|
1483
|
+
resolve();
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
let parsedBody;
|
|
1487
|
+
if (body.trim()) try {
|
|
1488
|
+
parsedBody = JSON.parse(body);
|
|
1489
|
+
} catch {
|
|
1490
|
+
parsedBody = void 0;
|
|
1491
|
+
}
|
|
1492
|
+
if (sessionId && sessions.has(sessionId)) await processSessionRequest(sessionId, async ()=>{
|
|
1493
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
1494
|
+
});
|
|
1495
|
+
else await transport.handleRequest(req, res, parsedBody);
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1498
|
+
if (!res.headersSent) {
|
|
1499
|
+
res.writeHead(500, {
|
|
1500
|
+
'Content-Type': 'application/json'
|
|
1501
|
+
});
|
|
1502
|
+
res.end(JSON.stringify({
|
|
1503
|
+
jsonrpc: '2.0',
|
|
1504
|
+
error: {
|
|
1505
|
+
code: -32000,
|
|
1506
|
+
message: errorMessage
|
|
1507
|
+
},
|
|
1508
|
+
id: null
|
|
1509
|
+
}));
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
resolve();
|
|
1513
|
+
});
|
|
1514
|
+
});
|
|
1482
1515
|
return;
|
|
1483
1516
|
}
|
|
1484
1517
|
if ('/' === url.pathname && 'GET' === req.method) {
|
|
@@ -1494,18 +1527,10 @@ async function main() {
|
|
|
1494
1527
|
sessionTimeout: `${timeout} minutes`,
|
|
1495
1528
|
endpoints: {
|
|
1496
1529
|
mcp: {
|
|
1497
|
-
POST: '/mcp
|
|
1498
|
-
GET: '/mcp
|
|
1530
|
+
POST: '/mcp',
|
|
1531
|
+
GET: '/mcp (SSE)'
|
|
1499
1532
|
},
|
|
1500
|
-
health: '/health
|
|
1501
|
-
},
|
|
1502
|
-
headers: {
|
|
1503
|
-
'Mcp-Session-Id': '会话 ID (初始化响应后获取,后续请求需携带)'
|
|
1504
|
-
},
|
|
1505
|
-
usage: {
|
|
1506
|
-
step1: '客户端发送 initialize 请求',
|
|
1507
|
-
step2: '服务器返回响应并包含 Mcp-Session-Id header',
|
|
1508
|
-
step3: '后续请求携带 Mcp-Session-Id header'
|
|
1533
|
+
health: '/health'
|
|
1509
1534
|
}
|
|
1510
1535
|
}, null, 2));
|
|
1511
1536
|
return;
|
|
@@ -1514,15 +1539,15 @@ async function main() {
|
|
|
1514
1539
|
'Content-Type': 'application/json'
|
|
1515
1540
|
});
|
|
1516
1541
|
res.end(JSON.stringify({
|
|
1517
|
-
error: '
|
|
1542
|
+
error: 'Unknown endpoint'
|
|
1518
1543
|
}));
|
|
1519
1544
|
});
|
|
1520
1545
|
const servers = [];
|
|
1521
1546
|
let startedCount = 0;
|
|
1522
1547
|
console.log(`
|
|
1523
|
-
|
|
1548
|
+
╔══════════════════════════════════════════════════════════════════════╗
|
|
1524
1549
|
║ Semi MCP Server (Streamable HTTP) v${version.padEnd(10)} ║
|
|
1525
|
-
|
|
1550
|
+
╠══════════════════════════════════════════════════════════════════════╣
|
|
1526
1551
|
║ 模式: ${stateless ? '无状态 (Stateless)' : '有状态 (Stateful) '} ║
|
|
1527
1552
|
║ 会话超时: ${String(timeout).padEnd(3)} 分钟 ║
|
|
1528
1553
|
║ ║`);
|
|
@@ -1549,7 +1574,7 @@ async function main() {
|
|
|
1549
1574
|
║ POST /mcp 发送 MCP 请求 ║
|
|
1550
1575
|
║ GET /mcp SSE 流 (服务器推送) ║
|
|
1551
1576
|
║ GET /health 健康检查 ║
|
|
1552
|
-
|
|
1577
|
+
╚══════════════════════════════════════════════════════════════════════╝
|
|
1553
1578
|
`);
|
|
1554
1579
|
console.log(`[${new Date().toISOString()}] 所有服务器已启动,监听 ${processedHosts.length} 个地址`);
|
|
1555
1580
|
console.log(`[${new Date().toISOString()}] 总计监听: ${processedHosts.join(', ')}`);
|
|
@@ -1561,16 +1586,12 @@ async function main() {
|
|
|
1561
1586
|
console.log('\n正在关闭服务器...');
|
|
1562
1587
|
for (const [sessionId, info] of sessions)try {
|
|
1563
1588
|
await info.transport.close();
|
|
1564
|
-
|
|
1565
|
-
} catch (error) {
|
|
1566
|
-
console.error(`关闭会话 ${sessionId} 时出错:`, error);
|
|
1567
|
-
}
|
|
1589
|
+
} catch {}
|
|
1568
1590
|
sessions.clear();
|
|
1569
1591
|
let closedCount = 0;
|
|
1570
1592
|
servers.forEach((server, index)=>{
|
|
1571
1593
|
server.close(()=>{
|
|
1572
1594
|
closedCount++;
|
|
1573
|
-
console.log(`[${new Date().toISOString()}] 服务器 ${index + 1}/${servers.length} 已关闭`);
|
|
1574
1595
|
if (closedCount === servers.length) {
|
|
1575
1596
|
console.log('所有服务器已关闭');
|
|
1576
1597
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douyinfe/semi-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
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",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"prepublishOnly": "npm run build && npm test"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
53
53
|
"@swc/core": "^1.15.8",
|
|
54
54
|
"oxc-parser": "^0.106.0"
|
|
55
55
|
},
|