@mcp-abap-adt/configurator 0.0.11 β†’ 0.1.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.
package/README.md CHANGED
@@ -27,7 +27,7 @@ mcp-conf tui
27
27
 
28
28
  ## TUI
29
29
 
30
- `mcp-conf tui` starts an interactive wizard (`ls`/`add`/`show`/`update`/`rm`/`enable`/`disable`).
30
+ `mcp-conf tui` starts an interactive wizard (`ls`/`show`/`add`/`update`/`rm`/`enable`/`disable`).
31
31
  - Step order: `operation` -> `client` -> `scope` (auto-skipped if only one scope is supported).
32
32
  - For `add` + `sse/http`: prompts for URL, timeout, and repeatable headers.
33
33
  - For `rm`/`enable`/`disable`: server name is selected from existing servers in chosen client/scope.
@@ -35,26 +35,36 @@ async function main() {
35
35
  };
36
36
 
37
37
  result.tuiAction = await askSelect("Operation", [
38
- "ls",
39
- "add",
40
- "show",
41
- "update",
42
- "rm",
43
- "enable",
44
- "disable",
38
+ { name: "ls", message: "πŸ“– ls" },
39
+ { name: "show", message: "πŸ”Ž show" },
40
+ { name: "sep-view", role: "separator", message: "────────────" },
41
+ { name: "add", message: "βž• add" },
42
+ { name: "update", message: "✏️ update" },
43
+ { name: "enable", message: "βœ… enable" },
44
+ { name: "disable", message: "⏸️ disable" },
45
+ { name: "rm", message: "πŸ—‘οΈ rm" },
46
+ { name: "sep-exit", role: "separator", message: "────────────" },
47
+ { name: "exit", message: "πŸšͺ exit" },
45
48
  ]);
49
+ if (result.tuiAction === "exit") {
50
+ emitResult({ tuiAction: "exit" });
51
+ return;
52
+ }
46
53
  const client = await askSelect(
47
54
  "Client",
48
55
  CLIENTS.map((item) => item.name),
49
56
  CLIENTS.map((item) => item.message),
50
57
  );
51
58
  result.clients = [client];
52
-
53
- const scopes = getSupportedScopes(client);
54
- result.scope = scopes.length === 1 ? scopes[0] : await askSelect("Scope", ["global", "local"]);
59
+ await configureScope(result, client);
55
60
 
