@mcp-abap-adt/configurator 0.0.12 β†’ 0.1.1

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
@@ -20,6 +20,7 @@ mcp-conf --client codex --name abap-http --transport http --url http://localhost
20
20
  mcp-conf --client opencode --name abap --transport http --url http://localhost:3000/mcp/stream/http
21
21
  mcp-conf --client kilo --name abap --transport http --url http://localhost:3000/mcp/stream/http
22
22
  mcp-conf --client copilot --name abap --transport http --url http://localhost:3000/mcp/stream/http --header x-mcp-destination=trial
23
+ mcp-conf --client qwen --name abap --transport http --url http://localhost:3000/mcp/stream/http
23
24
  mcp-conf --client crush --name abap --mcp TRIAL
24
25
  mcp-conf --client crush --name abap --transport http --url http://localhost:3000/mcp/stream/http
25
26
  mcp-conf tui
@@ -15,6 +15,7 @@ const CLIENTS = [
15
15
  { name: "opencode", message: "OpenCode (kilo)" },
16
16
  { name: "copilot", message: "GitHub Copilot" },
17
17
  { name: "antigravity", message: "Antigravity" },
18
+ { name: "qwen", message: "Qwen" },
18
19
  { name: "crush", message: "Crush" },
19
20
  ];
20
21
 
@@ -35,26 +36,36 @@ async function main() {
35
36
  };
36
37
 
37
38
  result.tuiAction = await askSelect("Operation", [
38
- "ls",
39
- "show",
40
- "add",
41
- "update",
42
- "rm",
43
- "enable",
44
- "disable",
39
+ { name: "ls", message: "πŸ“– ls" },
40
+ { name: "show", message: "πŸ”Ž show" },
41
+ { name: "sep-view", role: "separator", message: "────────────" },
42
+ { name: "add", message: "βž• add" },
43
+ { name: "update", message: "✏️ update" },
44
+ { name: "enable", message: "βœ… enable" },
45
+ { name: "disable", message: "⏸️ disable" },
46
+ { name: "rm", message: "πŸ—‘οΈ rm" },
47
+ { name: "sep-exit", role: "separator", message: "────────────" },
48
+ { name: "exit", message: "πŸšͺ exit" },
45
49
  ]);
50
+ if (result.tuiAction === "exit") {
51
+ emitResult({ tuiAction: "exit" });
52
+ return;
53
+ }
46
54
  const client = await askSelect(
47
55
  "Client",
48
56
  CLIENTS.map((item) => item.name),
49
57
  CLIENTS.map((item) => item.message),
50
58
  );
51
59
  result.clients = [client];
52
-
53
- const scopes = getSupportedScopes(client);
54
- result.scope = scopes.length === 1 ? scopes[0] : await askSelect("Scope", ["global", "local"]);
60
+ await configureScope(result, client);
55
61
 
