@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.
Files changed (107) 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 +38 -2
  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 +398 -212
  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/esm/auth/capability-discovery.d.ts +3 -1
  62. package/dist/esm/auth/capability-discovery.js +21 -11
  63. package/dist/esm/auth/capability-discovery.js.map +1 -1
  64. package/dist/esm/auth/discovery-fetch.d.ts +2 -0
  65. package/dist/esm/auth/discovery-fetch.js +64 -23
  66. package/dist/esm/auth/discovery-fetch.js.map +1 -1
  67. package/dist/esm/auth/interactive-oauth-flow.d.ts +1 -1
  68. package/dist/esm/auth/interactive-oauth-flow.js +13 -6
  69. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
  70. package/dist/esm/auth/oauth-callback-listener.d.ts +1 -1
  71. package/dist/esm/auth/oauth-callback-listener.js +32 -2
  72. package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
  73. package/dist/esm/auth/rfc9728-discovery.d.ts +7 -2
  74. package/dist/esm/auth/rfc9728-discovery.js +40 -16
  75. package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
  76. package/dist/esm/auth/types.d.ts +4 -0
  77. package/dist/esm/auth/types.js.map +1 -1
  78. package/dist/esm/connection/connect-client.d.ts +9 -1
  79. package/dist/esm/connection/connect-client.js +97 -35
  80. package/dist/esm/connection/connect-client.js.map +1 -1
  81. package/dist/esm/connection/existing-process-transport.d.ts +18 -18
  82. package/dist/esm/connection/existing-process-transport.js +132 -70
  83. package/dist/esm/connection/existing-process-transport.js.map +1 -1
  84. package/dist/esm/connection/wait-for-http-ready.d.ts +1 -1
  85. package/dist/esm/connection/wait-for-http-ready.js +19 -4
  86. package/dist/esm/connection/wait-for-http-ready.js.map +1 -1
  87. package/dist/esm/dcr/dcr-authenticator.d.ts +1 -1
  88. package/dist/esm/dcr/dcr-authenticator.js +26 -15
  89. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  90. package/dist/esm/dcr/dynamic-client-registrar.js +2 -1
  91. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
  92. package/dist/esm/index.d.ts +1 -1
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/spawn/owned-process-tree.d.ts +12 -0
  95. package/dist/esm/spawn/owned-process-tree.js +39 -0
  96. package/dist/esm/spawn/owned-process-tree.js.map +1 -0
  97. package/dist/esm/spawn/spawn-server.d.ts +8 -32
  98. package/dist/esm/spawn/spawn-server.js +224 -130
  99. package/dist/esm/spawn/spawn-server.js.map +1 -1
  100. package/dist/esm/spawn/spawn-servers.d.ts +18 -8
  101. package/dist/esm/spawn/spawn-servers.js +180 -55
  102. package/dist/esm/spawn/spawn-servers.js.map +1 -1
  103. package/dist/esm/types.d.ts +1 -1
  104. package/dist/esm/types.js.map +1 -1
  105. package/package.json +8 -5
  106. package/schemas/servers.d.ts +24 -0
  107. 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 { 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
  }
23
- function terminateWindowsProcessTree(pid, force) {
24
- const args = [
25
- '/PID',
26
- String(pid),
27
- '/T'
28
- ];
29
- if (force) args.push('/F');
30
- return new Promise((resolve, reject)=>{
31
- const taskkill = spawn('taskkill.exe', args, {
32
- stdio: 'ignore',
33
- windowsHide: true
34
- });
35
- taskkill.once('error', reject);
36
- taskkill.once('close', (code)=>{
37
- if (code === 0) {
38
- resolve();
39
- return;
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
- reject(new Error(`taskkill.exe failed to stop process tree rooted at PID ${pid} (exit code ${code !== null && code !== void 0 ? code : 'unknown'})`));
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, process, and stop function
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, _opts_shell;
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
- // Merge environment variables
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
- // Spawn the process
87
- const spawnOpts = {
181
+ const child = crossSpawn(command, args, {
88
182
  cwd,
89
183
  env,
90
184
  stdio,
91
- shell
92
- };
93
- const child = spawn(command, args, spawnOpts);
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
- // Attach lifecycle logging
99
- child.on('exit', (code, sig)=>logger.info(`[${name}] exited (code=${code}, signal=${sig || 'none'})`));
100
- child.on('error', (err)=>logger.info(`[${name}] process error: ${err.message}`));
101
- // Create stop function with graceful shutdown
102
- const stop = async (signal = 'SIGINT', opts = {})=>{
103
- var _opts_timeoutMs;
104
- // If already exited, return immediately
105
- if (child.exitCode !== null || child.signalCode !== null) {
106
- return {
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
- // Set timeout for forceful kill
134
- const timeout = setTimeout(()=>{
135
- timedOut = true;
136
- const forceKill = async ()=>{
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
- const requestShutdown = async ()=>{
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
- if (child.exitCode !== null || child.signalCode !== null) {
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
- rejectOnce(error instanceof Error ? error : new Error(String(error)));
242
+ errors.push(withCause(`Server '${name}' cooperative signal failed: ${asError(error).message}`, asError(error)));
173
243
  }
174
- };
175
- void requestShutdown();
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: stop
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":"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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 {};