56
61
  if (["rm", "enable", "disable", "show", "update"].includes(result.tuiAction)) {
57
- const serverNames = listExistingServers(client, result.scope);
62
+ const serverNames = listExistingServers(
63
+ client,
64
+ result.scope,
65
+ result.allProjects,
66
+ result.projectPath,
67
+ );
58
68
  if (serverNames.length === 0) {
59
69
  throw new Error(`No existing MCP servers found for ${client} (${result.scope})`);
60
70
  }
@@ -79,32 +89,37 @@ async function main() {
79
89
 
80
90
  if (result.transport === "stdio") {
81
91
  const authSource = await askSelect("Auth source for stdio", [
82
- "service key destination (--mcp)",
83
- "session environment (--env)",
84
- "specific env file (--env-path)",
92
+ "destination (--mcp=<name>)",
93
+ "env name (--env=<name>)",
94
+ "env file (--env-path=<name>)",
85
95
  ]);
86
- if (authSource.startsWith("service key")) {
96
+ if (authSource.startsWith("destination")) {
87
97
  result.mcpDestination = await askInput("Destination name", "TRIAL");
88
98
  result.useSessionEnv = false;
99
+ result.envName = null;
89
100
  result.envPath = null;
90
101
  result.url = null;
91
102
  emitResult(result);
92
103
  return;
93
104
  }
94
- if (authSource.startsWith("specific env file")) {
105
+ if (authSource.startsWith("env name")) {
106
+ result.envName = await askInput("Env name", "TRIAL");
107
+ result.useSessionEnv = false;
108
+ result.envPath = null;
109
+ result.mcpDestination = null;
110
+ result.url = null;
111
+ emitResult(result);
112
+ return;
113
+ }
114
+ if (authSource.startsWith("env file")) {
95
115
  result.envPath = await askInput("Path to .env file");
96
116
  result.useSessionEnv = false;
117
+ result.envName = null;
97
118
  result.mcpDestination = null;
98
119
  result.url = null;
99
120
  emitResult(result);
100
121
  return;
101
122
  }
102
- result.useSessionEnv = true;
103
- result.envPath = null;
104
- result.mcpDestination = null;
105
- result.url = null;
106
- emitResult(result);
107
- return;
108
123
  }
109
124
 
110
125
  result.url = await askInput("Server URL (http/https)");
@@ -118,7 +133,13 @@ async function main() {
118
133
  }
119
134
 
120
135
  async function configureUpdate(result, client) {
121
- const current = getServerConfig(client, result.scope, result.name);
136
+ const current = getServerConfig(
137
+ client,
138
+ result.scope,
139
+ result.name,
140
+ result.allProjects,
141
+ result.projectPath,
142
+ );
122
143
  const currentTransport = current.transport || "stdio";
123
144
  result.transport = currentTransport;
124
145
  result.command = current.command || "mcp-abap-adt";
@@ -128,42 +149,48 @@ async function configureUpdate(result, client) {
128
149
 
129
150
  if (currentTransport === "stdio") {
130
151
  const authChoices = [
131
- "service key destination (--mcp)",
132
- "session environment (--env)",
133
- "specific env file (--env-path)",
152
+ "destination (--mcp=<name>)",
153
+ "env name (--env=<name>)",
154
+ "env file (--env-path=<name>)",
134
155
  ];
135
156
  const authType = current.auth?.type || "unknown";
136
157
  const authInitial =
137
158
  authType === "mcp" ? 0 : authType === "env" ? 1 : authType === "env-path" ? 2 : 0;
138
159
  const authSource = await askSelect("Auth source for stdio", authChoices, null, authInitial);
139
- if (authSource.startsWith("service key")) {
160
+ if (authSource.startsWith("destination")) {
140
161
  result.mcpDestination = await askInput("Destination name", current.auth?.value || "TRIAL");
141
162
  result.useSessionEnv = false;
163
+ result.envName = null;
142
164
  result.envPath = null;
143
165
  result.url = null;
144
166
  result.headers = {};
145
167
  return;
146
168
  }
147
- if (authSource.startsWith("specific env file")) {
169
+ if (authSource.startsWith("env name")) {
170
+ result.envName = await askInput("Env name", current.auth?.value || "TRIAL");
171
+ result.useSessionEnv = false;
172
+ result.envPath = null;
173
+ result.mcpDestination = null;
174
+ result.url = null;
175
+ result.headers = {};
176
+ return;
177
+ }
178
+ if (authSource.startsWith("env file")) {
148
179
  result.envPath = await askInput("Path to .env file", current.auth?.value || undefined);
149
180
  result.useSessionEnv = false;
181
+ result.envName = null;
150
182
  result.mcpDestination = null;
151
183
  result.url = null;
152
184
  result.headers = {};
153
185
  return;
154
186
  }
155
- result.useSessionEnv = true;
156
- result.envPath = null;
157
- result.mcpDestination = null;
158
- result.url = null;
159
- result.headers = {};
160
- return;
161
187
  }
162
188
 
163
189
  result.url = await askInput("Server URL (http/https)", current.url || undefined);
164
190
  result.timeout = await askPositiveNumber("Timeout seconds", result.timeout);
165
191
  result.headers = await askHeaders(current.headers || {});
166
192
  result.useSessionEnv = false;
193
+ result.envName = null;
167
194
  result.envPath = null;
168
195
  result.mcpDestination = null;
169
196
  }
@@ -173,10 +200,15 @@ function emitResult(result) {
173
200
  }
174
201
 
175
202
  async function askSelect(message, choices, choiceLabels, initial = 0) {
176
- const promptChoices = choices.map((value, index) => ({
177
- name: value,
178
- message: choiceLabels?.[index] || value,
179
- }));
203
+ const promptChoices = choices.map((value, index) => {
204
+ if (value && typeof value === "object" && !Array.isArray(value)) {
205
+ return value;
206
+ }
207
+ return {
208
+ name: value,
209
+ message: choiceLabels?.[index] || value,
210
+ };
211
+ });
180
212
  const select = new Select({
181
213
  name: "value",
182
214
  message,
@@ -239,6 +271,33 @@ function getSupportedScopes(clientName) {
239
271
  return ["global", "local"];
240
272
  }
241
273
 
274
+ async function configureScope(result, clientName) {
275
+ result.allProjects = false;
276
+ result.projectPath = null;
277
+
278
+ if (clientName === "claude") {
279
+ result.scope = await askSelect("Scope", ["global", "local"]);
280
+ if (result.scope === "local") {
281
+ return;
282
+ }
283
+ if (supportsClaudeGlobalAllProjects(result.tuiAction)) {
284
+ const globalMode = await askSelect("Claude global", [
285
+ { name: "single", message: "for current project" },
286
+ { name: "all", message: "for all projects" },
287
+ ]);
288
+ result.allProjects = globalMode === "all";
289
+ }
290
+ return;
291
+ }
292
+
293
+ const scopes = getSupportedScopes(clientName);
294
+ result.scope = scopes.length === 1 ? scopes[0] : await askSelect("Scope", ["global", "local"]);
295
+ }
296
+
297
+ function supportsClaudeGlobalAllProjects(action) {
298
+ return ["ls", "show", "rm", "enable", "disable"].includes(action);
299
+ }
300
+
242
301
  function getSupportedTransports(clientName) {
243
302
  if (clientName === "codex") {
244
303
  return ["stdio", "http"];
@@ -246,10 +305,16 @@ function getSupportedTransports(clientName) {
246
305
  return ["stdio", "sse", "http"];
247
306
  }
248
307
 
249
- function listExistingServers(clientName, scope) {
308
+ function listExistingServers(clientName, scope, allProjects = false, projectPath = null) {
250
309
  const cliPath = path.join(__dirname, "mcp-conf.js");
251
310
  const scopeArg = scope === "local" ? "--local" : "--global";
252
- const run = spawnSync(process.execPath, [cliPath, "ls", "--client", clientName, scopeArg], {
311
+ const cliArgs = [cliPath, "ls", "--client", clientName, scopeArg];
312
+ if (allProjects) {
313
+ cliArgs.push("--all-projects");
314
+ } else if (projectPath) {
315
+ cliArgs.push("--project", projectPath);
316
+ }
317
+ const run = spawnSync(process.execPath, cliArgs, {
253
318
  encoding: "utf8",
254
319
  });
255
320
  if (run.status !== 0) {
@@ -271,16 +336,28 @@ function listExistingServers(clientName, scope) {
271
336
  return [...new Set(names)].sort((a, b) => a.localeCompare(b));
272
337
  }
273
338
 
274
- function getServerConfig(clientName, scope, serverName) {
339
+ function getServerConfig(clientName, scope, serverName, allProjects = false, projectPath = null) {
275
340
  const cliPath = path.join(__dirname, "mcp-conf.js");
276
341
  const scopeArg = scope === "local" ? "--local" : "--global";
277
- const run = spawnSync(
278
- process.execPath,
279
- [cliPath, "show", "--client", clientName, "--name", serverName, scopeArg, "--json"],
280
- {
281
- encoding: "utf8",
282
- },
283
- );
342
+ const cliArgs = [
343
+ cliPath,
344
+ "show",
345
+ "--client",
346
+ clientName,
347
+ "--name",
348
+ serverName,
349
+ scopeArg,
350
+ "--json",
351
+ "--normalized",
352
+ ];
353
+ if (allProjects) {
354
+ cliArgs.push("--all-projects");
355
+ } else if (projectPath) {
356
+ cliArgs.push("--project", projectPath);
357
+ }
358
+ const run = spawnSync(process.execPath, cliArgs, {
359
+ encoding: "utf8",
360
+ });
284
361
  if (run.status !== 0) {
285
362
  const stderr = String(run.stderr || "").trim();
286
363
  throw new Error(stderr || "Failed to read existing server config");
@@ -293,9 +370,40 @@ function getServerConfig(clientName, scope, serverName) {
293
370
  }
294
371
 
295
372
  main().catch((error) => {
296
- if (error === "") {
373
+ if (isPromptCancelError(error)) {
374
+ process.exit(0);
375
+ }
376
+ process.stderr.write(`${error?.message || String(error)}\n`);
377
+ process.exit(1);
378
+ });
379
+
380
+ process.on("uncaughtException", (error) => {
381
+ if (isPromptCancelError(error)) {
297
382
  process.exit(0);
298
383
  }
299
384
  process.stderr.write(`${error?.message || String(error)}\n`);
300
385
  process.exit(1);
301
386
  });
387
+
388
+ process.on("unhandledRejection", (error) => {
389
+ if (isPromptCancelError(error)) {
390
+ process.exit(0);
391
+ }
392
+ process.stderr.write(`${error?.message || String(error)}\n`);
393
+ process.exit(1);
394
+ });
395
+
396
+ function isPromptCancelError(error) {
397
+ if (error === "" || error === null || error === undefined) {
398
+ return true;
399
+ }
400
+ if (typeof error === "string") {
401
+ return error.toLowerCase().includes("cancel");
402
+ }
403
+ const message = String(error?.message || "");
404
+ return (
405
+ error?.code === "ERR_USE_AFTER_CLOSE" ||
406
+ message.toLowerCase().includes("cancel") ||
407
+ message.toLowerCase().includes("readline was closed")
408
+ );
409
+ }
package/bin/mcp-conf.js CHANGED
@@ -26,7 +26,7 @@ const args = process.argv.slice(2);
26
26
  const action = args[0] && !args[0].startsWith("-") ? args[0] : null;
27
27
  if (
28
28
  action &&
29
- ["add", "rm", "ls", "enable", "disable", "where", "show", "update", "tui", "help"].includes(
29
+ ["add", "rm", "ls", "show", "enable", "disable", "where", "update", "tui", "help"].includes(
30
30
  action,
31
31
  )
32
32
  ) {
@@ -34,6 +34,7 @@ if (
34
34
  }
35
35
  const options = {
36
36
  clients: [],
37
+ envName: null,
37
38
  envPath: null,
38
39
  useSessionEnv: false,
39
40
  mcpDestination: null,
@@ -52,6 +53,7 @@ const options = {
52
53
  where: false,
53
54
  show: false,
54
55
  outputJson: false,
56
+ outputNormalized: false,
55
57
  url: null,
56
58
  headers: {},
57
59
  timeout: 60,
@@ -62,7 +64,7 @@ if (args.includes("--help") || args.includes("-h") || action === "help") {
62
64
  action === "help"
63
65
  ? args[0]
64
66
  : action &&
65
- ["add", "rm", "ls", "enable", "disable", "where", "show", "update", "tui"].includes(
67
+ ["add", "rm", "ls", "show", "enable", "disable", "where", "update", "tui"].includes(
66
68
  action,
67
69
  )
68
70
  ? action
@@ -76,30 +78,45 @@ for (let i = 0; i < args.length; i += 1) {
76
78
  if (arg === "--client") {
77
79
  options.clients.push(normalizeClientName(args[i + 1]));
78
80
  i += 1;
81
+ } else if (arg.startsWith("--env=")) {
82
+ options.envName = arg.slice("--env=".length);
83
+ options.useSessionEnv = false;
84
+ options.envPath = null;
85
+ options.mcpDestination = null;
79
86
  } else if (arg === "--env") {
80
87
  const maybePath = args[i + 1];
81
88
  if (maybePath && !maybePath.startsWith("-")) {
82
- // Backward-compatible form: --env /path/to/.env
83
- options.envPath = maybePath;
89
+ if (looksLikeEnvPath(maybePath)) {
90
+ // Backward-compatible form: --env /path/to/.env
91
+ options.envPath = maybePath;
92
+ options.envName = null;
93
+ } else {
94
+ options.envName = maybePath;
95
+ options.envPath = null;
96
+ }
84
97
  options.useSessionEnv = false;
85
98
  options.mcpDestination = null;
86
99
  i += 1;
87
100
  } else {
88
101
  options.useSessionEnv = true;
102
+ options.envName = null;
89
103
  options.envPath = null;
90
104
  options.mcpDestination = null;
91
105
  }
92
106
  } else if (arg === "--env-path") {
93
107
  options.envPath = args[i + 1];
108
+ options.envName = null;
94
109
  options.useSessionEnv = false;
95
110
  options.mcpDestination = null;
96
111
  i += 1;
97
112
  } else if (arg === "--session-env") {
98
113
  options.useSessionEnv = true;
114
+ options.envName = null;
99
115
  options.envPath = null;
100
116
  options.mcpDestination = null;
101
117
  } else if (arg === "--mcp") {
102
118
  options.mcpDestination = args[i + 1];
119
+ options.envName = null;
103
120
  options.useSessionEnv = false;
104
121
  options.envPath = null;
105
122
  i += 1;
@@ -154,14 +171,16 @@ for (let i = 0; i < args.length; i += 1) {
154
171
  options.force = true;
155
172
  } else if (arg === "--json") {
156
173
  options.outputJson = true;
174
+ } else if (arg === "--normalized") {
175
+ options.outputNormalized = true;
157
176
  }
158
177
  }
159
178
 
160
179
  if (
161
180
  !action ||
162
- !["add", "rm", "ls", "enable", "disable", "where", "show", "update", "tui"].includes(action)
181
+ !["add", "rm", "ls", "show", "enable", "disable", "where", "update", "tui"].includes(action)
163
182
  ) {
164
- fail("Provide a command: add | rm | ls | enable | disable | where | show | update | tui.");
183
+ fail("Provide a command: add | rm | ls | show | enable | disable | where | update | tui.");
165
184
  }
166
185
 
167
186
  let effectiveAction = action;
@@ -169,6 +188,9 @@ if (action === "tui") {
169
188
  runTuiWizard(options);
170
189
  effectiveAction = options.tuiAction || "add";
171
190
  }
191
+ if (effectiveAction === "exit") {
192
+ process.exit(0);
193
+ }
172
194
 
173
195
  if (options.clients.length === 0) {
174
196
  fail("Provide at least one --client.");
@@ -242,8 +264,8 @@ const requiresConnectionParams =
242
264
  !options.remove && !options.toggle && !options.list && !options.where && !options.show;
243
265
 
244
266
  if (requiresConnectionParams && options.transport === "stdio") {
245
- if (!options.envPath && !options.mcpDestination && !options.useSessionEnv) {
246
- fail("Provide --env, --env-path <path>, or --mcp <destination>.");
267
+ if (!options.envName && !options.envPath && !options.mcpDestination && !options.useSessionEnv) {
268
+ fail("Provide --env <name>, --env-path <path>, --session-env, or --mcp <destination>.");
247
269
  }
248
270
  }
249
271
 
@@ -251,8 +273,8 @@ if (requiresConnectionParams && options.transport !== "stdio") {
251
273
  if (!options.url) {
252
274
  fail("Provide --url <http(s)://...> for sse/http transports.");
253
275
  }
254
- if (options.envPath || options.mcpDestination || options.useSessionEnv) {
255
- fail("--env/--env-path/--mcp are only valid for stdio transport.");
276
+ if (options.envName || options.envPath || options.mcpDestination || options.useSessionEnv) {
277
+ fail("--env/--env-path/--session-env/--mcp are only valid for stdio transport.");
256
278
  }
257
279
  }
258
280
 
@@ -263,13 +285,15 @@ const userProfile = process.env.USERPROFILE || home;
263
285
 
264
286
  const serverArgsRaw = [
265
287
  `--transport=${options.transport}`,
266
- options.useSessionEnv
267
- ? "--env"
268
- : options.envPath
269
- ? `--env-path=${options.envPath}`
270
- : options.mcpDestination
271
- ? `--mcp=${options.mcpDestination.toLowerCase()}`
272
- : undefined,
288
+ options.envName
289
+ ? `--env=${options.envName}`
290
+ : options.useSessionEnv
291
+ ? "--session-env"
292
+ : options.envPath
293
+ ? `--env-path=${options.envPath}`
294
+ : options.mcpDestination
295
+ ? `--mcp=${options.mcpDestination.toLowerCase()}`
296
+ : undefined,
273
297
  ];
274
298
  const serverArgs = serverArgsRaw.filter(Boolean);
275
299
 
@@ -489,7 +513,8 @@ function runTuiWizard(opts) {
489
513
  const rawPayload = run.output?.[3] || "";
490
514
  const payload = String(rawPayload).trim();
491
515
  if (!payload) {
492
- fail("TUI did not return configuration.");
516
+ opts.tuiAction = "exit";
517
+ return;
493
518
  }
494
519
  let selected;
495
520
  try {
@@ -1314,7 +1339,10 @@ function showJsonConfig(filePath, clientType, serverName) {
1314
1339
  if (!entry) {
1315
1340
  fail(`Server "${serverName}" not found in ${filePath}.`);
1316
1341
  }
1317
- outputShow(filePath, serverName, normalizeServerDetails(clientType, serverName, entry));
1342
+ const details = options.outputNormalized
1343
+ ? normalizeServerDetails(clientType, serverName, entry)
1344
+ : cloneJsonLike(entry);
1345
+ outputShow(filePath, serverName, details);
1318
1346
  }
1319
1347
 
1320
1348
  function showCodexConfig(filePath, serverName) {
@@ -1327,7 +1355,10 @@ function showCodexConfig(filePath, serverName) {
1327
1355
  if (!entry) {
1328
1356
  fail(`Server "${serverName}" not found in ${filePath}.`);
1329
1357
  }
1330
- outputShow(filePath, serverName, normalizeServerDetails("codex", serverName, entry));
1358
+ const details = options.outputNormalized
1359
+ ? normalizeServerDetails("codex", serverName, entry)
1360
+ : cloneJsonLike(entry);
1361
+ outputShow(filePath, serverName, details);
1331
1362
  }
1332
1363
 
1333
1364
  function showGooseConfig(filePath, serverName) {
@@ -1340,7 +1371,10 @@ function showGooseConfig(filePath, serverName) {
1340
1371
  if (!entry) {
1341
1372
  fail(`Server "${serverName}" not found in ${filePath}.`);
1342
1373
  }
1343
- outputShow(filePath, serverName, normalizeServerDetails("goose", serverName, entry));
1374
+ const details = options.outputNormalized
1375
+ ? normalizeServerDetails("goose", serverName, entry)
1376
+ : cloneJsonLike(entry);
1377
+ outputShow(filePath, serverName, details);
1344
1378
  }
1345
1379
 
1346
1380
  function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
@@ -1355,10 +1389,17 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1355
1389
  const projectNode = data.projects?.[key];
1356
1390
  const store = projectNode?.mcpServers || {};
1357
1391
  if (store[serverName]) {
1358
- results.push({
1359
- project: key,
1360
- ...normalizeServerDetails("claude", serverName, store[serverName]),
1361
- });
1392
+ results.push(
1393
+ options.outputNormalized
1394
+ ? {
1395
+ project: key,
1396
+ ...normalizeServerDetails("claude", serverName, store[serverName]),
1397
+ }
1398
+ : {
1399
+ project: key,
1400
+ raw: cloneJsonLike(store[serverName]),
1401
+ },
1402
+ );
1362
1403
  }
1363
1404
  }
1364
1405
  if (!results.length) {
@@ -1369,7 +1410,12 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1369
1410
  return;
1370
1411
  }
1371
1412
  for (const result of results) {
1372
- outputShow(filePath, serverName, result, result.project);
1413
+ outputShow(
1414
+ filePath,
1415
+ serverName,
1416
+ options.outputNormalized ? result : result.raw,
1417
+ result.project,
1418
+ );
1373
1419
  }
1374
1420
  return;
1375
1421
  }
@@ -1379,12 +1425,10 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1379
1425
  if (!entry) {
1380
1426
  fail(`Server "${serverName}" not found for ${projectKey}.`);
1381
1427
  }
1382
- outputShow(
1383
- filePath,
1384
- serverName,
1385
- normalizeServerDetails("claude", serverName, entry, projectKey),
1386
- projectKey,
1387
- );
1428
+ const details = options.outputNormalized
1429
+ ? normalizeServerDetails("claude", serverName, entry, projectKey)
1430
+ : cloneJsonLike(entry);
1431
+ outputShow(filePath, serverName, details, projectKey);
1388
1432
  return;
1389
1433
  }
1390
1434
  const store = data.mcpServers || {};
@@ -1392,7 +1436,10 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1392
1436
  if (!entry) {
1393
1437
  fail(`Server "${serverName}" not found in ${filePath}.`);
1394
1438
  }
1395
- outputShow(filePath, serverName, normalizeServerDetails("claude", serverName, entry));
1439
+ const details = options.outputNormalized
1440
+ ? normalizeServerDetails("claude", serverName, entry)
1441
+ : cloneJsonLike(entry);
1442
+ outputShow(filePath, serverName, details);
1396
1443
  }
1397
1444
 
1398
1445
  function normalizeServerDetails(clientType, serverName, entry, projectKey) {
@@ -1439,26 +1486,72 @@ function compactServerDetails(details) {
1439
1486
  return compact;
1440
1487
  }
1441
1488
 
1489
+ function cloneJsonLike(value) {
1490
+ if (value === null || value === undefined) {
1491
+ return value;
1492
+ }
1493
+ try {
1494
+ return JSON.parse(JSON.stringify(value));
1495
+ } catch {
1496
+ return value;
1497
+ }
1498
+ }
1499
+
1442
1500
  function parseServerArgs(args) {
1443
1501
  const parsed = {
1444
1502
  transport: null,
1503
+ envName: null,
1445
1504
  envPath: null,
1446
1505
  mcpDestination: null,
1447
1506
  useSessionEnv: false,
1448
1507
  };
1449
- for (const arg of args) {
1508
+ for (let i = 0; i < args.length; i += 1) {
1509
+ const arg = args[i];
1450
1510
  if (typeof arg !== "string") {
1451
1511
  continue;
1452
1512
  }
1453
1513
  if (arg.startsWith("--transport=")) {
1454
1514
  const value = arg.slice("--transport=".length);
1455
1515
  parsed.transport = value === "streamableHttp" ? "http" : value;
1456
- } else if (arg === "--env") {
1516
+ } else if (arg === "--session-env") {
1457
1517
  parsed.useSessionEnv = true;
1458
- } else if (arg.startsWith("--env-path=") || arg.startsWith("--env=")) {
1518
+ } else if (arg.startsWith("--env=")) {
1519
+ const value = arg.slice("--env=".length);
1520
+ if (looksLikeEnvPath(value)) {
1521
+ parsed.envPath = value;
1522
+ } else {
1523
+ parsed.envName = value;
1524
+ }
1525
+ } else if (arg === "--env") {
1526
+ const next = args[i + 1];
1527
+ if (typeof next === "string" && next && !next.startsWith("-")) {
1528
+ if (looksLikeEnvPath(next)) {
1529
+ // Backward-compatible form: --env /path/to/.env
1530
+ parsed.envPath = next;
1531
+ } else {
1532
+ parsed.envName = next;
1533
+ }
1534
+ parsed.useSessionEnv = false;
1535
+ i += 1;
1536
+ } else {
1537
+ parsed.useSessionEnv = true;
1538
+ }
1539
+ } else if (arg.startsWith("--env-path=")) {
1459
1540
  parsed.envPath = arg.slice(arg.indexOf("=") + 1);
1541
+ } else if (arg === "--env-path") {
1542
+ const next = args[i + 1];
1543
+ if (typeof next === "string" && next && !next.startsWith("-")) {
1544
+ parsed.envPath = next;
1545
+ i += 1;
1546
+ }
1460
1547
  } else if (arg.startsWith("--mcp=")) {
1461
1548
  parsed.mcpDestination = arg.slice("--mcp=".length);
1549
+ } else if (arg === "--mcp") {
1550
+ const next = args[i + 1];
1551
+ if (typeof next === "string" && next && !next.startsWith("-")) {
1552
+ parsed.mcpDestination = next;
1553
+ i += 1;
1554
+ }
1462
1555
  }
1463
1556
  }
1464
1557
  return parsed;
@@ -1534,15 +1627,31 @@ function inferAuth(parsedArgs) {
1534
1627
  if (parsedArgs.mcpDestination) {
1535
1628
  return { type: "mcp", value: parsedArgs.mcpDestination };
1536
1629
  }
1630
+ if (parsedArgs.envName) {
1631
+ return { type: "env", value: parsedArgs.envName };
1632
+ }
1537
1633
  if (parsedArgs.envPath) {
1538
1634
  return { type: "env-path", value: parsedArgs.envPath };
1539
1635
  }
1540
1636
  if (parsedArgs.useSessionEnv) {
1541
- return { type: "env", value: null };
1637
+ return { type: "session-env", value: null };
1542
1638
  }
1543
1639
  return { type: "unknown", value: null };
1544
1640
  }
1545
1641
 
1642
+ function looksLikeEnvPath(value) {
1643
+ if (!value || typeof value !== "string") {
1644
+ return false;
1645
+ }
1646
+ return (
1647
+ value.includes("/") ||
1648
+ value.includes("\\") ||
1649
+ value.endsWith(".env") ||
1650
+ value.startsWith(".") ||
1651
+ value.startsWith("~")
1652
+ );
1653
+ }
1654
+
1546
1655
  function readJson(filePath) {
1547
1656
  if (!fs.existsSync(filePath)) {
1548
1657
  return {};
@@ -1643,7 +1752,7 @@ function printHelp(command) {
1643
1752
  process.stdout.write(`${header}
1644
1753
 
1645
1754
  Usage:
1646
- mcp-conf <add|rm|ls|enable|disable|where|show|update> --client <name> [options]
1755
+ mcp-conf <add|rm|ls|show|enable|disable|where|update> --client <name> [options]
1647
1756
  mcp-conf tui
1648
1757
  mcp-conf help <command>
1649
1758
 
@@ -1651,10 +1760,10 @@ Commands:
1651
1760
  add add or update an MCP server entry
1652
1761
  rm remove an MCP server entry
1653
1762
  ls list MCP server entries
1763
+ show show server configuration details
1654
1764
  enable enable an existing entry
1655
1765
  disable disable an existing entry
1656
1766
  where show where a server name is defined
1657
- show show server configuration details
1658
1767
  update update an existing server entry
1659
1768
  tui interactive setup wizard
1660
1769
 
@@ -1674,13 +1783,14 @@ Notes:
1674
1783
  process.stdout.write(`${header} add
1675
1784
 
1676
1785
  Usage:
1677
- mcp-conf add --client <name> --name <serverName> [--env | --env-path <path> | --mcp <dest>] [options]
1786
+ mcp-conf add --client <name> --name <serverName> [--env <name> | --env-path <path> | --session-env | --mcp <dest>] [options]
1678
1787
 
1679
1788
  Options:
1680
1789
  --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1681
1790
  --name <serverName> required MCP server name key
1682
- --env use current shell/session env vars (stdio only)
1791
+ --env <name> env profile name (stdio only), writes --env=<name>
1683
1792
  --env-path <path> .env path (stdio only)
1793
+ --session-env use current shell/session env vars (stdio only)
1684
1794
  --mcp <dest> destination name (stdio only)
1685
1795
  --transport <type> stdio | sse | http (http => streamableHttp)
1686
1796
  --command <bin> command to run (default: mcp-abap-adt)
@@ -1803,6 +1913,7 @@ Options:
1803
1913
  --all-projects Claude global: show from all projects
1804
1914
  --project <path> Claude global: target a specific project path
1805
1915
  --json output JSON only (machine-readable)
1916
+ --normalized output normalized view (for tooling); default is raw config entry
1806
1917
 
1807
1918
  Notes:
1808
1919
  Antigravity local scope is not supported yet; use --global.
@@ -1812,13 +1923,14 @@ Notes:
1812
1923
  process.stdout.write(`${header} update
1813
1924
 
1814
1925
  Usage:
1815
- mcp-conf update --client <name> --name <serverName> [--env | --env-path <path> | --mcp <dest>] [options]
1926
+ mcp-conf update --client <name> --name <serverName> [--env <name> | --env-path <path> | --session-env | --mcp <dest>] [options]
1816
1927
 
1817
1928
  Options:
1818
1929
  --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1819
1930
  --name <serverName> required MCP server name key
1820
- --env use current shell/session env vars (stdio only)
1931
+ --env <name> env profile name (stdio only), writes --env=<name>
1821
1932
  --env-path <path> .env path (stdio only)
1933
+ --session-env use current shell/session env vars (stdio only)
1822
1934
  --mcp <dest> destination name (stdio only)
1823
1935
  --transport <type> stdio | sse | http (http => streamableHttp)
1824
1936
  --url <http(s)://...> required for sse/http
@@ -1840,7 +1952,7 @@ Usage:
1840
1952
  mcp-conf tui
1841
1953
 
1842
1954
  Description:
1843
- Start interactive setup wizard for ls/add/show/update/rm/enable/disable.
1955
+ Start interactive setup wizard for ls/show/add/update/rm/enable/disable.
1844
1956
  Flow: operation -> client -> scope (skips scope when only one is supported).
1845
1957
  For add/update + sse/http, wizard also asks timeout and repeatable headers.
1846
1958
  For rm/enable/disable/show/update, wizard selects server from existing entries.
@@ -92,10 +92,11 @@ mcp-conf tui
92
92
  - Controls: arrow keys + Enter, Ctrl+C to cancel.
93
93
 
94
94
  Options:
95
- - Commands: `add`, `rm`, `ls`, `enable`, `disable`, `where`, `show`, `update`, `tui` (first argument)
95
+ - Commands: `add`, `rm`, `ls`, `show`, `enable`, `disable`, `where`, `update`, `tui` (first argument)
96
96
  - `--client <name>` (repeatable): `cline`, `codex`, `claude`, `goose`, `cursor`, `windsurf`, `opencode` (`kilo` alias), `copilot`, `antigravity`, `crush`
97
- - `--env`: use shell/session environment variables (stdio only)
97
+ - `--env <name>`: use named env profile; writes `--env=<name>` (stdio only)
98
98
  - `--env-path <path>`: use a specific `.env` file (stdio only)
99
+ - `--session-env`: use shell/session environment variables (stdio only)
99
100
  - `--mcp <destination>`: use service key destination
100
101
  - `--name <serverName>`: MCP server name (required)
101
102
  - `--transport <type>`: `stdio`, `sse`, or `http` (`http` maps to `streamableHttp`)
@@ -110,9 +111,9 @@ Options:
110
111
  - `--json`: JSON-only output for `show`
111
112
 
112
113
  Notes:
113
- - `disable` and `rm` do not require `--env`, `--env-path`, or `--mcp`.
114
- - `--env`/`--env-path`/`--mcp` are only valid for `stdio` transport. For `sse/http`, use `--url` and optional `--header`.
115
- - `mcp-conf tui` starts an interactive wizard for `ls`/`add`/`show`/`update`/`rm`/`enable`/`disable`.
114
+ - `disable` and `rm` do not require `--env`, `--env-path`, `--session-env`, or `--mcp`.
115
+ - `--env`/`--env-path`/`--session-env`/`--mcp` are only valid for `stdio` transport. For `sse/http`, use `--url` and optional `--header`.
116
+ - `mcp-conf tui` starts an interactive wizard for `ls`/`show`/`add`/`update`/`rm`/`enable`/`disable`.
116
117
  - Cursor/Copilot enable/disable are not implemented yet.
117
118
  - Antigravity enable/disable uses `disabled: true|false` on the entry.
118
119
  - Antigravity local scope is not supported yet; use `--global`.
@@ -121,7 +122,7 @@ Notes:
121
122
  - Antigravity HTTP entries use `serverUrl` instead of `url`.
122
123
  - New entries for Cline, Codex, Windsurf, Goose, Claude, OpenCode, and Crush are added **disabled by default**. Use `enable` to turn them on.
123
124
  - Windsurf follows `disabled` like Cline. The configurator sets `disabled = true` for default-disabled entries.
124
- - `enable`/`disable` only work if the server entry already exists. Use add commands with `--env`, `--env-path`, or `--mcp` first.
125
+ - `enable`/`disable` only work if the server entry already exists. Use add commands with `--env`, `--env-path`, `--session-env`, or `--mcp` first.
125
126
  - Non-stdio transports are supported for Cline/Cursor/Windsurf/Claude/Goose. Codex supports `http` (streamable HTTP) but not `sse`.
126
127
  - Codex writes custom headers under `http_headers` in `~/.codex/config.toml` (or `./.codex/config.toml` for `--local`).
127
128
  - Codex HTTP entries include `startup_timeout_sec` (default: 60).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/configurator",
3
- "version": "0.0.11",
3
+ "version": "0.1.0",
4
4
  "description": "MCP client configurator for mcp-abap-adt and mcp-abap-adt-proxy",
5
5
  "license": "MIT",
6
6
  "repository": {