@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,8 @@
1
+ /**
2
+ * Search types for MCP capability discovery
3
+ *
4
+ * Enables agents to discover tools, prompts, and resources without
5
+ * loading full schemas into context.
6
+ */ /**
7
+ * Index containing all capabilities from connected servers
8
+ */ export { };
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/search/types.ts"],"sourcesContent":["/**\n * Search types for MCP capability discovery\n *\n * Enables agents to discover tools, prompts, and resources without\n * loading full schemas into context.\n */\n\nimport type { PromptArgument } from '../connection/types.ts';\n\n/**\n * Types of MCP capabilities that can be searched\n */\nexport type CapabilityType = 'tool' | 'prompt' | 'resource';\n\n/**\n * Fields that can be searched within capabilities\n */\nexport type SearchField = 'name' | 'description' | 'schema' | 'server';\n\n/**\n * Options for configuring search behavior\n */\nexport interface SearchOptions {\n /**\n * Filter to specific capability types\n * @default ['tool', 'prompt', 'resource']\n */\n types?: CapabilityType[];\n\n /**\n * Filter to specific servers by name\n * @default all servers in config\n */\n servers?: string[];\n\n /**\n * Which fields to search within\n * @default ['name', 'description', 'schema']\n */\n searchFields?: SearchField[];\n\n /**\n * Maximum number of results to return\n * @default 20\n */\n limit?: number;\n\n /**\n * Minimum relevance score (0-1) for results\n * @default 0\n */\n threshold?: number;\n}\n\n/**\n * A single search result representing a matched capability\n */\nexport interface SearchResult {\n /** The type of capability */\n type: CapabilityType;\n\n /** The server that provides this capability */\n server: string;\n\n /** The name of the capability */\n name: string;\n\n /** Human-readable description (may be truncated) */\n description: string | undefined;\n\n /** Which fields matched the search query */\n matchedOn: string[];\n\n /** Relevance score from 0 (low) to 1 (high) */\n score: number;\n}\n\n/**\n * Complete search response\n */\nexport interface SearchResponse {\n /** The original search query */\n query: string;\n\n /** Matching results, sorted by relevance */\n results: SearchResult[];\n\n /** Total number of matches before limit was applied */\n total: number;\n}\n\n/**\n * Internal representation of a tool for indexing\n */\nexport interface IndexedTool {\n type: 'tool';\n server: string;\n name: string;\n description: string | undefined;\n /** Flattened searchable text from inputSchema property descriptions */\n schemaText: string;\n}\n\n/**\n * Internal representation of a prompt for indexing\n */\nexport interface IndexedPrompt {\n type: 'prompt';\n server: string;\n name: string;\n description: string | undefined;\n /** Flattened searchable text from arguments */\n argumentsText: string;\n arguments: PromptArgument[] | undefined;\n}\n\n/**\n * Internal representation of a resource for indexing\n */\nexport interface IndexedResource {\n type: 'resource';\n server: string;\n name: string;\n description: string | undefined;\n uri: string;\n mimeType: string | undefined;\n}\n\n/**\n * Union of all indexed capability types\n */\nexport type IndexedCapability = IndexedTool | IndexedPrompt | IndexedResource;\n\n/**\n * Index containing all capabilities from connected servers\n */\nexport interface CapabilityIndex {\n /** All indexed capabilities */\n capabilities: IndexedCapability[];\n\n /** Servers that were indexed */\n servers: string[];\n\n /** When the index was created */\n indexedAt: Date;\n}\n"],"names":[],"mappings":"AAAA;;;;;CAKC,GAgID;;CAEC,GACD,WASC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Low-level single server spawning utilities.
3
+ * Provides core process spawning with path resolution, environment management, and lifecycle control.
4
+ */
5
+ import { type ChildProcess, type StdioOptions } from 'child_process';
6
+ /**
7
+ * Options for spawning a single server process.
8
+ * @internal
9
+ */
10
+ export interface SpawnProcessOptions {
11
+ /** Server name for logging */
12
+ name: string;
13
+ /** Command to execute (e.g., 'node', 'npx') */
14
+ command: string;
15
+ /** Command arguments (paths will be resolved relative to cwd) */
16
+ args?: string[];
17
+ /** Working directory (must be absolute path) */
18
+ cwd?: string;
19
+ /** Additional environment variables (merged with process.env) */
20
+ env?: Record<string, string>;
21
+ /** Standard I/O configuration */
22
+ stdio?: StdioOptions;
23
+ /** Use shell for command execution (default: false, true on Windows) */
24
+ shell?: boolean;
25
+ }
26
+ /**
27
+ * Handle to a spawned server process.
28
+ * Provides access to the process, resolved config, and lifecycle control.
29
+ * @hidden
30
+ */
31
+ export interface ServerProcess {
32
+ /**
33
+ * The resolved server configuration that was actually used.
34
+ * Useful for debugging and understanding what was spawned.
35
+ */
36
+ config: {
37
+ name: string;
38
+ command: string;
39
+ args: string[];
40
+ cwd: string;
41
+ env: Record<string, string>;
42
+ stdio: StdioOptions;
43
+ shell: boolean;
44
+ };
45
+ /**
46
+ * The spawned child process.
47
+ */
48
+ process: ChildProcess;
49
+ /**
50
+ * Close the server gracefully.
51
+ * Sends the specified signal (default: SIGINT), then SIGKILL after timeout.
52
+ *
53
+ * @param signal - Signal to send (default: SIGINT)
54
+ * @param opts - Options including timeout
55
+ * @returns Promise resolving to whether the process timed out and was force-killed
56
+ */
57
+ close: (signal?: NodeJS.Signals, opts?: {
58
+ timeoutMs?: number;
59
+ }) => Promise<{
60
+ timedOut: boolean;
61
+ killed: boolean;
62
+ }>;
63
+ }
64
+ /**
65
+ * Spawn a single server process with path resolution and environment management.
66
+ *
67
+ * @internal
68
+ * @param opts - Server spawn options
69
+ * @returns ServerProcess handle with resolved config, process, and stop function
70
+ *
71
+ * @example
72
+ * const handle = spawnProcess({
73
+ * name: 'echo',
74
+ * command: 'node',
75
+ * args: ['./bin/server.js', '--port', '3000'],
76
+ * cwd: '/home/user/project/test/lib/servers/echo',
77
+ * env: { LOG_LEVEL: 'error' }
78
+ * });
79
+ *
80
+ * // Later...
81
+ * await handle.close();
82
+ */
83
+ export declare function spawnProcess(opts: SpawnProcessOptions): ServerProcess;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Low-level single server spawning utilities.
3
+ * Provides core process spawning with path resolution, environment management, and lifecycle control.
4
+ */ import { spawn } from 'child_process';
5
+ import * as process from 'process';
6
+ import { logger } from '../utils/logger.js';
7
+ 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) {
11
+ const merged = {
12
+ ...process.env,
13
+ ...env || {}
14
+ };
15
+ const result = {};
16
+ for (const [key, value] of Object.entries(merged)){
17
+ if (value !== undefined) {
18
+ result[key] = value;
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+ /**
24
+ * Spawn a single server process with path resolution and environment management.
25
+ *
26
+ * @internal
27
+ * @param opts - Server spawn options
28
+ * @returns ServerProcess handle with resolved config, process, and stop function
29
+ *
30
+ * @example
31
+ * const handle = spawnProcess({
32
+ * name: 'echo',
33
+ * command: 'node',
34
+ * args: ['./bin/server.js', '--port', '3000'],
35
+ * cwd: '/home/user/project/test/lib/servers/echo',
36
+ * env: { LOG_LEVEL: 'error' }
37
+ * });
38
+ *
39
+ * // Later...
40
+ * await handle.close();
41
+ */ export function spawnProcess(opts) {
42
+ var _opts_cwd, _opts_stdio, _opts_shell;
43
+ const name = opts.name;
44
+ const command = opts.command;
45
+ const cwd = (_opts_cwd = opts.cwd) !== null && _opts_cwd !== void 0 ? _opts_cwd : process.cwd();
46
+ const stdio = (_opts_stdio = opts.stdio) !== null && _opts_stdio !== void 0 ? _opts_stdio : 'inherit';
47
+ const shell = (_opts_shell = opts.shell) !== null && _opts_shell !== void 0 ? _opts_shell : process.platform === 'win32';
48
+ // Resolve paths in args relative to the working directory
49
+ const args = opts.args ? resolveArgsPaths(opts.args, cwd) : [];
50
+ // Merge environment variables
51
+ const env = normalizeEnv(opts.env);
52
+ // Create resolved config for return value
53
+ const resolvedConfig = {
54
+ name,
55
+ command,
56
+ args,
57
+ cwd,
58
+ env,
59
+ stdio,
60
+ shell
61
+ };
62
+ // Log spawn operation
63
+ logger.info(`[${name}] → ${command} ${args.join(' ')}`);
64
+ // Spawn the process
65
+ const spawnOpts = {
66
+ cwd,
67
+ env,
68
+ stdio,
69
+ shell
70
+ };
71
+ const child = spawn(command, args, spawnOpts);
72
+ // Attach lifecycle logging
73
+ child.on('exit', (code, sig)=>logger.info(`[${name}] exited (code=${code}, signal=${sig || 'none'})`));
74
+ child.on('error', (err)=>logger.info(`[${name}] process error: ${err.message}`));
75
+ // Create stop function with graceful shutdown
76
+ const stop = async (signal = 'SIGINT', opts = {})=>{
77
+ var _opts_timeoutMs;
78
+ // If already exited, return immediately
79
+ if (child.exitCode !== null || child.signalCode !== null) {
80
+ return {
81
+ timedOut: false,
82
+ killed: false
83
+ };
84
+ }
85
+ const timeoutMs = (_opts_timeoutMs = opts.timeoutMs) !== null && _opts_timeoutMs !== void 0 ? _opts_timeoutMs : 500;
86
+ // Wait for 'close' event (process exit + stdio streams closed)
87
+ // This is better than 'exit' because it ensures stdio is fully cleaned up
88
+ const closePromise = new Promise((resolve)=>{
89
+ let isResolved = false;
90
+ let wasKilled = false;
91
+ const resolveOnce = (timedOut)=>{
92
+ if (isResolved) return;
93
+ isResolved = true;
94
+ clearTimeout(timeout);
95
+ resolve({
96
+ timedOut,
97
+ killed: wasKilled
98
+ });
99
+ };
100
+ // Set timeout for forceful kill
101
+ const timeout = setTimeout(()=>{
102
+ try {
103
+ // Check again before SIGKILL
104
+ if (child.exitCode === null && !child.killed) {
105
+ child.kill('SIGKILL');
106
+ wasKilled = true;
107
+ }
108
+ } catch (_) {}
109
+ // Even if kill fails, resolve
110
+ resolveOnce(true);
111
+ }, timeoutMs);
112
+ // Listen for 'close' event (not 'exit') to wait for stdio close
113
+ child.once('close', ()=>{
114
+ resolveOnce(false);
115
+ });
116
+ // Also listen for 'error' event in case spawn failed
117
+ // This prevents promise from hanging forever if process never started
118
+ child.once('error', ()=>{
119
+ resolveOnce(false);
120
+ });
121
+ // Send graceful shutdown signal
122
+ try {
123
+ // Check one more time before killing
124
+ if (child.exitCode !== null) {
125
+ resolveOnce(false);
126
+ return;
127
+ }
128
+ const killed = child.kill(signal);
129
+ // If kill returned false, process already exited
130
+ if (!killed) {
131
+ resolveOnce(false);
132
+ }
133
+ } catch (_err) {
134
+ // If kill throws, process is gone or unreachable
135
+ resolveOnce(false);
136
+ }
137
+ });
138
+ return closePromise;
139
+ };
140
+ return {
141
+ config: resolvedConfig,
142
+ process: child,
143
+ close: stop
144
+ };
145
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/spawn/spawn-server.ts"],"sourcesContent":["/**\n * Low-level single server spawning utilities.\n * Provides core process spawning with path resolution, environment management, and lifecycle control.\n */\n\nimport { type ChildProcess, type SpawnOptions, type StdioOptions, spawn } from 'child_process';\nimport * as process from 'process';\nimport { logger } from '../utils/logger.ts';\nimport { resolveArgsPaths } from '../utils/path-utils.ts';\n\n/**\n * Options for spawning a single server process.\n * @internal\n */\nexport interface SpawnProcessOptions {\n /** Server name for logging */\n name: string;\n /** Command to execute (e.g., 'node', 'npx') */\n command: string;\n /** Command arguments (paths will be resolved relative to cwd) */\n args?: string[];\n /** Working directory (must be absolute path) */\n cwd?: string;\n /** Additional environment variables (merged with process.env) */\n env?: Record<string, string>;\n /** Standard I/O configuration */\n stdio?: StdioOptions;\n /** Use shell for command execution (default: false, true on Windows) */\n shell?: boolean;\n}\n\n/**\n * Handle to a spawned server process.\n * Provides access to the process, resolved config, and lifecycle control.\n * @hidden\n */\nexport interface ServerProcess {\n /**\n * The resolved server configuration that was actually used.\n * Useful for debugging and understanding what was spawned.\n */\n config: {\n name: string;\n command: string;\n args: string[];\n cwd: string;\n env: Record<string, string>;\n stdio: StdioOptions;\n shell: boolean;\n };\n\n /**\n * The spawned child process.\n */\n process: ChildProcess;\n\n /**\n * Close the server gracefully.\n * Sends the specified signal (default: SIGINT), then SIGKILL after timeout.\n *\n * @param signal - Signal to send (default: SIGINT)\n * @param opts - Options including timeout\n * @returns Promise resolving to whether the process timed out and was force-killed\n */\n close: (signal?: NodeJS.Signals, opts?: { timeoutMs?: number }) => Promise<{ timedOut: boolean; killed: boolean }>;\n}\n\n/**\n * Normalize environment variables by merging with process.env and filtering undefined values.\n */\nfunction normalizeEnv(env?: Record<string, string>): Record<string, string> {\n const merged = { ...process.env, ...(env || {}) };\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(merged)) {\n if (value !== undefined) {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Spawn a single server process with path resolution and environment management.\n *\n * @internal\n * @param opts - Server spawn options\n * @returns ServerProcess handle with resolved config, process, and stop function\n *\n * @example\n * const handle = spawnProcess({\n * name: 'echo',\n * command: 'node',\n * args: ['./bin/server.js', '--port', '3000'],\n * cwd: '/home/user/project/test/lib/servers/echo',\n * env: { LOG_LEVEL: 'error' }\n * });\n *\n * // Later...\n * await handle.close();\n */\nexport function spawnProcess(opts: SpawnProcessOptions): ServerProcess {\n const name = opts.name;\n const command = opts.command;\n const cwd = opts.cwd ?? process.cwd();\n const stdio = opts.stdio ?? 'inherit';\n const shell = opts.shell ?? process.platform === 'win32';\n\n // Resolve paths in args relative to the working directory\n const args = opts.args ? resolveArgsPaths(opts.args, cwd) : [];\n\n // Merge environment variables\n const env = normalizeEnv(opts.env);\n\n // Create resolved config for return value\n const resolvedConfig = {\n name,\n command,\n args,\n cwd,\n env,\n stdio,\n shell,\n };\n\n // Log spawn operation\n logger.info(`[${name}] → ${command} ${args.join(' ')}`);\n\n // Spawn the process\n const spawnOpts: SpawnOptions = { cwd, env, stdio, shell };\n const child = spawn(command, args, spawnOpts);\n\n // Attach lifecycle logging\n child.on('exit', (code, sig) => logger.info(`[${name}] exited (code=${code}, signal=${sig || 'none'})`));\n child.on('error', (err) => logger.info(`[${name}] process error: ${err.message}`));\n\n // Create stop function with graceful shutdown\n const stop = async (signal: NodeJS.Signals = 'SIGINT', opts: { timeoutMs?: number } = {}): Promise<{ timedOut: boolean; killed: boolean }> => {\n // If already exited, return immediately\n if (child.exitCode !== null || child.signalCode !== null) {\n return { timedOut: false, killed: false };\n }\n\n const timeoutMs = opts.timeoutMs ?? 500;\n\n // Wait for 'close' event (process exit + stdio streams closed)\n // This is better than 'exit' because it ensures stdio is fully cleaned up\n const closePromise = new Promise<{ timedOut: boolean; killed: boolean }>((resolve) => {\n let isResolved = false;\n let wasKilled = false;\n\n const resolveOnce = (timedOut: boolean) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n resolve({ timedOut, killed: wasKilled });\n };\n\n // Set timeout for forceful kill\n const timeout = setTimeout(() => {\n try {\n // Check again before SIGKILL\n if (child.exitCode === null && !child.killed) {\n child.kill('SIGKILL');\n wasKilled = true;\n }\n } catch (_) {}\n // Even if kill fails, resolve\n resolveOnce(true);\n }, timeoutMs);\n\n // Listen for 'close' event (not 'exit') to wait for stdio close\n child.once('close', () => {\n resolveOnce(false);\n });\n\n // Also listen for 'error' event in case spawn failed\n // This prevents promise from hanging forever if process never started\n child.once('error', () => {\n resolveOnce(false);\n });\n\n // Send graceful shutdown signal\n try {\n // Check one more time before killing\n if (child.exitCode !== null) {\n resolveOnce(false);\n return;\n }\n\n const killed = child.kill(signal);\n // If kill returned false, process already exited\n if (!killed) {\n resolveOnce(false);\n }\n } catch (_err) {\n // If kill throws, process is gone or unreachable\n resolveOnce(false);\n }\n });\n\n return closePromise;\n };\n\n return {\n config: resolvedConfig,\n process: child,\n close: stop,\n };\n}\n"],"names":["spawn","process","logger","resolveArgsPaths","normalizeEnv","env","merged","result","key","value","Object","entries","undefined","spawnProcess","opts","name","command","cwd","stdio","shell","platform","args","resolvedConfig","info","join","spawnOpts","child","on","code","sig","err","message","stop","signal","exitCode","signalCode","timedOut","killed","timeoutMs","closePromise","Promise","resolve","isResolved","wasKilled","resolveOnce","clearTimeout","timeout","setTimeout","kill","_","once","_err","config","close"],"mappings":"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"}
@@ -0,0 +1,151 @@
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
+ */
7
+ import { type ManagedClient } from '../client-helpers.js';
8
+ import { connectMcpClient } from '../connection/connect-client.js';
9
+ import { type SearchOptions, type SearchResponse } from '../search/index.js';
10
+ import type { McpServerEntry } from '../types.js';
11
+ import { type ServerProcess } from './spawn-server.js';
12
+ /**
13
+ * Servers configuration type - a map of server names to their configurations.
14
+ */
15
+ export type ServersConfig = Record<string, McpServerEntry>;
16
+ /**
17
+ * Dialect for server spawning.
18
+ *
19
+ * - 'servers': Spawn stdio servers (Claude Code compatible)
20
+ * - 'start': Spawn HTTP servers with start blocks
21
+ */
22
+ export type Dialect = 'servers' | 'start';
23
+ /**
24
+ * Options for creating a server registry.
25
+ */
26
+ export interface CreateServerRegistryOptions {
27
+ /** Working directory for spawned processes (default: process.cwd()) */
28
+ cwd?: string;
29
+ /**
30
+ * Base environment for all servers.
31
+ * If provided, process.env is NOT included (caller has full control).
32
+ * If omitted, process.env is used as the base (default behavior).
33
+ */
34
+ env?: Record<string, string>;
35
+ /**
36
+ * Dialects controlling which servers to spawn.
37
+ * - ['servers']: Spawn stdio servers only (default, Claude Code compatible)
38
+ * - ['start']: Only spawn servers with start blocks (HTTP servers)
39
+ * - ['servers', 'start']: Spawn both stdio servers and start blocks
40
+ * @default ['servers']
41
+ */
42
+ dialects?: Dialect[];
43
+ }
44
+ /**
45
+ * Result of closing the registry.
46
+ */
47
+ export interface CloseResult {
48
+ /** Whether any process timed out during shutdown */
49
+ timedOut: boolean;
50
+ /** Number of processes that were force-killed */
51
+ killedCount: number;
52
+ }
53
+ /**
54
+ * A registry of spawned MCP servers with connection management.
55
+ * Provides access to individual server handles, connection management, and collection-wide close.
56
+ */
57
+ type RegistryConnectOptions = Parameters<typeof connectMcpClient>[2];
58
+ export interface ServerRegistry {
59
+ /**
60
+ * The resolved servers configuration that was used.
61
+ * Useful for debugging and understanding what was started.
62
+ */
63
+ config: ServersConfig;
64
+ /**
65
+ * Map of server name to server process handle.
66
+ * @hidden
67
+ */
68
+ servers: Map<string, ServerProcess>;
69
+ /**
70
+ * Set of connected clients tracked by this registry.
71
+ * Automatically populated when using registry.connect().
72
+ */
73
+ clients: Set<ManagedClient>;
74
+ /**
75
+ * Connect to a server by name.
76
+ * The connected client is automatically tracked for close.
77
+ *
78
+ * @param name - Server name from configuration
79
+ * @returns Connected MCP SDK Client
80
+ */
81
+ connect: (name: string, options?: RegistryConnectOptions) => Promise<ManagedClient>;
82
+ /**
83
+ * Close all clients and servers gracefully.
84
+ * First closes all tracked clients, then sends the specified signal to all server processes.
85
+ *
86
+ * @param signal - Signal to send to processes (default: SIGINT)
87
+ * @param opts - Options including timeout
88
+ * @returns Promise resolving to whether any process timed out and how many were force-killed
89
+ */
90
+ close: (signal?: NodeJS.Signals, opts?: {
91
+ timeoutMs?: number;
92
+ }) => Promise<CloseResult>;
93
+ /**
94
+ * Search indexed capabilities across all currently connected clients.
95
+ * Requires at least one connected client; respects SearchOptions filters.
96
+ */
97
+ searchCapabilities: (query: string, options?: SearchOptions) => Promise<SearchResponse>;
98
+ /**
99
+ * Support for `await using` pattern (automatic close).
100
+ */
101
+ [Symbol.asyncDispose]: () => Promise<void>;
102
+ }
103
+ /**
104
+ * Create a registry of MCP servers from configuration.
105
+ *
106
+ * **Fast start**: Returns immediately after processes are created.
107
+ * Use `registry.connect()` for lazy MCP connection.
108
+ *
109
+ * @param serversConfig - Map of server names to their configurations
110
+ * @param options - Options for registry creation
111
+ * @param options.cwd - Working directory for spawned processes (default: process.cwd())
112
+ * @param options.env - Base environment for all servers. If provided, process.env is NOT included.
113
+ * If omitted, process.env is used as the base (default behavior).
114
+ * @param options.dialects - Dialects controlling which servers to spawn (default: ['servers'])
115
+ * - ['servers']: Spawn stdio servers only (Claude Code compatible)
116
+ * - ['start']: Only spawn servers with start blocks (HTTP servers)
117
+ * - ['servers', 'start']: Spawn both stdio servers and start blocks
118
+ * @returns ServerRegistry instance with config, server map, connect method, and close function
119
+ *
120
+ * @example
121
+ * // Fast server start (does NOT wait for readiness)
122
+ * const registry = createServerRegistry({
123
+ * 'echo': { command: 'node', args: ['server.ts'] },
124
+ * });
125
+ *
126
+ * // Connect when needed (waits for MCP handshake)
127
+ * const client = await registry.connect('echo');
128
+ *
129
+ * // Cleanup (closes all clients AND processes)
130
+ * await registry.close();
131
+ *
132
+ * @example
133
+ * // Start HTTP servers with start blocks
134
+ * const registry = createServerRegistry(
135
+ * {
136
+ * 'http-server': {
137
+ * url: 'http://localhost:8080/mcp',
138
+ * start: { command: 'node', args: ['http.ts', '--port', '8080'] }
139
+ * },
140
+ * },
141
+ * { dialects: ['start'] }
142
+ * );
143
+ *
144
+ * @example
145
+ * // Using await using for automatic close
146
+ * await using registry = createServerRegistry(config);
147
+ * const client = await registry.connect('server');
148
+ * // Auto-disposed when scope exits
149
+ */
150
+ export declare function createServerRegistry(serversConfig: ServersConfig, options?: CreateServerRegistryOptions): ServerRegistry;
151
+ export {};