@mcp-z/client 1.0.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 (211) hide show
  1. package/AGENTS.md +159 -0
  2. package/LICENSE +21 -0
  3. package/README.md +90 -0
  4. package/dist/cjs/auth/capability-discovery.d.cts +25 -0
  5. package/dist/cjs/auth/capability-discovery.d.ts +25 -0
  6. package/dist/cjs/auth/capability-discovery.js +280 -0
  7. package/dist/cjs/auth/capability-discovery.js.map +1 -0
  8. package/dist/cjs/auth/index.d.cts +9 -0
  9. package/dist/cjs/auth/index.d.ts +9 -0
  10. package/dist/cjs/auth/index.js +28 -0
  11. package/dist/cjs/auth/index.js.map +1 -0
  12. package/dist/cjs/auth/interactive-oauth-flow.d.cts +58 -0
  13. package/dist/cjs/auth/interactive-oauth-flow.d.ts +58 -0
  14. package/dist/cjs/auth/interactive-oauth-flow.js +537 -0
  15. package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -0
  16. package/dist/cjs/auth/oauth-callback-listener.d.cts +56 -0
  17. package/dist/cjs/auth/oauth-callback-listener.d.ts +56 -0
  18. package/dist/cjs/auth/oauth-callback-listener.js +333 -0
  19. package/dist/cjs/auth/oauth-callback-listener.js.map +1 -0
  20. package/dist/cjs/auth/pkce.d.cts +17 -0
  21. package/dist/cjs/auth/pkce.d.ts +17 -0
  22. package/dist/cjs/auth/pkce.js +192 -0
  23. package/dist/cjs/auth/pkce.js.map +1 -0
  24. package/dist/cjs/auth/rfc9728-discovery.d.cts +34 -0
  25. package/dist/cjs/auth/rfc9728-discovery.d.ts +34 -0
  26. package/dist/cjs/auth/rfc9728-discovery.js +436 -0
  27. package/dist/cjs/auth/rfc9728-discovery.js.map +1 -0
  28. package/dist/cjs/auth/types.d.cts +137 -0
  29. package/dist/cjs/auth/types.d.ts +137 -0
  30. package/dist/cjs/auth/types.js +9 -0
  31. package/dist/cjs/auth/types.js.map +1 -0
  32. package/dist/cjs/client-helpers.d.cts +55 -0
  33. package/dist/cjs/client-helpers.d.ts +55 -0
  34. package/dist/cjs/client-helpers.js +128 -0
  35. package/dist/cjs/client-helpers.js.map +1 -0
  36. package/dist/cjs/config/server-loader.d.cts +27 -0
  37. package/dist/cjs/config/server-loader.d.ts +27 -0
  38. package/dist/cjs/config/server-loader.js +111 -0
  39. package/dist/cjs/config/server-loader.js.map +1 -0
  40. package/dist/cjs/config/validate-config.d.cts +15 -0
  41. package/dist/cjs/config/validate-config.d.ts +15 -0
  42. package/dist/cjs/config/validate-config.js +128 -0
  43. package/dist/cjs/config/validate-config.js.map +1 -0
  44. package/dist/cjs/connection/connect-client.d.cts +59 -0
  45. package/dist/cjs/connection/connect-client.d.ts +59 -0
  46. package/dist/cjs/connection/connect-client.js +536 -0
  47. package/dist/cjs/connection/connect-client.js.map +1 -0
  48. package/dist/cjs/connection/existing-process-transport.d.cts +40 -0
  49. package/dist/cjs/connection/existing-process-transport.d.ts +40 -0
  50. package/dist/cjs/connection/existing-process-transport.js +274 -0
  51. package/dist/cjs/connection/existing-process-transport.js.map +1 -0
  52. package/dist/cjs/connection/types.d.cts +61 -0
  53. package/dist/cjs/connection/types.d.ts +61 -0
  54. package/dist/cjs/connection/types.js +53 -0
  55. package/dist/cjs/connection/types.js.map +1 -0
  56. package/dist/cjs/connection/wait-for-http-ready.d.cts +15 -0
  57. package/dist/cjs/connection/wait-for-http-ready.d.ts +15 -0
  58. package/dist/cjs/connection/wait-for-http-ready.js +232 -0
  59. package/dist/cjs/connection/wait-for-http-ready.js.map +1 -0
  60. package/dist/cjs/dcr/dcr-authenticator.d.cts +73 -0
  61. package/dist/cjs/dcr/dcr-authenticator.d.ts +73 -0
  62. package/dist/cjs/dcr/dcr-authenticator.js +655 -0
  63. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -0
  64. package/dist/cjs/dcr/dynamic-client-registrar.d.cts +28 -0
  65. package/dist/cjs/dcr/dynamic-client-registrar.d.ts +28 -0
  66. package/dist/cjs/dcr/dynamic-client-registrar.js +245 -0
  67. package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -0
  68. package/dist/cjs/dcr/index.d.cts +8 -0
  69. package/dist/cjs/dcr/index.d.ts +8 -0
  70. package/dist/cjs/dcr/index.js +24 -0
  71. package/dist/cjs/dcr/index.js.map +1 -0
  72. package/dist/cjs/index.d.cts +21 -0
  73. package/dist/cjs/index.d.ts +21 -0
  74. package/dist/cjs/index.js +94 -0
  75. package/dist/cjs/index.js.map +1 -0
  76. package/dist/cjs/monkey-patches.d.cts +6 -0
  77. package/dist/cjs/monkey-patches.d.ts +6 -0
  78. package/dist/cjs/monkey-patches.js +236 -0
  79. package/dist/cjs/monkey-patches.js.map +1 -0
  80. package/dist/cjs/package.json +1 -0
  81. package/dist/cjs/response-wrappers.d.cts +41 -0
  82. package/dist/cjs/response-wrappers.d.ts +41 -0
  83. package/dist/cjs/response-wrappers.js +443 -0
  84. package/dist/cjs/response-wrappers.js.map +1 -0
  85. package/dist/cjs/search/index.d.cts +6 -0
  86. package/dist/cjs/search/index.d.ts +6 -0
  87. package/dist/cjs/search/index.js +25 -0
  88. package/dist/cjs/search/index.js.map +1 -0
  89. package/dist/cjs/search/search.d.cts +22 -0
  90. package/dist/cjs/search/search.d.ts +22 -0
  91. package/dist/cjs/search/search.js +630 -0
  92. package/dist/cjs/search/search.js.map +1 -0
  93. package/dist/cjs/search/types.d.cts +122 -0
  94. package/dist/cjs/search/types.d.ts +122 -0
  95. package/dist/cjs/search/types.js +10 -0
  96. package/dist/cjs/search/types.js.map +1 -0
  97. package/dist/cjs/spawn/spawn-server.d.cts +83 -0
  98. package/dist/cjs/spawn/spawn-server.d.ts +83 -0
  99. package/dist/cjs/spawn/spawn-server.js +410 -0
  100. package/dist/cjs/spawn/spawn-server.js.map +1 -0
  101. package/dist/cjs/spawn/spawn-servers.d.cts +151 -0
  102. package/dist/cjs/spawn/spawn-servers.d.ts +151 -0
  103. package/dist/cjs/spawn/spawn-servers.js +911 -0
  104. package/dist/cjs/spawn/spawn-servers.js.map +1 -0
  105. package/dist/cjs/types.d.cts +11 -0
  106. package/dist/cjs/types.d.ts +11 -0
  107. package/dist/cjs/types.js +10 -0
  108. package/dist/cjs/types.js.map +1 -0
  109. package/dist/cjs/utils/logger.d.cts +24 -0
  110. package/dist/cjs/utils/logger.d.ts +24 -0
  111. package/dist/cjs/utils/logger.js +80 -0
  112. package/dist/cjs/utils/logger.js.map +1 -0
  113. package/dist/cjs/utils/path-utils.d.cts +45 -0
  114. package/dist/cjs/utils/path-utils.d.ts +45 -0
  115. package/dist/cjs/utils/path-utils.js +158 -0
  116. package/dist/cjs/utils/path-utils.js.map +1 -0
  117. package/dist/cjs/utils/sanitizer.d.cts +30 -0
  118. package/dist/cjs/utils/sanitizer.d.ts +30 -0
  119. package/dist/cjs/utils/sanitizer.js +124 -0
  120. package/dist/cjs/utils/sanitizer.js.map +1 -0
  121. package/dist/esm/auth/capability-discovery.d.ts +25 -0
  122. package/dist/esm/auth/capability-discovery.js +110 -0
  123. package/dist/esm/auth/capability-discovery.js.map +1 -0
  124. package/dist/esm/auth/index.d.ts +9 -0
  125. package/dist/esm/auth/index.js +6 -0
  126. package/dist/esm/auth/index.js.map +1 -0
  127. package/dist/esm/auth/interactive-oauth-flow.d.ts +58 -0
  128. package/dist/esm/auth/interactive-oauth-flow.js +217 -0
  129. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -0
  130. package/dist/esm/auth/oauth-callback-listener.d.ts +56 -0
  131. package/dist/esm/auth/oauth-callback-listener.js +166 -0
  132. package/dist/esm/auth/oauth-callback-listener.js.map +1 -0
  133. package/dist/esm/auth/pkce.d.ts +17 -0
  134. package/dist/esm/auth/pkce.js +41 -0
  135. package/dist/esm/auth/pkce.js.map +1 -0
  136. package/dist/esm/auth/rfc9728-discovery.d.ts +34 -0
  137. package/dist/esm/auth/rfc9728-discovery.js +157 -0
  138. package/dist/esm/auth/rfc9728-discovery.js.map +1 -0
  139. package/dist/esm/auth/types.d.ts +137 -0
  140. package/dist/esm/auth/types.js +7 -0
  141. package/dist/esm/auth/types.js.map +1 -0
  142. package/dist/esm/client-helpers.d.ts +55 -0
  143. package/dist/esm/client-helpers.js +81 -0
  144. package/dist/esm/client-helpers.js.map +1 -0
  145. package/dist/esm/config/server-loader.d.ts +27 -0
  146. package/dist/esm/config/server-loader.js +49 -0
  147. package/dist/esm/config/server-loader.js.map +1 -0
  148. package/dist/esm/config/validate-config.d.ts +15 -0
  149. package/dist/esm/config/validate-config.js +76 -0
  150. package/dist/esm/config/validate-config.js.map +1 -0
  151. package/dist/esm/connection/connect-client.d.ts +59 -0
  152. package/dist/esm/connection/connect-client.js +272 -0
  153. package/dist/esm/connection/connect-client.js.map +1 -0
  154. package/dist/esm/connection/existing-process-transport.d.ts +40 -0
  155. package/dist/esm/connection/existing-process-transport.js +103 -0
  156. package/dist/esm/connection/existing-process-transport.js.map +1 -0
  157. package/dist/esm/connection/types.d.ts +61 -0
  158. package/dist/esm/connection/types.js +34 -0
  159. package/dist/esm/connection/types.js.map +1 -0
  160. package/dist/esm/connection/wait-for-http-ready.d.ts +15 -0
  161. package/dist/esm/connection/wait-for-http-ready.js +43 -0
  162. package/dist/esm/connection/wait-for-http-ready.js.map +1 -0
  163. package/dist/esm/dcr/dcr-authenticator.d.ts +73 -0
  164. package/dist/esm/dcr/dcr-authenticator.js +235 -0
  165. package/dist/esm/dcr/dcr-authenticator.js.map +1 -0
  166. package/dist/esm/dcr/dynamic-client-registrar.d.ts +28 -0
  167. package/dist/esm/dcr/dynamic-client-registrar.js +66 -0
  168. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -0
  169. package/dist/esm/dcr/index.d.ts +8 -0
  170. package/dist/esm/dcr/index.js +5 -0
  171. package/dist/esm/dcr/index.js.map +1 -0
  172. package/dist/esm/index.d.ts +21 -0
  173. package/dist/esm/index.js +22 -0
  174. package/dist/esm/index.js.map +1 -0
  175. package/dist/esm/monkey-patches.d.ts +6 -0
  176. package/dist/esm/monkey-patches.js +32 -0
  177. package/dist/esm/monkey-patches.js.map +1 -0
  178. package/dist/esm/package.json +1 -0
  179. package/dist/esm/response-wrappers.d.ts +41 -0
  180. package/dist/esm/response-wrappers.js +201 -0
  181. package/dist/esm/response-wrappers.js.map +1 -0
  182. package/dist/esm/search/index.d.ts +6 -0
  183. package/dist/esm/search/index.js +3 -0
  184. package/dist/esm/search/index.js.map +1 -0
  185. package/dist/esm/search/search.d.ts +22 -0
  186. package/dist/esm/search/search.js +236 -0
  187. package/dist/esm/search/search.js.map +1 -0
  188. package/dist/esm/search/types.d.ts +122 -0
  189. package/dist/esm/search/types.js +8 -0
  190. package/dist/esm/search/types.js.map +1 -0
  191. package/dist/esm/spawn/spawn-server.d.ts +83 -0
  192. package/dist/esm/spawn/spawn-server.js +145 -0
  193. package/dist/esm/spawn/spawn-server.js.map +1 -0
  194. package/dist/esm/spawn/spawn-servers.d.ts +151 -0
  195. package/dist/esm/spawn/spawn-servers.js +406 -0
  196. package/dist/esm/spawn/spawn-servers.js.map +1 -0
  197. package/dist/esm/types.d.ts +11 -0
  198. package/dist/esm/types.js +9 -0
  199. package/dist/esm/types.js.map +1 -0
  200. package/dist/esm/utils/logger.d.ts +24 -0
  201. package/dist/esm/utils/logger.js +59 -0
  202. package/dist/esm/utils/logger.js.map +1 -0
  203. package/dist/esm/utils/path-utils.d.ts +45 -0
  204. package/dist/esm/utils/path-utils.js +89 -0
  205. package/dist/esm/utils/path-utils.js.map +1 -0
  206. package/dist/esm/utils/sanitizer.d.ts +30 -0
  207. package/dist/esm/utils/sanitizer.js +43 -0
  208. package/dist/esm/utils/sanitizer.js.map +1 -0
  209. package/package.json +92 -0
  210. package/schemas/servers.d.ts +90 -0
  211. package/schemas/servers.schema.json +104 -0