56
62
  if (["rm", "enable", "disable", "show", "update"].includes(result.tuiAction)) {
57
- const serverNames = listExistingServers(client, result.scope);
63
+ const serverNames = listExistingServers(
64
+ client,
65
+ result.scope,
66
+ result.allProjects,
67
+ result.projectPath,
68
+ );
58
69
  if (serverNames.length === 0) {
59
70
  throw new Error(`No existing MCP servers found for ${client} (${result.scope})`);
60
71
  }
@@ -79,32 +90,37 @@ async function main() {
79
90
 
80
91
  if (result.transport === "stdio") {
81
92
  const authSource = await askSelect("Auth source for stdio", [
82
- "service key destination (--mcp)",
83
- "session environment (--env)",
84
- "specific env file (--env-path)",
93
+ "destination (--mcp=<name>)",
94
+ "env name (--env=<name>)",
95
+ "env file (--env-path=<name>)",
85
96
  ]);
86
- if (authSource.startsWith("service key")) {
97
+ if (authSource.startsWith("destination")) {
87
98
  result.mcpDestination = await askInput("Destination name", "TRIAL");
88
99
  result.useSessionEnv = false;
100
+ result.envName = null;
89
101
  result.envPath = null;
90
102
  result.url = null;
91
103
  emitResult(result);
92
104
  return;
93
105
  }
94
- if (authSource.startsWith("specific env file")) {
106
+ if (authSource.startsWith("env name")) {
107
+ result.envName = await askInput("Env name", "TRIAL");
108
+ result.useSessionEnv = false;
109
+ result.envPath = null;
110
+ result.mcpDestination = null;
111
+ result.url = null;
112
+ emitResult(result);
113
+ return;
114
+ }
115
+ if (authSource.startsWith("env file")) {
95
116
  result.envPath = await askInput("Path to .env file");
96
117
  result.useSessionEnv = false;
118
+ result.envName = null;
97
119
  result.mcpDestination = null;
98
120
  result.url = null;
99
121
  emitResult(result);
100
122
  return;
101
123
  }
102
- result.useSessionEnv = true;
103
- result.envPath = null;
104
- result.mcpDestination = null;
105
- result.url = null;
106
- emitResult(result);
107
- return;
108
124
  }
109
125
 
110
126
  result.url = await askInput("Server URL (http/https)");
@@ -118,7 +134,13 @@ async function main() {
118
134
  }
119
135
 
120
136
  async function configureUpdate(result, client) {
121
- const current = getServerConfig(client, result.scope, result.name);
137
+ const current = getServerConfig(
138
+ client,
139
+ result.scope,
140
+ result.name,
141
+ result.allProjects,
142
+ result.projectPath,
143
+ );
122
144
  const currentTransport = current.transport || "stdio";
123
145
  result.transport = currentTransport;
124
146
  result.command = current.command || "mcp-abap-adt";
@@ -128,42 +150,48 @@ async function configureUpdate(result, client) {
128
150
 
129
151
  if (currentTransport === "stdio") {
130
152
  const authChoices = [
131
- "service key destination (--mcp)",
132
- "session environment (--env)",
133
- "specific env file (--env-path)",
153
+ "destination (--mcp=<name>)",
154
+ "env name (--env=<name>)",
155
+ "env file (--env-path=<name>)",
134
156
  ];
135
157
  const authType = current.auth?.type || "unknown";
136
158
  const authInitial =
137
159
  authType === "mcp" ? 0 : authType === "env" ? 1 : authType === "env-path" ? 2 : 0;
138
160
  const authSource = await askSelect("Auth source for stdio", authChoices, null, authInitial);
139
- if (authSource.startsWith("service key")) {
161
+ if (authSource.startsWith("destination")) {
140
162
  result.mcpDestination = await askInput("Destination name", current.auth?.value || "TRIAL");
141
163
  result.useSessionEnv = false;
164
+ result.envName = null;
142
165
  result.envPath = null;
143
166
  result.url = null;
144
167
  result.headers = {};
145
168
  return;
146
169
  }
147
- if (authSource.startsWith("specific env file")) {
170
+ if (authSource.startsWith("env name")) {
171
+ result.envName = await askInput("Env name", current.auth?.value || "TRIAL");
172
+ result.useSessionEnv = false;
173
+ result.envPath = null;
174
+ result.mcpDestination = null;
175
+ result.url = null;
176
+ result.headers = {};
177
+ return;
178
+ }
179
+ if (authSource.startsWith("env file")) {
148
180
  result.envPath = await askInput("Path to .env file", current.auth?.value || undefined);
149
181
  result.useSessionEnv = false;
182
+ result.envName = null;
150
183
  result.mcpDestination = null;
151
184
  result.url = null;
152
185
  result.headers = {};
153
186
  return;
154
187
  }
155
- result.useSessionEnv = true;
156
- result.envPath = null;
157
- result.mcpDestination = null;
158
- result.url = null;
159
- result.headers = {};
160
- return;
161
188
  }
162
189
 
163
190
  result.url = await askInput("Server URL (http/https)", current.url || undefined);
164
191
  result.timeout = await askPositiveNumber("Timeout seconds", result.timeout);
165
192
  result.headers = await askHeaders(current.headers || {});
166
193
  result.useSessionEnv = false;
194
+ result.envName = null;
167
195
  result.envPath = null;
168
196
  result.mcpDestination = null;
169
197
  }
@@ -173,10 +201,15 @@ function emitResult(result) {
173
201
  }
174
202
 
175
203
  async function askSelect(message, choices, choiceLabels, initial = 0) {
176
- const promptChoices = choices.map((value, index) => ({
177
- name: value,
178
- message: choiceLabels?.[index] || value,
179
- }));
204
+ const promptChoices = choices.map((value, index) => {
205
+ if (value && typeof value === "object" && !Array.isArray(value)) {
206
+ return value;
207
+ }
208
+ return {
209
+ name: value,
210
+ message: choiceLabels?.[index] || value,
211
+ };
212
+ });
180
213
  const select = new Select({
181
214
  name: "value",
182
215
  message,
@@ -233,12 +266,39 @@ function getSupportedScopes(clientName) {
233
266
  if (clientName === "copilot") {
234
267
  return ["local"];
235
268
  }
236
- if (["cline", "goose", "windsurf", "antigravity"].includes(clientName)) {
269
+ if (["cline", "goose", "windsurf", "antigravity", "qwen"].includes(clientName)) {
237
270
  return ["global"];
238
271
  }
239
272
  return ["global", "local"];
240
273
  }
241
274
 
275
+ async function configureScope(result, clientName) {
276
+ result.allProjects = false;
277
+ result.projectPath = null;
278
+
279
+ if (clientName === "claude") {
280
+ result.scope = await askSelect("Scope", ["global", "local"]);
281
+ if (result.scope === "local") {
282
+ return;
283
+ }
284
+ if (supportsClaudeGlobalAllProjects(result.tuiAction)) {
285
+ const globalMode = await askSelect("Claude global", [
286
+ { name: "single", message: "for current project" },
287
+ { name: "all", message: "for all projects" },
288
+ ]);
289
+ result.allProjects = globalMode === "all";
290
+ }
291
+ return;
292
+ }
293
+
294
+ const scopes = getSupportedScopes(clientName);
295
+ result.scope = scopes.length === 1 ? scopes[0] : await askSelect("Scope", ["global", "local"]);
296
+ }
297
+
298
+ function supportsClaudeGlobalAllProjects(action) {
299
+ return ["ls", "show", "rm", "enable", "disable"].includes(action);
300
+ }
301
+
242
302
  function getSupportedTransports(clientName) {
243
303
  if (clientName === "codex") {
244
304
  return ["stdio", "http"];
@@ -246,10 +306,16 @@ function getSupportedTransports(clientName) {
246
306
  return ["stdio", "sse", "http"];
247
307
  }
248
308
 
249
- function listExistingServers(clientName, scope) {
309
+ function listExistingServers(clientName, scope, allProjects = false, projectPath = null) {
250
310
  const cliPath = path.join(__dirname, "mcp-conf.js");
251
311
  const scopeArg = scope === "local" ? "--local" : "--global";
252
- const run = spawnSync(process.execPath, [cliPath, "ls", "--client", clientName, scopeArg], {
312
+ const cliArgs = [cliPath, "ls", "--client", clientName, scopeArg];
313
+ if (allProjects) {
314
+ cliArgs.push("--all-projects");
315
+ } else if (projectPath) {
316
+ cliArgs.push("--project", projectPath);
317
+ }
318
+ const run = spawnSync(process.execPath, cliArgs, {
253
319
  encoding: "utf8",
254
320
  });
255
321
  if (run.status !== 0) {
@@ -271,16 +337,28 @@ function listExistingServers(clientName, scope) {
271
337
  return [...new Set(names)].sort((a, b) => a.localeCompare(b));
272
338
  }
273
339
 
274
- function getServerConfig(clientName, scope, serverName) {
340
+ function getServerConfig(clientName, scope, serverName, allProjects = false, projectPath = null) {
275
341
  const cliPath = path.join(__dirname, "mcp-conf.js");
276
342
  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
- );
343
+ const cliArgs = [
344
+ cliPath,
345
+ "show",
346
+ "--client",
347
+ clientName,
348
+ "--name",
349
+ serverName,
350
+ scopeArg,
351
+ "--json",
352
+ "--normalized",
353
+ ];
354
+ if (allProjects) {
355
+ cliArgs.push("--all-projects");
356
+ } else if (projectPath) {
357
+ cliArgs.push("--project", projectPath);
358
+ }
359
+ const run = spawnSync(process.execPath, cliArgs, {
360
+ encoding: "utf8",
361
+ });
284
362
  if (run.status !== 0) {
285
363
  const stderr = String(run.stderr || "").trim();
286
364
  throw new Error(stderr || "Failed to read existing server config");
@@ -293,9 +371,40 @@ function getServerConfig(clientName, scope, serverName) {
293
371
  }
294
372
 
295
373
  main().catch((error) => {
296
- if (error === "") {
374
+ if (isPromptCancelError(error)) {
375
+ process.exit(0);
376
+ }
377
+ process.stderr.write(`${error?.message || String(error)}\n`);
378
+ process.exit(1);
379
+ });
380
+
381
+ process.on("uncaughtException", (error) => {
382
+ if (isPromptCancelError(error)) {
297
383
  process.exit(0);
298
384
  }
299
385
  process.stderr.write(`${error?.message || String(error)}\n`);
300
386
  process.exit(1);
301
387
  });
388
+
389
+ process.on("unhandledRejection", (error) => {
390
+ if (isPromptCancelError(error)) {
391
+ process.exit(0);
392
+ }
393
+ process.stderr.write(`${error?.message || String(error)}\n`);
394
+ process.exit(1);
395
+ });
396
+
397
+ function isPromptCancelError(error) {
398
+ if (error === "" || error === null || error === undefined) {
399
+ return true;
400
+ }
401
+ if (typeof error === "string") {
402
+ return error.toLowerCase().includes("cancel");
403
+ }
404
+ const message = String(error?.message || "");
405
+ return (
406
+ error?.code === "ERR_USE_AFTER_CLOSE" ||
407
+ message.toLowerCase().includes("cancel") ||
408
+ message.toLowerCase().includes("readline was closed")
409
+ );
410
+ }
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
 
@@ -395,6 +419,18 @@ for (const client of options.clients) {
395
419
  writeJsonConfig(getAntigravityPath(home, scope), options.name, serverArgs, "antigravity");
396
420
  }
397
421
  break;
422
+ case "qwen":
423
+ requireScope("Qwen", ["global"], scope);
424
+ if (options.list) {
425
+ listJsonConfig(getQwenPath(home), "qwen");
426
+ } else if (options.show) {
427
+ showJsonConfig(getQwenPath(home), "qwen", options.name);
428
+ } else if (options.where) {
429
+ whereJsonConfig(getQwenPath(home), "qwen", options.name);
430
+ } else {
431
+ writeJsonConfig(getQwenPath(home), options.name, serverArgs, "qwen");
432
+ }
433
+ break;
398
434
  case "copilot":
399
435
  requireScope("GitHub Copilot", ["local"], scope);
400
436
  if (options.list) {
@@ -489,7 +525,8 @@ function runTuiWizard(opts) {
489
525
  const rawPayload = run.output?.[3] || "";
490
526
  const payload = String(rawPayload).trim();
491
527
  if (!payload) {
492
- fail("TUI did not return configuration.");
528
+ opts.tuiAction = "exit";
529
+ return;
493
530
  }
494
531
  let selected;
495
532
  try {
@@ -590,6 +627,10 @@ function getAntigravityPath(homeDir, scopeValue) {
590
627
  return path.join(homeDir, ".gemini", "antigravity", "mcp_config.json");
591
628
  }
592
629
 
630
+ function getQwenPath(homeDir) {
631
+ return path.join(homeDir, ".qwen", "settings.json");
632
+ }
633
+
593
634
  function getWindsurfPath(platformValue, homeDir, userProfileDir) {
594
635
  if (platformValue === "win32") {
595
636
  return path.join(userProfileDir, ".codeium", "windsurf", "mcp_config.json");
@@ -1314,7 +1355,10 @@ function showJsonConfig(filePath, clientType, serverName) {
1314
1355
  if (!entry) {
1315
1356
  fail(`Server "${serverName}" not found in ${filePath}.`);
1316
1357
  }
1317
- outputShow(filePath, serverName, normalizeServerDetails(clientType, serverName, entry));
1358
+ const details = options.outputNormalized
1359
+ ? normalizeServerDetails(clientType, serverName, entry)
1360
+ : cloneJsonLike(entry);
1361
+ outputShow(filePath, serverName, details);
1318
1362
  }
1319
1363
 
1320
1364
  function showCodexConfig(filePath, serverName) {
@@ -1327,7 +1371,10 @@ function showCodexConfig(filePath, serverName) {
1327
1371
  if (!entry) {
1328
1372
  fail(`Server "${serverName}" not found in ${filePath}.`);
1329
1373
  }
1330
- outputShow(filePath, serverName, normalizeServerDetails("codex", serverName, entry));
1374
+ const details = options.outputNormalized
1375
+ ? normalizeServerDetails("codex", serverName, entry)
1376
+ : cloneJsonLike(entry);
1377
+ outputShow(filePath, serverName, details);
1331
1378
  }
1332
1379
 
1333
1380
  function showGooseConfig(filePath, serverName) {
@@ -1340,7 +1387,10 @@ function showGooseConfig(filePath, serverName) {
1340
1387
  if (!entry) {
1341
1388
  fail(`Server "${serverName}" not found in ${filePath}.`);
1342
1389
  }
1343
- outputShow(filePath, serverName, normalizeServerDetails("goose", serverName, entry));
1390
+ const details = options.outputNormalized
1391
+ ? normalizeServerDetails("goose", serverName, entry)
1392
+ : cloneJsonLike(entry);
1393
+ outputShow(filePath, serverName, details);
1344
1394
  }
1345
1395
 
1346
1396
  function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
@@ -1355,10 +1405,17 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1355
1405
  const projectNode = data.projects?.[key];
1356
1406
  const store = projectNode?.mcpServers || {};
1357
1407
  if (store[serverName]) {
1358
- results.push({
1359
- project: key,
1360
- ...normalizeServerDetails("claude", serverName, store[serverName]),
1361
- });
1408
+ results.push(
1409
+ options.outputNormalized
1410
+ ? {
1411
+ project: key,
1412
+ ...normalizeServerDetails("claude", serverName, store[serverName]),
1413
+ }
1414
+ : {
1415
+ project: key,
1416
+ raw: cloneJsonLike(store[serverName]),
1417
+ },
1418
+ );
1362
1419
  }
1363
1420
  }
1364
1421
  if (!results.length) {
@@ -1369,7 +1426,12 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1369
1426
  return;
1370
1427
  }
1371
1428
  for (const result of results) {
1372
- outputShow(filePath, serverName, result, result.project);
1429
+ outputShow(
1430
+ filePath,
1431
+ serverName,
1432
+ options.outputNormalized ? result : result.raw,
1433
+ result.project,
1434
+ );
1373
1435
  }
1374
1436
  return;
1375
1437
  }
@@ -1379,12 +1441,10 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1379
1441
  if (!entry) {
1380
1442
  fail(`Server "${serverName}" not found for ${projectKey}.`);
1381
1443
  }
1382
- outputShow(
1383
- filePath,
1384
- serverName,
1385
- normalizeServerDetails("claude", serverName, entry, projectKey),
1386
- projectKey,
1387
- );
1444
+ const details = options.outputNormalized
1445
+ ? normalizeServerDetails("claude", serverName, entry, projectKey)
1446
+ : cloneJsonLike(entry);
1447
+ outputShow(filePath, serverName, details, projectKey);
1388
1448
  return;
1389
1449
  }
1390
1450
  const store = data.mcpServers || {};
@@ -1392,7 +1452,10 @@ function showClaudeConfig(filePath, serverName, allProjects, projectPath) {
1392
1452
  if (!entry) {
1393
1453
  fail(`Server "${serverName}" not found in ${filePath}.`);
1394
1454
  }
1395
- outputShow(filePath, serverName, normalizeServerDetails("claude", serverName, entry));
1455
+ const details = options.outputNormalized
1456
+ ? normalizeServerDetails("claude", serverName, entry)
1457
+ : cloneJsonLike(entry);
1458
+ outputShow(filePath, serverName, details);
1396
1459
  }
1397
1460
 
1398
1461
  function normalizeServerDetails(clientType, serverName, entry, projectKey) {
@@ -1439,9 +1502,21 @@ function compactServerDetails(details) {
1439
1502
  return compact;
1440
1503
  }
1441
1504
 
1505
+ function cloneJsonLike(value) {
1506
+ if (value === null || value === undefined) {
1507
+ return value;
1508
+ }
1509
+ try {
1510
+ return JSON.parse(JSON.stringify(value));
1511
+ } catch {
1512
+ return value;
1513
+ }
1514
+ }
1515
+
1442
1516
  function parseServerArgs(args) {
1443
1517
  const parsed = {
1444
1518
  transport: null,
1519
+ envName: null,
1445
1520
  envPath: null,
1446
1521
  mcpDestination: null,
1447
1522
  useSessionEnv: false,
@@ -1456,17 +1531,28 @@ function parseServerArgs(args) {
1456
1531
  parsed.transport = value === "streamableHttp" ? "http" : value;
1457
1532
  } else if (arg === "--session-env") {
1458
1533
  parsed.useSessionEnv = true;
1534
+ } else if (arg.startsWith("--env=")) {
1535
+ const value = arg.slice("--env=".length);
1536
+ if (looksLikeEnvPath(value)) {
1537
+ parsed.envPath = value;
1538
+ } else {
1539
+ parsed.envName = value;
1540
+ }
1459
1541
  } else if (arg === "--env") {
1460
1542
  const next = args[i + 1];
1461
1543
  if (typeof next === "string" && next && !next.startsWith("-")) {
1462
- // Backward-compatible form: --env /path/to/.env
1463
- parsed.envPath = next;
1544
+ if (looksLikeEnvPath(next)) {
1545
+ // Backward-compatible form: --env /path/to/.env
1546
+ parsed.envPath = next;
1547
+ } else {
1548
+ parsed.envName = next;
1549
+ }
1464
1550
  parsed.useSessionEnv = false;
1465
1551
  i += 1;
1466
1552
  } else {
1467
1553
  parsed.useSessionEnv = true;
1468
1554
  }
1469
- } else if (arg.startsWith("--env-path=") || arg.startsWith("--env=")) {
1555
+ } else if (arg.startsWith("--env-path=")) {
1470
1556
  parsed.envPath = arg.slice(arg.indexOf("=") + 1);
1471
1557
  } else if (arg === "--env-path") {
1472
1558
  const next = args[i + 1];
@@ -1557,15 +1643,31 @@ function inferAuth(parsedArgs) {
1557
1643
  if (parsedArgs.mcpDestination) {
1558
1644
  return { type: "mcp", value: parsedArgs.mcpDestination };
1559
1645
  }
1646
+ if (parsedArgs.envName) {
1647
+ return { type: "env", value: parsedArgs.envName };
1648
+ }
1560
1649
  if (parsedArgs.envPath) {
1561
1650
  return { type: "env-path", value: parsedArgs.envPath };
1562
1651
  }
1563
1652
  if (parsedArgs.useSessionEnv) {
1564
- return { type: "env", value: null };
1653
+ return { type: "session-env", value: null };
1565
1654
  }
1566
1655
  return { type: "unknown", value: null };
1567
1656
  }
1568
1657
 
1658
+ function looksLikeEnvPath(value) {
1659
+ if (!value || typeof value !== "string") {
1660
+ return false;
1661
+ }
1662
+ return (
1663
+ value.includes("/") ||
1664
+ value.includes("\\") ||
1665
+ value.endsWith(".env") ||
1666
+ value.startsWith(".") ||
1667
+ value.startsWith("~")
1668
+ );
1669
+ }
1670
+
1569
1671
  function readJson(filePath) {
1570
1672
  if (!fs.existsSync(filePath)) {
1571
1673
  return {};
@@ -1697,13 +1799,14 @@ Notes:
1697
1799
  process.stdout.write(`${header} add
1698
1800
 
1699
1801
  Usage:
1700
- mcp-conf add --client <name> --name <serverName> [--env | --env-path <path> | --mcp <dest>] [options]
1802
+ mcp-conf add --client <name> --name <serverName> [--env <name> | --env-path <path> | --session-env | --mcp <dest>] [options]
1701
1803
 
1702
1804
  Options:
1703
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1805
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1704
1806
  --name <serverName> required MCP server name key
1705
- --env use current shell/session env vars (stdio only)
1807
+ --env <name> env profile name (stdio only), writes --env=<name>
1706
1808
  --env-path <path> .env path (stdio only)
1809
+ --session-env use current shell/session env vars (stdio only)
1707
1810
  --mcp <dest> destination name (stdio only)
1708
1811
  --transport <type> stdio | sse | http (http => streamableHttp)
1709
1812
  --command <bin> command to run (default: mcp-abap-adt)
@@ -1717,7 +1820,7 @@ Options:
1717
1820
  --dry-run print changes without writing files
1718
1821
 
1719
1822
  Notes:
1720
- Antigravity local scope is not supported yet; use --global.
1823
+ Antigravity and Qwen are global-only; use --global.
1721
1824
  `);
1722
1825
  break;
1723
1826
  case "rm":
@@ -1727,7 +1830,7 @@ Usage:
1727
1830
  mcp-conf rm --client <name> --name <serverName> [options]
1728
1831
 
1729
1832
  Options:
1730
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1833
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1731
1834
  --name <serverName> required MCP server name key
1732
1835
  --global write to global user config (default)
1733
1836
  --local write to project config (where supported)
@@ -1736,7 +1839,7 @@ Options:
1736
1839
  --dry-run print changes without writing files
1737
1840
 
1738
1841
  Notes:
1739
- Antigravity local scope is not supported yet; use --global.
1842
+ Antigravity and Qwen are global-only; use --global.
1740
1843
  `);
1741
1844
  break;
1742
1845
  case "ls":
@@ -1746,14 +1849,14 @@ Usage:
1746
1849
  mcp-conf ls --client <name> [options]
1747
1850
 
1748
1851
  Options:
1749
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1852
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1750
1853
  --global write to global user config (default)
1751
1854
  --local write to project config (where supported)
1752
1855
  --all-projects Claude global: list across all projects
1753
1856
  --project <path> Claude global: target a specific project path
1754
1857
 
1755
1858
  Notes:
1756
- Antigravity local scope is not supported yet; use --global.
1859
+ Antigravity and Qwen are global-only; use --global.
1757
1860
  `);
1758
1861
  break;
1759
1862
  case "enable":
@@ -1763,7 +1866,7 @@ Usage:
1763
1866
  mcp-conf enable --client <name> --name <serverName> [options]
1764
1867
 
1765
1868
  Options:
1766
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1869
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1767
1870
  --name <serverName> required MCP server name key
1768
1871
  --global write to global user config (default)
1769
1872
  --local write to project config (where supported)
@@ -1772,7 +1875,7 @@ Options:
1772
1875
  --dry-run print changes without writing files
1773
1876
 
1774
1877
  Notes:
1775
- Antigravity local scope is not supported yet; use --global.
1878
+ Antigravity and Qwen are global-only; use --global.
1776
1879
  `);
1777
1880
  break;
1778
1881
  case "disable":
@@ -1782,7 +1885,7 @@ Usage:
1782
1885
  mcp-conf disable --client <name> --name <serverName> [options]
1783
1886
 
1784
1887
  Options:
1785
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1888
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1786
1889
  --name <serverName> required MCP server name key
1787
1890
  --global write to global user config (default)
1788
1891
  --local write to project config (where supported)
@@ -1791,7 +1894,7 @@ Options:
1791
1894
  --dry-run print changes without writing files
1792
1895
 
1793
1896
  Notes:
1794
- Antigravity local scope is not supported yet; use --global.
1897
+ Antigravity and Qwen are global-only; use --global.
1795
1898
  `);
1796
1899
  break;
1797
1900
  case "where":
@@ -1801,7 +1904,7 @@ Usage:
1801
1904
  mcp-conf where --client <name> --name <serverName> [options]
1802
1905
 
1803
1906
  Options:
1804
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1907
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1805
1908
  --name <serverName> required MCP server name key
1806
1909
  --global write to global user config (default)
1807
1910
  --local write to project config (where supported)
@@ -1809,7 +1912,7 @@ Options:
1809
1912
  --project <path> Claude global: target a specific project path
1810
1913
 
1811
1914
  Notes:
1812
- Antigravity local scope is not supported yet; use --global.
1915
+ Antigravity and Qwen are global-only; use --global.
1813
1916
  `);
1814
1917
  break;
1815
1918
  case "show":
@@ -1819,29 +1922,31 @@ Usage:
1819
1922
  mcp-conf show --client <name> --name <serverName> [options]
1820
1923
 
1821
1924
  Options:
1822
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1925
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1823
1926
  --name <serverName> required MCP server name key
1824
1927
  --global read from global user config (default)
1825
1928
  --local read from project config (where supported)
1826
1929
  --all-projects Claude global: show from all projects
1827
1930
  --project <path> Claude global: target a specific project path
1828
1931
  --json output JSON only (machine-readable)
1932
+ --normalized output normalized view (for tooling); default is raw config entry
1829
1933
 
1830
1934
  Notes:
1831
- Antigravity local scope is not supported yet; use --global.
1935
+ Antigravity and Qwen are global-only; use --global.
1832
1936
  `);
1833
1937
  break;
1834
1938
  case "update":
1835
1939
  process.stdout.write(`${header} update
1836
1940
 
1837
1941
  Usage:
1838
- mcp-conf update --client <name> --name <serverName> [--env | --env-path <path> | --mcp <dest>] [options]
1942
+ mcp-conf update --client <name> --name <serverName> [--env <name> | --env-path <path> | --session-env | --mcp <dest>] [options]
1839
1943
 
1840
1944
  Options:
1841
- --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | crush (repeatable)
1945
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | kilo | copilot | antigravity | qwen | crush (repeatable)
1842
1946
  --name <serverName> required MCP server name key
1843
- --env use current shell/session env vars (stdio only)
1947
+ --env <name> env profile name (stdio only), writes --env=<name>
1844
1948
  --env-path <path> .env path (stdio only)
1949
+ --session-env use current shell/session env vars (stdio only)
1845
1950
  --mcp <dest> destination name (stdio only)
1846
1951
  --transport <type> stdio | sse | http (http => streamableHttp)
1847
1952
  --url <http(s)://...> required for sse/http
@@ -1853,7 +1958,7 @@ Options:
1853
1958
  --dry-run print changes without writing files
1854
1959
 
1855
1960
  Notes:
1856
- Antigravity local scope is not supported yet; use --global.
1961
+ Antigravity and Qwen are global-only; use --global.
1857
1962
  `);
1858
1963
  break;
1859
1964
  case "tui":
@@ -24,6 +24,7 @@ mcp-conf add --client opencode --name abap --transport http --url http://localho
24
24
  mcp-conf add --client kilo --name abap --transport http --url http://localhost:3000/mcp/stream/http
25
25
  mcp-conf add --client copilot --name abap --transport http --url http://localhost:3000/mcp/stream/http --header x-mcp-destination=trial
26
26
  mcp-conf add --client antigravity --name abap --transport http --url http://localhost:3000/mcp/stream/http
27
+ mcp-conf add --client qwen --name abap --transport http --url http://localhost:3000/mcp/stream/http
27
28
  mcp-conf add --client crush --name abap --mcp TRIAL
28
29
  mcp-conf add --client crush --name abap --transport http --url http://localhost:3000/mcp/stream/http
29
30
  mcp-conf tui
@@ -51,6 +52,7 @@ Enable MCP:
51
52
  mcp-conf enable --client codex --name abap
52
53
  mcp-conf enable --client cline --name abap
53
54
  mcp-conf enable --client antigravity --name abap
55
+ mcp-conf enable --client qwen --name abap
54
56
  mcp-conf enable --client crush --name abap
55
57
  ```
56
58
 
@@ -60,6 +62,7 @@ mcp-conf rm --client codex --name abap
60
62
  mcp-conf rm --client cline --name abap
61
63
  mcp-conf rm --client claude --name abap
62
64
  mcp-conf rm --client antigravity --name abap
65
+ mcp-conf rm --client qwen --name abap
63
66
  mcp-conf rm --client crush --name abap
64
67
  ```
65
68
 
@@ -70,6 +73,7 @@ mcp-conf ls --client cline
70
73
  mcp-conf ls --client claude --local
71
74
  mcp-conf ls --client claude --all-projects
72
75
  mcp-conf ls --client antigravity --global
76
+ mcp-conf ls --client qwen --global
73
77
  mcp-conf ls --client crush
74
78
  mcp-conf ls --client crush --local
75
79
  ```
@@ -93,9 +97,10 @@ mcp-conf tui
93
97
 
94
98
  Options:
95
99
  - Commands: `add`, `rm`, `ls`, `show`, `enable`, `disable`, `where`, `update`, `tui` (first argument)
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)
100
+ - `--client <name>` (repeatable): `cline`, `codex`, `claude`, `goose`, `cursor`, `windsurf`, `opencode` (`kilo` alias), `copilot`, `antigravity`, `qwen`, `crush`
101
+ - `--env <name>`: use named env profile; writes `--env=<name>` (stdio only)
98
102
  - `--env-path <path>`: use a specific `.env` file (stdio only)
103
+ - `--session-env`: use shell/session environment variables (stdio only)
99
104
  - `--mcp <destination>`: use service key destination
100
105
  - `--name <serverName>`: MCP server name (required)
101
106
  - `--transport <type>`: `stdio`, `sse`, or `http` (`http` maps to `streamableHttp`)
@@ -110,18 +115,18 @@ Options:
110
115
  - `--json`: JSON-only output for `show`
111
116
 
112
117
  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`.
118
+ - `disable` and `rm` do not require `--env`, `--env-path`, `--session-env`, or `--mcp`.
119
+ - `--env`/`--env-path`/`--session-env`/`--mcp` are only valid for `stdio` transport. For `sse/http`, use `--url` and optional `--header`.
115
120
  - `mcp-conf tui` starts an interactive wizard for `ls`/`show`/`add`/`update`/`rm`/`enable`/`disable`.
116
121
  - Cursor/Copilot enable/disable are not implemented yet.
117
122
  - Antigravity enable/disable uses `disabled: true|false` on the entry.
118
- - Antigravity local scope is not supported yet; use `--global`.
123
+ - Antigravity and Qwen are global-only; use `--global`.
119
124
  - Claude stores enable/disable state under `enabledMcpServers` and `disabledMcpServers` for each project.
120
125
  - Claude enable/disable always updates `~/.claude.json` (global scope), even if you pass `--local`.
121
126
  - Antigravity HTTP entries use `serverUrl` instead of `url`.
122
127
  - New entries for Cline, Codex, Windsurf, Goose, Claude, OpenCode, and Crush are added **disabled by default**. Use `enable` to turn them on.
123
128
  - 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.
129
+ - `enable`/`disable` only work if the server entry already exists. Use add commands with `--env`, `--env-path`, `--session-env`, or `--mcp` first.
125
130
  - Non-stdio transports are supported for Cline/Cursor/Windsurf/Claude/Goose. Codex supports `http` (streamable HTTP) but not `sse`.
126
131
  - Codex writes custom headers under `http_headers` in `~/.codex/config.toml` (or `./.codex/config.toml` for `--local`).
127
132
  - Codex HTTP entries include `startup_timeout_sec` (default: 60).
@@ -162,6 +167,8 @@ Global (default) locations:
162
167
  - **Antigravity**:
163
168
  - Linux/macOS: `~/.gemini/antigravity/mcp_config.json`
164
169
  - Note: path is community-reported; verify against latest vendor docs.
170
+ - **Qwen**:
171
+ - Linux/macOS: `~/.qwen/settings.json` (uses `mcpServers.<name>`)
165
172
  - **Crush**:
166
173
  - Linux/macOS: `~/.config/crush/crush.json`
167
174
  - Windows: `%USERPROFILE%\AppData\Local\crush\crush.json`
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.1",
4
4
  "description": "MCP client configurator for mcp-abap-adt and mcp-abap-adt-proxy",
5
5
  "license": "MIT",
6
6
  "repository": {