@douyinfe/semi-mcp 1.0.12 → 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 +80 -42
- package/package.json +2 -2
package/dist/http.js
CHANGED
|
@@ -1326,6 +1326,8 @@ 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;
|
|
@@ -1383,27 +1385,40 @@ Endpoints:
|
|
|
1383
1385
|
timeout
|
|
1384
1386
|
};
|
|
1385
1387
|
}
|
|
1388
|
+
function cleanupSessions() {
|
|
1389
|
+
const now = Date.now();
|
|
1390
|
+
for (const [sessionId, info] of sessions)if (now - info.lastActivity > SESSION_TIMEOUT) sessions.delete(sessionId);
|
|
1391
|
+
}
|
|
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
|
+
}
|
|
1386
1411
|
async function main() {
|
|
1387
1412
|
const { port, hosts, stateless, timeout } = parseArgs();
|
|
1388
1413
|
const version = getPackageVersion();
|
|
1389
1414
|
let processedHosts = hosts;
|
|
1390
1415
|
const hasIPv4All = hosts.includes('0.0.0.0');
|
|
1391
1416
|
const hasIPv6All = hosts.includes('::');
|
|
1392
|
-
if (hasIPv4All && hasIPv6All)
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
if (0 === processedHosts.length) {
|
|
1397
|
-
processedHosts = [
|
|
1398
|
-
'::'
|
|
1399
|
-
];
|
|
1400
|
-
console.log(`[${new Date().toISOString()}] 使用默认配置: 监听 IPv6 (::)`);
|
|
1401
|
-
}
|
|
1417
|
+
if (hasIPv4All && hasIPv6All) processedHosts = hosts.filter((h)=>'0.0.0.0' !== h);
|
|
1418
|
+
if (0 === processedHosts.length) processedHosts = [
|
|
1419
|
+
'::'
|
|
1420
|
+
];
|
|
1402
1421
|
const server = createMCPServer();
|
|
1403
|
-
const transport = new StreamableHTTPServerTransport({
|
|
1404
|
-
sessionIdGenerator: stateless ? void 0 : ()=>crypto.randomUUID()
|
|
1405
|
-
});
|
|
1406
|
-
await server.connect(transport);
|
|
1407
1422
|
console.log(`[${new Date().toISOString()}] MCP 服务器已启动`);
|
|
1408
1423
|
console.log(`[${new Date().toISOString()}] 模式: ${stateless ? '无状态 (Stateless)' : '有状态 (Stateful)'}`);
|
|
1409
1424
|
const httpServer = createServer(async (req, res)=>{
|
|
@@ -1427,11 +1442,13 @@ async function main() {
|
|
|
1427
1442
|
version,
|
|
1428
1443
|
transport: 'streamable-http',
|
|
1429
1444
|
stateless,
|
|
1430
|
-
sessionTimeout: `${timeout} minutes
|
|
1445
|
+
sessionTimeout: `${timeout} minutes`,
|
|
1446
|
+
activeSessions: sessions.size
|
|
1431
1447
|
}));
|
|
1432
1448
|
return;
|
|
1433
1449
|
}
|
|
1434
1450
|
if ('/mcp' === url.pathname) {
|
|
1451
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
1435
1452
|
let body = '';
|
|
1436
1453
|
req.on('data', (chunk)=>{
|
|
1437
1454
|
body += chunk.toString();
|
|
@@ -1439,12 +1456,45 @@ async function main() {
|
|
|
1439
1456
|
await new Promise((resolve)=>{
|
|
1440
1457
|
req.on('end', async ()=>{
|
|
1441
1458
|
try {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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);
|
|
1445
1496
|
} catch (error) {
|
|
1446
1497
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1447
|
-
console.error(`[${new Date().toISOString()}] 请求处理错误:`, errorMessage);
|
|
1448
1498
|
if (!res.headersSent) {
|
|
1449
1499
|
res.writeHead(500, {
|
|
1450
1500
|
'Content-Type': 'application/json'
|
|
@@ -1477,18 +1527,10 @@ async function main() {
|
|
|
1477
1527
|
sessionTimeout: `${timeout} minutes`,
|
|
1478
1528
|
endpoints: {
|
|
1479
1529
|
mcp: {
|
|
1480
|
-
POST: '/mcp
|
|
1481
|
-
GET: '/mcp
|
|
1530
|
+
POST: '/mcp',
|
|
1531
|
+
GET: '/mcp (SSE)'
|
|
1482
1532
|
},
|
|
1483
|
-
health: '/health
|
|
1484
|
-
},
|
|
1485
|
-
headers: {
|
|
1486
|
-
'Mcp-Session-Id': '会话 ID (初始化响应后获取,后续请求需携带)'
|
|
1487
|
-
},
|
|
1488
|
-
usage: {
|
|
1489
|
-
step1: '客户端发送 initialize 请求',
|
|
1490
|
-
step2: '服务器返回响应并包含 Mcp-Session-Id header',
|
|
1491
|
-
step3: '后续请求携带 Mcp-Session-Id header'
|
|
1533
|
+
health: '/health'
|
|
1492
1534
|
}
|
|
1493
1535
|
}, null, 2));
|
|
1494
1536
|
return;
|
|
@@ -1497,15 +1539,15 @@ async function main() {
|
|
|
1497
1539
|
'Content-Type': 'application/json'
|
|
1498
1540
|
});
|
|
1499
1541
|
res.end(JSON.stringify({
|
|
1500
|
-
error: '
|
|
1542
|
+
error: 'Unknown endpoint'
|
|
1501
1543
|
}));
|
|
1502
1544
|
});
|
|
1503
1545
|
const servers = [];
|
|
1504
1546
|
let startedCount = 0;
|
|
1505
1547
|
console.log(`
|
|
1506
|
-
|
|
1548
|
+
╔══════════════════════════════════════════════════════════════════════╗
|
|
1507
1549
|
║ Semi MCP Server (Streamable HTTP) v${version.padEnd(10)} ║
|
|
1508
|
-
|
|
1550
|
+
╠══════════════════════════════════════════════════════════════════════╣
|
|
1509
1551
|
║ 模式: ${stateless ? '无状态 (Stateless)' : '有状态 (Stateful) '} ║
|
|
1510
1552
|
║ 会话超时: ${String(timeout).padEnd(3)} 分钟 ║
|
|
1511
1553
|
║ ║`);
|
|
@@ -1532,7 +1574,7 @@ async function main() {
|
|
|
1532
1574
|
║ POST /mcp 发送 MCP 请求 ║
|
|
1533
1575
|
║ GET /mcp SSE 流 (服务器推送) ║
|
|
1534
1576
|
║ GET /health 健康检查 ║
|
|
1535
|
-
|
|
1577
|
+
╚══════════════════════════════════════════════════════════════════════╝
|
|
1536
1578
|
`);
|
|
1537
1579
|
console.log(`[${new Date().toISOString()}] 所有服务器已启动,监听 ${processedHosts.length} 个地址`);
|
|
1538
1580
|
console.log(`[${new Date().toISOString()}] 总计监听: ${processedHosts.join(', ')}`);
|
|
@@ -1540,20 +1582,16 @@ async function main() {
|
|
|
1540
1582
|
});
|
|
1541
1583
|
servers.push(httpServer);
|
|
1542
1584
|
});
|
|
1543
|
-
processedHosts.length;
|
|
1544
1585
|
const shutdown = async ()=>{
|
|
1545
1586
|
console.log('\n正在关闭服务器...');
|
|
1546
|
-
try {
|
|
1547
|
-
await transport.close();
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
console.error('关闭 transport 时出错:', error);
|
|
1551
|
-
}
|
|
1587
|
+
for (const [sessionId, info] of sessions)try {
|
|
1588
|
+
await info.transport.close();
|
|
1589
|
+
} catch {}
|
|
1590
|
+
sessions.clear();
|
|
1552
1591
|
let closedCount = 0;
|
|
1553
1592
|
servers.forEach((server, index)=>{
|
|
1554
1593
|
server.close(()=>{
|
|
1555
1594
|
closedCount++;
|
|
1556
|
-
console.log(`[${new Date().toISOString()}] 服务器 ${index + 1}/${servers.length} 已关闭`);
|
|
1557
1595
|
if (closedCount === servers.length) {
|
|
1558
1596
|
console.log('所有服务器已关闭');
|
|
1559
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
|
},
|