@mcp-z/client 2.2.3 → 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.
Files changed (111) hide show
  1. package/README.md +12 -1
  2. package/dist/cjs/auth/capability-discovery.d.cts +3 -1
  3. package/dist/cjs/auth/capability-discovery.d.ts +3 -1
  4. package/dist/cjs/auth/capability-discovery.js +46 -32
  5. package/dist/cjs/auth/capability-discovery.js.map +1 -1
  6. package/dist/cjs/auth/discovery-fetch.d.cts +2 -0
  7. package/dist/cjs/auth/discovery-fetch.d.ts +2 -0
  8. package/dist/cjs/auth/discovery-fetch.js +85 -31
  9. package/dist/cjs/auth/discovery-fetch.js.map +1 -1
  10. package/dist/cjs/auth/interactive-oauth-flow.d.cts +1 -1
  11. package/dist/cjs/auth/interactive-oauth-flow.d.ts +1 -1
  12. package/dist/cjs/auth/interactive-oauth-flow.js +13 -6
  13. package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
  14. package/dist/cjs/auth/oauth-callback-listener.d.cts +1 -1
  15. package/dist/cjs/auth/oauth-callback-listener.d.ts +1 -1
  16. package/dist/cjs/auth/oauth-callback-listener.js +40 -4
  17. package/dist/cjs/auth/oauth-callback-listener.js.map +1 -1
  18. package/dist/cjs/auth/rfc9728-discovery.d.cts +7 -2
  19. package/dist/cjs/auth/rfc9728-discovery.d.ts +7 -2
  20. package/dist/cjs/auth/rfc9728-discovery.js +120 -88
  21. package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
  22. package/dist/cjs/auth/types.d.cts +4 -0
  23. package/dist/cjs/auth/types.d.ts +4 -0
  24. package/dist/cjs/auth/types.js.map +1 -1
  25. package/dist/cjs/connection/connect-client.d.cts +9 -1
  26. package/dist/cjs/connection/connect-client.d.ts +9 -1
  27. package/dist/cjs/connection/connect-client.js +282 -75
  28. package/dist/cjs/connection/connect-client.js.map +1 -1
  29. package/dist/cjs/connection/existing-process-transport.d.cts +18 -18
  30. package/dist/cjs/connection/existing-process-transport.d.ts +18 -18
  31. package/dist/cjs/connection/existing-process-transport.js +187 -82
  32. package/dist/cjs/connection/existing-process-transport.js.map +1 -1
  33. package/dist/cjs/connection/wait-for-http-ready.d.cts +1 -1
  34. package/dist/cjs/connection/wait-for-http-ready.d.ts +1 -1
  35. package/dist/cjs/connection/wait-for-http-ready.js +30 -5
  36. package/dist/cjs/connection/wait-for-http-ready.js.map +1 -1
  37. package/dist/cjs/dcr/dcr-authenticator.d.cts +1 -1
  38. package/dist/cjs/dcr/dcr-authenticator.d.ts +1 -1
  39. package/dist/cjs/dcr/dcr-authenticator.js +29 -18
  40. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
  41. package/dist/cjs/dcr/dynamic-client-registrar.js +2 -1
  42. package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
  43. package/dist/cjs/index.d.cts +1 -1
  44. package/dist/cjs/index.d.ts +1 -1
  45. package/dist/cjs/index.js.map +1 -1
  46. package/dist/cjs/spawn/owned-process-tree.d.cts +12 -0
  47. package/dist/cjs/spawn/owned-process-tree.d.ts +12 -0
  48. package/dist/cjs/spawn/owned-process-tree.js +93 -0
  49. package/dist/cjs/spawn/owned-process-tree.js.map +1 -0
  50. package/dist/cjs/spawn/spawn-server.d.cts +8 -32
  51. package/dist/cjs/spawn/spawn-server.d.ts +8 -32
  52. package/dist/cjs/spawn/spawn-server.js +406 -97
  53. package/dist/cjs/spawn/spawn-server.js.map +1 -1
  54. package/dist/cjs/spawn/spawn-servers.d.cts +18 -8
  55. package/dist/cjs/spawn/spawn-servers.d.ts +18 -8
  56. package/dist/cjs/spawn/spawn-servers.js +342 -113
  57. package/dist/cjs/spawn/spawn-servers.js.map +1 -1
  58. package/dist/cjs/types.d.cts +1 -1
  59. package/dist/cjs/types.d.ts +1 -1
  60. package/dist/cjs/types.js.map +1 -1
  61. package/dist/cjs/utils/path-utils.js +7 -4
  62. package/dist/cjs/utils/path-utils.js.map +1 -1
  63. package/dist/esm/auth/capability-discovery.d.ts +3 -1
  64. package/dist/esm/auth/capability-discovery.js +21 -11
  65. package/dist/esm/auth/capability-discovery.js.map +1 -1
  66. package/dist/esm/auth/discovery-fetch.d.ts +2 -0
  67. package/dist/esm/auth/discovery-fetch.js +64 -23
  68. package/dist/esm/auth/discovery-fetch.js.map +1 -1
  69. package/dist/esm/auth/interactive-oauth-flow.d.ts +1 -1
  70. package/dist/esm/auth/interactive-oauth-flow.js +13 -6
  71. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
  72. package/dist/esm/auth/oauth-callback-listener.d.ts +1 -1
  73. package/dist/esm/auth/oauth-callback-listener.js +34 -4
  74. package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
  75. package/dist/esm/auth/rfc9728-discovery.d.ts +7 -2
  76. package/dist/esm/auth/rfc9728-discovery.js +40 -16
  77. package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
  78. package/dist/esm/auth/types.d.ts +4 -0
  79. package/dist/esm/auth/types.js.map +1 -1
  80. package/dist/esm/connection/connect-client.d.ts +9 -1
  81. package/dist/esm/connection/connect-client.js +97 -35
  82. package/dist/esm/connection/connect-client.js.map +1 -1
  83. package/dist/esm/connection/existing-process-transport.d.ts +18 -18
  84. package/dist/esm/connection/existing-process-transport.js +132 -70
  85. package/dist/esm/connection/existing-process-transport.js.map +1 -1
  86. package/dist/esm/connection/wait-for-http-ready.d.ts +1 -1
  87. package/dist/esm/connection/wait-for-http-ready.js +19 -4
  88. package/dist/esm/connection/wait-for-http-ready.js.map +1 -1
  89. package/dist/esm/dcr/dcr-authenticator.d.ts +1 -1
  90. package/dist/esm/dcr/dcr-authenticator.js +26 -15
  91. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  92. package/dist/esm/dcr/dynamic-client-registrar.js +2 -1
  93. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
  94. package/dist/esm/index.d.ts +1 -1
  95. package/dist/esm/index.js.map +1 -1
  96. package/dist/esm/spawn/owned-process-tree.d.ts +12 -0
  97. package/dist/esm/spawn/owned-process-tree.js +39 -0
  98. package/dist/esm/spawn/owned-process-tree.js.map +1 -0
  99. package/dist/esm/spawn/spawn-server.d.ts +8 -32
  100. package/dist/esm/spawn/spawn-server.js +228 -99
  101. package/dist/esm/spawn/spawn-server.js.map +1 -1
  102. package/dist/esm/spawn/spawn-servers.d.ts +18 -8
  103. package/dist/esm/spawn/spawn-servers.js +180 -55
  104. package/dist/esm/spawn/spawn-servers.js.map +1 -1
  105. package/dist/esm/types.d.ts +1 -1
  106. package/dist/esm/types.js.map +1 -1
  107. package/dist/esm/utils/path-utils.js +7 -4
  108. package/dist/esm/utils/path-utils.js.map +1 -1
  109. package/package.json +8 -3
  110. package/schemas/servers.d.ts +24 -0
  111. package/schemas/servers.schema.json +31 -0
