@mcp-z/cli 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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/bin/cli.js +5 -0
  4. package/dist/cjs/cli.d.cts +1 -0
  5. package/dist/cjs/cli.d.ts +1 -0
  6. package/dist/cjs/cli.js +576 -0
  7. package/dist/cjs/cli.js.map +1 -0
  8. package/dist/cjs/commands/call-tool.d.cts +26 -0
  9. package/dist/cjs/commands/call-tool.d.ts +26 -0
  10. package/dist/cjs/commands/call-tool.js +362 -0
  11. package/dist/cjs/commands/call-tool.js.map +1 -0
  12. package/dist/cjs/commands/get-prompt.d.cts +26 -0
  13. package/dist/cjs/commands/get-prompt.d.ts +26 -0
  14. package/dist/cjs/commands/get-prompt.js +335 -0
  15. package/dist/cjs/commands/get-prompt.js.map +1 -0
  16. package/dist/cjs/commands/inspect.d.cts +35 -0
  17. package/dist/cjs/commands/inspect.d.ts +35 -0
  18. package/dist/cjs/commands/inspect.js +896 -0
  19. package/dist/cjs/commands/inspect.js.map +1 -0
  20. package/dist/cjs/commands/manifest/env-prompting.d.cts +21 -0
  21. package/dist/cjs/commands/manifest/env-prompting.d.ts +21 -0
  22. package/dist/cjs/commands/manifest/env-prompting.js +657 -0
  23. package/dist/cjs/commands/manifest/env-prompting.js.map +1 -0
  24. package/dist/cjs/commands/manifest/generate.d.cts +124 -0
  25. package/dist/cjs/commands/manifest/generate.d.ts +124 -0
  26. package/dist/cjs/commands/manifest/generate.js +2541 -0
  27. package/dist/cjs/commands/manifest/generate.js.map +1 -0
  28. package/dist/cjs/commands/manifest/index.d.cts +2 -0
  29. package/dist/cjs/commands/manifest/index.d.ts +2 -0
  30. package/dist/cjs/commands/manifest/index.js +229 -0
  31. package/dist/cjs/commands/manifest/index.js.map +1 -0
  32. package/dist/cjs/commands/manifest/metadata-reader.d.cts +71 -0
  33. package/dist/cjs/commands/manifest/metadata-reader.d.ts +71 -0
  34. package/dist/cjs/commands/manifest/metadata-reader.js +441 -0
  35. package/dist/cjs/commands/manifest/metadata-reader.js.map +1 -0
  36. package/dist/cjs/commands/manifest/validate.d.cts +1 -0
  37. package/dist/cjs/commands/manifest/validate.d.ts +1 -0
  38. package/dist/cjs/commands/manifest/validate.js +525 -0
  39. package/dist/cjs/commands/manifest/validate.js.map +1 -0
  40. package/dist/cjs/commands/read-resource.d.cts +24 -0
  41. package/dist/cjs/commands/read-resource.d.ts +24 -0
  42. package/dist/cjs/commands/read-resource.js +311 -0
  43. package/dist/cjs/commands/read-resource.js.map +1 -0
  44. package/dist/cjs/commands/search.d.cts +31 -0
  45. package/dist/cjs/commands/search.d.ts +31 -0
  46. package/dist/cjs/commands/search.js +464 -0
  47. package/dist/cjs/commands/search.js.map +1 -0
  48. package/dist/cjs/commands/up.d.cts +49 -0
  49. package/dist/cjs/commands/up.d.ts +49 -0
  50. package/dist/cjs/commands/up.js +235 -0
  51. package/dist/cjs/commands/up.js.map +1 -0
  52. package/dist/cjs/index.d.cts +7 -0
  53. package/dist/cjs/index.d.ts +7 -0
  54. package/dist/cjs/index.js +85 -0
  55. package/dist/cjs/index.js.map +1 -0
  56. package/dist/cjs/lib/find-config.d.cts +14 -0
  57. package/dist/cjs/lib/find-config.d.ts +14 -0
  58. package/dist/cjs/lib/find-config.js +93 -0
  59. package/dist/cjs/lib/find-config.js.map +1 -0
  60. package/dist/cjs/lib/json-schema.d.cts +18 -0
  61. package/dist/cjs/lib/json-schema.d.ts +18 -0
  62. package/dist/cjs/lib/json-schema.js +306 -0
  63. package/dist/cjs/lib/json-schema.js.map +1 -0
  64. package/dist/cjs/lib/resolve-server-config.d.cts +50 -0
  65. package/dist/cjs/lib/resolve-server-config.d.ts +50 -0
  66. package/dist/cjs/lib/resolve-server-config.js +214 -0
  67. package/dist/cjs/lib/resolve-server-config.js.map +1 -0
  68. package/dist/cjs/package.json +1 -0
  69. package/dist/cjs/types.d.cts +21 -0
  70. package/dist/cjs/types.d.ts +21 -0
  71. package/dist/cjs/types.js +32 -0
  72. package/dist/cjs/types.js.map +1 -0
  73. package/dist/esm/cli.d.ts +1 -0
  74. package/dist/esm/cli.js +129 -0
  75. package/dist/esm/cli.js.map +1 -0
  76. package/dist/esm/commands/call-tool.d.ts +26 -0
  77. package/dist/esm/commands/call-tool.js +151 -0
  78. package/dist/esm/commands/call-tool.js.map +1 -0
  79. package/dist/esm/commands/get-prompt.d.ts +26 -0
  80. package/dist/esm/commands/get-prompt.js +118 -0
  81. package/dist/esm/commands/get-prompt.js.map +1 -0
  82. package/dist/esm/commands/inspect.d.ts +35 -0
  83. package/dist/esm/commands/inspect.js +438 -0
  84. package/dist/esm/commands/inspect.js.map +1 -0
  85. package/dist/esm/commands/manifest/env-prompting.d.ts +21 -0
  86. package/dist/esm/commands/manifest/env-prompting.js +213 -0
  87. package/dist/esm/commands/manifest/env-prompting.js.map +1 -0
  88. package/dist/esm/commands/manifest/generate.d.ts +124 -0
  89. package/dist/esm/commands/manifest/generate.js +1087 -0
  90. package/dist/esm/commands/manifest/generate.js.map +1 -0
  91. package/dist/esm/commands/manifest/index.d.ts +2 -0
  92. package/dist/esm/commands/manifest/index.js +23 -0
  93. package/dist/esm/commands/manifest/index.js.map +1 -0
  94. package/dist/esm/commands/manifest/metadata-reader.d.ts +71 -0
  95. package/dist/esm/commands/manifest/metadata-reader.js +143 -0
  96. package/dist/esm/commands/manifest/metadata-reader.js.map +1 -0
  97. package/dist/esm/commands/manifest/validate.d.ts +1 -0
  98. package/dist/esm/commands/manifest/validate.js +167 -0
  99. package/dist/esm/commands/manifest/validate.js.map +1 -0
  100. package/dist/esm/commands/read-resource.d.ts +24 -0
  101. package/dist/esm/commands/read-resource.js +95 -0
  102. package/dist/esm/commands/read-resource.js.map +1 -0
  103. package/dist/esm/commands/search.d.ts +31 -0
  104. package/dist/esm/commands/search.js +145 -0
  105. package/dist/esm/commands/search.js.map +1 -0
  106. package/dist/esm/commands/up.d.ts +49 -0
  107. package/dist/esm/commands/up.js +74 -0
  108. package/dist/esm/commands/up.js.map +1 -0
  109. package/dist/esm/index.d.ts +7 -0
  110. package/dist/esm/index.js +11 -0
  111. package/dist/esm/index.js.map +1 -0
  112. package/dist/esm/lib/find-config.d.ts +14 -0
  113. package/dist/esm/lib/find-config.js +42 -0
  114. package/dist/esm/lib/find-config.js.map +1 -0
  115. package/dist/esm/lib/json-schema.d.ts +18 -0
  116. package/dist/esm/lib/json-schema.js +66 -0
  117. package/dist/esm/lib/json-schema.js.map +1 -0
  118. package/dist/esm/lib/resolve-server-config.d.ts +50 -0
  119. package/dist/esm/lib/resolve-server-config.js +154 -0
  120. package/dist/esm/lib/resolve-server-config.js.map +1 -0
  121. package/dist/esm/package.json +1 -0
  122. package/dist/esm/types.d.ts +21 -0
  123. package/dist/esm/types.js +11 -0
  124. package/dist/esm/types.js.map +1 -0
  125. package/package.json +99 -0
  126. package/schemas/server.schema.json +489 -0
