@douyinfe/semi-mcp 1.0.10 → 1.0.11

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 +50 -202
  2. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { createServer } from "http";
3
- import { randomUUID } from "crypto";
4
3
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
5
  import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -1368,7 +1367,6 @@ Options:
1368
1367
  Endpoints:
1369
1368
  POST /mcp MCP 消息端点 (Streamable HTTP)
1370
1369
  GET /mcp SSE 流端点 (用于服务器推送)
1371
- DELETE /mcp 关闭会话
1372
1370
  GET /health 健康检查端点
1373
1371
  `);
1374
1372
  process.exit(0);
@@ -1380,43 +1378,22 @@ Endpoints:
1380
1378
  timeout
1381
1379
  };
1382
1380
  }
1383
- const sessions = new Map();
1384
- function touchSession(sessionId) {
1385
- const session = sessions.get(sessionId);
1386
- if (session) session.lastActivity = Date.now();
1387
- }
1388
- async function cleanupExpiredSessions(timeoutMs) {
1389
- const now = Date.now();
1390
- const expiredSessions = [];
1391
- sessions.forEach((session, sessionId)=>{
1392
- if (now - session.lastActivity > timeoutMs) expiredSessions.push(sessionId);
1393
- });
1394
- for (const sessionId of expiredSessions){
1395
- const session = sessions.get(sessionId);
1396
- if (session) {
1397
- console.log(`[${new Date().toISOString()}] 会话超时清理: ${sessionId} (空闲 ${Math.round((now - session.lastActivity) / 1000 / 60)} 分钟)`);
1398
- try {
1399
- await session.transport.close();
1400
- } catch {}
1401
- sessions.delete(sessionId);
1402
- }
1403
- }
1404
- }
1405
1381
  async function main() {
1406
1382
  const { port, host, stateless, timeout } = parseArgs();
1407
1383
  const version = getPackageVersion();
1408
- const timeoutMs = 60 * timeout * 1000;
1409
- const cleanupInterval = setInterval(()=>{
1410
- cleanupExpiredSessions(timeoutMs).catch((error)=>{
1411
- console.error(`[${new Date().toISOString()}] 会话清理错误:`, error);
1412
- });
1413
- }, 60000);
1384
+ const server = createMCPServer();
1385
+ const transport = new StreamableHTTPServerTransport({
1386
+ sessionIdGenerator: stateless ? void 0 : ()=>crypto.randomUUID()
1387
+ });
1388
+ await server.connect(transport);
1389
+ console.log(`[${new Date().toISOString()}] MCP 服务器已启动`);
1390
+ console.log(`[${new Date().toISOString()}] 模式: ${stateless ? '无状态 (Stateless)' : '有状态 (Stateful)'}`);
1414
1391
  const httpServer = createServer(async (req, res)=>{
1415
1392
  const url = new URL(req.url || '/', `http://${req.headers.host}`);
1416
1393
  res.setHeader('Access-Control-Allow-Origin', '*');
1417
1394
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
1418
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, mcp-session-id');
1419
- res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id');
1395
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
1396
+ res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
1420
1397
  if ('OPTIONS' === req.method) {
1421
1398
  res.writeHead(204);
1422
1399
  res.end();
@@ -1432,168 +1409,42 @@ async function main() {
1432
1409
  version,
1433
1410
  transport: 'streamable-http',
1434
1411
  stateless,
1435
- sessionTimeout: `${timeout} minutes`,
1436
- activeSessions: sessions.size
1412
+ sessionTimeout: `${timeout} minutes`
1437
1413
  }));
1438
1414
  return;
1439
1415
  }