@@ -1,55 +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 { spawn } from 'child_process';
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
- * Normalize environment variables by merging with process.env and filtering undefined values.
10
- */ function normalizeEnv(env) {
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
  }
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
51
+ }
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
+ });
129
+ });
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
+ }
155
+ }
23
156
  /**
24
157
  * Spawn a single server process with path resolution and environment management.
25
158
  *
26
159
  * @internal
27
160
  * @param opts - Server spawn options
28
- * @returns ServerProcess handle with resolved config, process, and stop function
29
- *
30
- * @example
31
- * const handle = spawnProcess({
32
- * name: 'echo',
33
- * command: 'node',
34
- * args: ['./bin/server.js', '--port', '3000'],
35
- * cwd: '/home/user/project/test/lib/servers/echo',
36
- * env: { LOG_LEVEL: 'error' }
37
- * });
38
- *
39
- * // Later...
40
- * await handle.close();
161
+ * @returns ServerProcess handle with resolved config and lifecycle control
41
162
  */ export function spawnProcess(opts) {
42
- var _opts_cwd, _opts_stdio, _opts_shell;
163
+ var _opts_cwd, _opts_stdio, _opts_inheritEnv;
43
164
  const name = opts.name;
44
165
  const command = opts.command;
45
166
  const cwd = (_opts_cwd = opts.cwd) !== null && _opts_cwd !== void 0 ? _opts_cwd : process.cwd();
46
167
  const stdio = (_opts_stdio = opts.stdio) !== null && _opts_stdio !== void 0 ? _opts_stdio : 'inherit';
47
- const shell = (_opts_shell = opts.shell) !== null && _opts_shell !== void 0 ? _opts_shell : process.platform === 'win32';
48
- // Resolve paths in args relative to the working directory
49
168
  const args = opts.args ? resolveArgsPaths(opts.args, cwd) : [];
50
- // Merge environment variables
51
- const env = normalizeEnv(opts.env);
52
- // 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);
53
171
  const resolvedConfig = {
54
172
  name,
55
173
  command,
@@ -57,93 +175,104 @@ import { resolveArgsPaths } from '../utils/path-utils.js';
57
175
  cwd,
58
176
  env,
59
177
  stdio,
60
- shell
178
+ shell: false
61
179
  };
62
- // Log spawn operation
63
180
  logger.info(`[${name}] → ${command} ${args.join(' ')}`);
64
- // Spawn the process
65
- const spawnOpts = {
181
+ const child = crossSpawn(command, args, {
66
182
  cwd,
67
183
  env,
68
184
  stdio,
69
- shell
70
- };
71
- const child = spawn(command, args, spawnOpts);
72
- // Pipe stdio if not inherited
73
- if (child.stderr) child.stderr.on('data', (chunk)=>{
74
- process.stderr.write(chunk);
185
+ shell: false,
186
+ detached: process.platform !== 'win32',
187
+ windowsHide: true
75
188
  });
76
- // Attach lifecycle logging
77
- child.on('exit', (code, sig)=>logger.info(`[${name}] exited (code=${code}, signal=${sig || 'none'})`));
78
- child.on('error', (err)=>logger.info(`[${name}] process error: ${err.message}`));
79
- // Create stop function with graceful shutdown
80
- const stop = async (signal = 'SIGINT', opts = {})=>{
81
- var _opts_timeoutMs;
82
- // If already exited, return immediately
83
- if (child.exitCode !== null || child.signalCode !== null) {
84
- return {
85
- timedOut: false,
86
- killed: false
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
87
198
  };
88
- }
89
- const timeoutMs = (_opts_timeoutMs = opts.timeoutMs) !== null && _opts_timeoutMs !== void 0 ? _opts_timeoutMs : 500;
90
- // Wait for 'close' event (process exit + stdio streams closed)
91
- // This is better than 'exit' because it ensures stdio is fully cleaned up
92
- const closePromise = new Promise((resolve)=>{
93
- let isResolved = false;
94
- let wasKilled = false;
95
- const resolveOnce = (timedOut)=>{
96
- if (isResolved) return;
97
- isResolved = true;
98
- clearTimeout(timeout);
99
- resolve({
100
- timedOut,
101
- killed: wasKilled
102
- });
103
- };
104
- // Set timeout for forceful kill
105
- const timeout = setTimeout(()=>{
106
- try {
107
- // Check again before SIGKILL
108
- if (child.exitCode === null && !child.killed) {
109
- child.kill('SIGKILL');
110
- wasKilled = true;
111
- }
112
- } catch (_) {}
113
- // Even if kill fails, resolve
114
- resolveOnce(true);
115
- }, timeoutMs);
116
- // Listen for 'close' event (not 'exit') to wait for stdio close
117
- child.once('close', ()=>{
118
- resolveOnce(false);
119
- });
120
- // Also listen for 'error' event in case spawn failed
121
- // This prevents promise from hanging forever if process never started
122
- child.once('error', ()=>{
123
- resolveOnce(false);
199
+ logger.info(`[${name}] closed (code=${code}, signal=${signal || 'none'})`);
200
+ resolve({
201
+ code,
202
+ signal
124
203
  });
125
- // Send graceful shutdown signal
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) {
126
219
  try {
127
- // Check one more time before killing
128
- if (child.exitCode !== null) {
129
- resolveOnce(false);
130
- return;
131
- }
132
- const killed = child.kill(signal);
133
- // If kill returned false, process already exited
134
- if (!killed) {
135
- resolveOnce(false);
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') {
239
+ try {
240
+ requestProcessSignal(child, signal);
241
+ } catch (error) {
242
+ errors.push(withCause(`Server '${name}' cooperative signal failed: ${asError(error).message}`, asError(error)));
136
243
  }
137
- } catch (_err) {
138
- // If kill throws, process is gone or unreachable
139
- resolveOnce(false);
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.
140
248
  }
141
- });
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);
142
271
  return closePromise;
143
272
  };
144
273
  return {
145
274
  config: resolvedConfig,
146
275
  process: child,
147
- close: stop
276
+ close
148
277
  };
149
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.\n * Sends the specified signal (default: SIGINT), then SIGKILL 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\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) => {\n let isResolved = false;\n let wasKilled = false;\n\n const resolveOnce = (timedOut: boolean) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n resolve({ timedOut, killed: wasKilled });\n };\n\n // Set timeout for forceful kill\n const timeout = setTimeout(() => {\n try {\n // Check again before SIGKILL\n if (child.exitCode === null && !child.killed) {\n child.kill('SIGKILL');\n wasKilled = true;\n }\n } catch (_) {}\n // Even if kill fails, resolve\n resolveOnce(true);\n }, timeoutMs);\n\n // Listen for 'close' event (not 'exit') to wait for stdio close\n child.once('close', () => {\n 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 // Send graceful shutdown signal\n try {\n // Check one more time before killing\n if (child.exitCode !== null) {\n resolveOnce(false);\n return;\n }\n\n const killed = child.kill(signal);\n // If kill returned false, process already exited\n if (!killed) {\n resolveOnce(false);\n }\n } catch (_err) {\n // If kill throws, process is gone or unreachable\n resolveOnce(false);\n }\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","spawnProcess","opts","name","command","cwd","stdio","shell","platform","args","resolvedConfig","info","join","spawnOpts","child","stderr","on","chunk","write","code","sig","err","message","stop","signal","exitCode","signalCode","timedOut","killed","timeoutMs","closePromise","Promise","resolve","isResolved","wasKilled","resolveOnce","clearTimeout","timeout","setTimeout","kill","_","once","_err","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":"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"}
@@ -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 { connectMcpClient } from '../connection/connect-client.js';
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 timed out during shutdown */
50
+ /** Whether any owned process exceeded its cooperative shutdown timeout */
49
51
  timedOut: boolean;
50
- /** Number of processes that were force-killed */
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?: RegistryConnectOptions) => Promise<ManagedClient>;
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
- * First closes all tracked clients, then sends the specified signal to all server processes.
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 were force-killed
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 {};