mcp-aws-manager 0.4.3 → 0.4.5
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 +4 -0
- package/README_KO.md +4 -0
- package/bin/mcp-aws-manager.js +166 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -179,6 +179,10 @@ mcp-aws-manager setup --clients claude
|
|
|
179
179
|
|
|
180
180
|
Default behavior (`setup`/`bootstrap` without `--clients`) auto-detects installed clients and registers only detected CLIs.
|
|
181
181
|
|
|
182
|
+
Compatibility note:
|
|
183
|
+
- Cursor is registered through MCP config file sync (`~/.cursor/mcp.json` and platform user config path) to avoid editor tab side effects from `cursor mcp ...`.
|
|
184
|
+
- If another editor-style client does not expose stable CLI `mcp` subcommands, `setup`/`doctor` returns `manual configuration required` instead of running unsafe subcommands.
|
|
185
|
+
|
|
182
186
|
2. Health check:
|
|
183
187
|
|
|
184
188
|
```bash
|
package/README_KO.md
CHANGED
|
@@ -178,6 +178,10 @@ mcp-aws-manager setup --clients claude
|
|
|
178
178
|
|
|
179
179
|
기본 동작(`--clients` 없이 `setup`/`bootstrap`)은 설치된 클라이언트를 자동 감지해, 감지된 CLI만 등록합니다.
|
|
180
180
|
|
|
181
|
+
호환성 참고:
|
|
182
|
+
- Cursor는 `cursor mcp ...` 호출 부작용(편집기 탭 열림)을 피하기 위해 MCP 설정 파일(`~/.cursor/mcp.json` + 플랫폼 사용자 설정 경로) 동기화 방식으로 등록합니다.
|
|
183
|
+
- 다른 에디터형 클라이언트가 안정적인 CLI `mcp` 하위명령을 제공하지 않으면, `setup`/`doctor`는 위험한 하위명령 실행 대신 `manual configuration required`를 반환합니다.
|
|
184
|
+
|
|
181
185
|
2. 상태 점검:
|
|
182
186
|
|
|
183
187
|
```bash
|
package/bin/mcp-aws-manager.js
CHANGED
|
@@ -61,6 +61,7 @@ const DEFAULT_GOVERNANCE_LOG_NAME = "mcp-aws-governance-log.jsonl";
|
|
|
61
61
|
const DEFAULT_INCIDENT_REPORT_NAME = "incident-escalation.json";
|
|
62
62
|
const DEFAULT_ENTERPRISE_POLICY_NAME = "enterprise-policy.json";
|
|
63
63
|
const SUPPORTED_CLIENTS = new Set(["codex", "claude", "cursor", "windsurf", "antigravity"]);
|
|
64
|
+
const EDITOR_STYLE_CLIENTS = new Set(["cursor", "windsurf", "antigravity"]);
|
|
64
65
|
const INTERNAL_BACKEND_ID = "internal";
|
|
65
66
|
const AUTH_MODES = Object.freeze(["auto", "profile", "web-identity"]);
|
|
66
67
|
const SNAPSHOT_PROFILES = Object.freeze({
|
|
@@ -2311,13 +2312,14 @@ function listLocalAwsProfiles() {
|
|
|
2311
2312
|
}
|
|
2312
2313
|
|
|
2313
2314
|
function runCLICommand(cliBin, args, options = {}) {
|
|
2314
|
-
//
|
|
2315
|
+
// Some editor-style CLIs no longer reliably support `<cli> mcp ...`.
|
|
2315
2316
|
// Calling it can open editor tabs named like "mcp", "add", "get".
|
|
2316
|
-
|
|
2317
|
+
const normalizedCli = String(cliBin || "").toLowerCase();
|
|
2318
|
+
if (EDITOR_STYLE_CLIENTS.has(normalizedCli) && Array.isArray(args) && String(args[0] || "").toLowerCase() === "mcp") {
|
|
2317
2319
|
return {
|
|
2318
2320
|
status: 2,
|
|
2319
2321
|
stdout: "",
|
|
2320
|
-
stderr:
|
|
2322
|
+
stderr: `${normalizedCli} mcp subcommand is disabled in automation mode; use client MCP settings registration`
|
|
2321
2323
|
};
|
|
2322
2324
|
}
|
|
2323
2325
|
|
|
@@ -2361,28 +2363,14 @@ function commandExists(bin, checkArgs) {
|
|
|
2361
2363
|
}
|
|
2362
2364
|
|
|
2363
2365
|
function clientHelpAttempts(cliBin) {
|
|
2364
|
-
if (cliBin
|
|
2365
|
-
return [
|
|
2366
|
-
["--help"]
|
|
2367
|
-
];
|
|
2368
|
-
}
|
|
2369
|
-
if (cliBin === "windsurf" || cliBin === "antigravity") {
|
|
2366
|
+
if (EDITOR_STYLE_CLIENTS.has(String(cliBin || "").toLowerCase())) {
|
|
2370
2367
|
return [
|
|
2371
|
-
["mcp", "--help"],
|
|
2372
2368
|
["--help"]
|
|
2373
2369
|
];
|
|
2374
2370
|
}
|
|
2375
2371
|
return [["mcp", "--help"]];
|
|
2376
2372
|
}
|
|
2377
2373
|
|
|
2378
|
-
function cursorMcpConfigPath() {
|
|
2379
|
-
const explicitPath = envText("CURSOR_MCP_CONFIG_PATH");
|
|
2380
|
-
if (explicitPath) {
|
|
2381
|
-
return path.resolve(explicitPath);
|
|
2382
|
-
}
|
|
2383
|
-
return path.join(os.homedir(), ".cursor", "mcp.json");
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
2374
|
function normalizeComparableText(value) {
|
|
2387
2375
|
const text = String(value == null ? "" : value).trim();
|
|
2388
2376
|
return process.platform === "win32" ? text.toLowerCase() : text;
|
|
@@ -2392,37 +2380,128 @@ function normalizeComparableArgs(args) {
|
|
|
2392
2380
|
return Array.isArray(args) ? args.map((arg) => normalizeComparableText(arg)) : [];
|
|
2393
2381
|
}
|
|
2394
2382
|
|
|
2395
|
-
function
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2398
|
-
|
|
2383
|
+
function dedupePaths(paths) {
|
|
2384
|
+
const seen = new Set();
|
|
2385
|
+
const out = [];
|
|
2386
|
+
for (const candidate of paths) {
|
|
2387
|
+
const resolved = path.resolve(String(candidate || ""));
|
|
2388
|
+
const key = normalizeComparableText(resolved);
|
|
2389
|
+
if (!key || seen.has(key)) continue;
|
|
2390
|
+
seen.add(key);
|
|
2391
|
+
out.push(resolved);
|
|
2392
|
+
}
|
|
2393
|
+
return out;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
function cursorMcpConfigCandidates() {
|
|
2397
|
+
const explicitPath = envText("CURSOR_MCP_CONFIG_PATH");
|
|
2398
|
+
if (explicitPath) {
|
|
2399
|
+
return dedupePaths([path.resolve(explicitPath)]);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
const home = os.homedir();
|
|
2403
|
+
const candidates = [
|
|
2404
|
+
path.join(home, ".cursor", "mcp.json")
|
|
2405
|
+
];
|
|
2406
|
+
|
|
2407
|
+
if (process.platform === "win32") {
|
|
2408
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
2409
|
+
candidates.push(path.join(appData, "Cursor", "User", "mcp.json"));
|
|
2410
|
+
} else if (process.platform === "darwin") {
|
|
2411
|
+
candidates.push(path.join(home, "Library", "Application Support", "Cursor", "User", "mcp.json"));
|
|
2412
|
+
} else {
|
|
2413
|
+
candidates.push(path.join(home, ".config", "Cursor", "User", "mcp.json"));
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
return dedupePaths(candidates);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
function cursorMcpConfigPath() {
|
|
2420
|
+
const candidates = cursorMcpConfigCandidates();
|
|
2421
|
+
for (const candidate of candidates) {
|
|
2422
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
2423
|
+
}
|
|
2424
|
+
return candidates[0];
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
function parseCursorMcpConfigContent(raw, filePath) {
|
|
2428
|
+
const normalizedRaw = String(raw || "").replace(/^\uFEFF/, "");
|
|
2429
|
+
if (!normalizedRaw.trim()) {
|
|
2430
|
+
return { ok: true, data: { mcpServers: {} } };
|
|
2399
2431
|
}
|
|
2400
2432
|
try {
|
|
2401
|
-
const raw = fs.readFileSync(filePath, "utf8");
|
|
2402
|
-
const normalizedRaw = raw.replace(/^\uFEFF/, "");
|
|
2403
|
-
if (!normalizedRaw.trim()) {
|
|
2404
|
-
return { ok: true, filePath, exists: true, data: { mcpServers: {} } };
|
|
2405
|
-
}
|
|
2406
2433
|
const parsed = JSON.parse(normalizedRaw);
|
|
2407
2434
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2408
|
-
return { ok: false,
|
|
2435
|
+
return { ok: false, error: `Cursor MCP config must be a JSON object. (${filePath})` };
|
|
2409
2436
|
}
|
|
2410
2437
|
const mcpServers = parsed.mcpServers && typeof parsed.mcpServers === "object" && !Array.isArray(parsed.mcpServers)
|
|
2411
2438
|
? parsed.mcpServers
|
|
2412
2439
|
: {};
|
|
2413
|
-
return { ok: true,
|
|
2440
|
+
return { ok: true, data: { ...parsed, mcpServers } };
|
|
2414
2441
|
} catch (error) {
|
|
2415
2442
|
return {
|
|
2416
2443
|
ok: false,
|
|
2417
|
-
filePath
|
|
2418
|
-
|
|
2444
|
+
error: `Failed to parse Cursor MCP config: ${error && error.message ? error.message : String(error)} (${filePath})`
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
function readCursorMcpConfig() {
|
|
2450
|
+
const candidates = cursorMcpConfigCandidates();
|
|
2451
|
+
const errors = [];
|
|
2452
|
+
for (const candidate of candidates) {
|
|
2453
|
+
if (!fs.existsSync(candidate)) continue;
|
|
2454
|
+
try {
|
|
2455
|
+
const raw = fs.readFileSync(candidate, "utf8");
|
|
2456
|
+
const parsed = parseCursorMcpConfigContent(raw, candidate);
|
|
2457
|
+
if (!parsed.ok) {
|
|
2458
|
+
errors.push(parsed.error);
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
return {
|
|
2462
|
+
ok: true,
|
|
2463
|
+
filePath: candidate,
|
|
2464
|
+
exists: true,
|
|
2465
|
+
data: parsed.data,
|
|
2466
|
+
candidates
|
|
2467
|
+
};
|
|
2468
|
+
} catch (error) {
|
|
2469
|
+
errors.push(`Failed to read Cursor MCP config: ${error && error.message ? error.message : String(error)} (${candidate})`);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
if (errors.length) {
|
|
2474
|
+
return {
|
|
2475
|
+
ok: false,
|
|
2476
|
+
filePath: candidates[0],
|
|
2477
|
+
error: errors.join("; ")
|
|
2419
2478
|
};
|
|
2420
2479
|
}
|
|
2480
|
+
|
|
2481
|
+
return { ok: true, filePath: candidates[0], exists: false, data: { mcpServers: {} }, candidates };
|
|
2421
2482
|
}
|
|
2422
2483
|
|
|
2423
|
-
function writeCursorMcpConfig(filePath, data) {
|
|
2424
|
-
|
|
2425
|
-
|
|
2484
|
+
function writeCursorMcpConfig(filePath, data, mirrorPaths = []) {
|
|
2485
|
+
const allPaths = dedupePaths([filePath, ...(Array.isArray(mirrorPaths) ? mirrorPaths : [])]);
|
|
2486
|
+
const written = [];
|
|
2487
|
+
const mirrorFailures = [];
|
|
2488
|
+
|
|
2489
|
+
for (let i = 0; i < allPaths.length; i += 1) {
|
|
2490
|
+
const currentPath = allPaths[i];
|
|
2491
|
+
try {
|
|
2492
|
+
fs.mkdirSync(path.dirname(currentPath), { recursive: true });
|
|
2493
|
+
fs.writeFileSync(currentPath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
2494
|
+
written.push(currentPath);
|
|
2495
|
+
} catch (error) {
|
|
2496
|
+
const message = `${currentPath}: ${error && error.message ? error.message : String(error)}`;
|
|
2497
|
+
if (i === 0) {
|
|
2498
|
+
throw new Error(message);
|
|
2499
|
+
}
|
|
2500
|
+
mirrorFailures.push(message);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
return { written, mirrorFailures };
|
|
2426
2505
|
}
|
|
2427
2506
|
|
|
2428
2507
|
function cursorRegistrationMatches(entry, mcpCommand, mcpArgs = []) {
|
|
@@ -2455,7 +2534,18 @@ function ensureCursorRegistered(serverName, mcpCommand, mcpArgs = [], force = fa
|
|
|
2455
2534
|
if (force || !cursorRegistrationMatches(current, desiredEntry.command, desiredEntry.args)) {
|
|
2456
2535
|
config.mcpServers[serverName] = desiredEntry;
|
|
2457
2536
|
try {
|
|
2458
|
-
|
|
2537
|
+
const mirrors = (loaded.candidates || []).filter(
|
|
2538
|
+
(candidate) => normalizeComparableText(candidate) !== normalizeComparableText(loaded.filePath)
|
|
2539
|
+
);
|
|
2540
|
+
const writeResult = writeCursorMcpConfig(loaded.filePath, config, mirrors);
|
|
2541
|
+
const detailParts = [`primary=${loaded.filePath}`];
|
|
2542
|
+
if (writeResult.written.length > 1) {
|
|
2543
|
+
detailParts.push(`mirrored=${writeResult.written.slice(1).join(",")}`);
|
|
2544
|
+
}
|
|
2545
|
+
if (writeResult.mirrorFailures.length) {
|
|
2546
|
+
detailParts.push(`mirror_failures=${writeResult.mirrorFailures.join(" | ")}`);
|
|
2547
|
+
}
|
|
2548
|
+
return { ok: true, action: "registered", detail: detailParts.join("; ") };
|
|
2459
2549
|
} catch (error) {
|
|
2460
2550
|
return {
|
|
2461
2551
|
ok: false,
|
|
@@ -2463,9 +2553,8 @@ function ensureCursorRegistered(serverName, mcpCommand, mcpArgs = [], force = fa
|
|
|
2463
2553
|
detail: `${error && error.message ? error.message : String(error)} (${loaded.filePath})`
|
|
2464
2554
|
};
|
|
2465
2555
|
}
|
|
2466
|
-
return { ok: true, action: "registered", detail: loaded.filePath };
|
|
2467
2556
|
}
|
|
2468
|
-
return { ok: true, action: "already registered", detail: loaded.filePath };
|
|
2557
|
+
return { ok: true, action: "already registered", detail: `primary=${loaded.filePath}` };
|
|
2469
2558
|
}
|
|
2470
2559
|
|
|
2471
2560
|
function removeCursorRegistration(serverName) {
|
|
@@ -2474,17 +2563,32 @@ function removeCursorRegistration(serverName) {
|
|
|
2474
2563
|
if (!Object.prototype.hasOwnProperty.call(loaded.data.mcpServers, serverName)) return;
|
|
2475
2564
|
delete loaded.data.mcpServers[serverName];
|
|
2476
2565
|
try {
|
|
2477
|
-
|
|
2566
|
+
const mirrors = (loaded.candidates || []).filter(
|
|
2567
|
+
(candidate) => normalizeComparableText(candidate) !== normalizeComparableText(loaded.filePath)
|
|
2568
|
+
);
|
|
2569
|
+
writeCursorMcpConfig(loaded.filePath, loaded.data, mirrors);
|
|
2478
2570
|
} catch (_) {
|
|
2479
2571
|
// ignore best-effort cleanup
|
|
2480
2572
|
}
|
|
2481
2573
|
}
|
|
2482
2574
|
|
|
2483
2575
|
function isCursorRegistered(serverName, mcpCommand, mcpArgs = []) {
|
|
2484
|
-
const
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2576
|
+
const candidates = cursorMcpConfigCandidates();
|
|
2577
|
+
for (const candidate of candidates) {
|
|
2578
|
+
if (!fs.existsSync(candidate)) continue;
|
|
2579
|
+
try {
|
|
2580
|
+
const raw = fs.readFileSync(candidate, "utf8");
|
|
2581
|
+
const parsed = parseCursorMcpConfigContent(raw, candidate);
|
|
2582
|
+
if (!parsed.ok) continue;
|
|
2583
|
+
const entry = parsed.data && parsed.data.mcpServers ? parsed.data.mcpServers[serverName] : null;
|
|
2584
|
+
if (cursorRegistrationMatches(entry, mcpCommand, mcpArgs)) {
|
|
2585
|
+
return true;
|
|
2586
|
+
}
|
|
2587
|
+
} catch (_) {
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
return false;
|
|
2488
2592
|
}
|
|
2489
2593
|
|
|
2490
2594
|
function clientGetAttempts(cliBin, serverName) {
|
|
@@ -2666,6 +2770,19 @@ function runSetupInternal(config, options = {}) {
|
|
|
2666
2770
|
}
|
|
2667
2771
|
|
|
2668
2772
|
for (const cliBin of clients) {
|
|
2773
|
+
if (EDITOR_STYLE_CLIENTS.has(String(cliBin || "").toLowerCase()) && cliBin !== "cursor") {
|
|
2774
|
+
for (const target of activeTargets) {
|
|
2775
|
+
results.push({
|
|
2776
|
+
cliBin,
|
|
2777
|
+
serverName: target.serverName,
|
|
2778
|
+
ok: false,
|
|
2779
|
+
action: "manual configuration required",
|
|
2780
|
+
detail: `${cliBin} CLI mcp subcommands are unavailable in automation mode; configure MCP server in ${cliBin} client settings`
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
continue;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2669
2786
|
for (const target of activeTargets) {
|
|
2670
2787
|
const alreadyRegistered = isRegistered(cliBin, target.serverName, target);
|
|
2671
2788
|
if (config.force || !ensureOnly || alreadyRegistered) {
|
|
@@ -2763,6 +2880,13 @@ function runDoctor(config) {
|
|
|
2763
2880
|
}
|
|
2764
2881
|
|
|
2765
2882
|
foundClient = true;
|
|
2883
|
+
if (EDITOR_STYLE_CLIENTS.has(String(cliBin || "").toLowerCase()) && cliBin !== "cursor") {
|
|
2884
|
+
hasIssue = true;
|
|
2885
|
+
process.stdout.write(`${cliBin}: manual MCP configuration required\n`);
|
|
2886
|
+
process.stdout.write(` action: register mcp-aws-manager in ${cliBin} settings (CLI mcp subcommands unavailable)\n`);
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2766
2890
|
for (const target of targets) {
|
|
2767
2891
|
const registered = isRegistered(cliBin, target.serverName, target);
|
|
2768
2892
|
if (registered) {
|
package/package.json
CHANGED