@mcp-abap-adt/configurator 0.0.2 → 0.0.3

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/bin/mcp-conf.js CHANGED
@@ -22,6 +22,10 @@ try {
22
22
  }
23
23
 
24
24
  const args = process.argv.slice(2);
25
+ const action = args[0] && !args[0].startsWith("-") ? args[0] : null;
26
+ if (action && ["add", "rm", "ls", "enable", "disable", "where", "help"].includes(action)) {
27
+ args.shift();
28
+ }
25
29
  const options = {
26
30
  clients: [],
27
31
  envPath: null,
@@ -29,18 +33,29 @@ const options = {
29
33
  name: null,
30
34
  transport: "stdio",
31
35
  command: "mcp-abap-adt",
36
+ scope: null,
32
37
  dryRun: false,
33
38
  force: false,
34
39
  disabled: false,
35
40
  toggle: false,
36
41
  remove: false,
42
+ list: false,
43
+ allProjects: false,
44
+ projectPath: null,
45
+ where: false,
37
46
  url: null,
38
47
  headers: {},
39
48
  timeout: 60,
40
49
  };
41
50
 
42
- if (args.includes("--help") || args.includes("-h")) {
43
- printHelp();
51
+ if (args.includes("--help") || args.includes("-h") || action === "help") {
52
+ const helpAction =
53
+ action === "help"
54
+ ? args[0]
55
+ : action && ["add", "rm", "ls", "enable", "disable", "where"].includes(action)
56
+ ? action
57
+ : null;
58
+ printHelp(helpAction);
44
59
  process.exit(0);
45
60
  }
46
61
 
@@ -64,6 +79,21 @@ for (let i = 0; i < args.length; i += 1) {
64
79
  } else if (arg === "--command") {
65
80
  options.command = args[i + 1];
66
81
  i += 1;
82
+ } else if (arg === "--global") {
83
+ if (options.scope && options.scope !== "global") {
84
+ fail("Choose either --global or --local (not both).");
85
+ }
86
+ options.scope = "global";
87
+ } else if (arg === "--local") {
88
+ if (options.scope && options.scope !== "local") {
89
+ fail("Choose either --global or --local (not both).");
90
+ }
91
+ options.scope = "local";
92
+ } else if (arg === "--all-projects") {
93
+ options.allProjects = true;
94
+ } else if (arg === "--project") {
95
+ options.projectPath = args[i + 1];
96
+ i += 1;
67
97
  } else if (arg === "--url") {
68
98
  options.url = args[i + 1];
69
99
  i += 1;
@@ -92,18 +122,62 @@ for (let i = 0; i < args.length; i += 1) {
92
122
  }
93
123
  }
94
124
 
125
+ if (!action || !["add", "rm", "ls", "enable", "disable", "where"].includes(action)) {
126
+ fail("Provide a command: add | rm | ls | enable | disable | where.");
127
+ }
128
+
95
129
  if (options.clients.length === 0) {
96
130
  fail("Provide at least one --client.");
97
131
  }
98
132
 
99
- if (!options.name) {
133
+ if (!["ls"].includes(action) && !options.name) {
100
134
  fail("Provide --name <serverName> (required).");
101
135
  }
102
136
 
103
137
  const transportNormalized = options.transport === "http" ? "streamableHttp" : options.transport;
104
138
  options.transport = transportNormalized;
105
139
 
106
- const requiresConnectionParams = !options.remove && !options.toggle;
140
+ if (action === "rm") {
141
+ options.remove = true;
142
+ }
143
+ if (action === "ls") {
144
+ options.list = true;
145
+ }
146
+ if (action === "enable") {
147
+ options.toggle = true;
148
+ options.disabled = false;
149
+ }
150
+ if (action === "disable") {
151
+ options.toggle = true;
152
+ options.disabled = true;
153
+ }
154
+ if (action === "where") {
155
+ options.where = true;
156
+ }
157
+
158
+ if (options.remove && action !== "rm") {
159
+ fail("Use the rm command instead of --remove.");
160
+ }
161
+ if (options.list && options.toggle) {
162
+ fail("The ls command does not support --enable/--disable.");
163
+ }
164
+ if (options.remove && options.toggle) {
165
+ fail("The rm command does not support --enable/--disable.");
166
+ }
167
+ if (options.allProjects && !options.list && !options.toggle && !options.remove && !options.where) {
168
+ fail("--all-projects is only supported for rm/enable/disable/ls/where.");
169
+ }
170
+ if (options.projectPath && options.allProjects) {
171
+ fail("Use either --project or --all-projects (not both).");
172
+ }
173
+ if (options.where && (options.list || options.remove || options.toggle)) {
174
+ fail("The where command does not support ls/rm/enable/disable flags.");
175
+ }
176
+ if (options.projectPath && action === "add" && options.scope !== "global") {
177
+ fail("--project is only supported for Claude global config.");
178
+ }
179
+
180
+ const requiresConnectionParams = !options.remove && !options.toggle && !options.list && !options.where;
107
181
 
108
182
  if (requiresConnectionParams && options.transport === "stdio") {
109
183
  if (!options.envPath && !options.mcpDestination) {
@@ -135,44 +209,135 @@ const serverArgsRaw = [
135
209
  ];
136
210
  const serverArgs = serverArgsRaw.filter(Boolean);
137
211
 
138
- for (const client of options.clients) {
139
- switch (client) {
212
+ for (const client of options.clients) {
213
+ const scope = options.scope || getDefaultScope(client);
214
+ if (options.projectPath && client !== "claude") {
215
+ fail("--project is only supported for Claude.");
216
+ }
217
+ switch (client) {
140
218
  case "cline":
141
- writeJsonConfig(getClinePath(platform, home, appData), options.name, serverArgs, "cline");
219
+ requireScope("Cline", ["global"], scope);
220
+ if (options.list) {
221
+ listJsonConfig(getClinePath(platform, home, appData), "cline");
222
+ } else if (options.where) {
223
+ whereJsonConfig(getClinePath(platform, home, appData), "cline", options.name);
224
+ } else {
225
+ writeJsonConfig(getClinePath(platform, home, appData), options.name, serverArgs, "cline");
226
+ }
142
227
  break;
143
228
  case "codex":
144
229
  if (options.transport === "sse") {
145
230
  fail("Codex does not support SSE transport.");
146
231
  }
147
- writeCodexConfig(getCodexPath(platform, home, userProfile), options.name, serverArgs);
232
+ requireScope("Codex", ["global"], scope);
233
+ if (options.list) {
234
+ listCodexConfig(getCodexPath(platform, home, userProfile));
235
+ } else if (options.where) {
236
+ whereCodexConfig(getCodexPath(platform, home, userProfile), options.name);
237
+ } else {
238
+ writeCodexConfig(getCodexPath(platform, home, userProfile), options.name, serverArgs);
239
+ }
148
240
  break;
149
241
  case "claude":
150
- writeClaudeConfig(getClaudePath(platform, home, appData), options.name, serverArgs);
242
+ requireScope("Claude", ["global", "local"], scope);
243
+ const claudeToggleScope = options.toggle ? "global" : scope;
244
+ if (options.allProjects && claudeToggleScope !== "global") {
245
+ fail("--all-projects is only supported for Claude global config.");
246
+ }
247
+ if (options.projectPath && claudeToggleScope !== "global") {
248
+ fail("--project is only supported for Claude global config.");
249
+ }
250
+ if (options.toggle && scope === "local") {
251
+ const localPath = getClaudePath(platform, home, appData, "local");
252
+ if (!claudeLocalHasServer(localPath, options.name)) {
253
+ fail(`Server "${options.name}" not found in ${localPath}.`);
254
+ }
255
+ }
256
+ if (options.list) {
257
+ listClaudeConfig(
258
+ getClaudePath(platform, home, appData, claudeToggleScope),
259
+ options.allProjects,
260
+ options.projectPath,
261
+ );
262
+ } else if (options.where) {
263
+ whereClaudeConfig(
264
+ getClaudePath(platform, home, appData, claudeToggleScope),
265
+ options.name,
266
+ options.allProjects,
267
+ options.projectPath,
268
+ );
269
+ } else {
270
+ writeClaudeConfig(
271
+ getClaudePath(platform, home, appData, claudeToggleScope),
272
+ options.name,
273
+ serverArgs,
274
+ );
275
+ }
151
276
  break;
152
277
  case "goose":
153
- writeGooseConfig(getGoosePath(platform, home, appData), options.name, serverArgs);
278
+ requireScope("Goose", ["global"], scope);
279
+ if (options.list) {
280
+ listGooseConfig(getGoosePath(platform, home, appData));
281
+ } else if (options.where) {
282
+ whereGooseConfig(getGoosePath(platform, home, appData), options.name);
283
+ } else {
284
+ writeGooseConfig(getGoosePath(platform, home, appData), options.name, serverArgs);
285
+ }
154
286
  break;
155
287
  case "opencode":
156
- writeJsonConfig(getOpenCodePath(), options.name, serverArgs, "opencode");
288
+ requireScope("OpenCode", ["global", "local"], scope);
289
+ if (options.list) {
290
+ listJsonConfig(getOpenCodePath(platform, home, appData, scope), "opencode");
291
+ } else if (options.where) {
292
+ whereJsonConfig(getOpenCodePath(platform, home, appData, scope), "opencode", options.name);
293
+ } else {
294
+ writeJsonConfig(
295
+ getOpenCodePath(platform, home, appData, scope),
296
+ options.name,
297
+ serverArgs,
298
+ "opencode",
299
+ );
300
+ }
157
301
  break;
158
302
  case "copilot":
159
- writeJsonConfig(getCopilotPath(), options.name, serverArgs, "copilot");
303
+ requireScope("GitHub Copilot", ["local"], scope);
304
+ if (options.list) {
305
+ listJsonConfig(getCopilotPath(), "copilot");
306
+ } else if (options.where) {
307
+ whereJsonConfig(getCopilotPath(), "copilot", options.name);
308
+ } else {
309
+ writeJsonConfig(getCopilotPath(), options.name, serverArgs, "copilot");
310
+ }
160
311
  break;
161
312
  case "cursor":
162
- writeJsonConfig(
163
- getCursorPath(platform, home, userProfile),
164
- options.name,
165
- serverArgs,
166
- "cursor",
167
- );
313
+ requireScope("Cursor", ["global", "local"], scope);
314
+ if (options.list) {
315
+ listJsonConfig(getCursorPath(platform, home, userProfile, scope), "cursor");
316
+ } else if (options.where) {
317
+ whereJsonConfig(getCursorPath(platform, home, userProfile, scope), "cursor", options.name);
318
+ } else {
319
+ writeJsonConfig(
320
+ getCursorPath(platform, home, userProfile, scope),
321
+ options.name,
322
+ serverArgs,
323
+ "cursor",
324
+ );
325
+ }
168
326
  break;
169
327
  case "windsurf":
170
- writeJsonConfig(
171
- getWindsurfPath(platform, home, userProfile),
172
- options.name,
173
- serverArgs,
174
- "windsurf",
175
- );
328
+ requireScope("Windsurf", ["global"], scope);
329
+ if (options.list) {
330
+ listJsonConfig(getWindsurfPath(platform, home, userProfile), "windsurf");
331
+ } else if (options.where) {
332
+ whereJsonConfig(getWindsurfPath(platform, home, userProfile), "windsurf", options.name);
333
+ } else {
334
+ writeJsonConfig(
335
+ getWindsurfPath(platform, home, userProfile),
336
+ options.name,
337
+ serverArgs,
338
+ "windsurf",
339
+ );
340
+ }
176
341
  break;
177
342
  default:
178
343
  fail(`Unknown client: ${client}`);
@@ -210,7 +375,10 @@ function getCodexPath(platformValue, homeDir, userProfileDir) {
210
375
  return path.join(homeDir, ".codex", "config.toml");
211
376
  }
212
377
 
213
- function getClaudePath(platformValue, homeDir, appDataDir) {
378
+ function getClaudePath(platformValue, homeDir, appDataDir, scopeValue) {
379
+ if (scopeValue === "local") {
380
+ return path.join(process.cwd(), ".mcp.json");
381
+ }
214
382
  if (platformValue === "darwin") {
215
383
  return path.join(
216
384
  homeDir,
@@ -233,7 +401,10 @@ function getGoosePath(platformValue, homeDir, appDataDir) {
233
401
  return path.join(homeDir, ".config", "goose", "config.yaml");
234
402
  }
235
403
 
236
- function getCursorPath(platformValue, homeDir, userProfileDir) {
404
+ function getCursorPath(platformValue, homeDir, userProfileDir, scopeValue) {
405
+ if (scopeValue === "local") {
406
+ return path.join(process.cwd(), ".cursor", "mcp.json");
407
+ }
237
408
  const base = platformValue === "win32" ? userProfileDir : homeDir;
238
409
  return path.join(base, ".cursor", "mcp.json");
239
410
  }
@@ -242,8 +413,14 @@ function getCopilotPath() {
242
413
  return path.join(process.cwd(), ".vscode", "mcp.json");
243
414
  }
244
415
 
245
- function getOpenCodePath() {
246
- return path.join(process.cwd(), "opencode.json");
416
+ function getOpenCodePath(platformValue, homeDir, appDataDir, scopeValue) {
417
+ if (scopeValue === "local") {
418
+ return path.join(process.cwd(), "opencode.json");
419
+ }
420
+ if (platformValue === "win32") {
421
+ return path.join(appDataDir, "opencode", "opencode.json");
422
+ }
423
+ return path.join(homeDir, ".config", "opencode", "opencode.json");
247
424
  }
248
425
 
249
426
  function getWindsurfPath(platformValue, homeDir, userProfileDir) {
@@ -253,6 +430,50 @@ function getWindsurfPath(platformValue, homeDir, userProfileDir) {
253
430
  return path.join(homeDir, ".codeium", "windsurf", "mcp_config.json");
254
431
  }
255
432
 
433
+ function requireScope(clientLabel, allowedScopes, requestedScope) {
434
+ if (!allowedScopes.includes(requestedScope)) {
435
+ fail(
436
+ `${clientLabel} supports ${allowedScopes.join("/")} configuration only. ` +
437
+ `Use --${allowedScopes[0]}.`,
438
+ );
439
+ }
440
+ }
441
+
442
+ function getDefaultScope(clientType) {
443
+ if (clientType === "copilot") {
444
+ return "local";
445
+ }
446
+ return "global";
447
+ }
448
+
449
+ function resolveProjectSelector(data, projectPath) {
450
+ if (!projectPath) {
451
+ return resolveClaudeProjectKey(data);
452
+ }
453
+ if (!data.projects) {
454
+ return projectPath;
455
+ }
456
+ if (data.projects[projectPath]) {
457
+ return projectPath;
458
+ }
459
+ let desiredReal;
460
+ try {
461
+ desiredReal = fs.realpathSync(projectPath);
462
+ } catch {
463
+ return projectPath;
464
+ }
465
+ for (const key of Object.keys(data.projects)) {
466
+ try {
467
+ if (fs.realpathSync(key) === desiredReal) {
468
+ return key;
469
+ }
470
+ } catch {
471
+ // ignore invalid paths
472
+ }
473
+ }
474
+ return projectPath;
475
+ }
476
+
256
477
  function getDefaultDisabled(clientType) {
257
478
  return ["cline", "codex", "windsurf", "goose", "claude", "opencode"].includes(clientType);
258
479
  }
@@ -384,30 +605,7 @@ function writeClaudeConfig(filePath, serverName, argsArray) {
384
605
  const data = readJson(filePath);
385
606
  const isDesktopConfig =
386
607
  filePath.endsWith(".claude.json") || filePath.endsWith("claude_desktop_config.json");
387
- const projectPath = process.cwd();
388
- const resolveProjectKey = () => {
389
- const desired = projectPath;
390
- if (!data.projects || data.projects[desired]) {
391
- return desired;
392
- }
393
- let desiredReal;
394
- try {
395
- desiredReal = fs.realpathSync(desired);
396
- } catch {
397
- return desired;
398
- }
399
- const keys = Object.keys(data.projects || {});
400
- for (const key of keys) {
401
- try {
402
- if (fs.realpathSync(key) === desiredReal) {
403
- return key;
404
- }
405
- } catch {
406
- // ignore invalid paths
407
- }
408
- }
409
- return desired;
410
- };
608
+ const resolveProjectKey = () => resolveProjectSelector(data, options.projectPath);
411
609
  const updateClaudeMcpLists = (projectNode) => {
412
610
  projectNode.enabledMcpServers = projectNode.enabledMcpServers || [];
413
611
  projectNode.disabledMcpServers = projectNode.disabledMcpServers || [];
@@ -450,15 +648,35 @@ function writeClaudeConfig(filePath, serverName, argsArray) {
450
648
  };
451
649
  if (options.remove) {
452
650
  if (isDesktopConfig) {
453
- if (!data.projects?.[projectPath]?.mcpServers?.[serverName]) {
454
- fail(`Server "${serverName}" not found for ${projectPath}.`);
651
+ if (options.allProjects) {
652
+ const projects = Object.keys(data.projects || {});
653
+ let removed = false;
654
+ for (const key of projects) {
655
+ if (data.projects?.[key]?.mcpServers?.[serverName]) {
656
+ delete data.projects[key].mcpServers[serverName];
657
+ const projectNode = data.projects[key];
658
+ projectNode.enabledMcpServers =
659
+ projectNode.enabledMcpServers?.filter((name) => name !== serverName) || [];
660
+ projectNode.disabledMcpServers =
661
+ projectNode.disabledMcpServers?.filter((name) => name !== serverName) || [];
662
+ removed = true;
663
+ }
664
+ }
665
+ if (!removed) {
666
+ fail(`Server "${serverName}" not found in any Claude projects.`);
667
+ }
668
+ } else {
669
+ const projectKey = resolveProjectKey();
670
+ if (!data.projects?.[projectKey]?.mcpServers?.[serverName]) {
671
+ fail(`Server "${serverName}" not found for ${projectKey}.`);
672
+ }
673
+ delete data.projects[projectKey].mcpServers[serverName];
674
+ const projectNode = data.projects[projectKey];
675
+ projectNode.enabledMcpServers =
676
+ projectNode.enabledMcpServers?.filter((name) => name !== serverName) || [];
677
+ projectNode.disabledMcpServers =
678
+ projectNode.disabledMcpServers?.filter((name) => name !== serverName) || [];
455
679
  }
456
- delete data.projects[projectPath].mcpServers[serverName];
457
- const projectNode = data.projects[projectPath];
458
- projectNode.enabledMcpServers =
459
- projectNode.enabledMcpServers?.filter((name) => name !== serverName) || [];
460
- projectNode.disabledMcpServers =
461
- projectNode.disabledMcpServers?.filter((name) => name !== serverName) || [];
462
680
  } else {
463
681
  data.mcpServers = data.mcpServers || {};
464
682
  if (!data.mcpServers[serverName]) {
@@ -474,6 +692,42 @@ function writeClaudeConfig(filePath, serverName, argsArray) {
474
692
  }
475
693
  if (isDesktopConfig) {
476
694
  data.projects = data.projects || {};
695
+ if (options.toggle) {
696
+ if (options.allProjects) {
697
+ const projects = Object.keys(data.projects || {});
698
+ let toggled = false;
699
+ for (const key of projects) {
700
+ if (!data.projects[key]) {
701
+ continue;
702
+ }
703
+ data.projects[key].mcpServers = data.projects[key].mcpServers || {};
704
+ if (!data.projects[key].mcpServers[serverName]) {
705
+ continue;
706
+ }
707
+ updateClaudeMcpLists(data.projects[key]);
708
+ toggled = true;
709
+ }
710
+ if (!toggled) {
711
+ fail(`Server "${serverName}" not found in any Claude projects.`);
712
+ }
713
+ writeFile(filePath, JSON.stringify(data, null, 2));
714
+ return;
715
+ }
716
+ const projectKey = resolveProjectKey();
717
+ if (!data.projects[projectKey]) {
718
+ data.projects[projectKey] = {
719
+ allowedTools: [],
720
+ mcpContextUris: [],
721
+ mcpServers: {},
722
+ enabledMcpServers: [],
723
+ disabledMcpServers: [],
724
+ };
725
+ }
726
+ data.projects[projectKey].mcpServers = data.projects[projectKey].mcpServers || {};
727
+ updateClaudeMcpLists(data.projects[projectKey]);
728
+ writeFile(filePath, JSON.stringify(data, null, 2));
729
+ return;
730
+ }
477
731
  const projectKey = resolveProjectKey();
478
732
  if (!data.projects[projectKey]) {
479
733
  data.projects[projectKey] = {
@@ -488,14 +742,6 @@ function writeClaudeConfig(filePath, serverName, argsArray) {
488
742
  data.projects[projectKey].disabledMcpServers =
489
743
  data.projects[projectKey].disabledMcpServers || [];
490
744
  data.projects[projectKey].mcpServers = data.projects[projectKey].mcpServers || {};
491
- if (options.toggle) {
492
- if (!data.projects[projectKey].mcpServers[serverName]) {
493
- fail(`Server "${serverName}" not found for ${projectKey}.`);
494
- }
495
- updateClaudeMcpLists(data.projects[projectKey]);
496
- writeFile(filePath, JSON.stringify(data, null, 2));
497
- return;
498
- }
499
745
  if (data.projects[projectKey].mcpServers[serverName] && !options.force) {
500
746
  fail(`Server "${serverName}" already exists for ${projectKey}. Use --force to overwrite.`);
501
747
  }
@@ -532,7 +778,7 @@ function writeClaudeConfig(filePath, serverName, argsArray) {
532
778
  };
533
779
  } else {
534
780
  const entry = {
535
- type: options.transport,
781
+ type: options.transport === "streamableHttp" ? "http" : options.transport,
536
782
  url: options.url,
537
783
  timeout: options.timeout,
538
784
  };
@@ -545,6 +791,30 @@ function writeClaudeConfig(filePath, serverName, argsArray) {
545
791
  writeFile(filePath, JSON.stringify(data, null, 2));
546
792
  }
547
793
 
794
+ function resolveClaudeProjectKey(data, projectPath = process.cwd()) {
795
+ const desired = projectPath;
796
+ if (!data.projects || data.projects[desired]) {
797
+ return desired;
798
+ }
799
+ let desiredReal;
800
+ try {
801
+ desiredReal = fs.realpathSync(desired);
802
+ } catch {
803
+ return desired;
804
+ }
805
+ const keys = Object.keys(data.projects || {});
806
+ for (const key of keys) {
807
+ try {
808
+ if (fs.realpathSync(key) === desiredReal) {
809
+ return key;
810
+ }
811
+ } catch {
812
+ // ignore invalid paths
813
+ }
814
+ }
815
+ return desired;
816
+ }
817
+
548
818
  function writeCodexConfig(filePath, serverName, argsArray) {
549
819
  ensureDir(filePath);
550
820
  if (!toml) {
@@ -663,6 +933,137 @@ function writeGooseConfig(filePath, serverName, argsArray) {
663
933
  writeFile(filePath, yaml.stringify(data));
664
934
  }
665
935
 
936
+ function listJsonConfig(filePath, clientType) {
937
+ const data = readJson(filePath);
938
+ let store;
939
+ if (clientType === "opencode") {
940
+ store = data.mcp || {};
941
+ } else if (clientType === "copilot") {
942
+ store = data.servers || {};
943
+ } else {
944
+ store = data.mcpServers || {};
945
+ }
946
+ outputList(filePath, Object.keys(store));
947
+ }
948
+
949
+ function listCodexConfig(filePath) {
950
+ if (!toml) {
951
+ fail("TOML dependency not available. Install dependencies and retry.");
952
+ }
953
+ const data = readToml(filePath);
954
+ const store = data.mcp_servers || {};
955
+ outputList(filePath, Object.keys(store));
956
+ }
957
+
958
+ function listGooseConfig(filePath) {
959
+ if (!yaml) {
960
+ fail("YAML dependency not available. Install dependencies and retry.");
961
+ }
962
+ const data = readYaml(filePath);
963
+ const store = data.extensions || {};
964
+ outputList(filePath, Object.keys(store));
965
+ }
966
+
967
+ function listClaudeConfig(filePath, allProjects, projectPath) {
968
+ const data = readJson(filePath);
969
+ const isDesktopConfig =
970
+ filePath.endsWith(".claude.json") || filePath.endsWith("claude_desktop_config.json");
971
+ if (isDesktopConfig) {
972
+ const projects = Object.keys(data.projects || {});
973
+ if (allProjects) {
974
+ if (!projects.length) {
975
+ outputList(filePath, [], "no-projects");
976
+ return;
977
+ }
978
+ for (const key of projects.sort()) {
979
+ const projectNode = data.projects?.[key];
980
+ const store = projectNode?.mcpServers || {};
981
+ outputList(filePath, Object.keys(store), key);
982
+ }
983
+ return;
984
+ }
985
+ const projectKey = resolveProjectSelector(data, projectPath);
986
+ const projectNode = data.projects?.[projectKey];
987
+ const store = projectNode?.mcpServers || {};
988
+ outputList(filePath, Object.keys(store), projectKey);
989
+ return;
990
+ }
991
+ const store = data.mcpServers || {};
992
+ outputList(filePath, Object.keys(store));
993
+ }
994
+
995
+ function claudeLocalHasServer(filePath, serverName) {
996
+ const data = readJson(filePath);
997
+ const store = data.mcpServers || {};
998
+ return Boolean(store[serverName]);
999
+ }
1000
+
1001
+ function whereJsonConfig(filePath, clientType, serverName) {
1002
+ const data = readJson(filePath);
1003
+ let store;
1004
+ if (clientType === "opencode") {
1005
+ store = data.mcp || {};
1006
+ } else if (clientType === "copilot") {
1007
+ store = data.servers || {};
1008
+ } else {
1009
+ store = data.mcpServers || {};
1010
+ }
1011
+ outputWhere(filePath, serverName, Boolean(store[serverName]));
1012
+ }
1013
+
1014
+ function whereCodexConfig(filePath, serverName) {
1015
+ if (!toml) {
1016
+ fail("TOML dependency not available. Install dependencies and retry.");
1017
+ }
1018
+ const data = readToml(filePath);
1019
+ const store = data.mcp_servers || {};
1020
+ outputWhere(filePath, serverName, Boolean(store[serverName]));
1021
+ }
1022
+
1023
+ function whereGooseConfig(filePath, serverName) {
1024
+ if (!yaml) {
1025
+ fail("YAML dependency not available. Install dependencies and retry.");
1026
+ }
1027
+ const data = readYaml(filePath);
1028
+ const store = data.extensions || {};
1029
+ outputWhere(filePath, serverName, Boolean(store[serverName]));
1030
+ }
1031
+
1032
+ function whereClaudeConfig(filePath, serverName, allProjects, projectPath) {
1033
+ const data = readJson(filePath);
1034
+ const isDesktopConfig =
1035
+ filePath.endsWith(".claude.json") || filePath.endsWith("claude_desktop_config.json");
1036
+ if (isDesktopConfig) {
1037
+ const projects = Object.keys(data.projects || {});
1038
+ if (allProjects) {
1039
+ if (!projects.length) {
1040
+ outputWhere(filePath, serverName, false, "no-projects");
1041
+ return;
1042
+ }
1043
+ let found = false;
1044
+ for (const key of projects.sort()) {
1045
+ const projectNode = data.projects?.[key];
1046
+ const store = projectNode?.mcpServers || {};
1047
+ if (store[serverName]) {
1048
+ outputWhere(filePath, serverName, true, key);
1049
+ found = true;
1050
+ }
1051
+ }
1052
+ if (!found) {
1053
+ outputWhere(filePath, serverName, false, "all-projects");
1054
+ }
1055
+ return;
1056
+ }
1057
+ const projectKey = resolveProjectSelector(data, projectPath);
1058
+ const projectNode = data.projects?.[projectKey];
1059
+ const store = projectNode?.mcpServers || {};
1060
+ outputWhere(filePath, serverName, Boolean(store[serverName]), projectKey);
1061
+ return;
1062
+ }
1063
+ const store = data.mcpServers || {};
1064
+ outputWhere(filePath, serverName, Boolean(store[serverName]));
1065
+ }
1066
+
666
1067
  function readJson(filePath) {
667
1068
  if (!fs.existsSync(filePath)) {
668
1069
  return {};
@@ -708,6 +1109,24 @@ function readToml(filePath) {
708
1109
  }
709
1110
  }
710
1111
 
1112
+ function outputList(filePath, keys, projectKey) {
1113
+ const header = projectKey ? `# ${filePath} (${projectKey})` : `# ${filePath}`;
1114
+ process.stdout.write(`${header}\n`);
1115
+ if (!keys.length) {
1116
+ process.stdout.write("- (none)\n");
1117
+ return;
1118
+ }
1119
+ for (const name of keys.sort()) {
1120
+ process.stdout.write(`- ${name}\n`);
1121
+ }
1122
+ }
1123
+
1124
+ function outputWhere(filePath, serverName, found, projectKey) {
1125
+ const header = projectKey ? `# ${filePath} (${projectKey})` : `# ${filePath}`;
1126
+ process.stdout.write(`${header}\n`);
1127
+ process.stdout.write(`- ${serverName}: ${found ? "found" : "not found"}\n`);
1128
+ }
1129
+
711
1130
  function ensureDir(filePath) {
712
1131
  const dir = path.dirname(filePath);
713
1132
  if (!fs.existsSync(dir)) {
@@ -729,30 +1148,140 @@ function fail(message) {
729
1148
  process.exit(1);
730
1149
  }
731
1150
 
732
- function printHelp() {
733
- process.stdout.write(`mcp-conf
1151
+ function printHelp(command) {
1152
+ const header = "mcp-conf";
1153
+ if (!command) {
1154
+ process.stdout.write(`${header}
734
1155
 
735
1156
  Usage:
736
- mcp-conf --client <name> --name <serverName> [--env <path> | --mcp <dest>] [options]
1157
+ mcp-conf <add|rm|ls|enable|disable|where> --client <name> [options]
1158
+ mcp-conf help <command>
1159
+
1160
+ Commands:
1161
+ add add or update an MCP server entry
1162
+ rm remove an MCP server entry
1163
+ ls list MCP server entries
1164
+ enable enable an existing entry
1165
+ disable disable an existing entry
1166
+ where show where a server name is defined
1167
+
1168
+ Run:
1169
+ mcp-conf <command> --help
1170
+ mcp-conf help <command>
1171
+
1172
+ Notes:
1173
+ Scope defaults to --global (Copilot uses --local only).
1174
+ For Claude, --local maps to the project scope file ./.mcp.json.
1175
+ `);
1176
+ return;
1177
+ }
1178
+ switch (command) {
1179
+ case "add":
1180
+ process.stdout.write(`${header} add
1181
+
1182
+ Usage:
1183
+ mcp-conf add --client <name> --name <serverName> [--env <path> | --mcp <dest>] [options]
737
1184
 
738
1185
  Options:
739
1186
  --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | copilot (repeatable)
740
1187
  --name <serverName> required MCP server name key
741
- --env <path> .env path (add/update only)
742
- --mcp <dest> destination name (add/update only)
1188
+ --env <path> .env path (stdio only)
1189
+ --mcp <dest> destination name (stdio only)
743
1190
  --transport <type> stdio | sse | http (http => streamableHttp)
744
1191
  --command <bin> command to run (default: mcp-abap-adt)
1192
+ --global write to global user config (default)
1193
+ --local write to project config (where supported)
1194
+ --project <path> Claude global: target a specific project path
745
1195
  --url <http(s)://...> required for sse/http
746
1196
  --header key=value add request header (repeatable)
747
1197
  --timeout <seconds> entry timeout (default: 60)
748
- --disable disable entry (Codex/OpenCode/Cline/Windsurf/Goose/Claude; not Cursor/Copilot)
749
- --enable enable entry (Codex/OpenCode/Cline/Windsurf/Goose/Claude; not Cursor/Copilot)
750
- --remove remove entry
751
- --force overwrite existing entry (add/update)
1198
+ --force overwrite existing entry
752
1199
  --dry-run print changes without writing files
753
- -h, --help show this help
1200
+ `);
1201
+ break;
1202
+ case "rm":
1203
+ process.stdout.write(`${header} rm
754
1204
 
755
- Notes:
756
- New entries for Cline/Codex/Windsurf/Goose/Claude/OpenCode are added disabled by default.
1205
+ Usage:
1206
+ mcp-conf rm --client <name> --name <serverName> [options]
1207
+
1208
+ Options:
1209
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | copilot (repeatable)
1210
+ --name <serverName> required MCP server name key
1211
+ --global write to global user config (default)
1212
+ --local write to project config (where supported)
1213
+ --all-projects Claude global: remove across all projects
1214
+ --project <path> Claude global: target a specific project path
1215
+ --dry-run print changes without writing files
1216
+ `);
1217
+ break;
1218
+ case "ls":
1219
+ process.stdout.write(`${header} ls
1220
+
1221
+ Usage:
1222
+ mcp-conf ls --client <name> [options]
1223
+
1224
+ Options:
1225
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | copilot (repeatable)
1226
+ --global write to global user config (default)
1227
+ --local write to project config (where supported)
1228
+ --all-projects Claude global: list across all projects
1229
+ --project <path> Claude global: target a specific project path
1230
+ `);
1231
+ break;
1232
+ case "enable":
1233
+ process.stdout.write(`${header} enable
1234
+
1235
+ Usage:
1236
+ mcp-conf enable --client <name> --name <serverName> [options]
1237
+
1238
+ Options:
1239
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | copilot (repeatable)
1240
+ --name <serverName> required MCP server name key
1241
+ --global write to global user config (default)
1242
+ --local write to project config (where supported)
1243
+ --all-projects Claude global: enable across all projects
1244
+ --project <path> Claude global: target a specific project path
1245
+ --dry-run print changes without writing files
757
1246
  `);
1247
+ break;
1248
+ case "disable":
1249
+ process.stdout.write(`${header} disable
1250
+
1251
+ Usage:
1252
+ mcp-conf disable --client <name> --name <serverName> [options]
1253
+
1254
+ Options:
1255
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | copilot (repeatable)
1256
+ --name <serverName> required MCP server name key
1257
+ --global write to global user config (default)
1258
+ --local write to project config (where supported)
1259
+ --all-projects Claude global: disable across all projects
1260
+ --project <path> Claude global: target a specific project path
1261
+ --dry-run print changes without writing files
1262
+ `);
1263
+ break;
1264
+ case "where":
1265
+ process.stdout.write(`${header} where
1266
+
1267
+ Usage:
1268
+ mcp-conf where --client <name> --name <serverName> [options]
1269
+
1270
+ Options:
1271
+ --client <name> cline | codex | claude | goose | cursor | windsurf | opencode | copilot (repeatable)
1272
+ --name <serverName> required MCP server name key
1273
+ --global write to global user config (default)
1274
+ --local write to project config (where supported)
1275
+ --all-projects Claude global: search across all projects
1276
+ --project <path> Claude global: target a specific project path
1277
+ `);
1278
+ break;
1279
+ default:
1280
+ process.stdout.write(`${header}
1281
+
1282
+ Unknown command "${command}".
1283
+ Run:
1284
+ mcp-conf help
1285
+ `);
1286
+ }
758
1287
  }
@@ -11,79 +11,102 @@ npm install -g @mcp-abap-adt/configurator
11
11
  ## Usage
12
12
 
13
13
  ```bash
14
- mcp-conf --client cline --env /path/to/.env --name abap
15
- mcp-conf --client cline --mcp TRIAL --name abap
16
- mcp-conf --client cline --env /path/to/.env --name abap --transport stdio
17
- mcp-conf --client claude --mcp TRIAL --name abap
18
- mcp-conf --client codex --name abap --remove
19
- mcp-conf --client cline --name direct-jwt-test-001 --transport http --url http://localhost:4004/mcp/stream/http --header x-sap-url=https://... --header x-sap-client=210 --header x-sap-auth-type=jwt --header x-sap-jwt-token=...
20
- mcp-conf --client cline --name local-mcp-sse --transport sse --url http://localhost:3001/sse
21
- mcp-conf --client codex --name abap-http --transport http --url http://localhost:3000/mcp/stream/http
22
- mcp-conf --client codex --name abap-http --transport http --url http://localhost:3000/mcp/stream/http --header x-mcp-destination=trial
23
- mcp-conf --client opencode --name abap --transport http --url http://localhost:3000/mcp/stream/http
24
- mcp-conf --client copilot --name abap --transport http --url http://localhost:3000/mcp/stream/http --header x-mcp-destination=trial
14
+ mcp-conf add --client cline --env /path/to/.env --name abap
15
+ mcp-conf add --client cline --mcp TRIAL --name abap
16
+ mcp-conf add --client cline --env /path/to/.env --name abap --transport stdio
17
+ mcp-conf add --client claude --mcp TRIAL --name abap
18
+ mcp-conf rm --client codex --name abap
19
+ mcp-conf add --client cline --name direct-jwt-test-001 --transport http --url http://localhost:4004/mcp/stream/http --header x-sap-url=https://... --header x-sap-client=210 --header x-sap-auth-type=jwt --header x-sap-jwt-token=...
20
+ mcp-conf add --client cline --name local-mcp-sse --transport sse --url http://localhost:3001/sse
21
+ mcp-conf add --client codex --name abap-http --transport http --url http://localhost:3000/mcp/stream/http
22
+ mcp-conf add --client codex --name abap-http --transport http --url http://localhost:3000/mcp/stream/http --header x-mcp-destination=trial
23
+ mcp-conf add --client opencode --name abap --transport http --url http://localhost:3000/mcp/stream/http
24
+ mcp-conf add --client copilot --name abap --transport http --url http://localhost:3000/mcp/stream/http --header x-mcp-destination=trial
25
25
  ```
26
26
 
27
27
  ## Common Tasks
28
28
 
29
29
  Add MCP:
30
30
  ```bash
31
- mcp-conf --client codex --mcp TRIAL --name abap
32
- mcp-conf --client cline --env /path/to/.env --name abap
33
- mcp-conf --client claude --mcp TRIAL --name abap
31
+ mcp-conf add --client codex --mcp TRIAL --name abap
32
+ mcp-conf add --client cline --env /path/to/.env --name abap
33
+ mcp-conf add --client claude --mcp TRIAL --name abap
34
+ mcp-conf add --client claude --name abap-http --transport http --url http://localhost:3000/mcp/stream/http --header x-mcp-destination=trial
35
+ mcp-conf add --client claude --name abap --project /path/to/project --mcp TRIAL
34
36
  ```
35
37
 
36
38
  Disable MCP:
37
39
  ```bash
38
- mcp-conf --client codex --name abap --disable
39
- mcp-conf --client cline --name abap --disable
40
+ mcp-conf disable --client codex --name abap
41
+ mcp-conf disable --client cline --name abap
40
42
  ```
41
43
 
42
44
  Enable MCP:
43
45
  ```bash
44
- mcp-conf --client codex --name abap --enable
45
- mcp-conf --client cline --name abap --enable
46
+ mcp-conf enable --client codex --name abap
47
+ mcp-conf enable --client cline --name abap
46
48
  ```
47
49
 
48
50
  Remove MCP:
49
51
  ```bash
50
- mcp-conf --client codex --name abap --remove
51
- mcp-conf --client cline --name abap --remove
52
- mcp-conf --client claude --name abap --remove
52
+ mcp-conf rm --client codex --name abap
53
+ mcp-conf rm --client cline --name abap
54
+ mcp-conf rm --client claude --name abap
55
+ ```
56
+
57
+ List MCP servers:
58
+ ```bash
59
+ mcp-conf ls --client codex
60
+ mcp-conf ls --client cline
61
+ mcp-conf ls --client claude --local
62
+ mcp-conf ls --client claude --all-projects
63
+ ```
64
+
65
+ Find where a server is defined:
66
+ ```bash
67
+ mcp-conf where --client codex --name abap
68
+ mcp-conf where --client claude --name goose --project /path/to/project
69
+ mcp-conf where --client claude --name goose --all-projects
53
70
  ```
54
71
 
55
72
  Options:
73
+ - Commands: `add`, `rm`, `ls`, `enable`, `disable`, `where` (first argument)
56
74
  - `--client <name>` (repeatable): `cline`, `codex`, `claude`, `goose`, `cursor`, `windsurf`, `opencode`, `copilot`
57
75
  - `--env <path>`: use a specific `.env` file
58
76
  - `--mcp <destination>`: use service key destination
59
77
  - `--name <serverName>`: MCP server name (required)
60
78
  - `--transport <type>`: `stdio`, `sse`, or `http` (`http` maps to `streamableHttp`)
61
79
  - `--command <bin>`: command to run (default: `mcp-abap-adt`)
80
+ - `--global`: write to the global user config (default)
81
+ - `--local`: write to the project config (supported by `cursor`, `opencode`, `copilot`, `claude`)
82
+ - `--all-projects`: for Claude (global scope), apply `rm/enable/disable/ls/where` across all projects
83
+ - `--project <path>`: for Claude (global scope), target a specific project path
62
84
  - `--url <http(s)://...>`: required for `sse` and `http`
63
85
  - `--header key=value`: add request header (repeatable)
64
86
  - `--timeout <seconds>`: timeout value for client entries (default: 60)
65
- - `--disable`: disable server entry (Codex/OpenCode: `enabled = false`, Cline/Windsurf: `disabled = true`, Claude: moves name to `disabledMcpServers`; not Cursor/Copilot)
66
- - `--enable`: enable server entry (Codex/OpenCode: `enabled = true`, Cline/Windsurf: `disabled = false`, Claude: moves name to `enabledMcpServers`; not Cursor/Copilot)
67
- - `--remove`: remove server entry from client config
68
87
 
69
88
  Notes:
70
- - `--disable` and `--remove` do not require `--env` or `--mcp`.
89
+ - `disable` and `rm` do not require `--env` or `--mcp`.
71
90
  - `--env`/`--mcp` are only valid for `stdio` transport. For `sse/http`, use `--url` and optional `--header`.
72
91
  - Cursor/Copilot enable/disable are not implemented yet.
73
92
  - Claude stores enable/disable state under `enabledMcpServers` and `disabledMcpServers` for each project.
74
- - New entries for Cline, Codex, Windsurf, Goose, Claude, and OpenCode are added **disabled by default**. Use `--enable` to turn them on.
93
+ - Claude enable/disable always updates `~/.claude.json` (global scope), even if you pass `--local`.
94
+ - New entries for Cline, Codex, Windsurf, Goose, Claude, and OpenCode are added **disabled by default**. Use `enable` to turn them on.
75
95
  - Windsurf follows `disabled` like Cline. The configurator sets `disabled = true` for default-disabled entries.
76
- - `--enable`/`--disable` only work if the server entry already exists. Use add commands with `--env` or `--mcp` first.
96
+ - `enable`/`disable` only work if the server entry already exists. Use add commands with `--env` or `--mcp` first.
77
97
  - Non-stdio transports are supported for Cline/Cursor/Windsurf/Claude/Goose. Codex supports `http` (streamable HTTP) but not `sse`.
78
98
  - Codex writes custom headers under `http_headers` in `~/.codex/config.toml`.
79
99
  - Codex HTTP entries include `startup_timeout_sec` (default: 60).
80
100
  - `--dry-run`: print changes without writing files
81
101
  - `--force`: overwrite existing server entry if it exists
102
+ - Scope defaults to `--global` (GitHub Copilot is `--local` only).
103
+ - For Claude, `--local` maps to the documented project scope file `./.mcp.json`.
82
104
 
83
105
  ## Config Locations
84
106
 
85
107
  Paths are client-specific and OS-dependent. The installer writes config files in:
86
108
 
109
+ Global (default) locations:
87
110
  - **Cline**:
88
111
  - Linux/macOS: `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
89
112
  - Windows: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`
@@ -104,8 +127,16 @@ Paths are client-specific and OS-dependent. The installer writes config files in
104
127
  - **Windsurf**:
105
128
  - Linux/macOS: `~/.codeium/windsurf/mcp_config.json`
106
129
  - Windows: `%USERPROFILE%\.codeium\windsurf\mcp_config.json`
130
+ - **OpenCode**:
131
+ - Linux/macOS: `~/.config/opencode/opencode.json`
132
+ - Windows: `%APPDATA%\opencode\opencode.json`
133
+
134
+ Local (project) locations:
135
+ - **Claude Code**:
136
+ - Project: `./.mcp.json`
137
+ - **Cursor**:
138
+ - Project: `./.cursor/mcp.json`
107
139
  - **OpenCode**:
108
140
  - Project: `./opencode.json` (uses `mcp.<name>` entries with `enabled: true|false`)
109
141
  - **GitHub Copilot**:
110
142
  - Project: `./.vscode/mcp.json` (uses `servers.<name>` entries)
111
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/configurator",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "MCP client configurator for mcp-abap-adt and mcp-abap-adt-proxy",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,7 +27,7 @@
27
27
  "npm": ">=9.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@iarna/toml": "^2.2.5",
30
+ "@iarna/toml": "^3.0.0",
31
31
  "yaml": "^2.8.1"
32
32
  },
33
33
  "devDependencies": {