1440
1416
  if ('/mcp' === url.pathname) {
1441
- const sessionId = req.headers['mcp-session-id'];
1442
- if ('POST' === req.method) {
1443
- let body = '';
1444
- req.on('data', (chunk)=>{
1445
- body += chunk.toString();
1446
- });
1447
- await new Promise((resolve)=>{
1448
- req.on('end', async ()=>{
1449
- try {
1450
- const parsedBody = body ? JSON.parse(body) : void 0;
1451
- const isInitialize = parsedBody?.method === 'initialize';
1452
- if (isInitialize) {
1453
- const server = createMCPServer();
1454
- const transport = new StreamableHTTPServerTransport({
1455
- sessionIdGenerator: stateless ? void 0 : ()=>randomUUID()
1456
- });
1457
- await server.connect(transport);
1458
- transport.onclose = ()=>{
1459
- const sid = transport.sessionId;
1460
- if (sid) {
1461
- console.log(`[${new Date().toISOString()}] 会话关闭: ${sid}`);
1462
- sessions.delete(sid);
1463
- }
1464
- };
1465
- await transport.handleRequest(req, res, parsedBody);
1466
- const newSessionId = transport.sessionId;
1467
- if (newSessionId) {
1468
- sessions.set(newSessionId, {
1469
- transport,
1470
- lastActivity: Date.now()
1471
- });
1472
- console.log(`[${new Date().toISOString()}] 新会话创建: ${newSessionId}`);
1473
- }
1474
- } else {
1475
- if (!sessionId) {
1476
- res.writeHead(400, {
1477
- 'Content-Type': 'application/json'
1478
- });
1479
- res.end(JSON.stringify({
1480
- jsonrpc: '2.0',
1481
- error: {
1482
- code: -32000,
1483
- message: 'Bad Request: Missing mcp-session-id header'
1484
- },
1485
- id: parsedBody?.id ?? null
1486
- }));
1487
- resolve();
1488
- return;
1489
- }
1490
- const session = sessions.get(sessionId);
1491
- if (!session) {
1492
- res.writeHead(404, {
1493
- 'Content-Type': 'application/json'
1494
- });
1495
- res.end(JSON.stringify({
1496
- jsonrpc: '2.0',
1497
- error: {
1498
- code: -32000,
1499
- message: 'Not Found: Session not found or expired'
1500
- },
1501
- id: parsedBody?.id ?? null
1502
- }));
1503
- resolve();
1504
- return;
1505
- }
1506
- touchSession(sessionId);
1507
- await session.transport.handleRequest(req, res, parsedBody);
1508
- }
1509
- } catch (error) {
1510
- const errorMessage = error instanceof Error ? error.message : String(error);
1511
- console.error(`[${new Date().toISOString()}] 请求处理错误:`, errorMessage);
1512
- if (!res.headersSent) {
1513
- res.writeHead(500, {
1514
- 'Content-Type': 'application/json'
1515
- });
1516
- res.end(JSON.stringify({
1517
- jsonrpc: '2.0',
1518
- error: {
1519
- code: -32000,
1520
- message: errorMessage
1521
- },
1522
- id: null
1523
- }));
1524
- }
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
+ }));
1525
1442
  }
1526
- resolve();
1527
- });
1443
+ }
1444
+ resolve();
1528
1445
  });
1529
- return;
1530
- }
1531
- if ('GET' === req.method) {
1532
- if (!sessionId) {
1533
- res.writeHead(400, {
1534
- 'Content-Type': 'application/json'
1535
- });
1536
- res.end(JSON.stringify({
1537
- jsonrpc: '2.0',
1538
- error: {
1539
- code: -32000,
1540
- message: 'Bad Request: Missing mcp-session-id header'
1541
- },
1542
- id: null
1543
- }));
1544
- return;
1545
- }
1546
- const session = sessions.get(sessionId);
1547
- if (!session) {
1548
- res.writeHead(404, {
1549
- 'Content-Type': 'application/json'
1550
- });
1551
- res.end(JSON.stringify({
1552
- jsonrpc: '2.0',
1553
- error: {
1554
- code: -32000,
1555
- message: 'Not Found: Session not found'
1556
- },
1557
- id: null
1558
- }));
1559
- return;
1560
- }
1561
- touchSession(sessionId);
1562
- await session.transport.handleRequest(req, res);
1563
- return;
1564
- }
1565
- if ('DELETE' === req.method) {
1566
- if (!sessionId) {
1567
- res.writeHead(400, {
1568
- 'Content-Type': 'application/json'
1569
- });
1570
- res.end(JSON.stringify({
1571
- error: 'Missing mcp-session-id header'
1572
- }));
1573
- return;
1574
- }
1575
- const session = sessions.get(sessionId);
1576
- if (session) {
1577
- await session.transport.close();
1578
- sessions.delete(sessionId);
1579
- console.log(`[${new Date().toISOString()}] 会话已删除: ${sessionId}`);
1580
- res.writeHead(200, {
1581
- 'Content-Type': 'application/json'
1582
- });
1583
- res.end(JSON.stringify({
1584
- success: true,
1585
- message: '会话已关闭'
1586
- }));
1587
- } else {
1588
- res.writeHead(404, {
1589
- 'Content-Type': 'application/json'
1590
- });
1591
- res.end(JSON.stringify({
1592
- error: '会话不存在'
1593
- }));
1594
- }
1595
- return;
1596
- }
1446
+ });
1447
+ return;
1597
1448
  }
