mcp-aws-manager 0.4.6 → 0.4.7

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.
@@ -1990,7 +1990,7 @@ function parseDiscoverArgs(argv) {
1990
1990
  outPath: options.outPath ? path.resolve(expandHome(options.outPath)) : (envText("MCP_AWS_OUT") ? path.resolve(expandHome(envText("MCP_AWS_OUT"))) : null),
1991
1991
  noProgress: options.noProgress != null ? options.noProgress : (envNoProgress != null ? envBool("MCP_AWS_NO_PROGRESS") : true)
1992
1992
  };
1993
- }
1993
+ }
1994
1994
 
1995
1995
  function validateConfig(config, warnings, requiredActions) {
1996
1996
  if (config.outPath) {
@@ -2421,6 +2421,106 @@ function cursorMcpConfigCandidates() {
2421
2421
  return dedupePaths(candidates);
2422
2422
  }
2423
2423
 
2424
+ function claudeMcpConfigCandidates() {
2425
+ const explicitPath = envText("CLAUDE_MCP_CONFIG_PATH");
2426
+ if (explicitPath) {
2427
+ return dedupePaths([path.resolve(explicitPath)]);
2428
+ }
2429
+
2430
+ const home = os.homedir();
2431
+ const candidates = [];
2432
+
2433
+ if (process.platform === "win32") {
2434
+ const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
2435
+ candidates.push(path.join(appData, "Claude", "claude_desktop_config.json"));
2436
+ } else if (process.platform === "darwin") {
2437
+ candidates.push(path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"));
2438
+ } else {
2439
+ candidates.push(path.join(home, ".config", "Claude", "claude_desktop_config.json"));
2440
+ }
2441
+
2442
+ candidates.push(path.join(home, ".claude", "claude_desktop_config.json"));
2443
+ return dedupePaths(candidates);
2444
+ }
2445
+
2446
+ function claudeInstallFootprints() {
2447
+ const home = os.homedir();
2448
+ if (process.platform === "win32") {
2449
+ const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
2450
+ return dedupePaths([
2451
+ path.join(appData, "Claude"),
2452
+ path.join(home, ".claude")
2453
+ ]);
2454
+ }
2455
+ if (process.platform === "darwin") {
2456
+ return dedupePaths([
2457
+ path.join(home, "Library", "Application Support", "Claude"),
2458
+ path.join(home, ".claude")
2459
+ ]);
2460
+ }
2461
+ return dedupePaths([
2462
+ path.join(home, ".config", "Claude"),
2463
+ path.join(home, ".claude")
2464
+ ]);
2465
+ }
2466
+
2467
+ function parseClaudeMcpConfigContent(raw, filePath) {
2468
+ const normalizedRaw = String(raw || "").replace(/^\uFEFF/, "");
2469
+ if (!normalizedRaw.trim()) {
2470
+ return { ok: true, data: { mcpServers: {} } };
2471
+ }
2472
+ try {
2473
+ const parsed = JSON.parse(normalizedRaw);
2474
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2475
+ return { ok: false, error: `Claude MCP config must be a JSON object. (${filePath})` };
2476
+ }
2477
+ const mcpServers = parsed.mcpServers && typeof parsed.mcpServers === "object" && !Array.isArray(parsed.mcpServers)
2478
+ ? parsed.mcpServers
2479
+ : {};
2480
+ return { ok: true, data: { ...parsed, mcpServers } };
2481
+ } catch (error) {
2482
+ return {
2483
+ ok: false,
2484
+ error: `Failed to parse Claude MCP config: ${error && error.message ? error.message : String(error)} (${filePath})`
2485
+ };
2486
+ }
2487
+ }
2488
+
2489
+ function readClaudeMcpConfig() {
2490
+ const candidates = claudeMcpConfigCandidates();
2491
+ const errors = [];
2492
+ for (const candidate of candidates) {
2493
+ if (!fs.existsSync(candidate)) continue;
2494
+ try {
2495
+ const raw = fs.readFileSync(candidate, "utf8");
2496
+ const parsed = parseClaudeMcpConfigContent(raw, candidate);
2497
+ if (!parsed.ok) {
2498
+ errors.push(parsed.error);
2499
+ continue;
2500
+ }
2501
+ return {
2502
+ ok: true,
2503
+ filePath: candidate,
2504
+ exists: true,
2505
+ data: parsed.data,
2506
+ candidates
2507
+ };
2508
+ } catch (error) {
2509
+ errors.push(`Failed to read Claude MCP config: ${error && error.message ? error.message : String(error)} (${candidate})`);
2510
+ }
2511
+ }
2512
+
2513
+ if (errors.length) {
2514
+ return {
2515
+ ok: false,
2516
+ filePath: candidates[0],
2517
+ error: errors.join("; ")
2518
+ };
2519
+ }
2520
+
2521
+ return { ok: true, filePath: candidates[0], exists: false, data: { mcpServers: {} }, candidates };
2522
+ }
2523
+
2424
2524
  function cursorMcpConfigPath() {
2425
2525
  const candidates = cursorMcpConfigCandidates();
2426
2526
  for (const candidate of candidates) {
@@ -2509,6 +2609,29 @@ function writeCursorMcpConfig(filePath, data, mirrorPaths = []) {
2509
2609
  return { written, mirrorFailures };
2510
2610
  }
2511
2611
 
2612
+ function writeClaudeMcpConfig(filePath, data, mirrorPaths = []) {
2613
+ const allPaths = dedupePaths([filePath, ...(Array.isArray(mirrorPaths) ? mirrorPaths : [])]);
2614
+ const written = [];
2615
+ const mirrorFailures = [];
2616
+
2617
+ for (let i = 0; i < allPaths.length; i += 1) {
2618
+ const currentPath = allPaths[i];
2619
+ try {
2620
+ fs.mkdirSync(path.dirname(currentPath), { recursive: true });
2621
+ fs.writeFileSync(currentPath, JSON.stringify(data, null, 2) + "\n", "utf8");
2622
+ written.push(currentPath);
2623
+ } catch (error) {
2624
+ const message = `${currentPath}: ${error && error.message ? error.message : String(error)}`;
2625
+ if (i === 0) {
2626
+ throw new Error(message);
2627
+ }
2628
+ mirrorFailures.push(message);
2629
+ }
2630
+ }
2631
+
2632
+ return { written, mirrorFailures };
2633
+ }
2634
+
2512
2635
  function cursorRegistrationMatches(entry, mcpCommand, mcpArgs = []) {
2513
2636
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
2514
2637
  const entryCommand = normalizeComparableText(entry.command);
@@ -2525,6 +2648,22 @@ function cursorRegistrationMatches(entry, mcpCommand, mcpArgs = []) {
2525
2648
  return true;
2526
2649
  }
2527
2650
 
2651
+ function claudeRegistrationMatches(entry, mcpCommand, mcpArgs = []) {
2652
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
2653
+ const entryCommand = normalizeComparableText(entry.command);
2654
+ if (!entryCommand) return false;
2655
+ if (normalizeComparableText(mcpCommand) && entryCommand !== normalizeComparableText(mcpCommand)) {
2656
+ return false;
2657
+ }
2658
+ const expectedArgs = normalizeComparableArgs(mcpArgs);
2659
+ const actualArgs = normalizeComparableArgs(entry.args);
2660
+ if (expectedArgs.length !== actualArgs.length) return false;
2661
+ for (let i = 0; i < expectedArgs.length; i += 1) {
2662
+ if (expectedArgs[i] !== actualArgs[i]) return false;
2663
+ }
2664
+ return true;
2665
+ }
2666
+
2528
2667
  function ensureCursorRegistered(serverName, mcpCommand, mcpArgs = [], force = false) {
2529
2668
  const loaded = readCursorMcpConfig();
2530
2669
  if (!loaded.ok) {
@@ -2562,6 +2701,43 @@ function ensureCursorRegistered(serverName, mcpCommand, mcpArgs = [], force = fa
2562
2701
  return { ok: true, action: "already registered", detail: `primary=${loaded.filePath}` };
2563
2702
  }
2564
2703
 
2704
+ function ensureClaudeRegistered(serverName, mcpCommand, mcpArgs = [], force = false) {
2705
+ const loaded = readClaudeMcpConfig();
2706
+ if (!loaded.ok) {
2707
+ return { ok: false, action: "claude config invalid", detail: `${loaded.error} (${loaded.filePath})` };
2708
+ }
2709
+ const desiredEntry = {
2710
+ command: String(mcpCommand || ""),
2711
+ args: Array.isArray(mcpArgs) ? mcpArgs.map((arg) => String(arg)) : []
2712
+ };
2713
+ const config = loaded.data;
2714
+ const current = config.mcpServers[serverName];
2715
+ if (force || !claudeRegistrationMatches(current, desiredEntry.command, desiredEntry.args)) {
2716
+ config.mcpServers[serverName] = desiredEntry;
2717
+ try {
2718
+ const mirrors = (loaded.candidates || []).filter(
2719
+ (candidate) => normalizeComparableText(candidate) !== normalizeComparableText(loaded.filePath)
2720
+ );
2721
+ const writeResult = writeClaudeMcpConfig(loaded.filePath, config, mirrors);
2722
+ const detailParts = [`primary=${loaded.filePath}`];
2723
+ if (writeResult.written.length > 1) {
2724
+ detailParts.push(`mirrored=${writeResult.written.slice(1).join(",")}`);
2725
+ }
2726
+ if (writeResult.mirrorFailures.length) {
2727
+ detailParts.push(`mirror_failures=${writeResult.mirrorFailures.join(" | ")}`);
2728
+ }
2729
+ return { ok: true, action: "registered", detail: detailParts.join("; ") };
2730
+ } catch (error) {
2731
+ return {
2732
+ ok: false,
2733
+ action: "claude registration failed",
2734
+ detail: `${error && error.message ? error.message : String(error)} (${loaded.filePath})`
2735
+ };
2736
+ }
2737
+ }
2738
+ return { ok: true, action: "already registered", detail: `primary=${loaded.filePath}` };
2739
+ }
2740
+
2565
2741
  function removeCursorRegistration(serverName) {
2566
2742
  const loaded = readCursorMcpConfig();
2567
2743
  if (!loaded.ok || !loaded.exists) return;
@@ -2577,6 +2753,21 @@ function removeCursorRegistration(serverName) {
2577
2753
  }
2578
2754
  }
2579
2755
 
2756
+ function removeClaudeRegistration(serverName) {
2757
+ const loaded = readClaudeMcpConfig();
2758
+ if (!loaded.ok || !loaded.exists) return;
2759
+ if (!Object.prototype.hasOwnProperty.call(loaded.data.mcpServers, serverName)) return;
2760
+ delete loaded.data.mcpServers[serverName];
2761
+ try {
2762
+ const mirrors = (loaded.candidates || []).filter(
2763
+ (candidate) => normalizeComparableText(candidate) !== normalizeComparableText(loaded.filePath)
2764
+ );
2765
+ writeClaudeMcpConfig(loaded.filePath, loaded.data, mirrors);
2766
+ } catch (_) {
2767
+ // ignore best-effort cleanup
2768
+ }
2769
+ }
2770
+
2580
2771
  function isCursorRegistered(serverName, mcpCommand, mcpArgs = []) {
2581
2772
  const candidates = cursorMcpConfigCandidates();
2582
2773
  for (const candidate of candidates) {
@@ -2596,6 +2787,25 @@ function isCursorRegistered(serverName, mcpCommand, mcpArgs = []) {
2596
2787
  return false;
2597
2788
  }
2598
2789
 
2790
+ function isClaudeRegistered(serverName, mcpCommand, mcpArgs = []) {
2791
+ const candidates = claudeMcpConfigCandidates();
2792
+ for (const candidate of candidates) {
2793
+ if (!fs.existsSync(candidate)) continue;
2794
+ try {
2795
+ const raw = fs.readFileSync(candidate, "utf8");
2796
+ const parsed = parseClaudeMcpConfigContent(raw, candidate);
2797
+ if (!parsed.ok) continue;
2798
+ const entry = parsed.data && parsed.data.mcpServers ? parsed.data.mcpServers[serverName] : null;
2799
+ if (claudeRegistrationMatches(entry, mcpCommand, mcpArgs)) {
2800
+ return true;
2801
+ }
2802
+ } catch (_) {
2803
+ continue;
2804
+ }
2805
+ }
2806
+ return false;
2807
+ }
2808
+
2599
2809
  function clientGetAttempts(cliBin, serverName) {
2600
2810
  if (cliBin === "claude") {
2601
2811
  return [
@@ -2662,23 +2872,34 @@ function clientAddAttempts(cliBin, serverName, mcpCommand, mcpArgs = []) {
2662
2872
  ];
2663
2873
  }
2664
2874
 
2665
- function removeRegistration(cliBin, serverName) {
2875
+ function removeRegistration(cliBin, serverName, options = {}) {
2876
+ const mode = options && options.mode ? String(options.mode) : "cli";
2666
2877
  if (cliBin === "cursor") {
2667
2878
  removeCursorRegistration(serverName);
2668
2879
  return;
2669
2880
  }
2881
+ if (cliBin === "claude" && mode === "config") {
2882
+ removeClaudeRegistration(serverName);
2883
+ return;
2884
+ }
2670
2885
  const attempts = clientRemoveAttempts(cliBin, serverName);
2671
2886
  for (const args of attempts) {
2672
2887
  runCLICommand(cliBin, args, { stdio: "ignore" });
2673
2888
  }
2674
2889
  }
2675
2890
 
2676
- function isRegistered(cliBin, serverName, target = null) {
2891
+ function isRegistered(cliBin, serverName, target = null, options = {}) {
2892
+ const mode = options && options.mode ? String(options.mode) : "cli";
2677
2893
  if (cliBin === "cursor") {
2678
2894
  const mcpCommand = target && target.mcpCommand ? target.mcpCommand : "";
2679
2895
  const mcpArgs = target && Array.isArray(target.mcpArgs) ? target.mcpArgs : [];
2680
2896
  return isCursorRegistered(serverName, mcpCommand, mcpArgs);
2681
2897
  }
2898
+ if (cliBin === "claude" && mode === "config") {
2899
+ const mcpCommand = target && target.mcpCommand ? target.mcpCommand : "";
2900
+ const mcpArgs = target && Array.isArray(target.mcpArgs) ? target.mcpArgs : [];
2901
+ return isClaudeRegistered(serverName, mcpCommand, mcpArgs);
2902
+ }
2682
2903
  const attempts = clientGetAttempts(cliBin, serverName);
2683
2904
  for (const args of attempts) {
2684
2905
  const run = runCLICommand(cliBin, args, { stdio: "ignore" });
@@ -2719,7 +2940,26 @@ function detectClientAvailability(clients) {
2719
2940
  : Array.from(SUPPORTED_CLIENTS);
2720
2941
  return candidateClients.map((cliBin) => {
2721
2942
  const exists = clientHelpAttempts(cliBin).some((args) => commandExists(cliBin, args));
2722
- return { cliBin, exists };
2943
+ if (exists) return { cliBin, exists: true, mode: "cli", detail: "" };
2944
+ if (cliBin !== "claude") return { cliBin, exists: false, mode: "cli", detail: "" };
2945
+
2946
+ const explicitConfigPath = envText("CLAUDE_MCP_CONFIG_PATH");
2947
+ if (explicitConfigPath) {
2948
+ return { cliBin, exists: true, mode: "config", detail: `CLAUDE_MCP_CONFIG_PATH=${path.resolve(explicitConfigPath)}` };
2949
+ }
2950
+
2951
+ const config = readClaudeMcpConfig();
2952
+ if (config.ok && config.exists) {
2953
+ return { cliBin, exists: true, mode: "config", detail: `config=${config.filePath}` };
2954
+ }
2955
+
2956
+ for (const footprint of claudeInstallFootprints()) {
2957
+ if (fs.existsSync(footprint)) {
2958
+ return { cliBin, exists: true, mode: "config", detail: `footprint=${footprint}` };
2959
+ }
2960
+ }
2961
+
2962
+ return { cliBin, exists: false, mode: "cli", detail: "" };
2723
2963
  });
2724
2964
  }
2725
2965
 
@@ -2731,7 +2971,8 @@ function runSetupInternal(config, options = {}) {
2731
2971
  : [defaultTarget];
2732
2972
  const clientsExplicit = config && config.clientsExplicit === true;
2733
2973
  const availability = detectClientAvailability(config.clients);
2734
- const clients = availability.filter((row) => row.exists).map((row) => row.cliBin);
2974
+ const activeClients = availability.filter((row) => row.exists);
2975
+ const clients = activeClients.map((row) => row.cliBin);
2735
2976
  const missingClients = availability.filter((row) => !row.exists).map((row) => row.cliBin);
2736
2977
  const results = [];
2737
2978
  const commandChecks = targets.map((target) => ({
@@ -2774,7 +3015,9 @@ function runSetupInternal(config, options = {}) {
2774
3015
  return 2;
2775
3016
  }
2776
3017
 
2777
- for (const cliBin of clients) {
3018
+ for (const clientInfo of activeClients) {
3019
+ const cliBin = clientInfo.cliBin;
3020
+ const mode = clientInfo.mode || "cli";
2778
3021
  if (EDITOR_STYLE_CLIENTS.has(String(cliBin || "").toLowerCase()) && cliBin !== "cursor") {
2779
3022
  for (const target of activeTargets) {
2780
3023
  results.push({
@@ -2789,9 +3032,9 @@ function runSetupInternal(config, options = {}) {
2789
3032
  }
2790
3033
 
2791
3034
  for (const target of activeTargets) {
2792
- const alreadyRegistered = isRegistered(cliBin, target.serverName, target);
3035
+ const alreadyRegistered = isRegistered(cliBin, target.serverName, target, { mode });
2793
3036
  if (config.force || !ensureOnly || alreadyRegistered) {
2794
- removeRegistration(cliBin, target.serverName);
3037
+ removeRegistration(cliBin, target.serverName, { mode });
2795
3038
  }
2796
3039
 
2797
3040
  if (cliBin === "cursor") {
@@ -2819,6 +3062,31 @@ function runSetupInternal(config, options = {}) {
2819
3062
  continue;
2820
3063
  }
2821
3064
 
3065
+ if (cliBin === "claude" && mode === "config") {
3066
+ const registered = ensureClaudeRegistered(target.serverName, target.mcpCommand, target.mcpArgs || [], config.force);
3067
+ if (registered.ok) {
3068
+ const action = ensureOnly
3069
+ ? (alreadyRegistered ? "updated" : "registered")
3070
+ : (alreadyRegistered ? "re-registered" : "registered");
3071
+ results.push({
3072
+ cliBin,
3073
+ serverName: target.serverName,
3074
+ ok: true,
3075
+ action: `${action} (claude config)`,
3076
+ detail: registered.detail || ""
3077
+ });
3078
+ } else {
3079
+ results.push({
3080
+ cliBin,
3081
+ serverName: target.serverName,
3082
+ ok: false,
3083
+ action: registered.action || "registration failed",
3084
+ detail: registered.detail || "unknown"
3085
+ });
3086
+ }
3087
+ continue;
3088
+ }
3089
+
2822
3090
  const registered = tryRegister(cliBin, target.serverName, target.mcpCommand, target.mcpArgs || []);
2823
3091
  if (registered.ok) {
2824
3092
  const action = ensureOnly
@@ -2837,7 +3105,7 @@ function runSetupInternal(config, options = {}) {
2837
3105
 
2838
3106
  for (const row of results) {
2839
3107
  process.stdout.write(`${row.cliBin}/${row.serverName}: ${row.action}\n`);
2840
- if (row.detail && (!row.ok || /\bcursor mcp\.json\b/i.test(String(row.action || "")))) {
3108
+ if (row.detail && (!row.ok || /\b(cursor mcp\.json|claude config)\b/i.test(String(row.action || "")))) {
2841
3109
  process.stdout.write(` detail: ${row.detail}\n`);
2842
3110
  }
2843
3111
  }
@@ -2874,6 +3142,7 @@ function runDoctor(config) {
2874
3142
 
2875
3143
  for (const row of availability) {
2876
3144
  const cliBin = row.cliBin;
3145
+ const mode = row.mode || "cli";
2877
3146
  if (!row.exists) {
2878
3147
  if (clientsExplicit) {
2879
3148
  hasIssue = true;
@@ -2893,12 +3162,12 @@ function runDoctor(config) {
2893
3162
  }
2894
3163
 
2895
3164
  for (const target of targets) {
2896
- const registered = isRegistered(cliBin, target.serverName, target);
3165
+ const registered = isRegistered(cliBin, target.serverName, target, { mode });
2897
3166
  if (registered) {
2898
- process.stdout.write(`${cliBin}: registered (${target.serverName})\n`);
3167
+ process.stdout.write(`${cliBin}: registered (${target.serverName}${mode === "config" ? ", config mode" : ""})\n`);
2899
3168
  } else {
2900
3169
  hasIssue = true;
2901
- process.stdout.write(`${cliBin}: missing registration (${target.serverName})\n`);
3170
+ process.stdout.write(`${cliBin}: missing registration (${target.serverName}${mode === "config" ? ", config mode" : ""})\n`);
2902
3171
  const action = `mcp-aws-manager setup --clients ${cliBin}`;
2903
3172
  process.stdout.write(` action: ${action}\n`);
2904
3173
  }
@@ -12522,4 +12791,4 @@ async function main() {
12522
12791
 
12523
12792
  if (require.main === module) {
12524
12793
  void main();
12525
- }
12794
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-aws-manager",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "AWS operations CLI and MCP server (SSM-only) for EC2/Lambda inventory, remediation, and runtime snapshots",
5
5
  "license": "MIT",
6
6
  "publishConfig": {