@mako10k/shell-server 0.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/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/backoffice/index.d.ts +2 -0
- package/dist/backoffice/index.d.ts.map +1 -0
- package/dist/backoffice/index.js +47 -0
- package/dist/backoffice/index.js.map +1 -0
- package/dist/backoffice/server.d.ts +45 -0
- package/dist/backoffice/server.d.ts.map +1 -0
- package/dist/backoffice/server.js +610 -0
- package/dist/backoffice/server.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +525 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/config-manager.d.ts +80 -0
- package/dist/core/config-manager.d.ts.map +1 -0
- package/dist/core/config-manager.js +218 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/enhanced-history-manager.d.ts +84 -0
- package/dist/core/enhanced-history-manager.d.ts.map +1 -0
- package/dist/core/enhanced-history-manager.js +319 -0
- package/dist/core/enhanced-history-manager.js.map +1 -0
- package/dist/core/file-manager.d.ts +79 -0
- package/dist/core/file-manager.d.ts.map +1 -0
- package/dist/core/file-manager.js +338 -0
- package/dist/core/file-manager.js.map +1 -0
- package/dist/core/file-storage-subscriber.d.ts +38 -0
- package/dist/core/file-storage-subscriber.d.ts.map +1 -0
- package/dist/core/file-storage-subscriber.js +132 -0
- package/dist/core/file-storage-subscriber.js.map +1 -0
- package/dist/core/monitoring-manager.d.ts +32 -0
- package/dist/core/monitoring-manager.d.ts.map +1 -0
- package/dist/core/monitoring-manager.js +296 -0
- package/dist/core/monitoring-manager.js.map +1 -0
- package/dist/core/process-manager.d.ts +105 -0
- package/dist/core/process-manager.d.ts.map +1 -0
- package/dist/core/process-manager.js +1374 -0
- package/dist/core/process-manager.js.map +1 -0
- package/dist/core/realtime-stream-subscriber.d.ts +93 -0
- package/dist/core/realtime-stream-subscriber.d.ts.map +1 -0
- package/dist/core/realtime-stream-subscriber.js +200 -0
- package/dist/core/realtime-stream-subscriber.js.map +1 -0
- package/dist/core/remote-http-client.d.ts +15 -0
- package/dist/core/remote-http-client.d.ts.map +1 -0
- package/dist/core/remote-http-client.js +60 -0
- package/dist/core/remote-http-client.js.map +1 -0
- package/dist/core/remote-process-service.d.ts +50 -0
- package/dist/core/remote-process-service.d.ts.map +1 -0
- package/dist/core/remote-process-service.js +20 -0
- package/dist/core/remote-process-service.js.map +1 -0
- package/dist/core/server-manager.d.ts +71 -0
- package/dist/core/server-manager.d.ts.map +1 -0
- package/dist/core/server-manager.js +680 -0
- package/dist/core/server-manager.js.map +1 -0
- package/dist/core/stream-publisher.d.ts +75 -0
- package/dist/core/stream-publisher.d.ts.map +1 -0
- package/dist/core/stream-publisher.js +127 -0
- package/dist/core/stream-publisher.js.map +1 -0
- package/dist/core/streaming-pipeline-reader.d.ts +67 -0
- package/dist/core/streaming-pipeline-reader.d.ts.map +1 -0
- package/dist/core/streaming-pipeline-reader.js +191 -0
- package/dist/core/streaming-pipeline-reader.js.map +1 -0
- package/dist/core/terminal-manager.d.ts +96 -0
- package/dist/core/terminal-manager.d.ts.map +1 -0
- package/dist/core/terminal-manager.js +515 -0
- package/dist/core/terminal-manager.js.map +1 -0
- package/dist/daemon/server.d.ts +8 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +416 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/uds-transport.d.ts +31 -0
- package/dist/daemon/uds-transport.d.ts.map +1 -0
- package/dist/daemon/uds-transport.js +149 -0
- package/dist/daemon/uds-transport.js.map +1 -0
- package/dist/executor/server.d.ts +20 -0
- package/dist/executor/server.d.ts.map +1 -0
- package/dist/executor/server.js +375 -0
- package/dist/executor/server.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/daemon-runtime.d.ts +4 -0
- package/dist/runtime/daemon-runtime.d.ts.map +1 -0
- package/dist/runtime/daemon-runtime.js +4 -0
- package/dist/runtime/daemon-runtime.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/tool-runtime.d.ts +52 -0
- package/dist/runtime/tool-runtime.d.ts.map +1 -0
- package/dist/runtime/tool-runtime.js +161 -0
- package/dist/runtime/tool-runtime.js.map +1 -0
- package/dist/security/chat-completion-adapter.d.ts +443 -0
- package/dist/security/chat-completion-adapter.d.ts.map +1 -0
- package/dist/security/chat-completion-adapter.js +475 -0
- package/dist/security/chat-completion-adapter.js.map +1 -0
- package/dist/security/enhanced-evaluator.d.ts +139 -0
- package/dist/security/enhanced-evaluator.d.ts.map +1 -0
- package/dist/security/enhanced-evaluator.js +1208 -0
- package/dist/security/enhanced-evaluator.js.map +1 -0
- package/dist/security/evaluator-types.d.ts +614 -0
- package/dist/security/evaluator-types.d.ts.map +1 -0
- package/dist/security/evaluator-types.js +124 -0
- package/dist/security/evaluator-types.js.map +1 -0
- package/dist/security/manager.d.ts +76 -0
- package/dist/security/manager.d.ts.map +1 -0
- package/dist/security/manager.js +445 -0
- package/dist/security/manager.js.map +1 -0
- package/dist/security/security-llm-prompt-generator.d.ts +105 -0
- package/dist/security/security-llm-prompt-generator.d.ts.map +1 -0
- package/dist/security/security-llm-prompt-generator.js +323 -0
- package/dist/security/security-llm-prompt-generator.js.map +1 -0
- package/dist/security/security-tools.d.ts +174 -0
- package/dist/security/security-tools.d.ts.map +1 -0
- package/dist/security/security-tools.js +159 -0
- package/dist/security/security-tools.js.map +1 -0
- package/dist/security/validator-criteria-manager.d.ts +47 -0
- package/dist/security/validator-criteria-manager.d.ts.map +1 -0
- package/dist/security/validator-criteria-manager.js +169 -0
- package/dist/security/validator-criteria-manager.js.map +1 -0
- package/dist/tools/shell-tools.d.ts +474 -0
- package/dist/tools/shell-tools.d.ts.map +1 -0
- package/dist/tools/shell-tools.js +861 -0
- package/dist/tools/shell-tools.js.map +1 -0
- package/dist/types/enhanced-security.d.ts +529 -0
- package/dist/types/enhanced-security.d.ts.map +1 -0
- package/dist/types/enhanced-security.js +286 -0
- package/dist/types/enhanced-security.js.map +1 -0
- package/dist/types/index.d.ts +282 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +158 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/quick-schemas.d.ts +177 -0
- package/dist/types/quick-schemas.d.ts.map +1 -0
- package/dist/types/quick-schemas.js +113 -0
- package/dist/types/quick-schemas.js.map +1 -0
- package/dist/types/response-schemas.d.ts +41 -0
- package/dist/types/response-schemas.d.ts.map +1 -0
- package/dist/types/response-schemas.js +41 -0
- package/dist/types/response-schemas.js.map +1 -0
- package/dist/types/schemas.d.ts +578 -0
- package/dist/types/schemas.d.ts.map +1 -0
- package/dist/types/schemas.js +498 -0
- package/dist/types/schemas.js.map +1 -0
- package/dist/utils/criteria-manager.d.ts +47 -0
- package/dist/utils/criteria-manager.d.ts.map +1 -0
- package/dist/utils/criteria-manager.js +228 -0
- package/dist/utils/criteria-manager.js.map +1 -0
- package/dist/utils/errors.d.ts +27 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +67 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/helpers.d.ts +85 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +400 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/json-repair.d.ts +23 -0
- package/dist/utils/json-repair.d.ts.map +1 -0
- package/dist/utils/json-repair.js +208 -0
- package/dist/utils/json-repair.js.map +1 -0
- package/dist/utils/process-utils.d.ts +31 -0
- package/dist/utils/process-utils.d.ts.map +1 -0
- package/dist/utils/process-utils.js +217 -0
- package/dist/utils/process-utils.js.map +1 -0
- package/dist/utils/server-helpers.d.ts +4 -0
- package/dist/utils/server-helpers.d.ts.map +1 -0
- package/dist/utils/server-helpers.js +10 -0
- package/dist/utils/server-helpers.js.map +1 -0
- package/dist/utils/sse.d.ts +2 -0
- package/dist/utils/sse.d.ts.map +1 -0
- package/dist/utils/sse.js +6 -0
- package/dist/utils/sse.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-manager.d.ts","sourceRoot":"","sources":["../../src/core/server-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
import * as net from 'net';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { MCPShellError } from '../utils/errors.js';
|
|
10
|
+
const NOT_IMPLEMENTED_MESSAGE = 'Server management layer is not implemented yet.';
|
|
11
|
+
const DEFAULT_BRANCH = 'main';
|
|
12
|
+
const SOCKET_FILE_NAME = 'daemon.sock';
|
|
13
|
+
const SOCKET_CONNECT_TIMEOUT_MS = 250;
|
|
14
|
+
const SOCKET_READY_TIMEOUT_MS = 1000;
|
|
15
|
+
const SOCKET_READY_INTERVAL_MS = 50;
|
|
16
|
+
const SOCKET_REQUEST_TIMEOUT_MS = 1000;
|
|
17
|
+
export class StubServerManager {
|
|
18
|
+
createdAt = new Date().toISOString();
|
|
19
|
+
servers = new Map();
|
|
20
|
+
getBranch() {
|
|
21
|
+
return process.env['MCP_SHELL_SERVER_BRANCH'] || DEFAULT_BRANCH;
|
|
22
|
+
}
|
|
23
|
+
getRuntimeRoot() {
|
|
24
|
+
const runtimeDir = process.env['XDG_RUNTIME_DIR'] || os.tmpdir();
|
|
25
|
+
return path.join(runtimeDir, 'mcp-shell');
|
|
26
|
+
}
|
|
27
|
+
isDaemonEnabled() {
|
|
28
|
+
// Default ON to enable auto-start/reattach behavior.
|
|
29
|
+
// Opt-out explicitly with MCP_SHELL_DAEMON_ENABLED=false.
|
|
30
|
+
return process.env['MCP_SHELL_DAEMON_ENABLED'] !== 'false';
|
|
31
|
+
}
|
|
32
|
+
hashCwd(cwd) {
|
|
33
|
+
return crypto.createHash('sha256').update(path.resolve(cwd)).digest('hex');
|
|
34
|
+
}
|
|
35
|
+
buildSocketPath(cwd, branch) {
|
|
36
|
+
const runtimeRoot = this.getRuntimeRoot();
|
|
37
|
+
const cwdHash = this.hashCwd(cwd);
|
|
38
|
+
return path.join(runtimeRoot, cwdHash, branch, SOCKET_FILE_NAME);
|
|
39
|
+
}
|
|
40
|
+
parseServerId(serverId) {
|
|
41
|
+
const [hash, branch] = serverId.split(':');
|
|
42
|
+
if (!hash || !branch) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return { hash, branch };
|
|
46
|
+
}
|
|
47
|
+
buildSocketPathFromServerId(serverId) {
|
|
48
|
+
const parsed = this.parseServerId(serverId);
|
|
49
|
+
if (!parsed) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return path.join(this.getRuntimeRoot(), parsed.hash, parsed.branch, SOCKET_FILE_NAME);
|
|
53
|
+
}
|
|
54
|
+
resolveDaemonEntry() {
|
|
55
|
+
const override = process.env['MCP_SHELL_DAEMON_ENTRY'];
|
|
56
|
+
if (override) {
|
|
57
|
+
return override;
|
|
58
|
+
}
|
|
59
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
60
|
+
const packagedCandidate = path.resolve(moduleDir, '../daemon/server.js');
|
|
61
|
+
const candidates = [
|
|
62
|
+
// When running from a packaged build (e.g., node_modules), daemon lives next to dist/*.
|
|
63
|
+
packagedCandidate,
|
|
64
|
+
// When running from this mono-repo build output.
|
|
65
|
+
path.resolve(process.cwd(), 'dist/packages/shell-server/src/daemon/server.js'),
|
|
66
|
+
path.resolve(process.cwd(), 'dist/packages/shell-server/daemon/server.js'),
|
|
67
|
+
];
|
|
68
|
+
for (const candidate of candidates) {
|
|
69
|
+
if (existsSync(candidate)) {
|
|
70
|
+
return candidate;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Best guess (will be validated by fs.access at call site).
|
|
74
|
+
return packagedCandidate;
|
|
75
|
+
}
|
|
76
|
+
async socketExists(socketPath) {
|
|
77
|
+
try {
|
|
78
|
+
const stat = await fs.stat(socketPath);
|
|
79
|
+
return stat.isSocket();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async canConnectSocket(socketPath) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const socket = net.connect({ path: socketPath }, () => {
|
|
88
|
+
socket.end();
|
|
89
|
+
resolve(true);
|
|
90
|
+
});
|
|
91
|
+
const cleanup = () => {
|
|
92
|
+
socket.removeAllListeners();
|
|
93
|
+
};
|
|
94
|
+
socket.setTimeout(SOCKET_CONNECT_TIMEOUT_MS, () => {
|
|
95
|
+
socket.destroy();
|
|
96
|
+
cleanup();
|
|
97
|
+
resolve(false);
|
|
98
|
+
});
|
|
99
|
+
socket.on('error', () => {
|
|
100
|
+
cleanup();
|
|
101
|
+
resolve(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async waitForSocketReady(socketPath) {
|
|
106
|
+
const deadline = Date.now() + SOCKET_READY_TIMEOUT_MS;
|
|
107
|
+
while (Date.now() < deadline) {
|
|
108
|
+
if (await this.canConnectSocket(socketPath)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, SOCKET_READY_INTERVAL_MS));
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
async requestDaemon(socketPath, request) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const socket = net.connect({ path: socketPath }, () => {
|
|
118
|
+
socket.write(`${JSON.stringify(request)}\n`);
|
|
119
|
+
});
|
|
120
|
+
let buffer = '';
|
|
121
|
+
const timeout = setTimeout(() => {
|
|
122
|
+
socket.destroy();
|
|
123
|
+
reject(new MCPShellError('SYSTEM_013', 'Daemon request timed out', 'SYSTEM', {
|
|
124
|
+
socketPath,
|
|
125
|
+
action: request.action,
|
|
126
|
+
}));
|
|
127
|
+
}, SOCKET_REQUEST_TIMEOUT_MS);
|
|
128
|
+
const cleanup = () => {
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
socket.removeAllListeners();
|
|
131
|
+
};
|
|
132
|
+
socket.setEncoding('utf-8');
|
|
133
|
+
socket.on('data', (chunk) => {
|
|
134
|
+
buffer += chunk;
|
|
135
|
+
if (buffer.includes('\n')) {
|
|
136
|
+
socket.end();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
socket.on('end', () => {
|
|
140
|
+
cleanup();
|
|
141
|
+
const line = buffer.trim();
|
|
142
|
+
if (!line) {
|
|
143
|
+
reject(new MCPShellError('SYSTEM_013', 'Daemon response was empty', 'SYSTEM', {
|
|
144
|
+
socketPath,
|
|
145
|
+
action: request.action,
|
|
146
|
+
}));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
resolve(JSON.parse(line));
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
reject(new MCPShellError('SYSTEM_013', 'Failed to parse daemon response', 'SYSTEM', {
|
|
154
|
+
socketPath,
|
|
155
|
+
action: request.action,
|
|
156
|
+
error: String(error),
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
socket.on('error', (error) => {
|
|
161
|
+
cleanup();
|
|
162
|
+
reject(new MCPShellError('SYSTEM_013', 'Daemon request failed', 'SYSTEM', {
|
|
163
|
+
socketPath,
|
|
164
|
+
action: request.action,
|
|
165
|
+
error: String(error),
|
|
166
|
+
}));
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async openAttachConnection(socketPath) {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const socket = net.connect({ path: socketPath }, () => {
|
|
173
|
+
socket.write(`${JSON.stringify({ action: 'attach' })}\n`);
|
|
174
|
+
});
|
|
175
|
+
let buffer = '';
|
|
176
|
+
let responseSent = false;
|
|
177
|
+
const timeout = setTimeout(() => {
|
|
178
|
+
socket.destroy();
|
|
179
|
+
reject(new MCPShellError('SYSTEM_013', 'Attach request timed out', 'SYSTEM', {
|
|
180
|
+
socketPath,
|
|
181
|
+
}));
|
|
182
|
+
}, SOCKET_REQUEST_TIMEOUT_MS);
|
|
183
|
+
const cleanup = () => {
|
|
184
|
+
clearTimeout(timeout);
|
|
185
|
+
};
|
|
186
|
+
const handleHeartbeat = (message) => {
|
|
187
|
+
if (message.type === 'ping') {
|
|
188
|
+
try {
|
|
189
|
+
socket.write(`${JSON.stringify({ type: 'pong' })}\n`);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Ignore write failures on heartbeat.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
socket.setEncoding('utf-8');
|
|
197
|
+
socket.on('data', (chunk) => {
|
|
198
|
+
buffer += chunk;
|
|
199
|
+
const lines = buffer.split('\n');
|
|
200
|
+
buffer = lines.pop() || '';
|
|
201
|
+
for (const line of lines) {
|
|
202
|
+
const trimmed = line.trim();
|
|
203
|
+
if (!trimmed) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
let parsed;
|
|
207
|
+
try {
|
|
208
|
+
parsed = JSON.parse(trimmed);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!responseSent && 'ok' in parsed) {
|
|
214
|
+
responseSent = true;
|
|
215
|
+
cleanup();
|
|
216
|
+
const response = parsed;
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
socket.end();
|
|
219
|
+
reject(new MCPShellError('SYSTEM_013', 'Daemon attach failed', 'SYSTEM', {
|
|
220
|
+
socketPath,
|
|
221
|
+
error: response.error,
|
|
222
|
+
}));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
resolve({ socket, response });
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
handleHeartbeat(parsed);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
socket.on('error', (error) => {
|
|
232
|
+
cleanup();
|
|
233
|
+
reject(new MCPShellError('SYSTEM_013', 'Attach connection failed', 'SYSTEM', {
|
|
234
|
+
socketPath,
|
|
235
|
+
error: String(error),
|
|
236
|
+
}));
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
deriveStatus(attached, detached) {
|
|
241
|
+
if (detached) {
|
|
242
|
+
return 'detached';
|
|
243
|
+
}
|
|
244
|
+
if (attached) {
|
|
245
|
+
return 'running';
|
|
246
|
+
}
|
|
247
|
+
return 'running';
|
|
248
|
+
}
|
|
249
|
+
async removeIfEmpty(dirPath) {
|
|
250
|
+
try {
|
|
251
|
+
const entries = await fs.readdir(dirPath);
|
|
252
|
+
if (entries.length === 0) {
|
|
253
|
+
await fs.rmdir(dirPath);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Best-effort cleanup only.
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async cleanupStaleSocket(socketPath) {
|
|
261
|
+
try {
|
|
262
|
+
await fs.unlink(socketPath);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const branchDir = path.dirname(socketPath);
|
|
268
|
+
const hashDir = path.dirname(branchDir);
|
|
269
|
+
await this.removeIfEmpty(branchDir);
|
|
270
|
+
await this.removeIfEmpty(hashDir);
|
|
271
|
+
}
|
|
272
|
+
async listSocketsForCwd(cwd) {
|
|
273
|
+
const runtimeRoot = this.getRuntimeRoot();
|
|
274
|
+
const cwdHash = this.hashCwd(cwd);
|
|
275
|
+
const cwdRoot = path.join(runtimeRoot, cwdHash);
|
|
276
|
+
let branchEntries = [];
|
|
277
|
+
try {
|
|
278
|
+
branchEntries = await fs.readdir(cwdRoot, { withFileTypes: true });
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
const servers = [];
|
|
284
|
+
for (const entry of branchEntries) {
|
|
285
|
+
if (!entry.isDirectory()) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const branch = entry.name;
|
|
289
|
+
const socketPath = path.join(cwdRoot, branch, SOCKET_FILE_NAME);
|
|
290
|
+
if (!(await this.socketExists(socketPath))) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!(await this.canConnectSocket(socketPath))) {
|
|
294
|
+
await this.cleanupStaleSocket(socketPath);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
let status = 'running';
|
|
298
|
+
if (this.isDaemonEnabled()) {
|
|
299
|
+
try {
|
|
300
|
+
const response = await this.requestDaemon(socketPath, { action: 'status' });
|
|
301
|
+
status = this.deriveStatus(response.attached, response.detached);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
status = 'unknown';
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
servers.push({
|
|
308
|
+
serverId: `${cwdHash}:${branch}`,
|
|
309
|
+
status,
|
|
310
|
+
cwd,
|
|
311
|
+
socketPath,
|
|
312
|
+
createdAt: this.createdAt,
|
|
313
|
+
lastSeenAt: new Date().toISOString(),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return servers;
|
|
317
|
+
}
|
|
318
|
+
async current() {
|
|
319
|
+
const cwd = process.cwd();
|
|
320
|
+
const branch = this.getBranch();
|
|
321
|
+
const socketPath = this.buildSocketPath(cwd, branch);
|
|
322
|
+
const socketReady = await this.socketExists(socketPath)
|
|
323
|
+
? await this.canConnectSocket(socketPath)
|
|
324
|
+
: false;
|
|
325
|
+
if (!socketReady && (await this.socketExists(socketPath))) {
|
|
326
|
+
await this.cleanupStaleSocket(socketPath);
|
|
327
|
+
}
|
|
328
|
+
let status = 'running';
|
|
329
|
+
if (socketReady && this.isDaemonEnabled()) {
|
|
330
|
+
try {
|
|
331
|
+
const response = await this.requestDaemon(socketPath, { action: 'status' });
|
|
332
|
+
status = this.deriveStatus(response.attached, response.detached);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
status = 'unknown';
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
serverId: 'local',
|
|
340
|
+
status,
|
|
341
|
+
cwd,
|
|
342
|
+
...(socketReady ? { socketPath } : {}),
|
|
343
|
+
createdAt: this.createdAt,
|
|
344
|
+
lastSeenAt: new Date().toISOString(),
|
|
345
|
+
pid: process.pid,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
async listAttachable(_options) {
|
|
349
|
+
const resolvedCurrent = path.resolve(process.cwd());
|
|
350
|
+
const resolvedTarget = path.resolve(_options.cwd);
|
|
351
|
+
const discovered = await this.listSocketsForCwd(resolvedTarget);
|
|
352
|
+
if (discovered.length > 0) {
|
|
353
|
+
const attachable = await Promise.all(discovered.map(async (server) => {
|
|
354
|
+
if (!this.isDaemonEnabled() || !server.socketPath) {
|
|
355
|
+
return { ...server, attachable: true };
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const response = await this.requestDaemon(server.socketPath, { action: 'status' });
|
|
359
|
+
const canAttach = response.attached !== true || response.detached === true;
|
|
360
|
+
return {
|
|
361
|
+
...server,
|
|
362
|
+
status: this.deriveStatus(response.attached, response.detached),
|
|
363
|
+
attachable: canAttach,
|
|
364
|
+
...(canAttach ? {} : { reason: 'Already attached' }),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return {
|
|
369
|
+
...server,
|
|
370
|
+
attachable: false,
|
|
371
|
+
reason: 'Failed to query daemon status',
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}));
|
|
375
|
+
return attachable;
|
|
376
|
+
}
|
|
377
|
+
if (resolvedCurrent === resolvedTarget) {
|
|
378
|
+
const current = await this.current();
|
|
379
|
+
if (current) {
|
|
380
|
+
return [
|
|
381
|
+
{
|
|
382
|
+
...current,
|
|
383
|
+
attachable: true,
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
async start(options) {
|
|
391
|
+
const cwd = options.cwd;
|
|
392
|
+
const branch = this.getBranch();
|
|
393
|
+
const socketPath = options.socketPath ?? this.buildSocketPath(cwd, branch);
|
|
394
|
+
const serverId = `${this.hashCwd(cwd)}:${branch}`;
|
|
395
|
+
if (await this.socketExists(socketPath)) {
|
|
396
|
+
if (await this.canConnectSocket(socketPath)) {
|
|
397
|
+
if (options.allowExisting) {
|
|
398
|
+
return {
|
|
399
|
+
serverId,
|
|
400
|
+
status: 'running',
|
|
401
|
+
cwd,
|
|
402
|
+
socketPath,
|
|
403
|
+
createdAt: this.createdAt,
|
|
404
|
+
lastSeenAt: new Date().toISOString(),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
throw new MCPShellError('RESOURCE_006', 'Server is already running', 'RESOURCE', {
|
|
408
|
+
socketPath,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
await this.cleanupStaleSocket(socketPath);
|
|
412
|
+
}
|
|
413
|
+
await fs.mkdir(path.dirname(socketPath), { recursive: true });
|
|
414
|
+
if (this.isDaemonEnabled()) {
|
|
415
|
+
const daemonEntry = this.resolveDaemonEntry();
|
|
416
|
+
try {
|
|
417
|
+
await fs.access(daemonEntry);
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
throw new MCPShellError('SYSTEM_011', 'Daemon entry not found', 'SYSTEM', {
|
|
421
|
+
daemonEntry,
|
|
422
|
+
error: String(error),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const child = spawn(process.execPath, [daemonEntry, '--socket', socketPath, '--cwd', cwd, '--branch', branch], {
|
|
426
|
+
detached: true,
|
|
427
|
+
stdio: 'ignore',
|
|
428
|
+
env: {
|
|
429
|
+
...process.env,
|
|
430
|
+
MCP_SHELL_DAEMON_SOCKET: socketPath,
|
|
431
|
+
MCP_SHELL_DAEMON_CWD: cwd,
|
|
432
|
+
MCP_SHELL_DAEMON_BRANCH: branch,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
child.unref();
|
|
436
|
+
if (!(await this.waitForSocketReady(socketPath))) {
|
|
437
|
+
throw new MCPShellError('SYSTEM_012', 'Daemon socket did not become ready', 'SYSTEM', {
|
|
438
|
+
socketPath,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
this.servers.set(serverId, { socketPath, child, attached: false, detached: false });
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
const server = net.createServer((socket) => {
|
|
445
|
+
socket.destroy();
|
|
446
|
+
});
|
|
447
|
+
await new Promise((resolve, reject) => {
|
|
448
|
+
server.once('error', reject);
|
|
449
|
+
server.listen(socketPath, () => resolve());
|
|
450
|
+
});
|
|
451
|
+
await fs.chmod(socketPath, 0o600);
|
|
452
|
+
this.servers.set(serverId, { socketPath, server, attached: false, detached: false });
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
serverId,
|
|
456
|
+
status: 'running',
|
|
457
|
+
cwd,
|
|
458
|
+
socketPath,
|
|
459
|
+
createdAt: this.createdAt,
|
|
460
|
+
lastSeenAt: new Date().toISOString(),
|
|
461
|
+
pid: process.pid,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
async stop(options) {
|
|
465
|
+
const entry = this.servers.get(options.serverId);
|
|
466
|
+
if (entry) {
|
|
467
|
+
if (this.isDaemonEnabled()) {
|
|
468
|
+
try {
|
|
469
|
+
await this.requestDaemon(entry.socketPath, { action: 'stop' });
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
// Best-effort shutdown only.
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (entry.server) {
|
|
476
|
+
await new Promise((resolve) => {
|
|
477
|
+
entry.server?.close(() => resolve());
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (entry.child?.pid) {
|
|
481
|
+
try {
|
|
482
|
+
process.kill(entry.child.pid);
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
// Best-effort shutdown only.
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
this.servers.delete(options.serverId);
|
|
489
|
+
await this.cleanupStaleSocket(entry.socketPath);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const parsed = this.parseServerId(options.serverId);
|
|
493
|
+
if (!parsed) {
|
|
494
|
+
throw new MCPShellError('RESOURCE_001', 'Server not found', 'RESOURCE', {
|
|
495
|
+
serverId: options.serverId,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
const socketPath = path.join(this.getRuntimeRoot(), parsed.hash, parsed.branch, SOCKET_FILE_NAME);
|
|
499
|
+
if (await this.socketExists(socketPath)) {
|
|
500
|
+
if (this.isDaemonEnabled() && (await this.canConnectSocket(socketPath))) {
|
|
501
|
+
const response = await this.requestDaemon(socketPath, { action: 'stop' });
|
|
502
|
+
if (!response.ok) {
|
|
503
|
+
throw new MCPShellError('SYSTEM_013', 'Daemon stop failed', 'SYSTEM', {
|
|
504
|
+
serverId: options.serverId,
|
|
505
|
+
error: response.error,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
await this.cleanupStaleSocket(socketPath);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (options.force) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
throw new MCPShellError('RESOURCE_001', 'Server not found', 'RESOURCE', {
|
|
517
|
+
serverId: options.serverId,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
async get(options) {
|
|
521
|
+
const entry = this.servers.get(options.serverId);
|
|
522
|
+
if (entry) {
|
|
523
|
+
// When daemon mode is enabled, prefer querying the daemon for richer info
|
|
524
|
+
// (including mcpSocketPath) even if we have a local cache entry.
|
|
525
|
+
if (this.isDaemonEnabled()) {
|
|
526
|
+
try {
|
|
527
|
+
const response = await this.requestDaemon(entry.socketPath, { action: 'info' });
|
|
528
|
+
if (response.ok) {
|
|
529
|
+
return {
|
|
530
|
+
serverId: options.serverId,
|
|
531
|
+
status: this.deriveStatus(response.attached, response.detached),
|
|
532
|
+
cwd: response.cwd || process.cwd(),
|
|
533
|
+
...(response.socketPath ? { socketPath: response.socketPath } : { socketPath: entry.socketPath }),
|
|
534
|
+
...(response.mcpSocketPath ? { mcpSocketPath: response.mcpSocketPath } : {}),
|
|
535
|
+
createdAt: response.startedAt || this.createdAt,
|
|
536
|
+
lastSeenAt: new Date().toISOString(),
|
|
537
|
+
...(typeof response.pid === 'number'
|
|
538
|
+
? { pid: response.pid }
|
|
539
|
+
: { pid: entry.child?.pid ?? process.pid }),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
// Fall back to cached info.
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
serverId: options.serverId,
|
|
549
|
+
status: this.deriveStatus(entry.attached, entry.detached),
|
|
550
|
+
cwd: process.cwd(),
|
|
551
|
+
socketPath: entry.socketPath,
|
|
552
|
+
createdAt: this.createdAt,
|
|
553
|
+
lastSeenAt: new Date().toISOString(),
|
|
554
|
+
pid: entry.child?.pid ?? process.pid,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const socketPath = this.buildSocketPathFromServerId(options.serverId);
|
|
558
|
+
if (!socketPath || !(await this.socketExists(socketPath))) {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
if (this.isDaemonEnabled()) {
|
|
562
|
+
if (!(await this.canConnectSocket(socketPath))) {
|
|
563
|
+
await this.cleanupStaleSocket(socketPath);
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
const response = await this.requestDaemon(socketPath, { action: 'info' });
|
|
567
|
+
if (!response.ok) {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
serverId: options.serverId,
|
|
572
|
+
status: this.deriveStatus(response.attached, response.detached),
|
|
573
|
+
cwd: response.cwd || process.cwd(),
|
|
574
|
+
...(response.socketPath ? { socketPath: response.socketPath } : { socketPath }),
|
|
575
|
+
...(response.mcpSocketPath ? { mcpSocketPath: response.mcpSocketPath } : {}),
|
|
576
|
+
createdAt: response.startedAt || this.createdAt,
|
|
577
|
+
lastSeenAt: new Date().toISOString(),
|
|
578
|
+
...(typeof response.pid === 'number' ? { pid: response.pid } : {}),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
serverId: options.serverId,
|
|
583
|
+
status: 'running',
|
|
584
|
+
cwd: process.cwd(),
|
|
585
|
+
socketPath,
|
|
586
|
+
createdAt: this.createdAt,
|
|
587
|
+
lastSeenAt: new Date().toISOString(),
|
|
588
|
+
pid: process.pid,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
async detach(options) {
|
|
592
|
+
const entry = this.servers.get(options.serverId);
|
|
593
|
+
if (entry) {
|
|
594
|
+
entry.attached = false;
|
|
595
|
+
entry.detached = true;
|
|
596
|
+
if (entry.attachSocket) {
|
|
597
|
+
entry.attachSocket.end();
|
|
598
|
+
entry.attachSocket.destroy();
|
|
599
|
+
delete entry.attachSocket;
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const socketPath = this.buildSocketPathFromServerId(options.serverId);
|
|
604
|
+
if (!socketPath || !(await this.socketExists(socketPath))) {
|
|
605
|
+
throw new MCPShellError('RESOURCE_001', 'Server not found', 'RESOURCE', {
|
|
606
|
+
serverId: options.serverId,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
if (this.isDaemonEnabled()) {
|
|
610
|
+
const response = await this.requestDaemon(socketPath, { action: 'detach' });
|
|
611
|
+
if (!response.ok) {
|
|
612
|
+
throw new MCPShellError('SYSTEM_013', 'Daemon detach failed', 'SYSTEM', {
|
|
613
|
+
serverId: options.serverId,
|
|
614
|
+
error: response.error,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
throw new MCPShellError('SYSTEM_010', NOT_IMPLEMENTED_MESSAGE, 'SYSTEM', {
|
|
620
|
+
operation: 'detach',
|
|
621
|
+
serverId: options.serverId,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
async reattach(options) {
|
|
625
|
+
const entry = this.servers.get(options.serverId);
|
|
626
|
+
if (entry) {
|
|
627
|
+
if (entry.attached && !entry.detached) {
|
|
628
|
+
throw new MCPShellError('RESOURCE_006', 'Server is already attached', 'RESOURCE', {
|
|
629
|
+
serverId: options.serverId,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
entry.attached = true;
|
|
633
|
+
entry.detached = false;
|
|
634
|
+
return {
|
|
635
|
+
serverId: options.serverId,
|
|
636
|
+
status: 'running',
|
|
637
|
+
cwd: process.cwd(),
|
|
638
|
+
socketPath: entry.socketPath,
|
|
639
|
+
createdAt: this.createdAt,
|
|
640
|
+
lastSeenAt: new Date().toISOString(),
|
|
641
|
+
pid: entry.child?.pid ?? process.pid,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
const socketPath = this.buildSocketPathFromServerId(options.serverId);
|
|
645
|
+
if (!socketPath || !(await this.socketExists(socketPath))) {
|
|
646
|
+
throw new MCPShellError('RESOURCE_001', 'Server not found', 'RESOURCE', {
|
|
647
|
+
serverId: options.serverId,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
if (this.isDaemonEnabled()) {
|
|
651
|
+
const { socket, response } = await this.openAttachConnection(socketPath);
|
|
652
|
+
if (response.error === 'already_attached') {
|
|
653
|
+
socket.end();
|
|
654
|
+
throw new MCPShellError('RESOURCE_006', 'Server is already attached', 'RESOURCE', {
|
|
655
|
+
serverId: options.serverId,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
this.servers.set(options.serverId, {
|
|
659
|
+
socketPath,
|
|
660
|
+
attached: response.attached === true,
|
|
661
|
+
detached: response.detached === true,
|
|
662
|
+
attachSocket: socket,
|
|
663
|
+
});
|
|
664
|
+
return {
|
|
665
|
+
serverId: options.serverId,
|
|
666
|
+
status: this.deriveStatus(response.attached, response.detached),
|
|
667
|
+
cwd: response.cwd || process.cwd(),
|
|
668
|
+
socketPath,
|
|
669
|
+
createdAt: this.createdAt,
|
|
670
|
+
lastSeenAt: new Date().toISOString(),
|
|
671
|
+
...(typeof response.pid === 'number' ? { pid: response.pid } : {}),
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
throw new MCPShellError('SYSTEM_010', NOT_IMPLEMENTED_MESSAGE, 'SYSTEM', {
|
|
675
|
+
operation: 'reattach',
|
|
676
|
+
serverId: options.serverId,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
//# sourceMappingURL=server-manager.js.map
|