@mcp-abap-adt/configurator 0.0.12 β†’ 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.
@@ -35,26 +35,36 @@ async function main() {
35
35
  };
36
36
 
37
37
  result.tuiAction = await askSelect("Operation", [
38
- "ls",
39
- "show",
40
- "add",
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
@@ -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,
@@ -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,6 +171,8 @@ 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
 
@@ -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,9 +1486,21 @@ 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,
@@ -1456,17 +1515,28 @@ function parseServerArgs(args) {
1456
1515
  parsed.transport = value === "streamableHttp" ? "http" : value;
1457
1516
  } else if (arg === "--session-env") {
1458
1517
  parsed.useSessionEnv = true;
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
+ }
1459
1525
  } else if (arg === "--env") {
1460
1526
  const next = args[i + 1];
1461
1527
  if (typeof next === "string" && next && !next.startsWith("-")) {
1462
- // Backward-compatible form: --env /path/to/.env
1463
- parsed.envPath = next;
1528
+ if (looksLikeEnvPath(next)) {
1529
+ // Backward-compatible form: --env /path/to/.env
1530
+ parsed.envPath = next;
1531
+ } else {
1532
+ parsed.envName = next;
1533
+ }
1464
1534
  parsed.useSessionEnv = false;
1465
1535
  i += 1;
1466
1536
  } else {
1467
1537
  parsed.useSessionEnv = true;
1468
1538
  }
1469
- } else if (arg.startsWith("--env-path=") || arg.startsWith("--env=")) {
1539
+ } else if (arg.startsWith("--env-path=")) {
1470
1540
  parsed.envPath = arg.slice(arg.indexOf("=") + 1);
1471
1541
  } else if (arg === "--env-path") {
1472
1542
  const next = args[i + 1];
@@ -1557,15 +1627,31 @@ function inferAuth(parsedArgs) {
1557
1627
  if (parsedArgs.mcpDestination) {
1558
1628
  return { type: "mcp", value: parsedArgs.mcpDestination };
1559
1629
  }
1630
+ if (parsedArgs.envName) {
1631
+ return { type: "env", value: parsedArgs.envName };
1632
+ }
1560
1633
  if (parsedArgs.envPath) {
1561
1634
  return { type: "env-path", value: parsedArgs.envPath };
1562
1635
  }
1563
1636
  if (parsedArgs.useSessionEnv) {
1564
- return { type: "env", value: null };
1637
+ return { type: "session-env", value: null };
1565
1638
  }
1566
1639
  return { type: "unknown", value: null };
1567
1640
  }
1568
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
+
1569
1655
  function readJson(filePath) {
1570
1656
  if (!fs.existsSync(filePath)) {
1571
1657
  return {};
@@ -1697,13 +1783,14 @@ Notes:
1697
1783
  process.stdout.write(`${header} add
1698
1784
 
1699
1785
  Usage:
1700
- 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]
1701
1787
 
1702
1788
  Options:
1703
1789
  --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1704
1790
  --name <serverName> required MCP server name key
1705
- --env use current shell/session env vars (stdio only)
1791
+ --env <name> env profile name (stdio only), writes --env=<name>
1706
1792
  --env-path <path> .env path (stdio only)
1793
+ --session-env use current shell/session env vars (stdio only)
1707
1794
  --mcp <dest> destination name (stdio only)
1708
1795
  --transport <type> stdio | sse | http (http => streamableHttp)
1709
1796
  --command <bin> command to run (default: mcp-abap-adt)
@@ -1826,6 +1913,7 @@ Options:
1826
1913
  --all-projects Claude global: show from all projects
1827
1914
  --project <path> Claude global: target a specific project path
1828
1915
  --json output JSON only (machine-readable)
1916
+ --normalized output normalized view (for tooling); default is raw config entry
1829
1917
 
1830
1918
  Notes:
1831
1919
  Antigravity local scope is not supported yet; use --global.
@@ -1835,13 +1923,14 @@ Notes:
1835
1923
  process.stdout.write(`${header} update
1836
1924
 
1837
1925
  Usage:
1838
- 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]
1839
1927
 
1840
1928
  Options:
1841
1929
  --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1842
1930
  --name <serverName> required MCP server name key
1843
- --env use current shell/session env vars (stdio only)
1931
+ --env <name> env profile name (stdio only), writes --env=<name>
1844
1932
  --env-path <path> .env path (stdio only)
1933
+ --session-env use current shell/session env vars (stdio only)
1845
1934
  --mcp <dest> destination name (stdio only)
1846
1935
  --transport <type> stdio | sse | http (http => streamableHttp)
1847
1936
  --url <http(s)://...> required for sse/http
@@ -94,8 +94,9 @@ mcp-conf tui
94
94
  Options:
95
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,8 +111,8 @@ 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`.
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`.
115
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.
@@ -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.12",
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": {