1598
1449
  if ('/' === url.pathname && 'GET' === req.method) {
1599
1450
  res.writeHead(200, {
@@ -1608,19 +1459,18 @@ async function main() {
1608
1459
  sessionTimeout: `${timeout} minutes`,
1609
1460
  endpoints: {
1610
1461
  mcp: {
1611
- POST: '/mcp - 发送 MCP 请求 (初始化请求无需 session ID)',
1612
- GET: '/mcp - SSE 流 (需要 mcp-session-id 头)',
1613
- DELETE: '/mcp - 关闭会话'
1462
+ POST: '/mcp - 发送 MCP 请求',
1463
+ GET: '/mcp - SSE 流 (需要 Mcp-Session-Id 头)'
1614
1464
  },
1615
1465
  health: '/health - 健康检查'
1616
1466
  },
1617
1467
  headers: {
1618
- 'mcp-session-id': '会话 ID (初始化响应后获取,后续请求需携带)'
1468
+ 'Mcp-Session-Id': '会话 ID (初始化响应后获取,后续请求需携带)'
1619
1469
  },
1620
1470
  usage: {
1621
- step1: '发送 initialize 请求获取 session ID',
1622
- step2: '后续请求携带 mcp-session-id ',
1623
- example: `curl -X POST http://${'0.0.0.0' === host ? 'localhost' : host}:${port}/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'`
1471
+ step1: '客户端发送 initialize 请求',
1472
+ step2: '服务器返回响应并包含 Mcp-Session-Id header',
1473
+ step3: '后续请求携带 Mcp-Session-Id header'
1624
1474
  }
1625
1475
  }, null, 2));
1626
1476
  return;
@@ -1644,20 +1494,18 @@ async function main() {
1644
1494
  ║ 端点: ║
1645
1495
  ║ POST /mcp 发送 MCP 请求 ║
1646
1496
  ║ GET /mcp SSE 流 (服务器推送) ║
1647
- ║ DELETE /mcp 关闭会话 ║
1648
1497
  ║ GET /health 健康检查 ║
1649
1498
  ╚══════════════════════════════════════════════════════════════╝
1650
1499
  `);
1651
1500
  });
1652
1501
  const shutdown = async ()=>{
1653
1502
  console.log('\n正在关闭服务器...');
1654
- clearInterval(cleanupInterval);
1655
- const sessionEntries = Array.from(sessions.entries());
1656
- for (const [sessionId, session] of sessionEntries){
1657
- console.log(`关闭会话: ${sessionId}`);
1658
- await session.transport.close();
1503
+ try {
1504
+ await transport.close();
1505
+ console.log('Transport 已关闭');
1506
+ } catch (error) {
1507
+ console.error('关闭 transport 时出错:', error);
1659
1508
  }
1660
- sessions.clear();
1661
1509
  httpServer.close(()=>{
1662
1510
  console.log('服务器已关闭');
1663
1511
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-mcp",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
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",