@mcp-z/client 2.2.4 → 2.3.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/README.md +12 -1
- package/dist/cjs/auth/capability-discovery.d.cts +3 -1
- package/dist/cjs/auth/capability-discovery.d.ts +3 -1
- package/dist/cjs/auth/capability-discovery.js +46 -32
- package/dist/cjs/auth/capability-discovery.js.map +1 -1
- package/dist/cjs/auth/discovery-fetch.d.cts +2 -0
- package/dist/cjs/auth/discovery-fetch.d.ts +2 -0
- package/dist/cjs/auth/discovery-fetch.js +85 -31
- package/dist/cjs/auth/discovery-fetch.js.map +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.d.cts +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.d.ts +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.js +13 -6
- package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/cjs/auth/oauth-callback-listener.d.cts +1 -1
- package/dist/cjs/auth/oauth-callback-listener.d.ts +1 -1
- package/dist/cjs/auth/oauth-callback-listener.js +38 -2
- package/dist/cjs/auth/oauth-callback-listener.js.map +1 -1
- package/dist/cjs/auth/rfc9728-discovery.d.cts +7 -2
- package/dist/cjs/auth/rfc9728-discovery.d.ts +7 -2
- package/dist/cjs/auth/rfc9728-discovery.js +120 -88
- package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
- package/dist/cjs/auth/types.d.cts +4 -0
- package/dist/cjs/auth/types.d.ts +4 -0
- package/dist/cjs/auth/types.js.map +1 -1
- package/dist/cjs/connection/connect-client.d.cts +9 -1
- package/dist/cjs/connection/connect-client.d.ts +9 -1
- package/dist/cjs/connection/connect-client.js +282 -75
- package/dist/cjs/connection/connect-client.js.map +1 -1
- package/dist/cjs/connection/existing-process-transport.d.cts +18 -18
- package/dist/cjs/connection/existing-process-transport.d.ts +18 -18
- package/dist/cjs/connection/existing-process-transport.js +187 -82
- package/dist/cjs/connection/existing-process-transport.js.map +1 -1
- package/dist/cjs/connection/wait-for-http-ready.d.cts +1 -1
- package/dist/cjs/connection/wait-for-http-ready.d.ts +1 -1
- package/dist/cjs/connection/wait-for-http-ready.js +30 -5
- package/dist/cjs/connection/wait-for-http-ready.js.map +1 -1
- package/dist/cjs/dcr/dcr-authenticator.d.cts +1 -1
- package/dist/cjs/dcr/dcr-authenticator.d.ts +1 -1
- package/dist/cjs/dcr/dcr-authenticator.js +29 -18
- package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
- package/dist/cjs/dcr/dynamic-client-registrar.js +2 -1
- package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/spawn/owned-process-tree.d.cts +12 -0
- package/dist/cjs/spawn/owned-process-tree.d.ts +12 -0
- package/dist/cjs/spawn/owned-process-tree.js +93 -0
- package/dist/cjs/spawn/owned-process-tree.js.map +1 -0
- package/dist/cjs/spawn/spawn-server.d.cts +8 -32
- package/dist/cjs/spawn/spawn-server.d.ts +8 -32
- package/dist/cjs/spawn/spawn-server.js +398 -212
- package/dist/cjs/spawn/spawn-server.js.map +1 -1
- package/dist/cjs/spawn/spawn-servers.d.cts +18 -8
- package/dist/cjs/spawn/spawn-servers.d.ts +18 -8
- package/dist/cjs/spawn/spawn-servers.js +342 -113
- package/dist/cjs/spawn/spawn-servers.js.map +1 -1
- package/dist/cjs/types.d.cts +1 -1
- package/dist/cjs/types.d.ts +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/auth/capability-discovery.d.ts +3 -1
- package/dist/esm/auth/capability-discovery.js +21 -11
- package/dist/esm/auth/capability-discovery.js.map +1 -1
- package/dist/esm/auth/discovery-fetch.d.ts +2 -0
- package/dist/esm/auth/discovery-fetch.js +64 -23
- package/dist/esm/auth/discovery-fetch.js.map +1 -1
- package/dist/esm/auth/interactive-oauth-flow.d.ts +1 -1
- package/dist/esm/auth/interactive-oauth-flow.js +13 -6
- package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/esm/auth/oauth-callback-listener.d.ts +1 -1
- package/dist/esm/auth/oauth-callback-listener.js +32 -2
- package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
- package/dist/esm/auth/rfc9728-discovery.d.ts +7 -2
- package/dist/esm/auth/rfc9728-discovery.js +40 -16
- package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
- package/dist/esm/auth/types.d.ts +4 -0
- package/dist/esm/auth/types.js.map +1 -1
- package/dist/esm/connection/connect-client.d.ts +9 -1
- package/dist/esm/connection/connect-client.js +97 -35
- package/dist/esm/connection/connect-client.js.map +1 -1
- package/dist/esm/connection/existing-process-transport.d.ts +18 -18
- package/dist/esm/connection/existing-process-transport.js +132 -70
- package/dist/esm/connection/existing-process-transport.js.map +1 -1
- package/dist/esm/connection/wait-for-http-ready.d.ts +1 -1
- package/dist/esm/connection/wait-for-http-ready.js +19 -4
- package/dist/esm/connection/wait-for-http-ready.js.map +1 -1
- package/dist/esm/dcr/dcr-authenticator.d.ts +1 -1
- package/dist/esm/dcr/dcr-authenticator.js +26 -15
- package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
- package/dist/esm/dcr/dynamic-client-registrar.js +2 -1
- package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/spawn/owned-process-tree.d.ts +12 -0
- package/dist/esm/spawn/owned-process-tree.js +39 -0
- package/dist/esm/spawn/owned-process-tree.js.map +1 -0
- package/dist/esm/spawn/spawn-server.d.ts +8 -32
- package/dist/esm/spawn/spawn-server.js +224 -130
- package/dist/esm/spawn/spawn-server.js.map +1 -1
- package/dist/esm/spawn/spawn-servers.d.ts +18 -8
- package/dist/esm/spawn/spawn-servers.js +180 -55
- package/dist/esm/spawn/spawn-servers.js.map +1 -1
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/types.js.map +1 -1
- package/package.json +8 -5
- package/schemas/servers.d.ts +24 -0
- package/schemas/servers.schema.json +31 -0
|
@@ -1,77 +1,173 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Low-level single server spawning utilities.
|
|
3
3
|
* Provides core process spawning with path resolution, environment management, and lifecycle control.
|
|
4
|
-
*/ import
|
|
4
|
+
*/ import crossSpawn from 'cross-spawn';
|
|
5
|
+
import * as path from 'path';
|
|
5
6
|
import * as process from 'process';
|
|
6
7
|
import { logger } from '../utils/logger.js';
|
|
7
8
|
import { resolveArgsPaths } from '../utils/path-utils.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import { OwnedProcessTree } from './owned-process-tree.js';
|
|
10
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 5000;
|
|
11
|
+
const PROCESS_TERMINATION_TIMEOUT_MS = 5000;
|
|
12
|
+
const PROCESS_POLL_INTERVAL_MS = 25;
|
|
13
|
+
function normalizeEnv(env, inheritEnv = true) {
|
|
11
14
|
const merged = {
|
|
12
|
-
...process.env,
|
|
15
|
+
...inheritEnv ? process.env : {},
|
|
13
16
|
...env || {}
|
|
14
17
|
};
|
|
15
18
|
const result = {};
|
|
16
19
|
for (const [key, value] of Object.entries(merged)){
|
|
17
|
-
if (value !== undefined)
|
|
18
|
-
result[key] = value;
|
|
19
|
-
}
|
|
20
|
+
if (value !== undefined) result[key] = value;
|
|
20
21
|
}
|
|
21
22
|
return result;
|
|
22
23
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
function delay(ms) {
|
|
25
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
26
|
+
}
|
|
27
|
+
async function waitForCompletion(closeEvent, childClosed, tree, timeoutMs) {
|
|
28
|
+
if (!tree) return {
|
|
29
|
+
closed: await waitForChildClose(closeEvent, timeoutMs) !== undefined
|
|
30
|
+
};
|
|
31
|
+
const deadline = Date.now() + timeoutMs;
|
|
32
|
+
let lastError;
|
|
33
|
+
while(true){
|
|
34
|
+
let processGroupEmpty = false;
|
|
35
|
+
try {
|
|
36
|
+
processGroupEmpty = tree.isEmpty();
|
|
37
|
+
lastError = undefined;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
40
|
+
}
|
|
41
|
+
const closed = childClosed();
|
|
42
|
+
if (closed && processGroupEmpty) return {
|
|
43
|
+
closed,
|
|
44
|
+
processGroupEmpty
|
|
45
|
+
};
|
|
46
|
+
if (Date.now() >= deadline) return {
|
|
47
|
+
closed,
|
|
48
|
+
processGroupEmpty,
|
|
49
|
+
...lastError && {
|
|
50
|
+
error: lastError
|
|
40
51
|
}
|
|
41
|
-
|
|
52
|
+
};
|
|
53
|
+
await delay(Math.min(PROCESS_POLL_INTERVAL_MS, Math.max(1, deadline - Date.now())));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isCompletionSuccessful(completion) {
|
|
57
|
+
return completion.closed && completion.processGroupEmpty !== false;
|
|
58
|
+
}
|
|
59
|
+
function terminateChild(child, tree) {
|
|
60
|
+
if (tree) return tree.terminate();
|
|
61
|
+
try {
|
|
62
|
+
return {
|
|
63
|
+
killed: child.kill('SIGKILL')
|
|
64
|
+
};
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
killed: false,
|
|
68
|
+
error: asError(error)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function waitForChildClose(closeEvent, timeoutMs) {
|
|
73
|
+
let timer;
|
|
74
|
+
return Promise.race([
|
|
75
|
+
closeEvent,
|
|
76
|
+
new Promise((resolve)=>{
|
|
77
|
+
timer = setTimeout(()=>resolve(undefined), timeoutMs);
|
|
78
|
+
})
|
|
79
|
+
]).finally(()=>{
|
|
80
|
+
if (timer) clearTimeout(timer);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function requestProcessSignal(child, signal) {
|
|
84
|
+
if (!child.pid) throw new Error('Cannot request cooperative shutdown because the server has no process ID');
|
|
85
|
+
if (process.platform === 'win32') throw new Error('POSIX process signals are not available on Windows');
|
|
86
|
+
try {
|
|
87
|
+
process.kill(-child.pid, signal);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ESRCH')) throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function asError(error) {
|
|
93
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
94
|
+
}
|
|
95
|
+
function withCause(message, cause) {
|
|
96
|
+
return new Error(message, {
|
|
97
|
+
cause
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function runStopCommand(command, cwd, timeoutMs, inheritEnv) {
|
|
101
|
+
const stopCwd = command.cwd ? path.resolve(cwd, command.cwd) : cwd;
|
|
102
|
+
const env = normalizeEnv(command.env, inheritEnv);
|
|
103
|
+
const args = command.args ? resolveArgsPaths(command.args, stopCwd) : [];
|
|
104
|
+
const child = crossSpawn(command.command, args, {
|
|
105
|
+
cwd: stopCwd,
|
|
106
|
+
env,
|
|
107
|
+
stdio: 'ignore',
|
|
108
|
+
shell: false,
|
|
109
|
+
detached: process.platform !== 'win32',
|
|
110
|
+
windowsHide: true
|
|
111
|
+
});
|
|
112
|
+
let spawnError;
|
|
113
|
+
let closed = false;
|
|
114
|
+
let closeEventValue;
|
|
115
|
+
const closeEvent = new Promise((resolve)=>{
|
|
116
|
+
child.once('error', (error)=>{
|
|
117
|
+
spawnError = error;
|
|
118
|
+
});
|
|
119
|
+
child.once('close', (code, signal)=>{
|
|
120
|
+
closed = true;
|
|
121
|
+
closeEventValue = {
|
|
122
|
+
code,
|
|
123
|
+
signal
|
|
124
|
+
};
|
|
125
|
+
resolve({
|
|
126
|
+
code,
|
|
127
|
+
signal
|
|
128
|
+
});
|
|
42
129
|
});
|
|
43
130
|
});
|
|
131
|
+
if (!child.pid) {
|
|
132
|
+
const result = await waitForChildClose(closeEvent, timeoutMs);
|
|
133
|
+
if (!result) throw new Error(`Stop command did not start or close within ${timeoutMs}ms`);
|
|
134
|
+
if (spawnError) throw withCause(`Stop command could not be started: ${spawnError.message}`, spawnError);
|
|
135
|
+
}
|
|
136
|
+
const tree = child.pid && process.platform !== 'win32' ? new OwnedProcessTree(child.pid) : undefined;
|
|
137
|
+
const completion = await waitForCompletion(closeEvent, ()=>closed, tree, timeoutMs);
|
|
138
|
+
if (!isCompletionSuccessful(completion)) {
|
|
139
|
+
var _completion_error, _termination_error;
|
|
140
|
+
const termination = terminateChild(child, tree);
|
|
141
|
+
const afterTermination = await waitForCompletion(closeEvent, ()=>closed, tree, PROCESS_TERMINATION_TIMEOUT_MS);
|
|
142
|
+
const details = [
|
|
143
|
+
(_completion_error = completion.error) === null || _completion_error === void 0 ? void 0 : _completion_error.message,
|
|
144
|
+
(_termination_error = termination.error) === null || _termination_error === void 0 ? void 0 : _termination_error.message
|
|
145
|
+
].filter(Boolean).join('; ');
|
|
146
|
+
const reason = `Stop command exceeded ${timeoutMs}ms and required emergency process termination`;
|
|
147
|
+
const remaining = !isCompletionSuccessful(afterTermination) ? '; stop command or its POSIX process group remained alive' : '';
|
|
148
|
+
throw new Error(`${reason}${details ? `: ${details}` : ''}${remaining}`);
|
|
149
|
+
}
|
|
150
|
+
if (spawnError) throw withCause(`Stop command could not be started: ${spawnError.message}`, spawnError);
|
|
151
|
+
if (closeEventValue && (closeEventValue.code !== 0 || closeEventValue.signal !== null)) {
|
|
152
|
+
var _closeEventValue_code;
|
|
153
|
+
throw new Error(`Stop command exited with code ${(_closeEventValue_code = closeEventValue.code) !== null && _closeEventValue_code !== void 0 ? _closeEventValue_code : 'unknown'}${closeEventValue.signal ? ` (signal ${closeEventValue.signal})` : ''}`);
|
|
154
|
+
}
|
|
44
155
|
}
|
|
45
156
|
/**
|
|
46
157
|
* Spawn a single server process with path resolution and environment management.
|
|
47
158
|
*
|
|
48
159
|
* @internal
|
|
49
160
|
* @param opts - Server spawn options
|
|
50
|
-
* @returns ServerProcess handle with resolved config
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* const handle = spawnProcess({
|
|
54
|
-
* name: 'echo',
|
|
55
|
-
* command: 'node',
|
|
56
|
-
* args: ['./bin/server.js', '--port', '3000'],
|
|
57
|
-
* cwd: '/home/user/project/test/lib/servers/echo',
|
|
58
|
-
* env: { LOG_LEVEL: 'error' }
|
|
59
|
-
* });
|
|
60
|
-
*
|
|
61
|
-
* // Later...
|
|
62
|
-
* await handle.close();
|
|
161
|
+
* @returns ServerProcess handle with resolved config and lifecycle control
|
|
63
162
|
*/ export function spawnProcess(opts) {
|
|
64
|
-
var _opts_cwd, _opts_stdio,
|
|
163
|
+
var _opts_cwd, _opts_stdio, _opts_inheritEnv;
|
|
65
164
|
const name = opts.name;
|
|
66
165
|
const command = opts.command;
|
|
67
166
|
const cwd = (_opts_cwd = opts.cwd) !== null && _opts_cwd !== void 0 ? _opts_cwd : process.cwd();
|
|
68
167
|
const stdio = (_opts_stdio = opts.stdio) !== null && _opts_stdio !== void 0 ? _opts_stdio : 'inherit';
|
|
69
|
-
const shell = (_opts_shell = opts.shell) !== null && _opts_shell !== void 0 ? _opts_shell : process.platform === 'win32';
|
|
70
|
-
// Resolve paths in args relative to the working directory
|
|
71
168
|
const args = opts.args ? resolveArgsPaths(opts.args, cwd) : [];
|
|
72
|
-
|
|
73
|
-
const env = normalizeEnv(opts.env);
|
|
74
|
-
// Create resolved config for return value
|
|
169
|
+
const inheritEnv = (_opts_inheritEnv = opts.inheritEnv) !== null && _opts_inheritEnv !== void 0 ? _opts_inheritEnv : true;
|
|
170
|
+
const env = normalizeEnv(opts.env, inheritEnv);
|
|
75
171
|
const resolvedConfig = {
|
|
76
172
|
name,
|
|
77
173
|
command,
|
|
@@ -79,106 +175,104 @@ function terminateWindowsProcessTree(pid, force) {
|
|
|
79
175
|
cwd,
|
|
80
176
|
env,
|
|
81
177
|
stdio,
|
|
82
|
-
shell
|
|
178
|
+
shell: false
|
|
83
179
|
};
|
|
84
|
-
// Log spawn operation
|
|
85
180
|
logger.info(`[${name}] → ${command} ${args.join(' ')}`);
|
|
86
|
-
|
|
87
|
-
const spawnOpts = {
|
|
181
|
+
const child = crossSpawn(command, args, {
|
|
88
182
|
cwd,
|
|
89
183
|
env,
|
|
90
184
|
stdio,
|
|
91
|
-
shell
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Pipe stdio if not inherited
|
|
95
|
-
if (child.stderr) child.stderr.on('data', (chunk)=>{
|
|
96
|
-
process.stderr.write(chunk);
|
|
185
|
+
shell: false,
|
|
186
|
+
detached: process.platform !== 'win32',
|
|
187
|
+
windowsHide: true
|
|
97
188
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
timedOut: false,
|
|
108
|
-
killed: false
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
const timeoutMs = (_opts_timeoutMs = opts.timeoutMs) !== null && _opts_timeoutMs !== void 0 ? _opts_timeoutMs : 500;
|
|
112
|
-
// Wait for 'close' event (process exit + stdio streams closed)
|
|
113
|
-
// This is better than 'exit' because it ensures stdio is fully cleaned up
|
|
114
|
-
const closePromise = new Promise((resolve, reject)=>{
|
|
115
|
-
let isResolved = false;
|
|
116
|
-
let wasKilled = false;
|
|
117
|
-
let timedOut = false;
|
|
118
|
-
const resolveOnce = (didTimeout)=>{
|
|
119
|
-
if (isResolved) return;
|
|
120
|
-
isResolved = true;
|
|
121
|
-
clearTimeout(timeout);
|
|
122
|
-
resolve({
|
|
123
|
-
timedOut: didTimeout,
|
|
124
|
-
killed: wasKilled
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
const rejectOnce = (error)=>{
|
|
128
|
-
if (isResolved) return;
|
|
129
|
-
isResolved = true;
|
|
130
|
-
clearTimeout(timeout);
|
|
131
|
-
reject(error);
|
|
189
|
+
if (child.stderr) child.stderr.on('data', (chunk)=>process.stderr.write(chunk));
|
|
190
|
+
let isClosed = false;
|
|
191
|
+
let _closeEventValue;
|
|
192
|
+
const closeEvent = new Promise((resolve)=>{
|
|
193
|
+
child.once('close', (code, signal)=>{
|
|
194
|
+
isClosed = true;
|
|
195
|
+
_closeEventValue = {
|
|
196
|
+
code,
|
|
197
|
+
signal
|
|
132
198
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
if (process.platform === 'win32' && child.pid) {
|
|
139
|
-
await terminateWindowsProcessTree(child.pid, true);
|
|
140
|
-
wasKilled = true;
|
|
141
|
-
} else if (child.exitCode === null && !child.killed) {
|
|
142
|
-
wasKilled = child.kill('SIGKILL');
|
|
143
|
-
}
|
|
144
|
-
resolveOnce(true);
|
|
145
|
-
} catch (error) {
|
|
146
|
-
rejectOnce(error instanceof Error ? error : new Error(String(error)));
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
void forceKill();
|
|
150
|
-
}, timeoutMs);
|
|
151
|
-
// Listen for 'close' event (not 'exit') to wait for stdio close
|
|
152
|
-
child.once('close', ()=>{
|
|
153
|
-
if (!timedOut) resolveOnce(false);
|
|
154
|
-
});
|
|
155
|
-
// Also listen for 'error' event in case spawn failed
|
|
156
|
-
// This prevents promise from hanging forever if process never started
|
|
157
|
-
child.once('error', ()=>{
|
|
158
|
-
resolveOnce(false);
|
|
199
|
+
logger.info(`[${name}] closed (code=${code}, signal=${signal || 'none'})`);
|
|
200
|
+
resolve({
|
|
201
|
+
code,
|
|
202
|
+
signal
|
|
159
203
|
});
|
|
160
|
-
|
|
204
|
+
});
|
|
205
|
+
child.once('error', (error)=>logger.info(`[${name}] process error: ${error.message}`));
|
|
206
|
+
});
|
|
207
|
+
const tree = child.pid && process.platform !== 'win32' ? new OwnedProcessTree(child.pid) : undefined;
|
|
208
|
+
let closePromise;
|
|
209
|
+
const performClose = async (signal, timeoutMs)=>{
|
|
210
|
+
if (!child.pid) {
|
|
211
|
+
await waitForChildClose(closeEvent, timeoutMs);
|
|
212
|
+
throw new Error(`Cannot verify shutdown of server '${name}' because it has no process ID`);
|
|
213
|
+
}
|
|
214
|
+
let timedOut = false;
|
|
215
|
+
let killed = false;
|
|
216
|
+
const errors = [];
|
|
217
|
+
let stopSucceeded = false;
|
|
218
|
+
if (opts.stop) {
|
|
219
|
+
try {
|
|
220
|
+
await runStopCommand(opts.stop, cwd, timeoutMs, inheritEnv);
|
|
221
|
+
stopSucceeded = true;
|
|
222
|
+
} catch (error) {
|
|
223
|
+
errors.push(withCause(`Server '${name}' application stop command failed: ${asError(error).message}`, asError(error)));
|
|
224
|
+
}
|
|
225
|
+
} else if (child.stdin && !child.stdin.destroyed && !child.stdin.writableEnded) {
|
|
226
|
+
child.stdin.end();
|
|
227
|
+
} else if (process.platform !== 'win32') {
|
|
228
|
+
try {
|
|
229
|
+
requestProcessSignal(child, signal);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
errors.push(withCause(`Server '${name}' cooperative signal failed: ${asError(error).message}`, asError(error)));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
let completion = await waitForCompletion(closeEvent, ()=>isClosed, tree, timeoutMs);
|
|
235
|
+
if (completion.error) errors.push(withCause(`Could not inspect the owned process tree for server '${name}'`, completion.error));
|
|
236
|
+
if (!isCompletionSuccessful(completion)) {
|
|
237
|
+
timedOut = true;
|
|
238
|
+
if (!opts.stop && child.stdin && process.platform !== 'win32') {
|
|
161
239
|
try {
|
|
162
|
-
|
|
163
|
-
resolveOnce(false);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (process.platform === 'win32' && child.pid) {
|
|
167
|
-
await terminateWindowsProcessTree(child.pid, false);
|
|
168
|
-
} else if (!child.kill(signal)) {
|
|
169
|
-
resolveOnce(false);
|
|
170
|
-
}
|
|
240
|
+
requestProcessSignal(child, signal);
|
|
171
241
|
} catch (error) {
|
|
172
|
-
|
|
242
|
+
errors.push(withCause(`Server '${name}' cooperative signal failed: ${asError(error).message}`, asError(error)));
|
|
173
243
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
244
|
+
completion = await waitForCompletion(closeEvent, ()=>isClosed, tree, timeoutMs);
|
|
245
|
+
if (completion.error) errors.push(withCause(`Could not inspect the owned process tree for server '${name}'`, completion.error));
|
|
246
|
+
} else if (!opts.stop && process.platform !== 'win32' && !child.stdin) {
|
|
247
|
+
// The first signal was sent before waiting for an inherited-stdio HTTP process.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (!isCompletionSuccessful(completion)) {
|
|
251
|
+
const termination = terminateChild(child, tree);
|
|
252
|
+
killed || (killed = termination.killed);
|
|
253
|
+
if (termination.error) errors.push(withCause(`Emergency termination failed for server '${name}'`, termination.error));
|
|
254
|
+
completion = await waitForCompletion(closeEvent, ()=>isClosed, tree, PROCESS_TERMINATION_TIMEOUT_MS);
|
|
255
|
+
if (!isCompletionSuccessful(completion)) {
|
|
256
|
+
errors.push(new Error(`Server '${name}' did not close${completion.processGroupEmpty === false ? ' with its POSIX process group' : ''} after emergency termination`));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (opts.stop && !stopSucceeded && errors.length === 0) {
|
|
260
|
+
errors.push(new Error(`Server '${name}' application stop command did not complete successfully`));
|
|
261
|
+
}
|
|
262
|
+
if (errors.length > 0) throw new AggregateError(errors, `Server '${name}' shutdown completed with ${errors.length} failure(s)`);
|
|
263
|
+
return {
|
|
264
|
+
timedOut,
|
|
265
|
+
killed
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
const close = (signal = 'SIGINT', closeOptions = {})=>{
|
|
269
|
+
var _closeOptions_timeoutMs;
|
|
270
|
+
closePromise !== null && closePromise !== void 0 ? closePromise : closePromise = performClose(signal, (_closeOptions_timeoutMs = closeOptions.timeoutMs) !== null && _closeOptions_timeoutMs !== void 0 ? _closeOptions_timeoutMs : DEFAULT_CLOSE_TIMEOUT_MS);
|
|
177
271
|
return closePromise;
|
|
178
272
|
};
|
|
179
273
|
return {
|
|
180
274
|
config: resolvedConfig,
|
|
181
275
|
process: child,
|
|
182
|
-
close
|
|
276
|
+
close
|
|
183
277
|
};
|
|
184
278
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/spawn/spawn-server.ts"],"sourcesContent":["/**\n * Low-level single server spawning utilities.\n * Provides core process spawning with path resolution, environment management, and lifecycle control.\n */\n\nimport { type ChildProcess, type SpawnOptions, type StdioOptions, spawn } from 'child_process';\nimport * as process from 'process';\nimport { logger } from '../utils/logger.ts';\nimport { resolveArgsPaths } from '../utils/path-utils.ts';\n\n/**\n * Options for spawning a single server process.\n * @internal\n */\nexport interface SpawnProcessOptions {\n /** Server name for logging */\n name: string;\n /** Command to execute (e.g., 'node', 'npx') */\n command: string;\n /** Command arguments (paths will be resolved relative to cwd) */\n args?: string[];\n /** Working directory (must be absolute path) */\n cwd?: string;\n /** Additional environment variables (merged with process.env) */\n env?: Record<string, string>;\n /** Standard I/O configuration */\n stdio?: StdioOptions;\n /** Use shell for command execution (default: false, true on Windows) */\n shell?: boolean;\n}\n\n/**\n * Handle to a spawned server process.\n * Provides access to the process, resolved config, and lifecycle control.\n * @hidden\n */\nexport interface ServerProcess {\n /**\n * The resolved server configuration that was actually used.\n * Useful for debugging and understanding what was spawned.\n */\n config: {\n name: string;\n command: string;\n args: string[];\n cwd: string;\n env: Record<string, string>;\n stdio: StdioOptions;\n shell: boolean;\n };\n\n /**\n * The spawned child process.\n */\n process: ChildProcess;\n\n /**\n * Close the server gracefully, terminating its process tree on Windows.\n * Sends the specified signal (default: SIGINT), then force-kills after timeout.\n *\n * @param signal - Signal to send (default: SIGINT)\n * @param opts - Options including timeout\n * @returns Promise resolving to whether the process timed out and was force-killed\n */\n close: (signal?: NodeJS.Signals, opts?: { timeoutMs?: number }) => Promise<{ timedOut: boolean; killed: boolean }>;\n}\n\n/**\n * Normalize environment variables by merging with process.env and filtering undefined values.\n */\nfunction normalizeEnv(env?: Record<string, string>): Record<string, string> {\n const merged = { ...process.env, ...(env || {}) };\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(merged)) {\n if (value !== undefined) {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction terminateWindowsProcessTree(pid: number, force: boolean): Promise<void> {\n const args = ['/PID', String(pid), '/T'];\n if (force) args.push('/F');\n\n return new Promise((resolve, reject) => {\n const taskkill = spawn('taskkill.exe', args, { stdio: 'ignore', windowsHide: true });\n taskkill.once('error', reject);\n taskkill.once('close', (code) => {\n if (code === 0) {\n resolve();\n return;\n }\n reject(new Error(`taskkill.exe failed to stop process tree rooted at PID ${pid} (exit code ${code ?? 'unknown'})`));\n });\n });\n}\n\n/**\n * Spawn a single server process with path resolution and environment management.\n *\n * @internal\n * @param opts - Server spawn options\n * @returns ServerProcess handle with resolved config, process, and stop function\n *\n * @example\n * const handle = spawnProcess({\n * name: 'echo',\n * command: 'node',\n * args: ['./bin/server.js', '--port', '3000'],\n * cwd: '/home/user/project/test/lib/servers/echo',\n * env: { LOG_LEVEL: 'error' }\n * });\n *\n * // Later...\n * await handle.close();\n */\nexport function spawnProcess(opts: SpawnProcessOptions): ServerProcess {\n const name = opts.name;\n const command = opts.command;\n const cwd = opts.cwd ?? process.cwd();\n const stdio = opts.stdio ?? 'inherit';\n const shell = opts.shell ?? process.platform === 'win32';\n\n // Resolve paths in args relative to the working directory\n const args = opts.args ? resolveArgsPaths(opts.args, cwd) : [];\n\n // Merge environment variables\n const env = normalizeEnv(opts.env);\n\n // Create resolved config for return value\n const resolvedConfig = {\n name,\n command,\n args,\n cwd,\n env,\n stdio,\n shell,\n };\n\n // Log spawn operation\n logger.info(`[${name}] → ${command} ${args.join(' ')}`);\n\n // Spawn the process\n const spawnOpts: SpawnOptions = { cwd, env, stdio, shell };\n const child = spawn(command, args, spawnOpts);\n\n // Pipe stdio if not inherited\n if (child.stderr)\n child.stderr.on('data', (chunk) => {\n process.stderr.write(chunk);\n });\n\n // Attach lifecycle logging\n child.on('exit', (code, sig) => logger.info(`[${name}] exited (code=${code}, signal=${sig || 'none'})`));\n child.on('error', (err) => logger.info(`[${name}] process error: ${err.message}`));\n\n // Create stop function with graceful shutdown\n const stop = async (signal: NodeJS.Signals = 'SIGINT', opts: { timeoutMs?: number } = {}): Promise<{ timedOut: boolean; killed: boolean }> => {\n // If already exited, return immediately\n if (child.exitCode !== null || child.signalCode !== null) {\n return { timedOut: false, killed: false };\n }\n\n const timeoutMs = opts.timeoutMs ?? 500;\n\n // Wait for 'close' event (process exit + stdio streams closed)\n // This is better than 'exit' because it ensures stdio is fully cleaned up\n const closePromise = new Promise<{ timedOut: boolean; killed: boolean }>((resolve, reject) => {\n let isResolved = false;\n let wasKilled = false;\n let timedOut = false;\n\n const resolveOnce = (didTimeout: boolean) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n resolve({ timedOut: didTimeout, killed: wasKilled });\n };\n\n const rejectOnce = (error: Error) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n reject(error);\n };\n\n // Set timeout for forceful kill\n const timeout = setTimeout(() => {\n timedOut = true;\n const forceKill = async () => {\n try {\n if (process.platform === 'win32' && child.pid) {\n await terminateWindowsProcessTree(child.pid, true);\n wasKilled = true;\n } else if (child.exitCode === null && !child.killed) {\n wasKilled = child.kill('SIGKILL');\n }\n resolveOnce(true);\n } catch (error) {\n rejectOnce(error instanceof Error ? error : new Error(String(error)));\n }\n };\n void forceKill();\n }, timeoutMs);\n\n // Listen for 'close' event (not 'exit') to wait for stdio close\n child.once('close', () => {\n if (!timedOut) resolveOnce(false);\n });\n\n // Also listen for 'error' event in case spawn failed\n // This prevents promise from hanging forever if process never started\n child.once('error', () => {\n resolveOnce(false);\n });\n\n const requestShutdown = async () => {\n try {\n if (child.exitCode !== null || child.signalCode !== null) {\n resolveOnce(false);\n return;\n }\n\n if (process.platform === 'win32' && child.pid) {\n await terminateWindowsProcessTree(child.pid, false);\n } else if (!child.kill(signal)) {\n resolveOnce(false);\n }\n } catch (error) {\n rejectOnce(error instanceof Error ? error : new Error(String(error)));\n }\n };\n void requestShutdown();\n });\n\n return closePromise;\n };\n\n return {\n config: resolvedConfig,\n process: child,\n close: stop,\n };\n}\n"],"names":["spawn","process","logger","resolveArgsPaths","normalizeEnv","env","merged","result","key","value","Object","entries","undefined","terminateWindowsProcessTree","pid","force","args","String","push","Promise","resolve","reject","taskkill","stdio","windowsHide","once","code","Error","spawnProcess","opts","name","command","cwd","shell","platform","resolvedConfig","info","join","spawnOpts","child","stderr","on","chunk","write","sig","err","message","stop","signal","exitCode","signalCode","timedOut","killed","timeoutMs","closePromise","isResolved","wasKilled","resolveOnce","didTimeout","clearTimeout","timeout","rejectOnce","error","setTimeout","forceKill","kill","requestShutdown","config","close"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/spawn/spawn-server.ts"],"sourcesContent":["/**\n * Low-level single server spawning utilities.\n * Provides core process spawning with path resolution, environment management, and lifecycle control.\n */\n\nimport type { ChildProcess, StdioOptions } from 'child_process';\nimport crossSpawn from 'cross-spawn';\nimport * as path from 'path';\nimport * as process from 'process';\nimport type { StopCommandConfig } from '../types.ts';\nimport { logger } from '../utils/logger.ts';\nimport { resolveArgsPaths } from '../utils/path-utils.ts';\nimport { OwnedProcessTree } from './owned-process-tree.ts';\n\nconst DEFAULT_CLOSE_TIMEOUT_MS = 5000;\nconst PROCESS_TERMINATION_TIMEOUT_MS = 5000;\nconst PROCESS_POLL_INTERVAL_MS = 25;\n\ninterface ProcessCloseEvent {\n code: number | null;\n signal: NodeJS.Signals | null;\n}\n\ninterface ProcessCompletion {\n closed: boolean;\n processGroupEmpty?: boolean;\n error?: Error;\n}\n\n/**\n * Options for spawning a single server process.\n * @internal\n */\nexport interface SpawnProcessOptions {\n /** Server name for logging */\n name: string;\n /** Command to execute (e.g., 'node', 'npx') */\n command: string;\n /** Command arguments (paths will be resolved relative to cwd) */\n args?: string[];\n /** Working directory (must be absolute path) */\n cwd?: string;\n /** Additional environment variables (merged with process.env) */\n env?: Record<string, string>;\n /** Whether to add process.env to env; registry callers provide a pre-merged environment */\n inheritEnv?: boolean;\n /** Standard I/O configuration */\n stdio?: StdioOptions;\n /** Application-specific shutdown command for an owned HTTP server */\n stop?: StopCommandConfig;\n}\n\n/**\n * Handle to a spawned server process.\n * Provides access to the process, resolved config, and lifecycle control.\n * @hidden\n */\nexport interface ServerProcess {\n config: {\n name: string;\n command: string;\n args: string[];\n cwd: string;\n env: Record<string, string>;\n stdio: StdioOptions;\n shell: false;\n };\n process: ChildProcess;\n close: (signal?: NodeJS.Signals, opts?: { timeoutMs?: number }) => Promise<{ timedOut: boolean; killed: boolean }>;\n}\n\nfunction normalizeEnv(env?: Record<string, string>, inheritEnv = true): Record<string, string> {\n const merged = { ...(inheritEnv ? process.env : {}), ...(env || {}) };\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(merged)) {\n if (value !== undefined) result[key] = value;\n }\n return result;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function waitForCompletion(closeEvent: Promise<ProcessCloseEvent>, childClosed: () => boolean, tree: OwnedProcessTree | undefined, timeoutMs: number): Promise<ProcessCompletion> {\n if (!tree) return { closed: (await waitForChildClose(closeEvent, timeoutMs)) !== undefined };\n\n const deadline = Date.now() + timeoutMs;\n let lastError: Error | undefined;\n while (true) {\n let processGroupEmpty = false;\n try {\n processGroupEmpty = tree.isEmpty();\n lastError = undefined;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n }\n const closed = childClosed();\n if (closed && processGroupEmpty) return { closed, processGroupEmpty };\n if (Date.now() >= deadline) return { closed, processGroupEmpty, ...(lastError && { error: lastError }) };\n await delay(Math.min(PROCESS_POLL_INTERVAL_MS, Math.max(1, deadline - Date.now())));\n }\n}\n\nfunction isCompletionSuccessful(completion: ProcessCompletion): boolean {\n return completion.closed && completion.processGroupEmpty !== false;\n}\n\nfunction terminateChild(child: ChildProcess, tree: OwnedProcessTree | undefined): { killed: boolean; error?: Error } {\n if (tree) return tree.terminate();\n try {\n return { killed: child.kill('SIGKILL') };\n } catch (error) {\n return { killed: false, error: asError(error) };\n }\n}\n\nfunction waitForChildClose(closeEvent: Promise<ProcessCloseEvent>, timeoutMs: number): Promise<ProcessCloseEvent | undefined> {\n let timer: NodeJS.Timeout | undefined;\n return Promise.race([\n closeEvent,\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), timeoutMs);\n }),\n ]).finally(() => {\n if (timer) clearTimeout(timer);\n });\n}\n\nfunction requestProcessSignal(child: ChildProcess, signal: NodeJS.Signals): void {\n if (!child.pid) throw new Error('Cannot request cooperative shutdown because the server has no process ID');\n if (process.platform === 'win32') throw new Error('POSIX process signals are not available on Windows');\n try {\n process.kill(-child.pid, signal);\n } catch (error) {\n if (!(error instanceof Error && 'code' in error && error.code === 'ESRCH')) throw error;\n }\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction withCause(message: string, cause: Error): Error {\n return new Error(message, { cause });\n}\n\nasync function runStopCommand(command: StopCommandConfig, cwd: string, timeoutMs: number, inheritEnv: boolean): Promise<void> {\n const stopCwd = command.cwd ? path.resolve(cwd, command.cwd) : cwd;\n const env = normalizeEnv(command.env, inheritEnv);\n const args = command.args ? resolveArgsPaths(command.args, stopCwd) : [];\n const child = crossSpawn(command.command, args, {\n cwd: stopCwd,\n env,\n stdio: 'ignore',\n shell: false,\n detached: process.platform !== 'win32',\n windowsHide: true,\n });\n let spawnError: Error | undefined;\n let closed = false;\n let closeEventValue: ProcessCloseEvent | undefined;\n const closeEvent = new Promise<ProcessCloseEvent>((resolve) => {\n child.once('error', (error) => {\n spawnError = error;\n });\n child.once('close', (code, signal) => {\n closed = true;\n closeEventValue = { code, signal };\n resolve({ code, signal });\n });\n });\n\n if (!child.pid) {\n const result = await waitForChildClose(closeEvent, timeoutMs);\n if (!result) throw new Error(`Stop command did not start or close within ${timeoutMs}ms`);\n if (spawnError) throw withCause(`Stop command could not be started: ${spawnError.message}`, spawnError);\n }\n\n const tree = child.pid && process.platform !== 'win32' ? new OwnedProcessTree(child.pid) : undefined;\n const completion = await waitForCompletion(closeEvent, () => closed, tree, timeoutMs);\n if (!isCompletionSuccessful(completion)) {\n const termination = terminateChild(child, tree);\n const afterTermination = await waitForCompletion(closeEvent, () => closed, tree, PROCESS_TERMINATION_TIMEOUT_MS);\n const details = [completion.error?.message, termination.error?.message].filter(Boolean).join('; ');\n const reason = `Stop command exceeded ${timeoutMs}ms and required emergency process termination`;\n const remaining = !isCompletionSuccessful(afterTermination) ? '; stop command or its POSIX process group remained alive' : '';\n throw new Error(`${reason}${details ? `: ${details}` : ''}${remaining}`);\n }\n\n if (spawnError) throw withCause(`Stop command could not be started: ${spawnError.message}`, spawnError);\n if (closeEventValue && (closeEventValue.code !== 0 || closeEventValue.signal !== null)) {\n throw new Error(`Stop command exited with code ${closeEventValue.code ?? 'unknown'}${closeEventValue.signal ? ` (signal ${closeEventValue.signal})` : ''}`);\n }\n}\n\n/**\n * Spawn a single server process with path resolution and environment management.\n *\n * @internal\n * @param opts - Server spawn options\n * @returns ServerProcess handle with resolved config and lifecycle control\n */\nexport function spawnProcess(opts: SpawnProcessOptions): ServerProcess {\n const name = opts.name;\n const command = opts.command;\n const cwd = opts.cwd ?? process.cwd();\n const stdio = opts.stdio ?? 'inherit';\n const args = opts.args ? resolveArgsPaths(opts.args, cwd) : [];\n const inheritEnv = opts.inheritEnv ?? true;\n const env = normalizeEnv(opts.env, inheritEnv);\n const resolvedConfig = { name, command, args, cwd, env, stdio, shell: false as const };\n\n logger.info(`[${name}] → ${command} ${args.join(' ')}`);\n const child = crossSpawn(command, args, {\n cwd,\n env,\n stdio,\n shell: false,\n detached: process.platform !== 'win32',\n windowsHide: true,\n });\n\n if (child.stderr) child.stderr.on('data', (chunk) => process.stderr.write(chunk));\n\n let isClosed = false;\n let _closeEventValue: ProcessCloseEvent | undefined;\n const closeEvent = new Promise<ProcessCloseEvent>((resolve) => {\n child.once('close', (code, signal) => {\n isClosed = true;\n _closeEventValue = { code, signal };\n logger.info(`[${name}] closed (code=${code}, signal=${signal || 'none'})`);\n resolve({ code, signal });\n });\n child.once('error', (error) => logger.info(`[${name}] process error: ${error.message}`));\n });\n const tree = child.pid && process.platform !== 'win32' ? new OwnedProcessTree(child.pid) : undefined;\n let closePromise: Promise<{ timedOut: boolean; killed: boolean }> | undefined;\n\n const performClose = async (signal: NodeJS.Signals, timeoutMs: number): Promise<{ timedOut: boolean; killed: boolean }> => {\n if (!child.pid) {\n await waitForChildClose(closeEvent, timeoutMs);\n throw new Error(`Cannot verify shutdown of server '${name}' because it has no process ID`);\n }\n\n let timedOut = false;\n let killed = false;\n const errors: Error[] = [];\n let stopSucceeded = false;\n\n if (opts.stop) {\n try {\n await runStopCommand(opts.stop, cwd, timeoutMs, inheritEnv);\n stopSucceeded = true;\n } catch (error) {\n errors.push(withCause(`Server '${name}' application stop command failed: ${asError(error).message}`, asError(error)));\n }\n } else if (child.stdin && !child.stdin.destroyed && !child.stdin.writableEnded) {\n child.stdin.end();\n } else if (process.platform !== 'win32') {\n try {\n requestProcessSignal(child, signal);\n } catch (error) {\n errors.push(withCause(`Server '${name}' cooperative signal failed: ${asError(error).message}`, asError(error)));\n }\n }\n\n let completion = await waitForCompletion(closeEvent, () => isClosed, tree, timeoutMs);\n if (completion.error) errors.push(withCause(`Could not inspect the owned process tree for server '${name}'`, completion.error));\n\n if (!isCompletionSuccessful(completion)) {\n timedOut = true;\n if (!opts.stop && child.stdin && process.platform !== 'win32') {\n try {\n requestProcessSignal(child, signal);\n } catch (error) {\n errors.push(withCause(`Server '${name}' cooperative signal failed: ${asError(error).message}`, asError(error)));\n }\n completion = await waitForCompletion(closeEvent, () => isClosed, tree, timeoutMs);\n if (completion.error) errors.push(withCause(`Could not inspect the owned process tree for server '${name}'`, completion.error));\n } else if (!opts.stop && process.platform !== 'win32' && !child.stdin) {\n // The first signal was sent before waiting for an inherited-stdio HTTP process.\n }\n }\n\n if (!isCompletionSuccessful(completion)) {\n const termination = terminateChild(child, tree);\n killed ||= termination.killed;\n if (termination.error) errors.push(withCause(`Emergency termination failed for server '${name}'`, termination.error));\n completion = await waitForCompletion(closeEvent, () => isClosed, tree, PROCESS_TERMINATION_TIMEOUT_MS);\n if (!isCompletionSuccessful(completion)) {\n errors.push(new Error(`Server '${name}' did not close${completion.processGroupEmpty === false ? ' with its POSIX process group' : ''} after emergency termination`));\n }\n }\n\n if (opts.stop && !stopSucceeded && errors.length === 0) {\n errors.push(new Error(`Server '${name}' application stop command did not complete successfully`));\n }\n if (errors.length > 0) throw new AggregateError(errors, `Server '${name}' shutdown completed with ${errors.length} failure(s)`);\n return { timedOut, killed };\n };\n\n const close = (signal: NodeJS.Signals = 'SIGINT', closeOptions: { timeoutMs?: number } = {}): Promise<{ timedOut: boolean; killed: boolean }> => {\n closePromise ??= performClose(signal, closeOptions.timeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS);\n return closePromise;\n };\n\n return { config: resolvedConfig, process: child, close };\n}\n"],"names":["crossSpawn","path","process","logger","resolveArgsPaths","OwnedProcessTree","DEFAULT_CLOSE_TIMEOUT_MS","PROCESS_TERMINATION_TIMEOUT_MS","PROCESS_POLL_INTERVAL_MS","normalizeEnv","env","inheritEnv","merged","result","key","value","Object","entries","undefined","delay","ms","Promise","resolve","setTimeout","waitForCompletion","closeEvent","childClosed","tree","timeoutMs","closed","waitForChildClose","deadline","Date","now","lastError","processGroupEmpty","isEmpty","error","Error","String","Math","min","max","isCompletionSuccessful","completion","terminateChild","child","terminate","killed","kill","asError","timer","race","finally","clearTimeout","requestProcessSignal","signal","pid","platform","code","withCause","message","cause","runStopCommand","command","cwd","stopCwd","args","stdio","shell","detached","windowsHide","spawnError","closeEventValue","once","termination","afterTermination","details","filter","Boolean","join","reason","remaining","spawnProcess","opts","name","resolvedConfig","info","stderr","on","chunk","write","isClosed","_closeEventValue","closePromise","performClose","timedOut","errors","stopSucceeded","stop","push","stdin","destroyed","writableEnded","end","length","AggregateError","close","closeOptions","config"],"mappings":"AAAA;;;CAGC,GAGD,OAAOA,gBAAgB,cAAc;AACrC,YAAYC,UAAU,OAAO;AAC7B,YAAYC,aAAa,UAAU;AAEnC,SAASC,MAAM,QAAQ,qBAAqB;AAC5C,SAASC,gBAAgB,QAAQ,yBAAyB;AAC1D,SAASC,gBAAgB,QAAQ,0BAA0B;AAE3D,MAAMC,2BAA2B;AACjC,MAAMC,iCAAiC;AACvC,MAAMC,2BAA2B;AAuDjC,SAASC,aAAaC,GAA4B,EAAEC,aAAa,IAAI;IACnE,MAAMC,SAAS;QAAE,GAAID,aAAaT,QAAQQ,GAAG,GAAG,CAAC,CAAC;QAAG,GAAIA,OAAO,CAAC,CAAC;IAAE;IACpE,MAAMG,SAAiC,CAAC;IACxC,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACL,QAAS;QACjD,IAAIG,UAAUG,WAAWL,MAAM,CAACC,IAAI,GAAGC;IACzC;IACA,OAAOF;AACT;AAEA,SAASM,MAAMC,EAAU;IACvB,OAAO,IAAIC,QAAQ,CAACC,UAAYC,WAAWD,SAASF;AACtD;AAEA,eAAeI,kBAAkBC,UAAsC,EAAEC,WAA0B,EAAEC,IAAkC,EAAEC,SAAiB;IACxJ,IAAI,CAACD,MAAM,OAAO;QAAEE,QAAQ,AAAC,MAAMC,kBAAkBL,YAAYG,eAAgBV;IAAU;IAE3F,MAAMa,WAAWC,KAAKC,GAAG,KAAKL;IAC9B,IAAIM;IACJ,MAAO,KAAM;QACX,IAAIC,oBAAoB;QACxB,IAAI;YACFA,oBAAoBR,KAAKS,OAAO;YAChCF,YAAYhB;QACd,EAAE,OAAOmB,OAAO;YACdH,YAAYG,iBAAiBC,QAAQD,QAAQ,IAAIC,MAAMC,OAAOF;QAChE;QACA,MAAMR,SAASH;QACf,IAAIG,UAAUM,mBAAmB,OAAO;YAAEN;YAAQM;QAAkB;QACpE,IAAIH,KAAKC,GAAG,MAAMF,UAAU,OAAO;YAAEF;YAAQM;YAAmB,GAAID,aAAa;gBAAEG,OAAOH;YAAU,CAAC;QAAE;QACvG,MAAMf,MAAMqB,KAAKC,GAAG,CAACjC,0BAA0BgC,KAAKE,GAAG,CAAC,GAAGX,WAAWC,KAAKC,GAAG;IAChF;AACF;AAEA,SAASU,uBAAuBC,UAA6B;IAC3D,OAAOA,WAAWf,MAAM,IAAIe,WAAWT,iBAAiB,KAAK;AAC/D;AAEA,SAASU,eAAeC,KAAmB,EAAEnB,IAAkC;IAC7E,IAAIA,MAAM,OAAOA,KAAKoB,SAAS;IAC/B,IAAI;QACF,OAAO;YAAEC,QAAQF,MAAMG,IAAI,CAAC;QAAW;IACzC,EAAE,OAAOZ,OAAO;QACd,OAAO;YAAEW,QAAQ;YAAOX,OAAOa,QAAQb;QAAO;IAChD;AACF;AAEA,SAASP,kBAAkBL,UAAsC,EAAEG,SAAiB;IAClF,IAAIuB;IACJ,OAAO9B,QAAQ+B,IAAI,CAAC;QAClB3B;QACA,IAAIJ,QAAmB,CAACC;YACtB6B,QAAQ5B,WAAW,IAAMD,QAAQJ,YAAYU;QAC/C;KACD,EAAEyB,OAAO,CAAC;QACT,IAAIF,OAAOG,aAAaH;IAC1B;AACF;AAEA,SAASI,qBAAqBT,KAAmB,EAAEU,MAAsB;IACvE,IAAI,CAACV,MAAMW,GAAG,EAAE,MAAM,IAAInB,MAAM;IAChC,IAAIpC,QAAQwD,QAAQ,KAAK,SAAS,MAAM,IAAIpB,MAAM;IAClD,IAAI;QACFpC,QAAQ+C,IAAI,CAAC,CAACH,MAAMW,GAAG,EAAED;IAC3B,EAAE,OAAOnB,OAAO;QACd,IAAI,CAAEA,CAAAA,iBAAiBC,SAAS,UAAUD,SAASA,MAAMsB,IAAI,KAAK,OAAM,GAAI,MAAMtB;IACpF;AACF;AAEA,SAASa,QAAQb,KAAc;IAC7B,OAAOA,iBAAiBC,QAAQD,QAAQ,IAAIC,MAAMC,OAAOF;AAC3D;AAEA,SAASuB,UAAUC,OAAe,EAAEC,KAAY;IAC9C,OAAO,IAAIxB,MAAMuB,SAAS;QAAEC;IAAM;AACpC;AAEA,eAAeC,eAAeC,OAA0B,EAAEC,GAAW,EAAErC,SAAiB,EAAEjB,UAAmB;IAC3G,MAAMuD,UAAUF,QAAQC,GAAG,GAAGhE,KAAKqB,OAAO,CAAC2C,KAAKD,QAAQC,GAAG,IAAIA;IAC/D,MAAMvD,MAAMD,aAAauD,QAAQtD,GAAG,EAAEC;IACtC,MAAMwD,OAAOH,QAAQG,IAAI,GAAG/D,iBAAiB4D,QAAQG,IAAI,EAAED,WAAW,EAAE;IACxE,MAAMpB,QAAQ9C,WAAWgE,QAAQA,OAAO,EAAEG,MAAM;QAC9CF,KAAKC;QACLxD;QACA0D,OAAO;QACPC,OAAO;QACPC,UAAUpE,QAAQwD,QAAQ,KAAK;QAC/Ba,aAAa;IACf;IACA,IAAIC;IACJ,IAAI3C,SAAS;IACb,IAAI4C;IACJ,MAAMhD,aAAa,IAAIJ,QAA2B,CAACC;QACjDwB,MAAM4B,IAAI,CAAC,SAAS,CAACrC;YACnBmC,aAAanC;QACf;QACAS,MAAM4B,IAAI,CAAC,SAAS,CAACf,MAAMH;YACzB3B,SAAS;YACT4C,kBAAkB;gBAAEd;gBAAMH;YAAO;YACjClC,QAAQ;gBAAEqC;gBAAMH;YAAO;QACzB;IACF;IAEA,IAAI,CAACV,MAAMW,GAAG,EAAE;QACd,MAAM5C,SAAS,MAAMiB,kBAAkBL,YAAYG;QACnD,IAAI,CAACf,QAAQ,MAAM,IAAIyB,MAAM,CAAC,2CAA2C,EAAEV,UAAU,EAAE,CAAC;QACxF,IAAI4C,YAAY,MAAMZ,UAAU,CAAC,mCAAmC,EAAEY,WAAWX,OAAO,EAAE,EAAEW;IAC9F;IAEA,MAAM7C,OAAOmB,MAAMW,GAAG,IAAIvD,QAAQwD,QAAQ,KAAK,UAAU,IAAIrD,iBAAiByC,MAAMW,GAAG,IAAIvC;IAC3F,MAAM0B,aAAa,MAAMpB,kBAAkBC,YAAY,IAAMI,QAAQF,MAAMC;IAC3E,IAAI,CAACe,uBAAuBC,aAAa;YAGtBA,mBAA2B+B;QAF5C,MAAMA,cAAc9B,eAAeC,OAAOnB;QAC1C,MAAMiD,mBAAmB,MAAMpD,kBAAkBC,YAAY,IAAMI,QAAQF,MAAMpB;QACjF,MAAMsE,UAAU;aAACjC,oBAAAA,WAAWP,KAAK,cAAhBO,wCAAAA,kBAAkBiB,OAAO;aAAEc,qBAAAA,YAAYtC,KAAK,cAAjBsC,yCAAAA,mBAAmBd,OAAO;SAAC,CAACiB,MAAM,CAACC,SAASC,IAAI,CAAC;QAC7F,MAAMC,SAAS,CAAC,sBAAsB,EAAErD,UAAU,6CAA6C,CAAC;QAChG,MAAMsD,YAAY,CAACvC,uBAAuBiC,oBAAoB,6DAA6D;QAC3H,MAAM,IAAItC,MAAM,GAAG2C,SAASJ,UAAU,CAAC,EAAE,EAAEA,SAAS,GAAG,KAAKK,WAAW;IACzE;IAEA,IAAIV,YAAY,MAAMZ,UAAU,CAAC,mCAAmC,EAAEY,WAAWX,OAAO,EAAE,EAAEW;IAC5F,IAAIC,mBAAoBA,CAAAA,gBAAgBd,IAAI,KAAK,KAAKc,gBAAgBjB,MAAM,KAAK,IAAG,GAAI;YACrCiB;QAAjD,MAAM,IAAInC,MAAM,CAAC,8BAA8B,GAAEmC,wBAAAA,gBAAgBd,IAAI,cAApBc,mCAAAA,wBAAwB,YAAYA,gBAAgBjB,MAAM,GAAG,CAAC,SAAS,EAAEiB,gBAAgBjB,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI;IAC5J;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAAS2B,aAAaC,IAAyB;QAGxCA,WACEA,aAEKA;IALnB,MAAMC,OAAOD,KAAKC,IAAI;IACtB,MAAMrB,UAAUoB,KAAKpB,OAAO;IAC5B,MAAMC,OAAMmB,YAAAA,KAAKnB,GAAG,cAARmB,uBAAAA,YAAYlF,QAAQ+D,GAAG;IACnC,MAAMG,SAAQgB,cAAAA,KAAKhB,KAAK,cAAVgB,yBAAAA,cAAc;IAC5B,MAAMjB,OAAOiB,KAAKjB,IAAI,GAAG/D,iBAAiBgF,KAAKjB,IAAI,EAAEF,OAAO,EAAE;IAC9D,MAAMtD,cAAayE,mBAAAA,KAAKzE,UAAU,cAAfyE,8BAAAA,mBAAmB;IACtC,MAAM1E,MAAMD,aAAa2E,KAAK1E,GAAG,EAAEC;IACnC,MAAM2E,iBAAiB;QAAED;QAAMrB;QAASG;QAAMF;QAAKvD;QAAK0D;QAAOC,OAAO;IAAe;IAErFlE,OAAOoF,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,IAAI,EAAErB,QAAQ,CAAC,EAAEG,KAAKa,IAAI,CAAC,MAAM;IACtD,MAAMlC,QAAQ9C,WAAWgE,SAASG,MAAM;QACtCF;QACAvD;QACA0D;QACAC,OAAO;QACPC,UAAUpE,QAAQwD,QAAQ,KAAK;QAC/Ba,aAAa;IACf;IAEA,IAAIzB,MAAM0C,MAAM,EAAE1C,MAAM0C,MAAM,CAACC,EAAE,CAAC,QAAQ,CAACC,QAAUxF,QAAQsF,MAAM,CAACG,KAAK,CAACD;IAE1E,IAAIE,WAAW;IACf,IAAIC;IACJ,MAAMpE,aAAa,IAAIJ,QAA2B,CAACC;QACjDwB,MAAM4B,IAAI,CAAC,SAAS,CAACf,MAAMH;YACzBoC,WAAW;YACXC,mBAAmB;gBAAElC;gBAAMH;YAAO;YAClCrD,OAAOoF,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,eAAe,EAAE1B,KAAK,SAAS,EAAEH,UAAU,OAAO,CAAC,CAAC;YACzElC,QAAQ;gBAAEqC;gBAAMH;YAAO;QACzB;QACAV,MAAM4B,IAAI,CAAC,SAAS,CAACrC,QAAUlC,OAAOoF,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,iBAAiB,EAAEhD,MAAMwB,OAAO,EAAE;IACxF;IACA,MAAMlC,OAAOmB,MAAMW,GAAG,IAAIvD,QAAQwD,QAAQ,KAAK,UAAU,IAAIrD,iBAAiByC,MAAMW,GAAG,IAAIvC;IAC3F,IAAI4E;IAEJ,MAAMC,eAAe,OAAOvC,QAAwB5B;QAClD,IAAI,CAACkB,MAAMW,GAAG,EAAE;YACd,MAAM3B,kBAAkBL,YAAYG;YACpC,MAAM,IAAIU,MAAM,CAAC,kCAAkC,EAAE+C,KAAK,8BAA8B,CAAC;QAC3F;QAEA,IAAIW,WAAW;QACf,IAAIhD,SAAS;QACb,MAAMiD,SAAkB,EAAE;QAC1B,IAAIC,gBAAgB;QAEpB,IAAId,KAAKe,IAAI,EAAE;YACb,IAAI;gBACF,MAAMpC,eAAeqB,KAAKe,IAAI,EAAElC,KAAKrC,WAAWjB;gBAChDuF,gBAAgB;YAClB,EAAE,OAAO7D,OAAO;gBACd4D,OAAOG,IAAI,CAACxC,UAAU,CAAC,QAAQ,EAAEyB,KAAK,mCAAmC,EAAEnC,QAAQb,OAAOwB,OAAO,EAAE,EAAEX,QAAQb;YAC/G;QACF,OAAO,IAAIS,MAAMuD,KAAK,IAAI,CAACvD,MAAMuD,KAAK,CAACC,SAAS,IAAI,CAACxD,MAAMuD,KAAK,CAACE,aAAa,EAAE;YAC9EzD,MAAMuD,KAAK,CAACG,GAAG;QACjB,OAAO,IAAItG,QAAQwD,QAAQ,KAAK,SAAS;YACvC,IAAI;gBACFH,qBAAqBT,OAAOU;YAC9B,EAAE,OAAOnB,OAAO;gBACd4D,OAAOG,IAAI,CAACxC,UAAU,CAAC,QAAQ,EAAEyB,KAAK,6BAA6B,EAAEnC,QAAQb,OAAOwB,OAAO,EAAE,EAAEX,QAAQb;YACzG;QACF;QAEA,IAAIO,aAAa,MAAMpB,kBAAkBC,YAAY,IAAMmE,UAAUjE,MAAMC;QAC3E,IAAIgB,WAAWP,KAAK,EAAE4D,OAAOG,IAAI,CAACxC,UAAU,CAAC,qDAAqD,EAAEyB,KAAK,CAAC,CAAC,EAAEzC,WAAWP,KAAK;QAE7H,IAAI,CAACM,uBAAuBC,aAAa;YACvCoD,WAAW;YACX,IAAI,CAACZ,KAAKe,IAAI,IAAIrD,MAAMuD,KAAK,IAAInG,QAAQwD,QAAQ,KAAK,SAAS;gBAC7D,IAAI;oBACFH,qBAAqBT,OAAOU;gBAC9B,EAAE,OAAOnB,OAAO;oBACd4D,OAAOG,IAAI,CAACxC,UAAU,CAAC,QAAQ,EAAEyB,KAAK,6BAA6B,EAAEnC,QAAQb,OAAOwB,OAAO,EAAE,EAAEX,QAAQb;gBACzG;gBACAO,aAAa,MAAMpB,kBAAkBC,YAAY,IAAMmE,UAAUjE,MAAMC;gBACvE,IAAIgB,WAAWP,KAAK,EAAE4D,OAAOG,IAAI,CAACxC,UAAU,CAAC,qDAAqD,EAAEyB,KAAK,CAAC,CAAC,EAAEzC,WAAWP,KAAK;YAC/H,OAAO,IAAI,CAAC+C,KAAKe,IAAI,IAAIjG,QAAQwD,QAAQ,KAAK,WAAW,CAACZ,MAAMuD,KAAK,EAAE;YACrE,gFAAgF;YAClF;QACF;QAEA,IAAI,CAAC1D,uBAAuBC,aAAa;YACvC,MAAM+B,cAAc9B,eAAeC,OAAOnB;YAC1CqB,WAAAA,SAAW2B,YAAY3B,MAAM;YAC7B,IAAI2B,YAAYtC,KAAK,EAAE4D,OAAOG,IAAI,CAACxC,UAAU,CAAC,yCAAyC,EAAEyB,KAAK,CAAC,CAAC,EAAEV,YAAYtC,KAAK;YACnHO,aAAa,MAAMpB,kBAAkBC,YAAY,IAAMmE,UAAUjE,MAAMpB;YACvE,IAAI,CAACoC,uBAAuBC,aAAa;gBACvCqD,OAAOG,IAAI,CAAC,IAAI9D,MAAM,CAAC,QAAQ,EAAE+C,KAAK,eAAe,EAAEzC,WAAWT,iBAAiB,KAAK,QAAQ,kCAAkC,GAAG,4BAA4B,CAAC;YACpK;QACF;QAEA,IAAIiD,KAAKe,IAAI,IAAI,CAACD,iBAAiBD,OAAOQ,MAAM,KAAK,GAAG;YACtDR,OAAOG,IAAI,CAAC,IAAI9D,MAAM,CAAC,QAAQ,EAAE+C,KAAK,wDAAwD,CAAC;QACjG;QACA,IAAIY,OAAOQ,MAAM,GAAG,GAAG,MAAM,IAAIC,eAAeT,QAAQ,CAAC,QAAQ,EAAEZ,KAAK,0BAA0B,EAAEY,OAAOQ,MAAM,CAAC,WAAW,CAAC;QAC9H,OAAO;YAAET;YAAUhD;QAAO;IAC5B;IAEA,MAAM2D,QAAQ,CAACnD,SAAyB,QAAQ,EAAEoD,eAAuC,CAAC,CAAC;YACnDA;QAAtCd,yBAAAA,0BAAAA,eAAAA,eAAiBC,aAAavC,SAAQoD,0BAAAA,aAAahF,SAAS,cAAtBgF,qCAAAA,0BAA0BtG;QAChE,OAAOwF;IACT;IAEA,OAAO;QAAEe,QAAQvB;QAAgBpF,SAAS4C;QAAO6D;IAAM;AACzD"}
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* Supports stdio, http, and ws transports.
|
|
5
5
|
* Implements Claude Code-compatible configuration with start extension support.
|
|
6
6
|
*/
|
|
7
|
+
import type { VersionNegotiationOptions } from '@modelcontextprotocol/client';
|
|
7
8
|
import { type ManagedClient } from '../client-helpers.js';
|
|
8
|
-
import {
|
|
9
|
+
import type { DcrAuthenticatorOptions } from '../dcr/dcr-authenticator.js';
|
|
9
10
|
import { type SearchOptions, type SearchResponse } from '../search/index.js';
|
|
10
11
|
import type { McpServerEntry } from '../types.js';
|
|
12
|
+
import { type Logger } from '../utils/logger.js';
|
|
11
13
|
import { type ServerProcess } from './spawn-server.js';
|
|
12
14
|
/**
|
|
13
15
|
* Servers configuration type - a map of server names to their configurations.
|
|
@@ -45,16 +47,15 @@ export interface CreateServerRegistryOptions {
|
|
|
45
47
|
* Result of closing the registry.
|
|
46
48
|
*/
|
|
47
49
|
export interface CloseResult {
|
|
48
|
-
/** Whether any process
|
|
50
|
+
/** Whether any owned process exceeded its cooperative shutdown timeout */
|
|
49
51
|
timedOut: boolean;
|
|
50
|
-
/** Number of processes that
|
|
52
|
+
/** Number of owned processes that required emergency force termination */
|
|
51
53
|
killedCount: number;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* A registry of spawned MCP servers with connection management.
|
|
55
57
|
* Provides access to individual server handles, connection management, and collection-wide close.
|
|
56
58
|
*/
|
|
57
|
-
type RegistryConnectOptions = Parameters<typeof connectMcpClient>[2];
|
|
58
59
|
export interface ServerRegistry {
|
|
59
60
|
/**
|
|
60
61
|
* The resolved servers configuration that was used.
|
|
@@ -76,16 +77,26 @@ export interface ServerRegistry {
|
|
|
76
77
|
* The connected client is automatically tracked for close.
|
|
77
78
|
*
|
|
78
79
|
* @param name - Server name from configuration
|
|
80
|
+
* @param options - Authentication, logging, and protocol negotiation options
|
|
79
81
|
* @returns Connected MCP SDK Client
|
|
80
82
|
*/
|
|
81
|
-
connect: (name: string, options?:
|
|
83
|
+
connect: (name: string, options?: {
|
|
84
|
+
dcrAuthenticator?: Partial<DcrAuthenticatorOptions>;
|
|
85
|
+
logger?: Logger;
|
|
86
|
+
versionNegotiation?: VersionNegotiationOptions;
|
|
87
|
+
}) => Promise<ManagedClient>;
|
|
82
88
|
/**
|
|
83
89
|
* Close all clients and servers gracefully.
|
|
84
|
-
*
|
|
90
|
+
* Stops new connections, waits for in-flight connections, closes tracked clients, and then
|
|
91
|
+
* stops only processes owned by this registry. Stdio children receive stdin EOF first
|
|
92
|
+
* and the requested signal on POSIX if they remain open; owned HTTP children may use
|
|
93
|
+
* their configured application-specific stop command. POSIX cleanup tracks the detached
|
|
94
|
+
* process group; Windows can terminate only the direct child and cannot verify descendants.
|
|
95
|
+
* Cleanup failures are reported after every owned resource has been attempted.
|
|
85
96
|
*
|
|
86
97
|
* @param signal - Signal to send to processes (default: SIGINT)
|
|
87
98
|
* @param opts - Options including timeout
|
|
88
|
-
* @returns Promise resolving to whether any process timed out and how many
|
|
99
|
+
* @returns Promise resolving to whether any process timed out and how many required emergency force termination
|
|
89
100
|
*/
|
|
90
101
|
close: (signal?: NodeJS.Signals, opts?: {
|
|
91
102
|
timeoutMs?: number;
|
|
@@ -148,4 +159,3 @@ export interface ServerRegistry {
|
|
|
148
159
|
* // Auto-disposed when scope exits
|
|
149
160
|
*/
|
|
150
161
|
export declare function createServerRegistry(serversConfig: ServersConfig, options?: CreateServerRegistryOptions): ServerRegistry;
|
|
151
|
-
export {};
|