@atlisp/mcp 1.0.27 → 1.1.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/package.json +2 -2
- package/src/atlisp-mcp.js +85 -238
- package/src/cli.js +0 -59
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlisp/mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP Server for @lisp on CAD",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"atlisp-mcp": "./src/
|
|
7
|
+
"atlisp-mcp": "./src/atlisp-mcp.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "src/atlisp-mcp.js",
|
|
10
10
|
"exports": {
|
package/src/atlisp-mcp.js
CHANGED
|
@@ -1,10 +1,49 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { SERVER_VERSION } from './constants.js';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
if (process.argv[1]?.endsWith('atlisp-mcp.js')) {
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
if (args[i] === '--version' || args[i] === '-v') {
|
|
11
|
+
console.log(`atlisp-mcp v${SERVER_VERSION}`);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
14
|
+
console.log(`
|
|
15
|
+
@lisp MCP Server v${SERVER_VERSION}
|
|
16
|
+
|
|
17
|
+
Usage: atlisp-mcp [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-v, --version Show version number
|
|
21
|
+
--transport <type> Transport type: http or stdio (default: http)
|
|
22
|
+
--port <port> HTTP port (default: 8110)
|
|
23
|
+
--host <host> HTTP host (default: 0.0.0.0)
|
|
24
|
+
--stdio Shorthand for --transport stdio
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
atlisp-mcp # Start HTTP server on port 8110
|
|
29
|
+
atlisp-mcp --port 3000 # Start HTTP server on port 3000
|
|
30
|
+
atlisp-mcp --stdio # Start in stdio mode for MCP clients
|
|
31
|
+
atlisp-mcp --transport stdio # Same as --stdio
|
|
32
|
+
`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
1
38
|
import express from 'express';
|
|
2
39
|
import rawBody from 'raw-body';
|
|
3
40
|
import iconv from 'iconv-lite';
|
|
4
41
|
import rateLimit from 'express-rate-limit';
|
|
42
|
+
import { randomUUID } from 'crypto';
|
|
5
43
|
const { decode: charsetDecode, encodingExists } = iconv;
|
|
6
44
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
45
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
46
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
8
47
|
import {
|
|
9
48
|
ListToolsRequestSchema,
|
|
10
49
|
CallToolRequestSchema,
|
|
@@ -24,8 +63,6 @@ import * as functionHandlers from './handlers/function-handlers.js';
|
|
|
24
63
|
import { listResources, readResource } from './handlers/resource-handlers.js';
|
|
25
64
|
import { listPrompts, getPrompt } from './handlers/prompt-handlers.js';
|
|
26
65
|
import * as subMgr from './subscription-manager.js';
|
|
27
|
-
import { SseSession, SSE_EVENTS, createSseResponseHandler, parseLastEventId } from './sse-session.js';
|
|
28
|
-
import { SseSessionManager } from './sse-session-manager.js';
|
|
29
66
|
import { createError, toMcpError } from './errors.js';
|
|
30
67
|
import config from './config.js';
|
|
31
68
|
|
|
@@ -41,10 +78,9 @@ const SERVER_INFO = {
|
|
|
41
78
|
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }
|
|
42
79
|
};
|
|
43
80
|
|
|
44
|
-
const { port, host, transport, apiKey,
|
|
81
|
+
const { port, host, transport, apiKey, rateLimitWindow, rateLimitMax } = config;
|
|
45
82
|
|
|
46
83
|
if (apiKey) log('API Key authentication enabled');
|
|
47
|
-
if (enableSse) log('SSE mode enabled');
|
|
48
84
|
|
|
49
85
|
const mcpLimiter = rateLimit({
|
|
50
86
|
windowMs: rateLimitWindow,
|
|
@@ -54,16 +90,6 @@ const mcpLimiter = rateLimit({
|
|
|
54
90
|
message: { error: '请求过于频繁,请稍后再试' }
|
|
55
91
|
});
|
|
56
92
|
|
|
57
|
-
const sseSessionManager = new SseSessionManager();
|
|
58
|
-
|
|
59
|
-
subMgr.setNotify((uri, data) => {
|
|
60
|
-
sseSessionManager.broadcast(SSE_EVENTS.MESSAGE, {
|
|
61
|
-
jsonrpc: '2.0',
|
|
62
|
-
method: 'notifications/resources/updated',
|
|
63
|
-
params: { uri, data }
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
93
|
export { CAD_PLATFORMS, FILE_EXTENSIONS, MOCK_PACKAGES } from './constants.js';
|
|
68
94
|
|
|
69
95
|
const TOOL_HANDLERS = {
|
|
@@ -371,9 +397,15 @@ export async function startServer() {
|
|
|
371
397
|
if (config.transport === 'stdio') {
|
|
372
398
|
await initCadConnection();
|
|
373
399
|
const mcpServer = createMcpServer();
|
|
374
|
-
const transport = new StdioServerTransport();
|
|
400
|
+
const transport = new StdioServerTransport();
|
|
375
401
|
await mcpServer.connect(transport);
|
|
376
402
|
console.error(`${SERVER_NAME} 运行在 stdio 模式`);
|
|
403
|
+
|
|
404
|
+
subMgr.setNotify((uri, data) => {
|
|
405
|
+
mcpServer.sendResourceUpdated({ uri }).catch(err => {
|
|
406
|
+
log(`Failed to send resource update: ${err.message}`);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
377
409
|
} else {
|
|
378
410
|
const app = express();
|
|
379
411
|
|
|
@@ -396,7 +428,7 @@ const transport = new StdioServerTransport();
|
|
|
396
428
|
});
|
|
397
429
|
|
|
398
430
|
app.use((req, res, next) => {
|
|
399
|
-
log(`${req.method} ${req.path}
|
|
431
|
+
log(`${req.method} ${req.path}`);
|
|
400
432
|
next();
|
|
401
433
|
});
|
|
402
434
|
|
|
@@ -414,25 +446,6 @@ const transport = new StdioServerTransport();
|
|
|
414
446
|
res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
|
|
415
447
|
});
|
|
416
448
|
|
|
417
|
-
app.use((req, res, next) => {
|
|
418
|
-
const path = req.path;
|
|
419
|
-
if (path.startsWith('/%7B') || path.startsWith('/{"')) {
|
|
420
|
-
try {
|
|
421
|
-
const decoded = decodeURIComponent(path.slice(1));
|
|
422
|
-
if (decoded.startsWith('{"path":')) {
|
|
423
|
-
const data = JSON.parse(decoded);
|
|
424
|
-
const endpoint = data.path;
|
|
425
|
-
log(`Client sent SSE endpoint in URL path, extracting: ${endpoint}`);
|
|
426
|
-
req.url = endpoint + ((req._parsedUrl && req._parsedUrl.search) || '');
|
|
427
|
-
try { req.path = endpoint; } catch (_) { /* Express 5: req.path is read-only, req.url is sufficient */ }
|
|
428
|
-
}
|
|
429
|
-
} catch (e) {
|
|
430
|
-
log(`Failed to parse malformed path: ${e.message}`);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
next();
|
|
434
|
-
});
|
|
435
|
-
|
|
436
449
|
if (config.enableCors) {
|
|
437
450
|
app.use((req, res, next) => {
|
|
438
451
|
res.setHeader('Access-Control-Allow-Origin', config.corsOrigin);
|
|
@@ -446,224 +459,58 @@ const transport = new StdioServerTransport();
|
|
|
446
459
|
});
|
|
447
460
|
}
|
|
448
461
|
|
|
449
|
-
createMcpServer();
|
|
450
|
-
|
|
451
|
-
function setupSseConnection(req, res) {
|
|
452
|
-
log(`SSE connect: ${req.method} ${req.path} - session: ${req.get('mcp-session-id') || 'new'}`);
|
|
453
|
-
const sessionId = req.get('mcp-session-id');
|
|
454
|
-
const lastEventId = parseLastEventId(req);
|
|
455
|
-
const clientId = sessionId || crypto.randomUUID();
|
|
456
|
-
|
|
457
|
-
res.setHeader('mcp-session-id', clientId);
|
|
458
|
-
|
|
459
|
-
let session;
|
|
460
|
-
if (sessionId && sseSessionManager.has(sessionId)) {
|
|
461
|
-
session = sseSessionManager.get(sessionId);
|
|
462
|
-
} else {
|
|
463
|
-
session = new SseSession(res, { clientId, resumeFromId: lastEventId });
|
|
464
|
-
sseSessionManager.add(clientId, session);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (lastEventId && lastEventId > 0) {
|
|
468
|
-
session.sendEvent(SSE_EVENTS.MESSAGE, { type: 'resume', lastEventId }, lastEventId + 1);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
session.sendPing();
|
|
472
|
-
|
|
473
|
-
res.on('close', () => {
|
|
474
|
-
log(`SSE close: session ${clientId}`);
|
|
475
|
-
sseSessionManager.remove(clientId);
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (enableSse) {
|
|
480
|
-
app.get('/sse', (req, res) => setupSseConnection(req, res));
|
|
481
|
-
app.get('/mcp/sse', (req, res) => setupSseConnection(req, res));
|
|
482
|
-
|
|
483
|
-
app.post('/mcp/message', mcpLimiter, async (req, res) => {
|
|
484
|
-
const sessionId = req.get('mcp-session-id') || '';
|
|
485
|
-
const accept = req.get('Accept') || '';
|
|
486
|
-
const wantsSse = accept.includes('text/event-stream');
|
|
487
|
-
log(`POST /mcp/message - session: ${sessionId || 'new'} - wantsSSE: ${wantsSse}`);
|
|
488
|
-
|
|
489
|
-
let session;
|
|
490
|
-
if (sessionId && sseSessionManager.has(sessionId)) {
|
|
491
|
-
session = sseSessionManager.get(sessionId);
|
|
492
|
-
} else if (wantsSse) {
|
|
493
|
-
const clientId = sessionId || crypto.randomUUID();
|
|
494
|
-
res.setHeader('mcp-session-id', clientId);
|
|
495
|
-
session = new SseSession(res, { clientId });
|
|
496
|
-
sseSessionManager.add(clientId, session);
|
|
497
|
-
log(`POST /mcp/message - created new session: ${clientId}`);
|
|
498
|
-
res.on('close', () => {
|
|
499
|
-
log(`POST SSE close: session ${clientId}`);
|
|
500
|
-
sseSessionManager.remove(clientId);
|
|
501
|
-
});
|
|
502
|
-
}
|
|
462
|
+
const mcpServer = createMcpServer();
|
|
503
463
|
|
|
504
|
-
|
|
505
|
-
|
|
464
|
+
const transport = new StreamableHTTPServerTransport({
|
|
465
|
+
sessionIdGenerator: () => randomUUID(),
|
|
466
|
+
});
|
|
506
467
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
else res.status(202).end();
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
468
|
+
await mcpServer.connect(transport);
|
|
469
|
+
log('MCP Server connected to StreamableHTTPServerTransport');
|
|
512
470
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
} catch (e) {
|
|
517
|
-
log('SSE message error: ' + e.message);
|
|
518
|
-
session.sendError(-32603, e.message, id);
|
|
519
|
-
}
|
|
520
|
-
res.status(204).end();
|
|
521
|
-
} else {
|
|
522
|
-
await dispatchMcpMethod(
|
|
523
|
-
(response) => {
|
|
524
|
-
res.setHeader('Content-Type', 'application/json');
|
|
525
|
-
res.status(200).json(response);
|
|
526
|
-
},
|
|
527
|
-
method, params, id
|
|
528
|
-
);
|
|
529
|
-
}
|
|
471
|
+
subMgr.setNotify((uri, data) => {
|
|
472
|
+
mcpServer.sendResourceUpdated({ uri }).catch(err => {
|
|
473
|
+
log(`Failed to send resource update: ${err.message}`);
|
|
530
474
|
});
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
app.post('/mcp/stream', mcpLimiter, async (req, res) => {
|
|
534
|
-
const body = req.body || {};
|
|
535
|
-
const { method, params, id } = body;
|
|
536
|
-
const accept = req.get('Accept') || '';
|
|
537
|
-
const wantsSse = accept.includes('text/event-stream');
|
|
538
|
-
const sessionId = req.get('mcp-session-id') || '';
|
|
539
|
-
log(`POST /mcp/stream - session: ${sessionId} - method: ${method} - wantsSSE: ${wantsSse}`);
|
|
540
|
-
|
|
541
|
-
if (wantsSse) {
|
|
542
|
-
if (!enableSse) {
|
|
543
|
-
res.status(400).json({ error: 'SSE mode is disabled' });
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const clientId = sessionId || crypto.randomUUID();
|
|
548
|
-
res.setHeader('mcp-session-id', clientId);
|
|
549
|
-
|
|
550
|
-
let session;
|
|
551
|
-
if (sessionId && sseSessionManager.has(sessionId)) {
|
|
552
|
-
session = sseSessionManager.get(sessionId);
|
|
553
|
-
if (!id) {
|
|
554
|
-
res.status(204).end();
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
await handleMcpRequest(session, method, params, id);
|
|
558
|
-
res.status(204).end();
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
session = new SseSession(res, { clientId });
|
|
563
|
-
sseSessionManager.add(clientId, session);
|
|
564
|
-
|
|
565
|
-
if (!id) {
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
await handleMcpRequest(session, method, params, id);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
res.setHeader('mcp-session-id', sessionId || crypto.randomUUID());
|
|
573
|
-
const { send } = createSseResponseHandler(res, wantsSse);
|
|
574
|
-
|
|
575
|
-
if (!id) return res.status(204).end();
|
|
576
|
-
|
|
577
|
-
await dispatchMcpMethod(
|
|
578
|
-
(response) => send(response, true),
|
|
579
|
-
method, params, id
|
|
580
|
-
);
|
|
581
475
|
});
|
|
582
476
|
|
|
583
|
-
app.
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
477
|
+
app.all('/mcp', mcpLimiter, async (req, res) => {
|
|
478
|
+
try {
|
|
479
|
+
await transport.handleRequest(req, res, req.body);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
log(`Transport error: ${error.message}`);
|
|
482
|
+
if (!res.headersSent) {
|
|
483
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
589
484
|
}
|
|
590
|
-
setupSseConnection(req, res);
|
|
591
|
-
return;
|
|
592
485
|
}
|
|
593
|
-
res.status(200).json({
|
|
594
|
-
name: SERVER_NAME,
|
|
595
|
-
version: SERVER_VERSION,
|
|
596
|
-
transport: 'streamable-http',
|
|
597
|
-
sseEndpoint: '/sse',
|
|
598
|
-
messageEndpoint: '/mcp/message'
|
|
599
|
-
});
|
|
600
486
|
});
|
|
601
487
|
|
|
602
|
-
app.
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if (wantsSse) {
|
|
611
|
-
if (!enableSse) {
|
|
612
|
-
res.status(400).json({ error: 'SSE mode is disabled' });
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const clientId = sessionId || crypto.randomUUID();
|
|
617
|
-
res.setHeader('mcp-session-id', clientId);
|
|
618
|
-
|
|
619
|
-
let session;
|
|
620
|
-
if (sessionId && sseSessionManager.has(sessionId)) {
|
|
621
|
-
session = sseSessionManager.get(sessionId);
|
|
622
|
-
if (!id) {
|
|
623
|
-
res.status(204).end();
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
await handleMcpRequest(session, method, params, id);
|
|
627
|
-
res.status(204).end();
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
session = new SseSession(res, { clientId });
|
|
632
|
-
sseSessionManager.add(clientId, session);
|
|
633
|
-
|
|
634
|
-
if (!id) {
|
|
635
|
-
return;
|
|
488
|
+
app.all('/mcp/:path', mcpLimiter, async (req, res) => {
|
|
489
|
+
try {
|
|
490
|
+
await transport.handleRequest(req, res, req.body);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
log(`Transport error: ${error.message}`);
|
|
493
|
+
if (!res.headersSent) {
|
|
494
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
636
495
|
}
|
|
637
|
-
await handleMcpRequest(session, method, params, id);
|
|
638
|
-
return;
|
|
639
496
|
}
|
|
497
|
+
});
|
|
640
498
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (!id) return res.status(204).end();
|
|
645
|
-
|
|
646
|
-
await dispatchMcpMethod(
|
|
647
|
-
(response) => send(response, true),
|
|
648
|
-
method, params, id
|
|
649
|
-
);
|
|
499
|
+
const httpServer = app.listen(config.port, config.host, async () => {
|
|
500
|
+
await initCadConnection();
|
|
501
|
+
console.error(`@lisp MCP Server 启动 - http://${config.host}:${config.port} - Streamable HTTP (SDK)`);
|
|
650
502
|
});
|
|
651
503
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
httpServer.close(() => process.exit(0));
|
|
663
|
-
setTimeout(() => process.exit(1), 5000);
|
|
664
|
-
}
|
|
665
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
666
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
504
|
+
async function shutdown(signal) {
|
|
505
|
+
console.error(`\n收到 ${signal},正在优雅关闭...`);
|
|
506
|
+
await transport.close();
|
|
507
|
+
await cad.disconnect();
|
|
508
|
+
closeLog();
|
|
509
|
+
httpServer.close(() => process.exit(0));
|
|
510
|
+
setTimeout(() => process.exit(1), 5000);
|
|
511
|
+
}
|
|
512
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
513
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
667
514
|
}
|
|
668
515
|
}
|
|
669
516
|
|
package/src/cli.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { SERVER_VERSION } from './constants.js';
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const serverPath = path.join(__dirname, 'atlisp-mcp.js');
|
|
9
|
-
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
const env = { ...process.env };
|
|
12
|
-
|
|
13
|
-
for (let i = 0; i < args.length; i++) {
|
|
14
|
-
if (args[i] === '--version' || args[i] === '-v') {
|
|
15
|
-
console.log(`atlisp-mcp v${SERVER_VERSION}`);
|
|
16
|
-
process.exit(0);
|
|
17
|
-
} else if (args[i] === '--transport' && args[i + 1]) {
|
|
18
|
-
env.TRANSPORT = args[i + 1];
|
|
19
|
-
i++;
|
|
20
|
-
} else if (args[i] === '--port' && args[i + 1]) {
|
|
21
|
-
env.PORT = args[i + 1];
|
|
22
|
-
i++;
|
|
23
|
-
} else if (args[i] === '--host' && args[i + 1]) {
|
|
24
|
-
env.HOST = args[i + 1];
|
|
25
|
-
i++;
|
|
26
|
-
} else if (args[i] === '--stdio') {
|
|
27
|
-
env.TRANSPORT = 'stdio';
|
|
28
|
-
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
29
|
-
console.log(`
|
|
30
|
-
@lisp MCP Server v${SERVER_VERSION}
|
|
31
|
-
|
|
32
|
-
Usage: atlisp-mcp [options]
|
|
33
|
-
|
|
34
|
-
Options:
|
|
35
|
-
-v, --version Show version number
|
|
36
|
-
--transport <type> Transport type: http or stdio (default: http)
|
|
37
|
-
--port <port> HTTP port (default: 8110)
|
|
38
|
-
--host <host> HTTP host (default: 0.0.0.0)
|
|
39
|
-
--stdio Shorthand for --transport stdio
|
|
40
|
-
-h, --help Show this help message
|
|
41
|
-
|
|
42
|
-
Examples:
|
|
43
|
-
atlisp-mcp # Start HTTP server on port 8110
|
|
44
|
-
atlisp-mcp --port 3000 # Start HTTP server on port 3000
|
|
45
|
-
atlisp-mcp --stdio # Start in stdio mode for MCP clients
|
|
46
|
-
atlisp-mcp --transport stdio # Same as --stdio
|
|
47
|
-
`);
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const child = spawn('node', [serverPath], {
|
|
53
|
-
env,
|
|
54
|
-
stdio: 'inherit'
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
child.on('exit', (code) => {
|
|
58
|
-
process.exit(code);
|
|
59
|
-
});
|