@@ -0,0 +1,1087 @@
1
+ import checkbox from '@inquirer/checkbox';
2
+ import confirm from '@inquirer/confirm';
3
+ import input from '@inquirer/input';
4
+ import select from '@inquirer/select';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { validateSchema } from '../../lib/json-schema.js';
8
+ import { promptForEnvVars, substituteTemplateVars } from './env-prompting.js';
9
+ import { MetadataReader } from './metadata-reader.js';
10
+ /** Maps user-facing transport names to internal types */ export const TRANSPORT_MAP = {
11
+ stdio: 'stdio',
12
+ http: 'streamable-http'
13
+ };
14
+ /**
15
+ * Build config choices from combinations and transports.
16
+ * Used for both "select all" and "select specific" flows.
17
+ * @param combinations - Array of config combinations
18
+ * @param transports - Array of transport names ('stdio', 'http', or 'streamable-http')
19
+ * @returns Array of ConfigChoice objects
20
+ */ export function createConfigChoices(combinations, transports) {
21
+ const choices = [];
22
+ for (const combination of combinations){
23
+ for (const t of transports){
24
+ const transport = TRANSPORT_MAP[t] || t;
25
+ choices.push({
26
+ combination,
27
+ transport,
28
+ label: `${combination.name} (${t})`
29
+ });
30
+ }
31
+ }
32
+ return choices;
33
+ }
34
+ /**
35
+ * Filter config choices by selected labels.
36
+ * @param allChoices - All available config choices
37
+ * @param selectedLabels - Labels of configs to include
38
+ * @returns Filtered array of ConfigChoice objects
39
+ */ export function filterConfigChoices(allChoices, selectedLabels) {
40
+ return allChoices.filter((choice)=>selectedLabels.includes(choice.label));
41
+ }
42
+ /**
43
+ * Generate cartesian product of all dimension choices.
44
+ * Example: [AUTH_MODE: [a, b], DCR_MODE: [x, y]] → [[a,x], [a,y], [b,x], [b,y]]
45
+ */ function generateCartesianProduct(dimensions) {
46
+ if (dimensions.length === 0) return [
47
+ []
48
+ ];
49
+ const first = dimensions[0];
50
+ if (!first || !first.choices || first.choices.length === 0) return [
51
+ []
52
+ ];
53
+ if (dimensions.length === 1) {
54
+ return first.choices.map((c)=>[
55
+ c
56
+ ]);
57
+ }
58
+ const rest = dimensions.slice(1);
59
+ const restProduct = generateCartesianProduct(rest);
60
+ const result = [];
61
+ for (const choice of first.choices){
62
+ for (const restCombination of restProduct){
63
+ result.push([
64
+ choice,
65
+ ...restCombination
66
+ ]);
67
+ }
68
+ }
69
+ return result;
70
+ }
71
+ /**
72
+ * Generate combinations from env vars with choices (simple cartesian product).
73
+ * All combinations are generated - user can filter via selection pass.
74
+ */ export function generateMatrixCombinations(envVars) {
75
+ const dimensions = envVars.map((e)=>({
76
+ name: e.name,
77
+ choices: e.choices
78
+ }));
79
+ const product = generateCartesianProduct(dimensions);
80
+ return product.map((values)=>{
81
+ const dimensionValues = {};
82
+ dimensions.forEach((dim, i)=>{
83
+ dimensionValues[dim.name] = values[i] || '';
84
+ });
85
+ const nameParts = dimensions.map((dim, i)=>`${dim.name.toLowerCase()}-${values[i]}`);
86
+ return {
87
+ name: nameParts.length > 0 ? nameParts.join('_') : 'minimal',
88
+ envKeys: dimensions.map((d)=>d.name),
89
+ argNames: [],
90
+ defaults: {},
91
+ argDefaults: {},
92
+ dimensionValues
93
+ };
94
+ });
95
+ }
96
+ /**
97
+ * Generate combinations respecting dependsOn relationships.
98
+ * Conditional dimensions are only included when their dependencies are satisfied.
99
+ * Example: DCR_MODE only generates variations when AUTH_MODE=dcr
100
+ */ export function generateConditionalCombinations(envVars) {
101
+ // Separate primary dimensions (no dependsOn) from conditional ones
102
+ const primaryDims = envVars.filter((e)=>!e.dependsOn);
103
+ const conditionalDims = envVars.filter((e)=>e.dependsOn);
104
+ if (primaryDims.length === 0) {
105
+ // No primary dimensions - return empty or minimal
106
+ return [
107
+ {
108
+ name: 'minimal',
109
+ envKeys: [],
110
+ argNames: [],
111
+ defaults: {},
112
+ argDefaults: {},
113
+ dimensionValues: {}
114
+ }
115
+ ];
116
+ }
117
+ // Generate all combinations of primary dimensions first
118
+ const primaryProduct = generateCartesianProduct(primaryDims);
119
+ const combinations = [];
120
+ for (const primaryValues of primaryProduct){
121
+ // Build base dimension values from primary
122
+ const baseDimensionValues = {};
123
+ primaryDims.forEach((dim, i)=>{
124
+ baseDimensionValues[dim.name] = primaryValues[i] || '';
125
+ });
126
+ // Find which conditional dimensions apply to this primary combination
127
+ const applicableConditionals = conditionalDims.filter((cond)=>shouldPromptEnvVar(cond, baseDimensionValues));
128
+ if (applicableConditionals.length === 0) {
129
+ // No conditional dimensions apply - single combination
130
+ // Only include dimensions with multiple choices in the name (single-choice dimensions don't add discriminating value)
131
+ const nameParts = primaryDims.filter((dim)=>dim.choices.length > 1).map((dim, _i)=>`${dim.name.toLowerCase()}-${primaryValues[primaryDims.indexOf(dim)]}`);
132
+ combinations.push({
133
+ name: nameParts.length > 0 ? nameParts.join('_') : 'default',
134
+ envKeys: primaryDims.map((d)=>d.name),
135
+ argNames: [],
136
+ defaults: {},
137
+ argDefaults: {},
138
+ dimensionValues: {
139
+ ...baseDimensionValues
140
+ }
141
+ });
142
+ } else {
143
+ // Generate sub-combinations for applicable conditional dimensions
144
+ const conditionalProduct = generateCartesianProduct(applicableConditionals);
145
+ for (const condValues of conditionalProduct){
146
+ const fullDimensionValues = {
147
+ ...baseDimensionValues
148
+ };
149
+ applicableConditionals.forEach((dim, i)=>{
150
+ fullDimensionValues[dim.name] = condValues[i] || '';
151
+ });
152
+ // Build name from dimensions with multiple choices only (single-choice dimensions don't add discriminating value)
153
+ const allDims = [
154
+ ...primaryDims,
155
+ ...applicableConditionals
156
+ ];
157
+ const nameParts = allDims.filter((dim)=>dim.choices.length > 1).map((dim)=>`${dim.name.toLowerCase()}-${fullDimensionValues[dim.name]}`);
158
+ combinations.push({
159
+ name: nameParts.length > 0 ? nameParts.join('_') : 'default',
160
+ envKeys: [
161
+ ...primaryDims,
162
+ ...applicableConditionals
163
+ ].map((d)=>d.name),
164
+ argNames: [],
165
+ defaults: {},
166
+ argDefaults: {},
167
+ dimensionValues: fullDimensionValues
168
+ });
169
+ }
170
+ }
171
+ }
172
+ return combinations;
173
+ }
174
+ /**
175
+ * Check if an environment variable should be prompted based on dependsOn conditions.
176
+ * Returns true if the envVar has no dependencies, or if all dependencies are satisfied.
177
+ *
178
+ * @param envVar - Environment variable metadata with optional dependsOn field
179
+ * @param selectedValues - Currently selected dimension values (e.g., { AUTH_MODE: 'loopback-oauth' })
180
+ * @returns true if the env var should be prompted, false if it should be skipped
181
+ */ export function shouldPromptEnvVar(envVar, selectedValues) {
182
+ if (!envVar.dependsOn) return true;
183
+ // All dependencies must be satisfied
184
+ for (const [depName, allowedValues] of Object.entries(envVar.dependsOn)){
185
+ const selectedValue = selectedValues[depName];
186
+ // If dependency not yet selected, or selected value not in allowed list, skip
187
+ if (!selectedValue || !allowedValues.includes(selectedValue)) {
188
+ return false;
189
+ }
190
+ }
191
+ return true;
192
+ }
193
+ /**
194
+ * Generate server configuration interactively.
195
+ * Discovers server.json in current directory, prompts for configuration,
196
+ * and generates config files for selected combinations and transports.
197
+ *
198
+ * @param options - Command options
199
+ * @param options.source - Use source code paths (node instead of npx)
200
+ * @param options.json - Output to stdout instead of writing files
201
+ * @param options.matrix - Non-interactive mode: generate all matrix combinations
202
+ * @param options.output - Output directory (default: examples for --matrix, . otherwise)
203
+ * @param options.quick - Skip all optional env var prompts, use defaults
204
+ */ export async function generateCommand(options = {}) {
205
+ console.log('🔧 MCP Config Generator\n');
206
+ const useSource = options.source || false;
207
+ const jsonOutput = options.json || false;
208
+ const matrixMode = options.matrix || false;
209
+ const quickMode = options.quick || false;
210
+ // 1. Discover server.json in current directory or parent
211
+ const serverJsonPath = discoverServerJson();
212
+ if (!serverJsonPath) {
213
+ console.error('❌ No server.json found in current directory or parent');
214
+ console.error(' Run this command from an MCP server package directory');
215
+ process.exit(1);
216
+ }
217
+ // 2. Read package.json to get package name
218
+ const packageDir = path.dirname(serverJsonPath);
219
+ const packageJsonPath = path.join(packageDir, 'package.json');
220
+ if (!fs.existsSync(packageJsonPath)) {
221
+ console.error('❌ No package.json found alongside server.json');
222
+ console.error(' Package.json is required to get package name');
223
+ process.exit(1);
224
+ }
225
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
226
+ const packageName = packageJson.name;
227
+ const serverName = extractServerName(packageName);
228
+ // Get bin entry for source mode
229
+ let binPath;
230
+ if (packageJson.bin) {
231
+ if (typeof packageJson.bin === 'string') {
232
+ // Default bin entry
233
+ binPath = packageJson.bin;
234
+ } else {
235
+ // Named bin entries - try to match package name without org
236
+ const packageNameWithoutOrg = packageName.replace(/^@[^/]+\//, '');
237
+ binPath = packageJson.bin[packageNameWithoutOrg] || packageJson.bin[Object.keys(packageJson.bin)[0] || ''];
238
+ }
239
+ }
240
+ console.log(`📦 Package: ${packageName}`);
241
+ // 3. Read and validate server.json metadata
242
+ const serverJsonContent = fs.readFileSync(serverJsonPath, 'utf-8');
243
+ let metadata;
244
+ try {
245
+ metadata = JSON.parse(serverJsonContent);
246
+ await validateSchema(metadata, serverName);
247
+ } catch (error) {
248
+ console.error(`❌ Failed to read or validate server.json: ${error instanceof Error ? error.message : String(error)}`);
249
+ process.exit(1);
250
+ }
251
+ const metadataReader = new MetadataReader();
252
+ // 4. Get stdio package to analyze env vars
253
+ const stdioPackage = metadataReader.getPackageForTransport(metadata, 'stdio');
254
+ if (!stdioPackage) {
255
+ console.error('❌ No stdio transport found in server.json');
256
+ process.exit(1);
257
+ }
258
+ // 5. Find all environment variables and package arguments with choices
259
+ // Filter to only mandatory matrix items (isMandatoryForMatrix !== false)
260
+ // Non-mandatory items like LOG_LEVEL should not explode the test matrix
261
+ const envVarsWithChoices = (stdioPackage.environmentVariables || []).filter((envVar)=>envVar.choices && envVar.choices.length > 0 && envVar.isMandatoryForMatrix !== false);
262
+ const argsWithChoices = (stdioPackage.packageArguments || []).filter((arg)=>arg.choices !== undefined && arg.choices.length > 0);
263
+ if (envVarsWithChoices.length === 0 && argsWithChoices.length === 0) {
264
+ console.error('❌ No environment variables or package arguments with choices found in server.json');
265
+ console.error(' Cannot generate configurations without choice-based configuration options');
266
+ process.exit(1);
267
+ }
268
+ console.log('\n🔍 Available configuration options:');
269
+ if (envVarsWithChoices.length > 0) {
270
+ console.log(` Environment variables (${envVarsWithChoices.length}):`);
271
+ for (const envVar of envVarsWithChoices){
272
+ var _envVar_choices;
273
+ console.log(` • ${envVar.name}: ${((_envVar_choices = envVar.choices) === null || _envVar_choices === void 0 ? void 0 : _envVar_choices.join(', ')) || ''}`);
274
+ }
275
+ }
276
+ if (argsWithChoices.length > 0) {
277
+ console.log(` Package arguments (${argsWithChoices.length}):`);
278
+ for (const arg of argsWithChoices){
279
+ var _arg_choices;
280
+ console.log(` • ${arg.name}: ${((_arg_choices = arg.choices) === null || _arg_choices === void 0 ? void 0 : _arg_choices.join(', ')) || ''}`);
281
+ }
282
+ }
283
+ console.log();
284
+ // Matrix mode: non-interactive generation of all combinations
285
+ if (matrixMode) {
286
+ const outputDir = options.output || 'examples';
287
+ const transports = [
288
+ 'stdio'
289
+ ];
290
+ // Add http transport if available
291
+ const httpPackage = metadataReader.getPackageForTransport(metadata, 'streamable-http');
292
+ if (httpPackage) {
293
+ transports.push('streamable-http');
294
+ }
295
+ // Build env vars for matrix generation (with dependsOn for conditional filtering)
296
+ const envVarsForMatrix = envVarsWithChoices.map((envVar)=>({
297
+ name: envVar.name,
298
+ choices: envVar.choices || [],
299
+ dependsOn: envVar.dependsOn
300
+ }));
301
+ // Collect defaults for non-dimension env vars (ones without choices or non-mandatory)
302
+ // Support both 'default' (MCP schema standard) and 'value' (legacy) fields
303
+ const defaults = {};
304
+ for (const envVar of stdioPackage.environmentVariables || []){
305
+ if (!envVarsWithChoices.some((e)=>e.name === envVar.name)) {
306
+ var _envVar_default;
307
+ const defaultValue = (_envVar_default = envVar.default) !== null && _envVar_default !== void 0 ? _envVar_default : envVar.value;
308
+ if (defaultValue) {
309
+ defaults[envVar.name] = defaultValue;
310
+ }
311
+ }
312
+ }
313
+ // Generate combinations respecting dependsOn relationships (no unnecessary combinations)
314
+ const combinations = generateConditionalCombinations(envVarsForMatrix);
315
+ // Add defaults to each combination
316
+ for (const combo of combinations){
317
+ combo.defaults = defaults;
318
+ }
319
+ console.log(`📊 Matrix mode: Generating ${combinations.length} combination(s) × ${transports.length} transport(s)`);
320
+ console.log(` Output: ${outputDir}/`);
321
+ console.log(` Transports: ${transports.join(', ')}`);
322
+ console.log();
323
+ // Create output directory
324
+ fs.mkdirSync(outputDir, {
325
+ recursive: true
326
+ });
327
+ // Default HTTP settings for matrix mode
328
+ const httpHost = 'localhost';
329
+ const httpPort = 3000;
330
+ let generatedCount = 0;
331
+ for (const combination of combinations){
332
+ for (const transport of transports){
333
+ await generateConfigFile({
334
+ serverName,
335
+ combination,
336
+ transport,
337
+ outputDir,
338
+ packageName,
339
+ packageDir,
340
+ ...binPath !== undefined && {
341
+ binPath
342
+ },
343
+ metadata,
344
+ metadataReader,
345
+ httpHost,
346
+ httpPort,
347
+ useSource,
348
+ quick: true
349
+ });
350
+ generatedCount++;
351
+ }
352
+ }
353
+ console.log(`\n✅ Generated ${generatedCount} config file(s) in ${outputDir}`);
354
+ return;
355
+ }
356
+ // 5a. Ask what to configure (env/args/both/none)
357
+ const configMode = await select({
358
+ message: 'What do you want to configure?',
359
+ choices: [
360
+ {
361
+ name: 'Environment variables only',
362
+ value: 'env',
363
+ disabled: envVarsWithChoices.length === 0
364
+ },
365
+ {
366
+ name: 'Package arguments only',
367
+ value: 'args',
368
+ disabled: argsWithChoices.length === 0
369
+ },
370
+ {
371
+ name: 'Both environment variables and package arguments',
372
+ value: 'both',
373
+ disabled: envVarsWithChoices.length === 0 || argsWithChoices.length === 0
374
+ },
375
+ {
376
+ name: 'None (minimal server config)',
377
+ value: 'none'
378
+ }
379
+ ].filter((choice)=>!choice.disabled)
380
+ });
381
+ if (configMode === 'none') {
382
+ // Generate single minimal config
383
+ const finalServerName = await input({
384
+ message: 'Server name for mcpServers config:',
385
+ default: serverName,
386
+ validate: (input)=>input.length > 0 || 'Server name is required'
387
+ });
388
+ const outputDir = jsonOutput ? '.' : await input({
389
+ message: 'Output directory:',
390
+ default: '.'
391
+ });
392
+ const combination = {
393
+ name: 'minimal',
394
+ envKeys: [],
395
+ argNames: [],
396
+ defaults: {},
397
+ argDefaults: {},
398
+ dimensionValues: {}
399
+ };
400
+ if (jsonOutput) {
401
+ const config = await generateConfigObject({
402
+ serverName: finalServerName,
403
+ combination,
404
+ transport: 'stdio',
405
+ packageName,
406
+ packageDir,
407
+ ...binPath !== undefined && {
408
+ binPath
409
+ },
410
+ metadata,
411
+ metadataReader,
412
+ useSource,
413
+ quick: quickMode
414
+ });
415
+ console.log(JSON.stringify(config, null, 2));
416
+ } else {
417
+ fs.mkdirSync(outputDir, {
418
+ recursive: true
419
+ });
420
+ await generateConfigFile({
421
+ serverName: finalServerName,
422
+ combination,
423
+ transport: 'stdio',
424
+ outputDir,
425
+ packageName,
426
+ packageDir,
427
+ ...binPath !== undefined && {
428
+ binPath
429
+ },
430
+ metadata,
431
+ metadataReader,
432
+ useSource,
433
+ quick: quickMode
434
+ });
435
+ console.log(`\n✅ Generated 1 config file in ${outputDir}`);
436
+ }
437
+ return;
438
+ }
439
+ // 6. Select transports
440
+ const transports = await checkbox({
441
+ message: 'Select transports:',
442
+ choices: [
443
+ {
444
+ name: 'stdio',
445
+ value: 'stdio',
446
+ checked: true
447
+ },
448
+ {
449
+ name: 'http',
450
+ value: 'http',
451
+ checked: true
452
+ }
453
+ ],
454
+ validate: (choices)=>choices.length > 0 || 'Select at least one transport'
455
+ });
456
+ // 7. HTTP configuration if HTTP transport selected
457
+ let httpHost = 'localhost';
458
+ let httpPort = 3000;
459
+ if (transports.includes('http')) {
460
+ httpHost = await input({
461
+ message: 'HTTP host:',
462
+ default: 'localhost'
463
+ });
464
+ const portStr = await input({
465
+ message: 'HTTP port:',
466
+ default: '3000',
467
+ validate: (input)=>{
468
+ const port = Number.parseInt(input, 10);
469
+ return !Number.isNaN(port) && port > 0 && port < 65536 || 'Port must be between 1 and 65535';
470
+ }
471
+ });
472
+ httpPort = Number.parseInt(portStr, 10);
473
+ }
474
+ // 8. Collect available dimensions based on configuration mode
475
+ const availableDimensions = [];
476
+ if (configMode === 'env' || configMode === 'both') {
477
+ for (const envVar of envVarsWithChoices){
478
+ availableDimensions.push({
479
+ name: envVar.name,
480
+ type: 'env',
481
+ choices: envVar.choices || []
482
+ });
483
+ }
484
+ }
485
+ if (configMode === 'args' || configMode === 'both') {
486
+ for (const arg of argsWithChoices){
487
+ availableDimensions.push({
488
+ name: arg.name,
489
+ type: 'arg',
490
+ choices: arg.choices
491
+ });
492
+ }
493
+ }
494
+ // 8a. Ask user which dimensions to use for combinations
495
+ const selectedDimensionNames = await checkbox({
496
+ message: 'Select dimensions to generate combinations from (order determines filename pattern):',
497
+ choices: availableDimensions.map((d)=>({
498
+ name: `${d.type === 'env' ? 'ENV' : 'ARG'} ${d.name} (${d.choices.join(', ')})`,
499
+ value: d.name,
500
+ checked: true
501
+ })),
502
+ validate: (choices)=>choices.length > 0 || 'Select at least one dimension'
503
+ });
504
+ const selectedDimensions = availableDimensions.filter((d)=>selectedDimensionNames.includes(d.name));
505
+ // 9. For each dimension, select which choices to include (with "skip" option)
506
+ const dimensionsWithFilteredChoices = [];
507
+ for (const dim of selectedDimensions){
508
+ const choicesWithSkip = [
509
+ ...dim.choices,
510
+ 'skip'
511
+ ];
512
+ const selectedChoices = await checkbox({
513
+ message: `For ${dim.type === 'env' ? 'ENV' : 'ARG'} ${dim.name}, select choices to include:`,
514
+ choices: choicesWithSkip.map((choice)=>({
515
+ name: choice === 'skip' ? 'skip (do not configure)' : choice,
516
+ value: choice,
517
+ checked: choice !== 'skip'
518
+ })),
519
+ validate: (choices)=>choices.length > 0 || 'Select at least one choice (or skip)'
520
+ });
521
+ dimensionsWithFilteredChoices.push({
522
+ ...dim,
523
+ choices: selectedChoices
524
+ });
525
+ }
526
+ // 10. For non-dimensions with choices, get default values (including "skip")
527
+ const dimensionNames = selectedDimensions.map((d)=>d.name);
528
+ const nonDimensionEnvVars = envVarsWithChoices.filter((e)=>!dimensionNames.includes(e.name));
529
+ const nonDimensionArgs = argsWithChoices.filter((a)=>!dimensionNames.includes(a.name));
530
+ const defaults = {};
531
+ const argDefaults = {};
532
+ // Get defaults for env vars (if configured)
533
+ if (configMode === 'env' || configMode === 'both') {
534
+ for (const envVar of nonDimensionEnvVars){
535
+ const choicesWithSkip = [
536
+ ...envVar.choices || [],
537
+ 'skip'
538
+ ];
539
+ const defaultValue = await select({
540
+ message: `Select default value for ENV ${envVar.name} (used in all configs)`,
541
+ choices: choicesWithSkip.map((c)=>({
542
+ name: c === 'skip' ? 'skip (do not configure)' : c,
543
+ value: c
544
+ }))
545
+ });
546
+ if (defaultValue !== 'skip') {
547
+ defaults[envVar.name] = defaultValue;
548
+ }
549
+ }
550
+ }
551
+ // Get defaults for args (if configured)
552
+ if (configMode === 'args' || configMode === 'both') {
553
+ for (const arg of nonDimensionArgs){
554
+ const choicesWithSkip = [
555
+ ...arg.choices,
556
+ 'skip'
557
+ ];
558
+ const defaultValue = await select({
559
+ message: `Select default value for ARG ${arg.name} (used in all configs)`,
560
+ choices: choicesWithSkip.map((c)=>({
561
+ name: c === 'skip' ? 'skip (do not configure)' : c,
562
+ value: c
563
+ }))
564
+ });
565
+ if (defaultValue !== 'skip') {
566
+ argDefaults[arg.name] = defaultValue;
567
+ }
568
+ }
569
+ }
570
+ // 11. Generate combinations as cartesian product
571
+ const combinations = generateCartesianProduct(dimensionsWithFilteredChoices).map((choiceValues)=>{
572
+ // Build name from dimensions that have multiple choices (single-choice dimensions don't add discriminating value)
573
+ // Also filter out "skip" values
574
+ const nameParts = [];
575
+ for(let i = 0; i < choiceValues.length; i++){
576
+ const dim = dimensionsWithFilteredChoices[i];
577
+ const value = choiceValues[i];
578
+ // Only include in name if: not "skip" AND dimension has multiple selected choices
579
+ if (dim && value !== 'skip' && dim.choices.filter((c)=>c !== 'skip').length > 1) {
580
+ nameParts.push(value || '');
581
+ }
582
+ }
583
+ const name = nameParts.length > 0 ? nameParts.join('-').toLowerCase() : 'defaults';
584
+ // Map choice values back to dimension names (excluding "skip")
585
+ const dimensionValues = {};
586
+ for(let i = 0; i < choiceValues.length; i++){
587
+ const dimension = dimensionsWithFilteredChoices[i];
588
+ const value = choiceValues[i];
589
+ if (dimension && value !== 'skip') {
590
+ dimensionValues[dimension.name] = value || '';
591
+ }
592
+ }
593
+ // Include env vars and args based on what's configured
594
+ const allEnvKeys = (stdioPackage.environmentVariables || []).map((e)=>e.name);
595
+ const allArgNames = (stdioPackage.packageArguments || []).map((a)=>a.name);
596
+ return {
597
+ name,
598
+ envKeys: configMode === 'env' || configMode === 'both' ? allEnvKeys : [],
599
+ argNames: configMode === 'args' || configMode === 'both' ? allArgNames : [],
600
+ defaults,
601
+ argDefaults,
602
+ dimensionValues
603
+ };
604
+ });
605
+ // 12. Show preview with final counts
606
+ const totalConfigs = combinations.length * transports.length;
607
+ console.log('\n📋 Preview:');
608
+ console.log(` ${combinations.length} combination(s) × ${transports.length} transport(s) = ${totalConfigs} config file(s)`);
609
+ console.log('\n Combinations:');
610
+ for (const combo of combinations){
611
+ console.log(` • ${combo.name}`);
612
+ }
613
+ console.log(`\n Transports: ${transports.join(', ')}`);
614
+ if (Object.keys(defaults).length > 0) {
615
+ console.log('\n Default ENV values (used in all configs):');
616
+ for (const [key, value] of Object.entries(defaults)){
617
+ console.log(` • ${key}=${value}`);
618
+ }
619
+ }
620
+ if (Object.keys(argDefaults).length > 0) {
621
+ console.log('\n Default ARG values (used in all configs):');
622
+ for (const [key, value] of Object.entries(argDefaults)){
623
+ console.log(` • ${key}=${value}`);
624
+ }
625
+ }
626
+ console.log();
627
+ const generationMode = await select({
628
+ message: 'How would you like to proceed?',
629
+ choices: [
630
+ {
631
+ name: 'Generate all configurations',
632
+ value: 'all'
633
+ },
634
+ {
635
+ name: 'Select specific configurations',
636
+ value: 'select'
637
+ },
638
+ {
639
+ name: 'Cancel',
640
+ value: 'cancel'
641
+ }
642
+ ]
643
+ });
644
+ if (generationMode === 'cancel') {
645
+ console.log('❌ Cancelled');
646
+ process.exit(0);
647
+ }
648
+ // 12b. If selecting, show checkbox for each combination × transport
649
+ let selectedConfigs;
650
+ // Build all config choices
651
+ const allChoices = createConfigChoices(combinations, transports);
652
+ if (generationMode === 'select') {
653
+ const selectedLabels = await checkbox({
654
+ message: 'Select configurations to generate:',
655
+ choices: allChoices.map((choice)=>({
656
+ name: choice.label,
657
+ value: choice.label,
658
+ checked: true
659
+ })),
660
+ required: true
661
+ });
662
+ selectedConfigs = filterConfigChoices(allChoices, selectedLabels);
663
+ if (selectedConfigs.length === 0) {
664
+ console.log('❌ No configurations selected');
665
+ process.exit(0);
666
+ }
667
+ console.log(`\n📋 Selected ${selectedConfigs.length} configuration(s)`);
668
+ } else {
669
+ // Generate all
670
+ selectedConfigs = allChoices;
671
+ }
672
+ // 13. Final prompts
673
+ const finalServerName = await input({
674
+ message: 'Server name for mcpServers config:',
675
+ default: serverName,
676
+ validate: (input)=>input.length > 0 || 'Server name is required'
677
+ });
678
+ const outputDir = jsonOutput ? '.' : await input({
679
+ message: 'Output directory:',
680
+ default: '.'
681
+ });
682
+ // 13a. Select which optional env vars to prompt for (per-config prompting happens later)
683
+ // This is just a FILTER - actual prompting happens in buildServerConfig with config context
684
+ const optionalVarsToPrompt = new Set();
685
+ if (!quickMode && process.stdin.isTTY) {
686
+ var _selectedDimensions_;
687
+ const envVarsWithContext = new Map();
688
+ // Get the primary dimension (usually AUTH_MODE) for context grouping
689
+ const primaryDimensionName = selectedDimensions.length > 0 ? (_selectedDimensions_ = selectedDimensions[0]) === null || _selectedDimensions_ === void 0 ? void 0 : _selectedDimensions_.name : undefined;
690
+ for (const { combination } of selectedConfigs){
691
+ const allEnvVars = stdioPackage.environmentVariables || [];
692
+ for (const envVar of allEnvVars){
693
+ // Skip if already in defaults or dimension values
694
+ if (combination.envKeys.includes(envVar.name) && Object.keys(combination.dimensionValues).includes(envVar.name)) {
695
+ continue;
696
+ }
697
+ if (Object.keys(combination.defaults).includes(envVar.name)) {
698
+ continue;
699
+ }
700
+ // Check dependsOn
701
+ if (!shouldPromptEnvVar(envVar, combination.dimensionValues)) {
702
+ continue;
703
+ }
704
+ // Only collect optional env vars (required ones are always prompted per-config)
705
+ if (!envVar.isRequired) {
706
+ if (!envVarsWithContext.has(envVar.name)) {
707
+ envVarsWithContext.set(envVar.name, {
708
+ envVar,
709
+ relevantModes: new Set(),
710
+ appliesToAll: !envVar.dependsOn
711
+ });
712
+ }
713
+ const context = envVarsWithContext.get(envVar.name);
714
+ // Track which primary dimension value this var is relevant to
715
+ if (context && primaryDimensionName && combination.dimensionValues[primaryDimensionName]) {
716
+ context.relevantModes.add(combination.dimensionValues[primaryDimensionName]);
717
+ }
718
+ }
719
+ }
720
+ }
721
+ if (envVarsWithContext.size > 0) {
722
+ // Group env vars by their relevance for clearer display
723
+ const allSelectedModes = new Set(selectedConfigs.map((c)=>primaryDimensionName ? c.combination.dimensionValues[primaryDimensionName] : '').filter(Boolean));
724
+ const groupedVars = new Map();
725
+ for (const { envVar, relevantModes, appliesToAll } of envVarsWithContext.values()){
726
+ var _groupedVars_get;
727
+ let groupKey;
728
+ if (appliesToAll || relevantModes.size === allSelectedModes.size) {
729
+ groupKey = 'all';
730
+ } else {
731
+ // Sort modes for consistent display
732
+ groupKey = Array.from(relevantModes).sort().join(', ');
733
+ }
734
+ if (!groupedVars.has(groupKey)) {
735
+ groupedVars.set(groupKey, []);
736
+ }
737
+ (_groupedVars_get = groupedVars.get(groupKey)) === null || _groupedVars_get === void 0 ? void 0 : _groupedVars_get.push(envVar);
738
+ }
739
+ console.log(`\n📋 ${envVarsWithContext.size} optional env var(s) can be configured:`);
740
+ // Build template vars from HTTP settings for placeholder substitution
741
+ const templateVars = {};
742
+ if (transports.includes('http') || transports.includes('streamable-http')) {
743
+ templateVars.HOST = httpHost;
744
+ templateVars.PORT = String(httpPort);
745
+ }
746
+ // Display grouped by relevance
747
+ for (const [groupKey, vars] of groupedVars.entries()){
748
+ if (groupKey === 'all') {
749
+ console.log('\n For all configurations:');
750
+ } else {
751
+ console.log(`\n For ${groupKey} mode:`);
752
+ }
753
+ for (const envVar of vars){
754
+ var _ref, _envVar_default1;
755
+ const rawDefault = (_ref = (_envVar_default1 = envVar.default) !== null && _envVar_default1 !== void 0 ? _envVar_default1 : envVar.value) !== null && _ref !== void 0 ? _ref : envVar.placeholder;
756
+ const defaultVal = substituteTemplateVars(rawDefault, templateVars);
757
+ const label = defaultVal ? `(default: ${defaultVal})` : '(no default)';
758
+ console.log(` • ${envVar.name} - ${envVar.description || 'optional'} ${label}`);
759
+ }
760
+ }
761
+ // Build flat list of all optional var names for checkbox selection
762
+ const allOptionalVarNames = Array.from(envVarsWithContext.keys());
763
+ const configureChoice = await select({
764
+ message: '\nConfigure optional env vars?',
765
+ choices: [
766
+ {
767
+ name: 'None (use defaults)',
768
+ value: 'none'
769
+ },
770
+ {
771
+ name: 'All (prompt for each config)',
772
+ value: 'all'
773
+ },
774
+ {
775
+ name: 'Select specific ones',
776
+ value: 'select'
777
+ }
778
+ ],
779
+ default: 'none'
780
+ });
781
+ if (configureChoice === 'all') {
782
+ // Add all optional vars to the prompt set
783
+ for (const name of allOptionalVarNames){
784
+ optionalVarsToPrompt.add(name);
785
+ }
786
+ } else if (configureChoice === 'select') {
787
+ // Show multi-select checkbox grouped by relevance
788
+ const checkboxChoices = [];
789
+ for (const [groupKey, vars] of groupedVars.entries()){
790
+ // Add group separator
791
+ checkboxChoices.push({
792
+ name: groupKey === 'all' ? '── For all configurations ──' : `── For ${groupKey} mode ──`,
793
+ value: `__separator__${groupKey}`,
794
+ disabled: ' '
795
+ });
796
+ for (const envVar of vars){
797
+ var _ref1, _envVar_default2;
798
+ const rawDefault = (_ref1 = (_envVar_default2 = envVar.default) !== null && _envVar_default2 !== void 0 ? _envVar_default2 : envVar.value) !== null && _ref1 !== void 0 ? _ref1 : envVar.placeholder;
799
+ const defaultVal = substituteTemplateVars(rawDefault, templateVars);
800
+ const label = defaultVal ? ` (default: ${defaultVal})` : '';
801
+ checkboxChoices.push({
802
+ name: `${envVar.name} - ${envVar.description || 'optional'}${label}`,
803
+ value: envVar.name
804
+ });
805
+ }
806
+ }
807
+ const selectedVarNames = await checkbox({
808
+ message: 'Select which env vars to configure:',
809
+ choices: checkboxChoices
810
+ });
811
+ // Add selected vars to the prompt set (excluding separators)
812
+ for (const name of selectedVarNames){
813
+ if (!name.startsWith('__separator__')) {
814
+ optionalVarsToPrompt.add(name);
815
+ }
816
+ }
817
+ }
818
+ // configureChoice === 'none' leaves optionalVarsToPrompt empty
819
+ }
820
+ }
821
+ // 14. Generate configs for selected combinations
822
+ if (jsonOutput) {
823
+ // Output selected configs as JSON array to stdout
824
+ const configs = [];
825
+ for (const { combination, transport } of selectedConfigs){
826
+ const config = await generateConfigObject({
827
+ serverName: finalServerName,
828
+ combination,
829
+ transport,
830
+ packageName,
831
+ packageDir,
832
+ ...binPath !== undefined && {
833
+ binPath
834
+ },
835
+ metadata,
836
+ metadataReader,
837
+ httpHost,
838
+ httpPort,
839
+ useSource,
840
+ quick: quickMode,
841
+ optionalVarsToPrompt
842
+ });
843
+ configs.push(config);
844
+ }
845
+ console.log(JSON.stringify(configs, null, 2));
846
+ } else {
847
+ fs.mkdirSync(outputDir, {
848
+ recursive: true
849
+ });
850
+ let generatedCount = 0;
851
+ for (const { combination, transport } of selectedConfigs){
852
+ const success = await generateConfigFile({
853
+ serverName: finalServerName,
854
+ combination,
855
+ transport,
856
+ outputDir,
857
+ packageName,
858
+ packageDir,
859
+ ...binPath !== undefined && {
860
+ binPath
861
+ },
862
+ metadata,
863
+ metadataReader,
864
+ httpHost,
865
+ httpPort,
866
+ useSource,
867
+ quick: quickMode,
868
+ optionalVarsToPrompt
869
+ });
870
+ if (success) {
871
+ generatedCount++;
872
+ }
873
+ }
874
+ console.log(`\n✅ Generated ${generatedCount} config file(s) in ${outputDir}`);
875
+ }
876
+ }
877
+ /** Exported for testing */ export function discoverServerJson(basePath = process.cwd()) {
878
+ // Check current directory
879
+ const currentPath = path.join(basePath, 'server.json');
880
+ if (fs.existsSync(currentPath)) {
881
+ return currentPath;
882
+ }
883
+ // Check parent directory (for monorepo structure)
884
+ const parentPath = path.join(basePath, '..', 'server.json');
885
+ if (fs.existsSync(parentPath)) {
886
+ return parentPath;
887
+ }
888
+ return null;
889
+ }
890
+ /** Exported for testing */ export function extractServerName(packageName) {
891
+ // Strip org prefix if present: "@org/package" -> "package"
892
+ const withoutOrg = packageName.replace(/^@[^/]+\//, '');
893
+ return withoutOrg || 'server';
894
+ }
895
+ /** Build config object (shared logic) */ async function buildServerConfig(params, envPromptFn = promptForEnvVars) {
896
+ // Get package config for transport
897
+ const transportType = params.transport === 'http' ? 'streamable-http' : 'stdio';
898
+ const pkg = params.metadataReader.getPackageForTransport(params.metadata, transportType);
899
+ if (!pkg) {
900
+ throw new Error(`No ${params.transport} transport available`);
901
+ }
902
+ // Separate env vars and package args from dimensionValues
903
+ const envDimensions = {};
904
+ const argDimensions = {};
905
+ const allPackageArgNames = (pkg.packageArguments || []).map((a)=>a.name);
906
+ for (const [key, value] of Object.entries(params.combination.dimensionValues)){
907
+ if (allPackageArgNames.includes(key)) {
908
+ argDimensions[key] = value;
909
+ } else {
910
+ envDimensions[key] = value;
911
+ }
912
+ }
913
+ // Filter environmentVariables based on combination's envKeys
914
+ const relevantEnvVars = (pkg.environmentVariables || []).filter((envVar)=>params.combination.envKeys.includes(envVar.name));
915
+ // Filter by dependsOn - only prompt for env vars relevant to this combination's dimension values
916
+ const envVarsMatchingDependsOn = relevantEnvVars.filter((envVar)=>shouldPromptEnvVar(envVar, params.combination.dimensionValues));
917
+ // Filter defaults to only include env vars whose dependsOn is satisfied for this combination
918
+ // This ensures DCR_STORE_URI isn't included when AUTH_MODE=loopback-oauth, etc.
919
+ const allEnvVars = pkg.environmentVariables || [];
920
+ const filteredDefaults = {};
921
+ for (const [envName, envValue] of Object.entries(params.combination.defaults)){
922
+ const envVarMeta = allEnvVars.find((e)=>e.name === envName);
923
+ if (!envVarMeta || shouldPromptEnvVar(envVarMeta, params.combination.dimensionValues)) {
924
+ filteredDefaults[envName] = envValue;
925
+ }
926
+ }
927
+ // Separate env vars into those with pre-populated values and those to prompt for
928
+ const envDefaultKeys = Object.keys(filteredDefaults);
929
+ const envDimensionKeys = Object.keys(envDimensions);
930
+ const prePopulatedEnvKeys = [
931
+ ...envDefaultKeys,
932
+ ...envDimensionKeys
933
+ ];
934
+ // Determine which env vars to prompt for
935
+ let envVarsToPrompt;
936
+ if (params.optionalVarsToPrompt === undefined) {
937
+ // No filter provided (e.g., tests, non-interactive) - use old behavior: prompt all relevant vars
938
+ envVarsToPrompt = envVarsMatchingDependsOn.filter((envVar)=>!prePopulatedEnvKeys.includes(envVar.name));
939
+ } else if (params.optionalVarsToPrompt.size === 0) {
940
+ // Empty filter - only prompt for required vars, skip optional
941
+ envVarsToPrompt = envVarsMatchingDependsOn.filter((envVar)=>!prePopulatedEnvKeys.includes(envVar.name) && envVar.isRequired);
942
+ } else {
943
+ // Filter provided - prompt required vars + selected optional vars
944
+ const requiredVars = envVarsMatchingDependsOn.filter((envVar)=>!prePopulatedEnvKeys.includes(envVar.name) && envVar.isRequired);
945
+ const selectedOptionalVars = allEnvVars.filter((envVar)=>{
946
+ var // Only optional vars
947
+ _params_optionalVarsToPrompt;
948
+ return !envVar.isRequired && ((_params_optionalVarsToPrompt = params.optionalVarsToPrompt) === null || _params_optionalVarsToPrompt === void 0 ? void 0 : _params_optionalVarsToPrompt.has(envVar.name)) && // User selected it
949
+ !prePopulatedEnvKeys.includes(envVar.name) && // Not already populated
950
+ shouldPromptEnvVar(envVar, params.combination.dimensionValues) // Relevant to this config
951
+ ;
952
+ });
953
+ envVarsToPrompt = [
954
+ ...requiredVars,
955
+ ...selectedOptionalVars
956
+ ];
957
+ }
958
+ // Prompt for environment variable values using shared logic
959
+ // In quick mode, skip prompts and use defaults/placeholders via the yes option
960
+ const promptContext = `${params.combination.name}/${params.transport}`;
961
+ // Build template vars for placeholder substitution (e.g., {HOST}, {PORT})
962
+ const templateVars = {};
963
+ if (params.httpHost) templateVars.HOST = params.httpHost;
964
+ if (params.httpPort) templateVars.PORT = String(params.httpPort);
965
+ const promptedEnv = await envPromptFn(promptContext, envVarsToPrompt, {
966
+ yes: params.quick,
967
+ templateVars
968
+ });
969
+ // Merge all env values: filtered defaults + dimension values + prompted values
970
+ const env = {
971
+ ...filteredDefaults,
972
+ ...envDimensions,
973
+ ...promptedEnv
974
+ };
975
+ // Build command and args based on source vs installed mode
976
+ let command;
977
+ let args;
978
+ if (params.useSource) {
979
+ // Source mode: use node with relative path to bin entry
980
+ if (!params.packageDir) {
981
+ throw new Error('packageDir is required when useSource is true');
982
+ }
983
+ if (!params.binPath) {
984
+ throw new Error('binPath is required when useSource is true (check package.json bin entry)');
985
+ }
986
+ const absolutePackageDir = path.resolve(params.packageDir);
987
+ const relativePath = path.join(absolutePackageDir, params.binPath);
988
+ command = 'node';
989
+ args = [
990
+ relativePath
991
+ ];
992
+ } else {
993
+ // Installed mode: use npx (current behavior)
994
+ command = 'npx';
995
+ args = [
996
+ '-y',
997
+ params.packageName
998
+ ];
999
+ }
1000
+ // Add package arguments from argDefaults and argDimensions
1001
+ const allArgValues = {
1002
+ ...params.combination.argDefaults,
1003
+ ...argDimensions
1004
+ };
1005
+ for (const [argName, argValue] of Object.entries(allArgValues)){
1006
+ // Named arguments are added as --arg value
1007
+ args.push(argName, argValue);
1008
+ }
1009
+ // Add HTTP-specific configuration
1010
+ if (params.transport === 'http' || params.transport === 'streamable-http') {
1011
+ var _params_httpPort;
1012
+ const port = (_params_httpPort = params.httpPort) !== null && _params_httpPort !== void 0 ? _params_httpPort : 3000;
1013
+ args.push('--port', String(port));
1014
+ }
1015
+ // Build server config - structure differs for stdio vs http
1016
+ let serverConfig;
1017
+ if (params.transport === 'http' || params.transport === 'streamable-http') {
1018
+ var _params_httpHost, _params_httpPort1;
1019
+ // HTTP servers use start block with stdio config structure
1020
+ const startBlock = {
1021
+ command,
1022
+ ...args.length > 0 && {
1023
+ args
1024
+ },
1025
+ ...Object.keys(env).length > 0 && {
1026
+ env
1027
+ }
1028
+ };
1029
+ const httpConfig = {
1030
+ type: 'http',
1031
+ url: `http://${(_params_httpHost = params.httpHost) !== null && _params_httpHost !== void 0 ? _params_httpHost : 'localhost'}:${(_params_httpPort1 = params.httpPort) !== null && _params_httpPort1 !== void 0 ? _params_httpPort1 : 3000}/mcp`,
1032
+ start: startBlock
1033
+ };
1034
+ serverConfig = httpConfig;
1035
+ } else {
1036
+ // Stdio servers use top-level stdio config structure
1037
+ const stdioConfig = {
1038
+ command,
1039
+ ...args.length > 0 && {
1040
+ args
1041
+ },
1042
+ ...Object.keys(env).length > 0 && {
1043
+ env
1044
+ }
1045
+ };
1046
+ serverConfig = stdioConfig;
1047
+ }
1048
+ return serverConfig;
1049
+ }
1050
+ /** Generate config object (for JSON output or testing) - Exported for testing */ export async function generateConfigObject(params, envPromptFn = promptForEnvVars) {
1051
+ const serverConfig = await buildServerConfig(params, envPromptFn);
1052
+ return {
1053
+ mcpServers: {
1054
+ [params.serverName]: serverConfig
1055
+ }
1056
+ };
1057
+ }
1058
+ /** Generate config file - Exported for testing */ export async function generateConfigFile(params, envPromptFn = promptForEnvVars) {
1059
+ // Normalize transport name for filename ('streamable-http' → 'http' to match config type)
1060
+ const transportName = params.transport === 'streamable-http' ? 'http' : params.transport;
1061
+ const filename = `.mcp.${params.combination.name}-${transportName}.json`;
1062
+ const filepath = path.join(params.outputDir, filename);
1063
+ // Check if file exists and ask user whether to overwrite
1064
+ if (fs.existsSync(filepath) && process.stdin.isTTY) {
1065
+ const shouldOverwrite = await confirm({
1066
+ message: `${filename} already exists. Overwrite?`,
1067
+ default: false
1068
+ });
1069
+ if (!shouldOverwrite) {
1070
+ console.log(` ⏭️ Skipped ${filename}`);
1071
+ return false;
1072
+ }
1073
+ }
1074
+ try {
1075
+ // Ensure output directory exists
1076
+ fs.mkdirSync(params.outputDir, {
1077
+ recursive: true
1078
+ });
1079
+ const config = await generateConfigObject(params, envPromptFn);
1080
+ fs.writeFileSync(filepath, `${JSON.stringify(config, null, 2)}\n`);
1081
+ console.log(` ✅ ${filename}`);
1082
+ return true;
1083
+ } catch (error) {
1084
+ console.error(` ❌ ${filename}: ${error instanceof Error ? error.message : String(error)}`);
1085
+ return false;
1086
+ }
1087
+ }