@@ -0,0 +1,406 @@
1
+ /**
2
+ * High-level multi-server registry management.
3
+ * Starts multiple servers from a servers configuration object.
4
+ * Supports stdio, http, and ws transports.
5
+ * Implements Claude Code-compatible configuration with start extension support.
6
+ */ import * as fs from 'fs';
7
+ import * as process from 'process';
8
+ import { decorateClient } from '../client-helpers.js';
9
+ import { validateServers } from '../config/validate-config.js';
10
+ import { connectMcpClient } from '../connection/connect-client.js';
11
+ import { buildCapabilityIndex, searchCapabilities as executeCapabilitySearch } from '../search/index.js';
12
+ import { logger } from '../utils/logger.js';
13
+ import { spawnProcess } from './spawn-server.js';
14
+ /**
15
+ * Infer transport type: explicit type > URL protocol > default 'stdio'
16
+ */ function inferTransportType(config) {
17
+ if (config.type) {
18
+ return config.type;
19
+ }
20
+ if (config.url) {
21
+ const url = new URL(config.url);
22
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
23
+ return 'http';
24
+ }
25
+ }
26
+ return 'stdio';
27
+ }
28
+ /**
29
+ * Helper to filter undefined values from environment
30
+ */ function filterEnv(env) {
31
+ const filtered = {};
32
+ for (const [key, value] of Object.entries(env)){
33
+ if (value !== undefined) {
34
+ filtered[key] = value;
35
+ }
36
+ }
37
+ return filtered;
38
+ }
39
+ /**
40
+ * Determine spawn behavior based on dialects and config structure.
41
+ *
42
+ * Dialects:
43
+ * - ['servers'] (default): Claude Code compatible - ignores start blocks, stdio only
44
+ * - ['start']: Only spawn servers with start blocks (HTTP server testing)
45
+ * - ['servers', 'start']: Spawn both start blocks and stdio servers (full MCP-Z extension support)
46
+ *
47
+ * Environment merging (when baseEnv provided):
48
+ * - HTTP servers (start block): { ...baseEnv, ...entry.start.env }
49
+ * - Stdio servers: { ...baseEnv, ...entry.env }
50
+ *
51
+ * When baseEnv is not provided, process.env is used as the base.
52
+ */ function getSpawnConfig(entry, dialects, baseEnv) {
53
+ const transportType = inferTransportType(entry);
54
+ const hasServers = dialects.includes('servers');
55
+ const hasStart = dialects.includes('start');
56
+ // If only 'servers' dialect: Claude Code compatible (ignore start blocks, stdio only)
57
+ if (hasServers && !hasStart) {
58
+ if (transportType === 'stdio' && entry.command) {
59
+ return {
60
+ shouldSpawn: true,
61
+ command: entry.command,
62
+ args: entry.args || [],
63
+ env: filterEnv({
64
+ ...baseEnv,
65
+ ...entry.env
66
+ })
67
+ };
68
+ }
69
+ return {
70
+ shouldSpawn: false
71
+ };
72
+ }
73
+ // If only 'start' dialect: Only spawn servers with start blocks
74
+ if (hasStart && !hasServers) {
75
+ if (entry.start) {
76
+ return {
77
+ shouldSpawn: true,
78
+ command: entry.start.command,
79
+ args: entry.start.args || [],
80
+ env: filterEnv({
81
+ ...baseEnv,
82
+ ...entry.start.env
83
+ })
84
+ };
85
+ }
86
+ return {
87
+ shouldSpawn: false
88
+ };
89
+ }
90
+ // Both dialects: Spawn both start blocks and stdio servers
91
+ // Priority: start blocks first, then stdio
92
+ if (entry.start) {
93
+ return {
94
+ shouldSpawn: true,
95
+ command: entry.start.command,
96
+ args: entry.start.args || [],
97
+ env: filterEnv({
98
+ ...baseEnv,
99
+ ...entry.start.env
100
+ })
101
+ };
102
+ }
103
+ if (transportType === 'stdio' && entry.command) {
104
+ return {
105
+ shouldSpawn: true,
106
+ command: entry.command,
107
+ args: entry.args || [],
108
+ env: filterEnv({
109
+ ...baseEnv,
110
+ ...entry.env
111
+ })
112
+ };
113
+ }
114
+ return {
115
+ shouldSpawn: false
116
+ };
117
+ }
118
+ /**
119
+ * Create a registry of MCP servers from configuration.
120
+ *
121
+ * **Fast start**: Returns immediately after processes are created.
122
+ * Use `registry.connect()` for lazy MCP connection.
123
+ *
124
+ * @param serversConfig - Map of server names to their configurations
125
+ * @param options - Options for registry creation
126
+ * @param options.cwd - Working directory for spawned processes (default: process.cwd())
127
+ * @param options.env - Base environment for all servers. If provided, process.env is NOT included.
128
+ * If omitted, process.env is used as the base (default behavior).
129
+ * @param options.dialects - Dialects controlling which servers to spawn (default: ['servers'])
130
+ * - ['servers']: Spawn stdio servers only (Claude Code compatible)
131
+ * - ['start']: Only spawn servers with start blocks (HTTP servers)
132
+ * - ['servers', 'start']: Spawn both stdio servers and start blocks
133
+ * @returns ServerRegistry instance with config, server map, connect method, and close function
134
+ *
135
+ * @example
136
+ * // Fast server start (does NOT wait for readiness)
137
+ * const registry = createServerRegistry({
138
+ * 'echo': { command: 'node', args: ['server.ts'] },
139
+ * });
140
+ *
141
+ * // Connect when needed (waits for MCP handshake)
142
+ * const client = await registry.connect('echo');
143
+ *
144
+ * // Cleanup (closes all clients AND processes)
145
+ * await registry.close();
146
+ *
147
+ * @example
148
+ * // Start HTTP servers with start blocks
149
+ * const registry = createServerRegistry(
150
+ * {
151
+ * 'http-server': {
152
+ * url: 'http://localhost:8080/mcp',
153
+ * start: { command: 'node', args: ['http.ts', '--port', '8080'] }
154
+ * },
155
+ * },
156
+ * { dialects: ['start'] }
157
+ * );
158
+ *
159
+ * @example
160
+ * // Using await using for automatic close
161
+ * await using registry = createServerRegistry(config);
162
+ * const client = await registry.connect('server');
163
+ * // Auto-disposed when scope exits
164
+ */ export function createServerRegistry(serversConfig, options) {
165
+ var _ref, _ref1, _ref2;
166
+ const cwd = (_ref = options === null || options === void 0 ? void 0 : options.cwd) !== null && _ref !== void 0 ? _ref : process.cwd();
167
+ const dialects = (_ref1 = options === null || options === void 0 ? void 0 : options.dialects) !== null && _ref1 !== void 0 ? _ref1 : [
168
+ 'servers'
169
+ ];
170
+ // Determine base environment:
171
+ // - If options.env provided, use it (process.env NOT included)
172
+ // - If options.env omitted, use process.env as base
173
+ const baseEnv = (_ref2 = options === null || options === void 0 ? void 0 : options.env) !== null && _ref2 !== void 0 ? _ref2 : process.env;
174
+ // Validate working directory exists (fail fast for configuration errors)
175
+ if (!fs.existsSync(cwd)) {
176
+ throw new Error(`Cannot start servers: working directory '${cwd}' does not exist`);
177
+ }
178
+ // Validate configuration (fail fast with clear errors)
179
+ const validation = validateServers(serversConfig);
180
+ if (!validation.valid) {
181
+ var _ref3;
182
+ var _validation_errors;
183
+ throw new Error(`Invalid servers configuration:\n${(_ref3 = (_validation_errors = validation.errors) === null || _validation_errors === void 0 ? void 0 : _validation_errors.join('\n')) !== null && _ref3 !== void 0 ? _ref3 : 'Unknown validation error'}`);
184
+ }
185
+ // Log validation warnings (non-blocking)
186
+ if (validation.warnings && validation.warnings.length > 0) {
187
+ for (const warning of validation.warnings){
188
+ logger.warn(warning);
189
+ }
190
+ }
191
+ const servers = new Map();
192
+ const clients = new Set();
193
+ const sharedStdioClients = new Map();
194
+ // Start each server in the configuration
195
+ for (const [name, entry] of Object.entries(serversConfig)){
196
+ // Infer transport type from config
197
+ const transportType = inferTransportType(entry);
198
+ // Determine spawn behavior based on dialects
199
+ const spawnConfig = getSpawnConfig(entry, dialects, baseEnv);
200
+ // Check if we should spawn this server
201
+ if (!spawnConfig.shouldSpawn) {
202
+ // External server - just log, no spawn needed
203
+ if (entry.url) {
204
+ logger.info(`[${name}] external ${transportType} server (url: ${entry.url})`);
205
+ } else {
206
+ logger.warn(`[${name}] skipping: no spawn configuration (missing start or command) and no url for external server`);
207
+ }
208
+ continue;
209
+ }
210
+ try {
211
+ // Validate spawn config
212
+ if (!spawnConfig.command) {
213
+ throw new Error(`Server "${name}" missing command field`);
214
+ }
215
+ // All servers use the same working directory (cwd from options)
216
+ const resolvedCwd = cwd;
217
+ // Start the server
218
+ logger.info(`[${name}] starting ${transportType} server (${spawnConfig.command} ${(spawnConfig.args || []).join(' ')})`);
219
+ // stdio servers need 'pipe' for MCP communication over stdin/stdout
220
+ // network servers use 'inherit' so we see their logs
221
+ const stdio = transportType === 'stdio' ? 'pipe' : 'inherit';
222
+ const handle = spawnProcess({
223
+ name,
224
+ command: spawnConfig.command,
225
+ ...spawnConfig.args !== undefined && {
226
+ args: spawnConfig.args
227
+ },
228
+ cwd: resolvedCwd,
229
+ ...spawnConfig.env && Object.keys(spawnConfig.env).length > 0 && {
230
+ env: spawnConfig.env
231
+ },
232
+ stdio
233
+ });
234
+ // Add server to registry (starting is fast, readiness is lazy)
235
+ servers.set(name, handle);
236
+ logger.info(`[${name}] started successfully`);
237
+ } catch (e) {
238
+ logger.info(`[${name}] start ERROR: ${e instanceof Error ? e.message : String(e)}`);
239
+ }
240
+ }
241
+ // Create connect function that tracks clients
242
+ const connect = async (name, options)=>{
243
+ const serverEntry = serversConfig[name];
244
+ if (!serverEntry) {
245
+ const available = Object.keys(serversConfig).join(', ');
246
+ throw new Error(`Server '${name}' not found in config. Available servers: ${available || 'none'}`);
247
+ }
248
+ const transportType = inferTransportType(serverEntry);
249
+ if (transportType === 'stdio') {
250
+ // Stdio is a single logical connection; reuse one client and lease references.
251
+ let entry = sharedStdioClients.get(name);
252
+ if (!entry) {
253
+ entry = {
254
+ refs: 0
255
+ };
256
+ sharedStdioClients.set(name, entry);
257
+ }
258
+ if (!entry.client) {
259
+ if (!entry.connecting) {
260
+ entry.connecting = (async ()=>{
261
+ // Pass minimal RegistryLike object to connectMcpClient
262
+ const registryLike = {
263
+ config: serversConfig,
264
+ servers
265
+ };
266
+ const rawClient = await connectMcpClient(registryLike, name, options);
267
+ const decorated = decorateClient(rawClient, {
268
+ serverName: name
269
+ });
270
+ entry.client = decorated;
271
+ entry.connecting = undefined;
272
+ return decorated;
273
+ })().catch((error)=>{
274
+ sharedStdioClients.delete(name);
275
+ throw error;
276
+ });
277
+ }
278
+ await entry.connecting;
279
+ }
280
+ if (!entry.client) {
281
+ throw new Error(`Failed to connect to stdio server '${name}'`);
282
+ }
283
+ entry.refs += 1;
284
+ let released = false;
285
+ let lease;
286
+ lease = new Proxy(entry.client, {
287
+ get (target, prop) {
288
+ if (prop === 'close') {
289
+ return async ()=>{
290
+ if (released) return;
291
+ released = true;
292
+ clients.delete(lease);
293
+ entry.refs = Math.max(0, entry.refs - 1);
294
+ if (entry.refs === 0) {
295
+ sharedStdioClients.delete(name);
296
+ await target.close();
297
+ }
298
+ };
299
+ }
300
+ const value = Reflect.get(target, prop, target);
301
+ if (typeof value === 'function') {
302
+ return value.bind(target);
303
+ }
304
+ return value;
305
+ }
306
+ });
307
+ clients.add(lease);
308
+ return lease;
309
+ }
310
+ // Pass minimal RegistryLike object to connectMcpClient
311
+ const registryLike = {
312
+ config: serversConfig,
313
+ servers
314
+ };
315
+ const rawClient = await connectMcpClient(registryLike, name, options);
316
+ const decorated = decorateClient(rawClient, {
317
+ serverName: name
318
+ });
319
+ clients.add(decorated);
320
+ return decorated;
321
+ };
322
+ // Create close function that stops all clients and servers
323
+ const close = async (signal = 'SIGINT', opts = {})=>{
324
+ logger.info(`[registry] closing (${signal})`);
325
+ // First, close all tracked clients
326
+ const clientClosePromises = Array.from(clients).map(async (client)=>{
327
+ try {
328
+ await client.close();
329
+ } catch {
330
+ // Ignore errors during client close
331
+ }
332
+ });
333
+ await Promise.all(clientClosePromises);
334
+ clients.clear();
335
+ // Then close all server processes
336
+ if (servers.size === 0) {
337
+ return {
338
+ timedOut: false,
339
+ killedCount: 0
340
+ };
341
+ }
342
+ // Close all servers in parallel
343
+ const closeResults = await Promise.all(Array.from(servers.values()).map((server)=>server.close(signal, opts)));
344
+ // Check if any timed out and count how many were force-killed
345
+ const timedOut = closeResults.some((result)=>result.timedOut);
346
+ const killedCount = closeResults.filter((result)=>result.killed).length;
347
+ return {
348
+ timedOut,
349
+ killedCount
350
+ };
351
+ };
352
+ const searchFromRegistry = async (query, options = {})=>{
353
+ var _options_servers;
354
+ const requestedServers = (_options_servers = options.servers) !== null && _options_servers !== void 0 ? _options_servers : Object.keys(serversConfig);
355
+ if (requestedServers.length === 0) {
356
+ throw new Error('Cannot search capabilities: registry has no configured servers');
357
+ }
358
+ const unknownServers = requestedServers.filter((name)=>!(name in serversConfig));
359
+ if (unknownServers.length > 0) {
360
+ throw new Error(`Cannot search capabilities: unknown server(s) [${unknownServers.join(', ')}]`);
361
+ }
362
+ const capabilityClients = new Map();
363
+ const failures = [];
364
+ const ensureClient = async (serverName)=>{
365
+ for (const client of clients){
366
+ if (client.serverName === serverName) {
367
+ return client;
368
+ }
369
+ }
370
+ return connect(serverName);
371
+ };
372
+ await Promise.all(requestedServers.map(async (serverName)=>{
373
+ try {
374
+ const managed = await ensureClient(serverName);
375
+ capabilityClients.set(serverName, managed.nativeClient);
376
+ } catch (error) {
377
+ failures.push({
378
+ server: serverName,
379
+ reason: error instanceof Error ? error.message : String(error)
380
+ });
381
+ }
382
+ }));
383
+ if (capabilityClients.size === 0) {
384
+ const failureDetails = failures.length > 0 ? ` Connection failures: ${failures.map((f)=>`${f.server} (${f.reason})`).join('; ')}` : '';
385
+ throw new Error(`Cannot search capabilities: unable to connect to any requested servers.${failureDetails}`);
386
+ }
387
+ if (failures.length > 0) {
388
+ throw new Error(`Cannot search capabilities: failed to connect to server(s) [${failures.map((f)=>f.server).join(', ')}]. Reasons: ${failures.map((f)=>`${f.server}: ${f.reason}`).join('; ')}`);
389
+ }
390
+ const index = await buildCapabilityIndex(capabilityClients);
391
+ return executeCapabilitySearch(index, query, options);
392
+ };
393
+ // Async dispose for `await using` pattern
394
+ const asyncDispose = async ()=>{
395
+ await close();
396
+ };
397
+ return {
398
+ config: serversConfig,
399
+ servers,
400
+ clients,
401
+ connect,
402
+ close,
403
+ searchCapabilities: searchFromRegistry,
404
+ [Symbol.asyncDispose]: asyncDispose
405
+ };
406
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/spawn/spawn-servers.ts"],"sourcesContent":["/**\n * High-level multi-server registry management.\n * Starts multiple servers from a servers configuration object.\n * Supports stdio, http, and ws transports.\n * Implements Claude Code-compatible configuration with start extension support.\n */\n\nimport * as fs from 'fs';\nimport * as process from 'process';\nimport { decorateClient, type ManagedClient } from '../client-helpers.ts';\nimport { validateServers } from '../config/validate-config.ts';\nimport { connectMcpClient } from '../connection/connect-client.ts';\nimport { buildCapabilityIndex, type CapabilityClient, searchCapabilities as executeCapabilitySearch, type SearchOptions, type SearchResponse } from '../search/index.ts';\nimport type { McpServerEntry, TransportType } from '../types.ts';\nimport { logger } from '../utils/logger.ts';\nimport { type ServerProcess, spawnProcess } from './spawn-server.ts';\n\n/**\n * Servers configuration type - a map of server names to their configurations.\n */\nexport type ServersConfig = Record<string, McpServerEntry>;\n\n/**\n * Dialect for server spawning.\n *\n * - 'servers': Spawn stdio servers (Claude Code compatible)\n * - 'start': Spawn HTTP servers with start blocks\n */\nexport type Dialect = 'servers' | 'start';\n\n/**\n * Options for creating a server registry.\n */\nexport interface CreateServerRegistryOptions {\n /** Working directory for spawned processes (default: process.cwd()) */\n cwd?: string;\n\n /**\n * Base environment for all servers.\n * If provided, process.env is NOT included (caller has full control).\n * If omitted, process.env is used as the base (default behavior).\n */\n env?: Record<string, string>;\n\n /**\n * Dialects controlling which servers to spawn.\n * - ['servers']: Spawn stdio servers only (default, Claude Code compatible)\n * - ['start']: Only spawn servers with start blocks (HTTP servers)\n * - ['servers', 'start']: Spawn both stdio servers and start blocks\n * @default ['servers']\n */\n dialects?: Dialect[];\n}\n\n/**\n * Result of closing the registry.\n */\nexport interface CloseResult {\n /** Whether any process timed out during shutdown */\n timedOut: boolean;\n /** Number of processes that were force-killed */\n killedCount: number;\n}\n\n/**\n * Infer transport type: explicit type > URL protocol > default 'stdio'\n */\nfunction inferTransportType(config: { type?: TransportType; url?: string }): TransportType {\n if (config.type) {\n return config.type;\n }\n\n if (config.url) {\n const url = new URL(config.url);\n if (url.protocol === 'http:' || url.protocol === 'https:') {\n return 'http';\n }\n }\n\n return 'stdio';\n}\n\n/**\n * Spawn configuration result (internal)\n */\ninterface SpawnConfig {\n shouldSpawn: boolean;\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\n/**\n * Helper to filter undefined values from environment\n */\nfunction filterEnv(env: Record<string, string | undefined>): Record<string, string> {\n const filtered: Record<string, string> = {};\n for (const [key, value] of Object.entries(env)) {\n if (value !== undefined) {\n filtered[key] = value;\n }\n }\n return filtered;\n}\n\n/**\n * Determine spawn behavior based on dialects and config structure.\n *\n * Dialects:\n * - ['servers'] (default): Claude Code compatible - ignores start blocks, stdio only\n * - ['start']: Only spawn servers with start blocks (HTTP server testing)\n * - ['servers', 'start']: Spawn both start blocks and stdio servers (full MCP-Z extension support)\n *\n * Environment merging (when baseEnv provided):\n * - HTTP servers (start block): { ...baseEnv, ...entry.start.env }\n * - Stdio servers: { ...baseEnv, ...entry.env }\n *\n * When baseEnv is not provided, process.env is used as the base.\n */\nfunction getSpawnConfig(entry: McpServerEntry, dialects: Dialect[], baseEnv: Record<string, string | undefined>): SpawnConfig {\n const transportType = inferTransportType(entry);\n const hasServers = dialects.includes('servers');\n const hasStart = dialects.includes('start');\n\n // If only 'servers' dialect: Claude Code compatible (ignore start blocks, stdio only)\n if (hasServers && !hasStart) {\n if (transportType === 'stdio' && entry.command) {\n return {\n shouldSpawn: true,\n command: entry.command,\n args: entry.args || [],\n env: filterEnv({ ...baseEnv, ...entry.env }),\n };\n }\n return { shouldSpawn: false };\n }\n\n // If only 'start' dialect: Only spawn servers with start blocks\n if (hasStart && !hasServers) {\n if (entry.start) {\n return {\n shouldSpawn: true,\n command: entry.start.command,\n args: entry.start.args || [],\n env: filterEnv({ ...baseEnv, ...entry.start.env }),\n };\n }\n return { shouldSpawn: false };\n }\n\n // Both dialects: Spawn both start blocks and stdio servers\n // Priority: start blocks first, then stdio\n if (entry.start) {\n return {\n shouldSpawn: true,\n command: entry.start.command,\n args: entry.start.args || [],\n env: filterEnv({ ...baseEnv, ...entry.start.env }),\n };\n }\n\n if (transportType === 'stdio' && entry.command) {\n return {\n shouldSpawn: true,\n command: entry.command,\n args: entry.args || [],\n env: filterEnv({ ...baseEnv, ...entry.env }),\n };\n }\n\n return { shouldSpawn: false };\n}\n\n/**\n * A registry of spawned MCP servers with connection management.\n * Provides access to individual server handles, connection management, and collection-wide close.\n */\ntype RegistryConnectOptions = Parameters<typeof connectMcpClient>[2];\n\nexport interface ServerRegistry {\n /**\n * The resolved servers configuration that was used.\n * Useful for debugging and understanding what was started.\n */\n config: ServersConfig;\n\n /**\n * Map of server name to server process handle.\n * @hidden\n */\n servers: Map<string, ServerProcess>;\n\n /**\n * Set of connected clients tracked by this registry.\n * Automatically populated when using registry.connect().\n */\n clients: Set<ManagedClient>;\n\n /**\n * Connect to a server by name.\n * The connected client is automatically tracked for close.\n *\n * @param name - Server name from configuration\n * @returns Connected MCP SDK Client\n */\n connect: (name: string, options?: RegistryConnectOptions) => Promise<ManagedClient>;\n\n /**\n * Close all clients and servers gracefully.\n * First closes all tracked clients, then sends the specified signal to all server processes.\n *\n * @param signal - Signal to send to processes (default: SIGINT)\n * @param opts - Options including timeout\n * @returns Promise resolving to whether any process timed out and how many were force-killed\n */\n close: (signal?: NodeJS.Signals, opts?: { timeoutMs?: number }) => Promise<CloseResult>;\n\n /**\n * Search indexed capabilities across all currently connected clients.\n * Requires at least one connected client; respects SearchOptions filters.\n */\n searchCapabilities: (query: string, options?: SearchOptions) => Promise<SearchResponse>;\n\n /**\n * Support for `await using` pattern (automatic close).\n */\n [Symbol.asyncDispose]: () => Promise<void>;\n}\n\n/**\n * Create a registry of MCP servers from configuration.\n *\n * **Fast start**: Returns immediately after processes are created.\n * Use `registry.connect()` for lazy MCP connection.\n *\n * @param serversConfig - Map of server names to their configurations\n * @param options - Options for registry creation\n * @param options.cwd - Working directory for spawned processes (default: process.cwd())\n * @param options.env - Base environment for all servers. If provided, process.env is NOT included.\n * If omitted, process.env is used as the base (default behavior).\n * @param options.dialects - Dialects controlling which servers to spawn (default: ['servers'])\n * - ['servers']: Spawn stdio servers only (Claude Code compatible)\n * - ['start']: Only spawn servers with start blocks (HTTP servers)\n * - ['servers', 'start']: Spawn both stdio servers and start blocks\n * @returns ServerRegistry instance with config, server map, connect method, and close function\n *\n * @example\n * // Fast server start (does NOT wait for readiness)\n * const registry = createServerRegistry({\n * 'echo': { command: 'node', args: ['server.ts'] },\n * });\n *\n * // Connect when needed (waits for MCP handshake)\n * const client = await registry.connect('echo');\n *\n * // Cleanup (closes all clients AND processes)\n * await registry.close();\n *\n * @example\n * // Start HTTP servers with start blocks\n * const registry = createServerRegistry(\n * {\n * 'http-server': {\n * url: 'http://localhost:8080/mcp',\n * start: { command: 'node', args: ['http.ts', '--port', '8080'] }\n * },\n * },\n * { dialects: ['start'] }\n * );\n *\n * @example\n * // Using await using for automatic close\n * await using registry = createServerRegistry(config);\n * const client = await registry.connect('server');\n * // Auto-disposed when scope exits\n */\nexport function createServerRegistry(serversConfig: ServersConfig, options?: CreateServerRegistryOptions): ServerRegistry {\n const cwd = options?.cwd ?? process.cwd();\n const dialects = options?.dialects ?? ['servers'];\n\n // Determine base environment:\n // - If options.env provided, use it (process.env NOT included)\n // - If options.env omitted, use process.env as base\n const baseEnv: Record<string, string | undefined> = options?.env ?? process.env;\n\n // Validate working directory exists (fail fast for configuration errors)\n if (!fs.existsSync(cwd)) {\n throw new Error(`Cannot start servers: working directory '${cwd}' does not exist`);\n }\n\n // Validate configuration (fail fast with clear errors)\n const validation = validateServers(serversConfig);\n if (!validation.valid) {\n throw new Error(`Invalid servers configuration:\\n${validation.errors?.join('\\n') ?? 'Unknown validation error'}`);\n }\n\n // Log validation warnings (non-blocking)\n if (validation.warnings && validation.warnings.length > 0) {\n for (const warning of validation.warnings) {\n logger.warn(warning);\n }\n }\n\n const servers = new Map<string, ServerProcess>();\n const clients = new Set<ManagedClient>();\n const sharedStdioClients = new Map<string, { client?: ManagedClient; connecting?: Promise<ManagedClient>; refs: number }>();\n\n // Start each server in the configuration\n for (const [name, entry] of Object.entries(serversConfig)) {\n // Infer transport type from config\n const transportType = inferTransportType(entry);\n\n // Determine spawn behavior based on dialects\n const spawnConfig = getSpawnConfig(entry, dialects, baseEnv);\n\n // Check if we should spawn this server\n if (!spawnConfig.shouldSpawn) {\n // External server - just log, no spawn needed\n if (entry.url) {\n logger.info(`[${name}] external ${transportType} server (url: ${entry.url})`);\n } else {\n logger.warn(`[${name}] skipping: no spawn configuration (missing start or command) and no url for external server`);\n }\n continue;\n }\n\n try {\n // Validate spawn config\n if (!spawnConfig.command) {\n throw new Error(`Server \"${name}\" missing command field`);\n }\n\n // All servers use the same working directory (cwd from options)\n const resolvedCwd = cwd;\n\n // Start the server\n logger.info(`[${name}] starting ${transportType} server (${spawnConfig.command} ${(spawnConfig.args || []).join(' ')})`);\n\n // stdio servers need 'pipe' for MCP communication over stdin/stdout\n // network servers use 'inherit' so we see their logs\n const stdio = transportType === 'stdio' ? 'pipe' : 'inherit';\n\n const handle = spawnProcess({\n name,\n command: spawnConfig.command,\n ...(spawnConfig.args !== undefined && { args: spawnConfig.args }),\n cwd: resolvedCwd,\n ...(spawnConfig.env && Object.keys(spawnConfig.env).length > 0 && { env: spawnConfig.env }),\n stdio,\n });\n\n // Add server to registry (starting is fast, readiness is lazy)\n servers.set(name, handle);\n logger.info(`[${name}] started successfully`);\n } catch (e) {\n logger.info(`[${name}] start ERROR: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n // Create connect function that tracks clients\n const connect = async (name: string, options?: RegistryConnectOptions): Promise<ManagedClient> => {\n const serverEntry = serversConfig[name];\n if (!serverEntry) {\n const available = Object.keys(serversConfig).join(', ');\n throw new Error(`Server '${name}' not found in config. Available servers: ${available || 'none'}`);\n }\n\n const transportType = inferTransportType(serverEntry);\n\n if (transportType === 'stdio') {\n // Stdio is a single logical connection; reuse one client and lease references.\n let entry = sharedStdioClients.get(name);\n if (!entry) {\n entry = { refs: 0 };\n sharedStdioClients.set(name, entry);\n }\n\n if (!entry.client) {\n if (!entry.connecting) {\n entry.connecting = (async () => {\n // Pass minimal RegistryLike object to connectMcpClient\n const registryLike = { config: serversConfig, servers };\n const rawClient = await connectMcpClient(registryLike, name, options);\n const decorated = decorateClient(rawClient, { serverName: name });\n entry.client = decorated;\n entry.connecting = undefined;\n return decorated;\n })().catch((error) => {\n sharedStdioClients.delete(name);\n throw error;\n });\n }\n\n await entry.connecting;\n }\n\n if (!entry.client) {\n throw new Error(`Failed to connect to stdio server '${name}'`);\n }\n\n entry.refs += 1;\n let released = false;\n let lease: ManagedClient;\n\n lease = new Proxy(entry.client, {\n get(target, prop) {\n if (prop === 'close') {\n return async () => {\n if (released) return;\n released = true;\n clients.delete(lease);\n entry.refs = Math.max(0, entry.refs - 1);\n if (entry.refs === 0) {\n sharedStdioClients.delete(name);\n await target.close();\n }\n };\n }\n\n const value = Reflect.get(target, prop, target) as unknown;\n if (typeof value === 'function') {\n return (value as (...args: unknown[]) => unknown).bind(target);\n }\n return value;\n },\n }) as ManagedClient;\n\n clients.add(lease);\n return lease;\n }\n\n // Pass minimal RegistryLike object to connectMcpClient\n const registryLike = { config: serversConfig, servers };\n const rawClient = await connectMcpClient(registryLike, name, options);\n const decorated = decorateClient(rawClient, { serverName: name });\n clients.add(decorated);\n return decorated;\n };\n\n // Create close function that stops all clients and servers\n const close = async (signal: NodeJS.Signals = 'SIGINT', opts: { timeoutMs?: number } = {}): Promise<CloseResult> => {\n logger.info(`[registry] closing (${signal})`);\n\n // First, close all tracked clients\n const clientClosePromises = Array.from(clients).map(async (client) => {\n try {\n await client.close();\n } catch {\n // Ignore errors during client close\n }\n });\n await Promise.all(clientClosePromises);\n clients.clear();\n\n // Then close all server processes\n if (servers.size === 0) {\n return { timedOut: false, killedCount: 0 };\n }\n\n // Close all servers in parallel\n const closeResults = await Promise.all(Array.from(servers.values()).map((server) => server.close(signal, opts)));\n\n // Check if any timed out and count how many were force-killed\n const timedOut = closeResults.some((result) => result.timedOut);\n const killedCount = closeResults.filter((result) => result.killed).length;\n\n return { timedOut, killedCount };\n };\n\n const searchFromRegistry = async (query: string, options: SearchOptions = {}): Promise<SearchResponse> => {\n const requestedServers = options.servers ?? Object.keys(serversConfig);\n if (requestedServers.length === 0) {\n throw new Error('Cannot search capabilities: registry has no configured servers');\n }\n\n const unknownServers = requestedServers.filter((name) => !(name in serversConfig));\n if (unknownServers.length > 0) {\n throw new Error(`Cannot search capabilities: unknown server(s) [${unknownServers.join(', ')}]`);\n }\n\n const capabilityClients = new Map<string, CapabilityClient>();\n const failures: Array<{ server: string; reason: string }> = [];\n\n const ensureClient = async (serverName: string): Promise<ManagedClient> => {\n for (const client of clients) {\n if (client.serverName === serverName) {\n return client;\n }\n }\n return connect(serverName);\n };\n\n await Promise.all(\n requestedServers.map(async (serverName) => {\n try {\n const managed = await ensureClient(serverName);\n capabilityClients.set(serverName, managed.nativeClient);\n } catch (error) {\n failures.push({\n server: serverName,\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n })\n );\n\n if (capabilityClients.size === 0) {\n const failureDetails = failures.length > 0 ? ` Connection failures: ${failures.map((f) => `${f.server} (${f.reason})`).join('; ')}` : '';\n throw new Error(`Cannot search capabilities: unable to connect to any requested servers.${failureDetails}`);\n }\n\n if (failures.length > 0) {\n throw new Error(`Cannot search capabilities: failed to connect to server(s) [${failures.map((f) => f.server).join(', ')}]. Reasons: ${failures.map((f) => `${f.server}: ${f.reason}`).join('; ')}`);\n }\n\n const index = await buildCapabilityIndex(capabilityClients);\n return executeCapabilitySearch(index, query, options);\n };\n\n // Async dispose for `await using` pattern\n const asyncDispose = async (): Promise<void> => {\n await close();\n };\n\n return {\n config: serversConfig,\n servers,\n clients,\n connect,\n close,\n searchCapabilities: searchFromRegistry,\n [Symbol.asyncDispose]: asyncDispose,\n };\n}\n"],"names":["fs","process","decorateClient","validateServers","connectMcpClient","buildCapabilityIndex","searchCapabilities","executeCapabilitySearch","logger","spawnProcess","inferTransportType","config","type","url","URL","protocol","filterEnv","env","filtered","key","value","Object","entries","undefined","getSpawnConfig","entry","dialects","baseEnv","transportType","hasServers","includes","hasStart","command","shouldSpawn","args","start","createServerRegistry","serversConfig","options","cwd","existsSync","Error","validation","valid","errors","join","warnings","length","warning","warn","servers","Map","clients","Set","sharedStdioClients","name","spawnConfig","info","resolvedCwd","stdio","handle","keys","set","e","message","String","connect","serverEntry","available","get","refs","client","connecting","registryLike","rawClient","decorated","serverName","catch","error","delete","released","lease","Proxy","target","prop","Math","max","close","Reflect","bind","add","signal","opts","clientClosePromises","Array","from","map","Promise","all","clear","size","timedOut","killedCount","closeResults","values","server","some","result","filter","killed","searchFromRegistry","query","requestedServers","unknownServers","capabilityClients","failures","ensureClient","managed","nativeClient","push","reason","failureDetails","f","index","asyncDispose","Symbol"],"mappings":"AAAA;;;;;CAKC,GAED,YAAYA,QAAQ,KAAK;AACzB,YAAYC,aAAa,UAAU;AACnC,SAASC,cAAc,QAA4B,uBAAuB;AAC1E,SAASC,eAAe,QAAQ,+BAA+B;AAC/D,SAASC,gBAAgB,QAAQ,kCAAkC;AACnE,SAASC,oBAAoB,EAAyBC,sBAAsBC,uBAAuB,QAAiD,qBAAqB;AAEzK,SAASC,MAAM,QAAQ,qBAAqB;AAC5C,SAA6BC,YAAY,QAAQ,oBAAoB;AAiDrE;;CAEC,GACD,SAASC,mBAAmBC,MAA8C;IACxE,IAAIA,OAAOC,IAAI,EAAE;QACf,OAAOD,OAAOC,IAAI;IACpB;IAEA,IAAID,OAAOE,GAAG,EAAE;QACd,MAAMA,MAAM,IAAIC,IAAIH,OAAOE,GAAG;QAC9B,IAAIA,IAAIE,QAAQ,KAAK,WAAWF,IAAIE,QAAQ,KAAK,UAAU;YACzD,OAAO;QACT;IACF;IAEA,OAAO;AACT;AAYA;;CAEC,GACD,SAASC,UAAUC,GAAuC;IACxD,MAAMC,WAAmC,CAAC;IAC1C,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACL,KAAM;QAC9C,IAAIG,UAAUG,WAAW;YACvBL,QAAQ,CAACC,IAAI,GAAGC;QAClB;IACF;IACA,OAAOF;AACT;AAEA;;;;;;;;;;;;;CAaC,GACD,SAASM,eAAeC,KAAqB,EAAEC,QAAmB,EAAEC,OAA2C;IAC7G,MAAMC,gBAAgBlB,mBAAmBe;IACzC,MAAMI,aAAaH,SAASI,QAAQ,CAAC;IACrC,MAAMC,WAAWL,SAASI,QAAQ,CAAC;IAEnC,sFAAsF;IACtF,IAAID,cAAc,CAACE,UAAU;QAC3B,IAAIH,kBAAkB,WAAWH,MAAMO,OAAO,EAAE;YAC9C,OAAO;gBACLC,aAAa;gBACbD,SAASP,MAAMO,OAAO;gBACtBE,MAAMT,MAAMS,IAAI,IAAI,EAAE;gBACtBjB,KAAKD,UAAU;oBAAE,GAAGW,OAAO;oBAAE,GAAGF,MAAMR,GAAG;gBAAC;YAC5C;QACF;QACA,OAAO;YAAEgB,aAAa;QAAM;IAC9B;IAEA,gEAAgE;IAChE,IAAIF,YAAY,CAACF,YAAY;QAC3B,IAAIJ,MAAMU,KAAK,EAAE;YACf,OAAO;gBACLF,aAAa;gBACbD,SAASP,MAAMU,KAAK,CAACH,OAAO;gBAC5BE,MAAMT,MAAMU,KAAK,CAACD,IAAI,IAAI,EAAE;gBAC5BjB,KAAKD,UAAU;oBAAE,GAAGW,OAAO;oBAAE,GAAGF,MAAMU,KAAK,CAAClB,GAAG;gBAAC;YAClD;QACF;QACA,OAAO;YAAEgB,aAAa;QAAM;IAC9B;IAEA,2DAA2D;IAC3D,2CAA2C;IAC3C,IAAIR,MAAMU,KAAK,EAAE;QACf,OAAO;YACLF,aAAa;YACbD,SAASP,MAAMU,KAAK,CAACH,OAAO;YAC5BE,MAAMT,MAAMU,KAAK,CAACD,IAAI,IAAI,EAAE;YAC5BjB,KAAKD,UAAU;gBAAE,GAAGW,OAAO;gBAAE,GAAGF,MAAMU,KAAK,CAAClB,GAAG;YAAC;QAClD;IACF;IAEA,IAAIW,kBAAkB,WAAWH,MAAMO,OAAO,EAAE;QAC9C,OAAO;YACLC,aAAa;YACbD,SAASP,MAAMO,OAAO;YACtBE,MAAMT,MAAMS,IAAI,IAAI,EAAE;YACtBjB,KAAKD,UAAU;gBAAE,GAAGW,OAAO;gBAAE,GAAGF,MAAMR,GAAG;YAAC;QAC5C;IACF;IAEA,OAAO;QAAEgB,aAAa;IAAM;AAC9B;AA0DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CC,GACD,OAAO,SAASG,qBAAqBC,aAA4B,EAAEC,OAAqC;;IACtG,MAAMC,cAAMD,oBAAAA,8BAAAA,QAASC,GAAG,uCAAItC,QAAQsC,GAAG;IACvC,MAAMb,oBAAWY,oBAAAA,8BAAAA,QAASZ,QAAQ,yCAAI;QAAC;KAAU;IAEjD,8BAA8B;IAC9B,+DAA+D;IAC/D,oDAAoD;IACpD,MAAMC,mBAA8CW,oBAAAA,8BAAAA,QAASrB,GAAG,yCAAIhB,QAAQgB,GAAG;IAE/E,yEAAyE;IACzE,IAAI,CAACjB,GAAGwC,UAAU,CAACD,MAAM;QACvB,MAAM,IAAIE,MAAM,CAAC,yCAAyC,EAAEF,IAAI,gBAAgB,CAAC;IACnF;IAEA,uDAAuD;IACvD,MAAMG,aAAavC,gBAAgBkC;IACnC,IAAI,CAACK,WAAWC,KAAK,EAAE;;YAC8BD;QAAnD,MAAM,IAAID,MAAM,CAAC,gCAAgC,YAAEC,qBAAAA,WAAWE,MAAM,cAAjBF,yCAAAA,mBAAmBG,IAAI,CAAC,8CAAS,4BAA4B;IAClH;IAEA,yCAAyC;IACzC,IAAIH,WAAWI,QAAQ,IAAIJ,WAAWI,QAAQ,CAACC,MAAM,GAAG,GAAG;QACzD,KAAK,MAAMC,WAAWN,WAAWI,QAAQ,CAAE;YACzCtC,OAAOyC,IAAI,CAACD;QACd;IACF;IAEA,MAAME,UAAU,IAAIC;IACpB,MAAMC,UAAU,IAAIC;IACpB,MAAMC,qBAAqB,IAAIH;IAE/B,yCAAyC;IACzC,KAAK,MAAM,CAACI,MAAM9B,MAAM,IAAIJ,OAAOC,OAAO,CAACe,eAAgB;QACzD,mCAAmC;QACnC,MAAMT,gBAAgBlB,mBAAmBe;QAEzC,6CAA6C;QAC7C,MAAM+B,cAAchC,eAAeC,OAAOC,UAAUC;QAEpD,uCAAuC;QACvC,IAAI,CAAC6B,YAAYvB,WAAW,EAAE;YAC5B,8CAA8C;YAC9C,IAAIR,MAAMZ,GAAG,EAAE;gBACbL,OAAOiD,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,WAAW,EAAE3B,cAAc,cAAc,EAAEH,MAAMZ,GAAG,CAAC,CAAC,CAAC;YAC9E,OAAO;gBACLL,OAAOyC,IAAI,CAAC,CAAC,CAAC,EAAEM,KAAK,4FAA4F,CAAC;YACpH;YACA;QACF;QAEA,IAAI;YACF,wBAAwB;YACxB,IAAI,CAACC,YAAYxB,OAAO,EAAE;gBACxB,MAAM,IAAIS,MAAM,CAAC,QAAQ,EAAEc,KAAK,uBAAuB,CAAC;YAC1D;YAEA,gEAAgE;YAChE,MAAMG,cAAcnB;YAEpB,mBAAmB;YACnB/B,OAAOiD,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,WAAW,EAAE3B,cAAc,SAAS,EAAE4B,YAAYxB,OAAO,CAAC,CAAC,EAAE,AAACwB,CAAAA,YAAYtB,IAAI,IAAI,EAAE,AAAD,EAAGW,IAAI,CAAC,KAAK,CAAC,CAAC;YAEvH,oEAAoE;YACpE,qDAAqD;YACrD,MAAMc,QAAQ/B,kBAAkB,UAAU,SAAS;YAEnD,MAAMgC,SAASnD,aAAa;gBAC1B8C;gBACAvB,SAASwB,YAAYxB,OAAO;gBAC5B,GAAIwB,YAAYtB,IAAI,KAAKX,aAAa;oBAAEW,MAAMsB,YAAYtB,IAAI;gBAAC,CAAC;gBAChEK,KAAKmB;gBACL,GAAIF,YAAYvC,GAAG,IAAII,OAAOwC,IAAI,CAACL,YAAYvC,GAAG,EAAE8B,MAAM,GAAG,KAAK;oBAAE9B,KAAKuC,YAAYvC,GAAG;gBAAC,CAAC;gBAC1F0C;YACF;YAEA,+DAA+D;YAC/DT,QAAQY,GAAG,CAACP,MAAMK;YAClBpD,OAAOiD,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,sBAAsB,CAAC;QAC9C,EAAE,OAAOQ,GAAG;YACVvD,OAAOiD,IAAI,CAAC,CAAC,CAAC,EAAEF,KAAK,eAAe,EAAEQ,aAAatB,QAAQsB,EAAEC,OAAO,GAAGC,OAAOF,IAAI;QACpF;IACF;IAEA,8CAA8C;IAC9C,MAAMG,UAAU,OAAOX,MAAcjB;QACnC,MAAM6B,cAAc9B,aAAa,CAACkB,KAAK;QACvC,IAAI,CAACY,aAAa;YAChB,MAAMC,YAAY/C,OAAOwC,IAAI,CAACxB,eAAeQ,IAAI,CAAC;YAClD,MAAM,IAAIJ,MAAM,CAAC,QAAQ,EAAEc,KAAK,0CAA0C,EAAEa,aAAa,QAAQ;QACnG;QAEA,MAAMxC,gBAAgBlB,mBAAmByD;QAEzC,IAAIvC,kBAAkB,SAAS;YAC7B,+EAA+E;YAC/E,IAAIH,QAAQ6B,mBAAmBe,GAAG,CAACd;YACnC,IAAI,CAAC9B,OAAO;gBACVA,QAAQ;oBAAE6C,MAAM;gBAAE;gBAClBhB,mBAAmBQ,GAAG,CAACP,MAAM9B;YAC/B;YAEA,IAAI,CAACA,MAAM8C,MAAM,EAAE;gBACjB,IAAI,CAAC9C,MAAM+C,UAAU,EAAE;oBACrB/C,MAAM+C,UAAU,GAAG,AAAC,CAAA;wBAClB,uDAAuD;wBACvD,MAAMC,eAAe;4BAAE9D,QAAQ0B;4BAAea;wBAAQ;wBACtD,MAAMwB,YAAY,MAAMtE,iBAAiBqE,cAAclB,MAAMjB;wBAC7D,MAAMqC,YAAYzE,eAAewE,WAAW;4BAAEE,YAAYrB;wBAAK;wBAC/D9B,MAAM8C,MAAM,GAAGI;wBACflD,MAAM+C,UAAU,GAAGjD;wBACnB,OAAOoD;oBACT,CAAA,IAAKE,KAAK,CAAC,CAACC;wBACVxB,mBAAmByB,MAAM,CAACxB;wBAC1B,MAAMuB;oBACR;gBACF;gBAEA,MAAMrD,MAAM+C,UAAU;YACxB;YAEA,IAAI,CAAC/C,MAAM8C,MAAM,EAAE;gBACjB,MAAM,IAAI9B,MAAM,CAAC,mCAAmC,EAAEc,KAAK,CAAC,CAAC;YAC/D;YAEA9B,MAAM6C,IAAI,IAAI;YACd,IAAIU,WAAW;YACf,IAAIC;YAEJA,QAAQ,IAAIC,MAAMzD,MAAM8C,MAAM,EAAE;gBAC9BF,KAAIc,MAAM,EAAEC,IAAI;oBACd,IAAIA,SAAS,SAAS;wBACpB,OAAO;4BACL,IAAIJ,UAAU;4BACdA,WAAW;4BACX5B,QAAQ2B,MAAM,CAACE;4BACfxD,MAAM6C,IAAI,GAAGe,KAAKC,GAAG,CAAC,GAAG7D,MAAM6C,IAAI,GAAG;4BACtC,IAAI7C,MAAM6C,IAAI,KAAK,GAAG;gCACpBhB,mBAAmByB,MAAM,CAACxB;gCAC1B,MAAM4B,OAAOI,KAAK;4BACpB;wBACF;oBACF;oBAEA,MAAMnE,QAAQoE,QAAQnB,GAAG,CAACc,QAAQC,MAAMD;oBACxC,IAAI,OAAO/D,UAAU,YAAY;wBAC/B,OAAO,AAACA,MAA0CqE,IAAI,CAACN;oBACzD;oBACA,OAAO/D;gBACT;YACF;YAEAgC,QAAQsC,GAAG,CAACT;YACZ,OAAOA;QACT;QAEA,uDAAuD;QACvD,MAAMR,eAAe;YAAE9D,QAAQ0B;YAAea;QAAQ;QACtD,MAAMwB,YAAY,MAAMtE,iBAAiBqE,cAAclB,MAAMjB;QAC7D,MAAMqC,YAAYzE,eAAewE,WAAW;YAAEE,YAAYrB;QAAK;QAC/DH,QAAQsC,GAAG,CAACf;QACZ,OAAOA;IACT;IAEA,2DAA2D;IAC3D,MAAMY,QAAQ,OAAOI,SAAyB,QAAQ,EAAEC,OAA+B,CAAC,CAAC;QACvFpF,OAAOiD,IAAI,CAAC,CAAC,oBAAoB,EAAEkC,OAAO,CAAC,CAAC;QAE5C,mCAAmC;QACnC,MAAME,sBAAsBC,MAAMC,IAAI,CAAC3C,SAAS4C,GAAG,CAAC,OAAOzB;YACzD,IAAI;gBACF,MAAMA,OAAOgB,KAAK;YACpB,EAAE,OAAM;YACN,oCAAoC;YACtC;QACF;QACA,MAAMU,QAAQC,GAAG,CAACL;QAClBzC,QAAQ+C,KAAK;QAEb,kCAAkC;QAClC,IAAIjD,QAAQkD,IAAI,KAAK,GAAG;YACtB,OAAO;gBAAEC,UAAU;gBAAOC,aAAa;YAAE;QAC3C;QAEA,gCAAgC;QAChC,MAAMC,eAAe,MAAMN,QAAQC,GAAG,CAACJ,MAAMC,IAAI,CAAC7C,QAAQsD,MAAM,IAAIR,GAAG,CAAC,CAACS,SAAWA,OAAOlB,KAAK,CAACI,QAAQC;QAEzG,8DAA8D;QAC9D,MAAMS,WAAWE,aAAaG,IAAI,CAAC,CAACC,SAAWA,OAAON,QAAQ;QAC9D,MAAMC,cAAcC,aAAaK,MAAM,CAAC,CAACD,SAAWA,OAAOE,MAAM,EAAE9D,MAAM;QAEzE,OAAO;YAAEsD;YAAUC;QAAY;IACjC;IAEA,MAAMQ,qBAAqB,OAAOC,OAAezE,UAAyB,CAAC,CAAC;YACjDA;QAAzB,MAAM0E,oBAAmB1E,mBAAAA,QAAQY,OAAO,cAAfZ,8BAAAA,mBAAmBjB,OAAOwC,IAAI,CAACxB;QACxD,IAAI2E,iBAAiBjE,MAAM,KAAK,GAAG;YACjC,MAAM,IAAIN,MAAM;QAClB;QAEA,MAAMwE,iBAAiBD,iBAAiBJ,MAAM,CAAC,CAACrD,OAAS,CAAEA,CAAAA,QAAQlB,aAAY;QAC/E,IAAI4E,eAAelE,MAAM,GAAG,GAAG;YAC7B,MAAM,IAAIN,MAAM,CAAC,+CAA+C,EAAEwE,eAAepE,IAAI,CAAC,MAAM,CAAC,CAAC;QAChG;QAEA,MAAMqE,oBAAoB,IAAI/D;QAC9B,MAAMgE,WAAsD,EAAE;QAE9D,MAAMC,eAAe,OAAOxC;YAC1B,KAAK,MAAML,UAAUnB,QAAS;gBAC5B,IAAImB,OAAOK,UAAU,KAAKA,YAAY;oBACpC,OAAOL;gBACT;YACF;YACA,OAAOL,QAAQU;QACjB;QAEA,MAAMqB,QAAQC,GAAG,CACfc,iBAAiBhB,GAAG,CAAC,OAAOpB;YAC1B,IAAI;gBACF,MAAMyC,UAAU,MAAMD,aAAaxC;gBACnCsC,kBAAkBpD,GAAG,CAACc,YAAYyC,QAAQC,YAAY;YACxD,EAAE,OAAOxC,OAAO;gBACdqC,SAASI,IAAI,CAAC;oBACZd,QAAQ7B;oBACR4C,QAAQ1C,iBAAiBrC,QAAQqC,MAAMd,OAAO,GAAGC,OAAOa;gBAC1D;YACF;QACF;QAGF,IAAIoC,kBAAkBd,IAAI,KAAK,GAAG;YAChC,MAAMqB,iBAAiBN,SAASpE,MAAM,GAAG,IAAI,CAAC,sBAAsB,EAAEoE,SAASnB,GAAG,CAAC,CAAC0B,IAAM,GAAGA,EAAEjB,MAAM,CAAC,EAAE,EAAEiB,EAAEF,MAAM,CAAC,CAAC,CAAC,EAAE3E,IAAI,CAAC,OAAO,GAAG;YACtI,MAAM,IAAIJ,MAAM,CAAC,uEAAuE,EAAEgF,gBAAgB;QAC5G;QAEA,IAAIN,SAASpE,MAAM,GAAG,GAAG;YACvB,MAAM,IAAIN,MAAM,CAAC,4DAA4D,EAAE0E,SAASnB,GAAG,CAAC,CAAC0B,IAAMA,EAAEjB,MAAM,EAAE5D,IAAI,CAAC,MAAM,YAAY,EAAEsE,SAASnB,GAAG,CAAC,CAAC0B,IAAM,GAAGA,EAAEjB,MAAM,CAAC,EAAE,EAAEiB,EAAEF,MAAM,EAAE,EAAE3E,IAAI,CAAC,OAAO;QACpM;QAEA,MAAM8E,QAAQ,MAAMtH,qBAAqB6G;QACzC,OAAO3G,wBAAwBoH,OAAOZ,OAAOzE;IAC/C;IAEA,0CAA0C;IAC1C,MAAMsF,eAAe;QACnB,MAAMrC;IACR;IAEA,OAAO;QACL5E,QAAQ0B;QACRa;QACAE;QACAc;QACAqB;QACAjF,oBAAoBwG;QACpB,CAACe,OAAOD,YAAY,CAAC,EAAEA;IACzB;AACF"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP Configuration Types
3
+ *
4
+ * Auto-generated from schemas/servers.schema.json
5
+ */
6
+ export type { MCPServers, McpServerEntry, StartConfig } from '../schemas/servers.d.js';
7
+ /**
8
+ * Transport type for server configuration
9
+ * Standard MCP transport types matching Claude Code's schemas
10
+ */
11
+ export type TransportType = 'stdio' | 'http' | 'sse-ide' | 'ws-ide' | 'sdk';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * MCP Configuration Types
3
+ *
4
+ * Auto-generated from schemas/servers.schema.json
5
+ */ // Re-export all generated types
6
+ /**
7
+ * Transport type for server configuration
8
+ * Standard MCP transport types matching Claude Code's schemas
9
+ */ export { };
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/types.ts"],"sourcesContent":["/**\n * MCP Configuration Types\n *\n * Auto-generated from schemas/servers.schema.json\n */\n\n// Re-export all generated types\nexport type { MCPServers, McpServerEntry, StartConfig } from '../schemas/servers.d.ts';\n\n/**\n * Transport type for server configuration\n * Standard MCP transport types matching Claude Code's schemas\n */\nexport type TransportType = 'stdio' | 'http' | 'sse-ide' | 'ws-ide' | 'sdk';\n"],"names":[],"mappings":"AAAA;;;;CAIC,GAED,gCAAgC;AAGhC;;;CAGC,GACD,WAA4E"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Sanitized logger with log level filtering
3
+ * Provides credential redaction and verbosity control for all log output
4
+ */
5
+ /**
6
+ * Log level for filtering output
7
+ */
8
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
9
+ /**
10
+ * Logger interface - subset of Console
11
+ */
12
+ export type Logger = Pick<Console, 'info' | 'error' | 'warn' | 'debug'>;
13
+ /**
14
+ * Set the global log level
15
+ * Users can call this to control library verbosity
16
+ *
17
+ * @param level - Log level: 'debug' (all), 'info', 'warn', 'error', 'silent' (none)
18
+ */
19
+ export declare function setLogLevel(level: LogLevel): void;
20
+ /**
21
+ * Get current log level
22
+ */
23
+ export declare function getLogLevel(): LogLevel;
24
+ export declare const logger: Logger;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Sanitized logger with log level filtering
3
+ * Provides credential redaction and verbosity control for all log output
4
+ */ import { sanitizeForLogging } from './sanitizer.js';
5
+ // Log level hierarchy (higher number = more important)
6
+ const levels = {
7
+ debug: 0,
8
+ info: 1,
9
+ warn: 2,
10
+ error: 3,
11
+ silent: 4
12
+ };
13
+ // Default to 'warn' - quiet library, only show warnings and errors
14
+ let currentLevel = process.env.LOG_LEVEL || 'warn';
15
+ /**
16
+ * Check if a log should be output based on current level
17
+ */ function shouldLog(level) {
18
+ return levels[level] >= levels[currentLevel];
19
+ }
20
+ /**
21
+ * Set the global log level
22
+ * Users can call this to control library verbosity
23
+ *
24
+ * @param level - Log level: 'debug' (all), 'info', 'warn', 'error', 'silent' (none)
25
+ */ export function setLogLevel(level) {
26
+ currentLevel = level;
27
+ }
28
+ /**
29
+ * Get current log level
30
+ */ export function getLogLevel() {
31
+ return currentLevel;
32
+ }
33
+ /**
34
+ * Create a sanitized logger with log level filtering
35
+ */ function createSanitizedLogger() {
36
+ const createLogMethod = (consoleMethod)=>{
37
+ return (message, ...args)=>{
38
+ // Check if this log level should be output
39
+ if (!shouldLog(consoleMethod)) {
40
+ return;
41
+ }
42
+ const metadata = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
43
+ const { message: clean, meta: cleanMeta } = sanitizeForLogging(message, metadata);
44
+ if (Object.keys(cleanMeta).length > 0) {
45
+ console[consoleMethod](clean, cleanMeta);
46
+ } else {
47
+ console[consoleMethod](clean);
48
+ }
49
+ };
50
+ };
51
+ return {
52
+ info: createLogMethod('info'),
53
+ warn: createLogMethod('warn'),
54
+ error: createLogMethod('error'),
55
+ debug: createLogMethod('debug')
56
+ };
57
+ }
58
+ // Singleton instance - shared across all operations
59
+ export const logger = createSanitizedLogger();
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/utils/logger.ts"],"sourcesContent":["/**\n * Sanitized logger with log level filtering\n * Provides credential redaction and verbosity control for all log output\n */\n\nimport type { SpawnMetadata } from '../connection/types.ts';\nimport { sanitizeForLogging } from './sanitizer.ts';\n\n/**\n * Log level for filtering output\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\n/**\n * Logger interface - subset of Console\n */\nexport type Logger = Pick<Console, 'info' | 'error' | 'warn' | 'debug'>;\n\n// Log level hierarchy (higher number = more important)\nconst levels: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n silent: 4,\n};\n\n// Default to 'warn' - quiet library, only show warnings and errors\nlet currentLevel: LogLevel = (process.env.LOG_LEVEL as LogLevel) || 'warn';\n\n/**\n * Check if a log should be output based on current level\n */\nfunction shouldLog(level: keyof Omit<typeof levels, 'silent'>): boolean {\n return levels[level] >= levels[currentLevel];\n}\n\n/**\n * Set the global log level\n * Users can call this to control library verbosity\n *\n * @param level - Log level: 'debug' (all), 'info', 'warn', 'error', 'silent' (none)\n */\nexport function setLogLevel(level: LogLevel): void {\n currentLevel = level;\n}\n\n/**\n * Get current log level\n */\nexport function getLogLevel(): LogLevel {\n return currentLevel;\n}\n\n/**\n * Create a sanitized logger with log level filtering\n */\nfunction createSanitizedLogger(): Logger {\n const createLogMethod = (consoleMethod: 'info' | 'warn' | 'error' | 'debug') => {\n return (message: string, ...args: unknown[]) => {\n // Check if this log level should be output\n if (!shouldLog(consoleMethod)) {\n return;\n }\n\n const metadata = args.length > 0 && typeof args[0] === 'object' ? (args[0] as SpawnMetadata) : {};\n const { message: clean, meta: cleanMeta } = sanitizeForLogging(message, metadata);\n if (Object.keys(cleanMeta).length > 0) {\n console[consoleMethod](clean, cleanMeta);\n } else {\n console[consoleMethod](clean);\n }\n };\n };\n\n return {\n info: createLogMethod('info'),\n warn: createLogMethod('warn'),\n error: createLogMethod('error'),\n debug: createLogMethod('debug'),\n };\n}\n\n// Singleton instance - shared across all operations\nexport const logger = createSanitizedLogger();\n"],"names":["sanitizeForLogging","levels","debug","info","warn","error","silent","currentLevel","process","env","LOG_LEVEL","shouldLog","level","setLogLevel","getLogLevel","createSanitizedLogger","createLogMethod","consoleMethod","message","args","metadata","length","clean","meta","cleanMeta","Object","keys","console","logger"],"mappings":"AAAA;;;CAGC,GAGD,SAASA,kBAAkB,QAAQ,iBAAiB;AAYpD,uDAAuD;AACvD,MAAMC,SAAmC;IACvCC,OAAO;IACPC,MAAM;IACNC,MAAM;IACNC,OAAO;IACPC,QAAQ;AACV;AAEA,mEAAmE;AACnE,IAAIC,eAAyB,AAACC,QAAQC,GAAG,CAACC,SAAS,IAAiB;AAEpE;;CAEC,GACD,SAASC,UAAUC,KAA0C;IAC3D,OAAOX,MAAM,CAACW,MAAM,IAAIX,MAAM,CAACM,aAAa;AAC9C;AAEA;;;;;CAKC,GACD,OAAO,SAASM,YAAYD,KAAe;IACzCL,eAAeK;AACjB;AAEA;;CAEC,GACD,OAAO,SAASE;IACd,OAAOP;AACT;AAEA;;CAEC,GACD,SAASQ;IACP,MAAMC,kBAAkB,CAACC;QACvB,OAAO,CAACC,SAAiB,GAAGC;YAC1B,2CAA2C;YAC3C,IAAI,CAACR,UAAUM,gBAAgB;gBAC7B;YACF;YAEA,MAAMG,WAAWD,KAAKE,MAAM,GAAG,KAAK,OAAOF,IAAI,CAAC,EAAE,KAAK,WAAYA,IAAI,CAAC,EAAE,GAAqB,CAAC;YAChG,MAAM,EAAED,SAASI,KAAK,EAAEC,MAAMC,SAAS,EAAE,GAAGxB,mBAAmBkB,SAASE;YACxE,IAAIK,OAAOC,IAAI,CAACF,WAAWH,MAAM,GAAG,GAAG;gBACrCM,OAAO,CAACV,cAAc,CAACK,OAAOE;YAChC,OAAO;gBACLG,OAAO,CAACV,cAAc,CAACK;YACzB;QACF;IACF;IAEA,OAAO;QACLnB,MAAMa,gBAAgB;QACtBZ,MAAMY,gBAAgB;QACtBX,OAAOW,gBAAgB;QACvBd,OAAOc,gBAAgB;IACzB;AACF;AAEA,oDAAoD;AACpD,OAAO,MAAMY,SAASb,wBAAwB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Path resolution utilities for cluster spawning.
3
+ * Handles ~, relative paths, absolute paths, and special cases (URLs, npm packages, flags).
4
+ */
5
+ /**
6
+ * Resolve a file path relative to a working directory.
7
+ *
8
+ * Handles three cases:
9
+ * - `~` or `~/path` - Expands to home directory
10
+ * - Absolute paths - Returns as-is
11
+ * - Relative paths - Resolves relative to cwd
12
+ *
13
+ * @param filePath - The path to resolve
14
+ * @param cwd - The working directory for resolving relative paths
15
+ * @returns The resolved absolute path
16
+ *
17
+ * @example
18
+ * resolvePath('~/config.json', '/unused') // → '/home/user/config.json'
19
+ * resolvePath('/absolute/path', '/unused') // → '/absolute/path'
20
+ * resolvePath('./relative', '/base') // → '/base/relative'
21
+ */
22
+ export declare function resolvePath(filePath: string, cwd: string): string;
23
+ /**
24
+ * Resolve paths in command arguments array.
25
+ *
26
+ * Intelligently handles different argument types:
27
+ * - Flags with paths: `--env-file=./path` → `--env-file=/absolute/path`
28
+ * - Flags without paths: `--port=3000` → unchanged
29
+ * - Command flags: `--verbose` → unchanged
30
+ * - URLs: `http://example.com` → unchanged
31
+ * - npm packages: `@scope/package` or `package-name` → unchanged
32
+ * - Regular paths: `./script.js` → `/absolute/script.js`
33
+ *
34
+ * @param args - Array of command arguments
35
+ * @param cwd - Working directory for resolving relative paths
36
+ * @returns Array with paths resolved
37
+ *
38
+ * @example
39
+ * resolveArgsPaths(['./bin/server.js', '--env-file=./config.env', '--port=3000'], '/home/user')
40
+ * // → ['/home/user/bin/server.js', '--env-file=/home/user/config.env', '--port=3000']
41
+ *
42
+ * resolveArgsPaths(['@scope/package', 'https://example.com'], '/unused')
43
+ * // → ['@scope/package', 'https://example.com'] (unchanged)
44
+ */
45
+ export declare function resolveArgsPaths(args: string[], cwd: string): string[];