mcp-aws-manager 0.4.2 → 0.4.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/README.md CHANGED
@@ -4,14 +4,33 @@ AWS operations CLI + MCP stdio server (SSM-first).
4
4
 
5
5
  This package orchestrates AWS operations (inventory/runtime/remediation) with a normalized output schema and `ACTION_REQUIRED` guidance. It is not a plain AWS CLI wrapper.
6
6
 
7
- ## TL;DR
7
+ ## Agent-First Quick Start
8
+
9
+ Use this as the default flow for agent environments:
8
10
 
9
11
  ```bash
10
12
  npm install -g mcp-aws-manager
11
13
  mcp-aws-manager --version
12
- mcp-aws-manager
14
+ mcp-aws-manager setup --force
13
15
  mcp-aws-manager doctor
14
- mcp-aws-manager discover --profiles default --no-progress
16
+ ```
17
+
18
+ Agent Prompt Examples:
19
+
20
+ Ask in natural language like below; the agent should run fetch/analysis steps as needed.
21
+
22
+ ```text
23
+ Give me a fresh full AWS server status summary.
24
+ ```
25
+
26
+ Then try requests like:
27
+
28
+ ```text
29
+ Compare current AWS server status with the previous check and show only what changed.
30
+ ```
31
+
32
+ ```text
33
+ Summarize AWS server status with required actions and high-impact warnings in priority order.
15
34
  ```
16
35
 
17
36
  ## What It Does
@@ -168,18 +187,6 @@ mcp-aws-manager doctor
168
187
 
169
188
  Default behavior (`doctor` without `--clients`) auto-detects installed clients and skips non-installed CLIs.
170
189
 
171
- ### Agent Instruction Snippet (Any Client)
172
-
173
- When asking an agent to check AWS status, use this exact sequence first:
174
-
175
- ```bash
176
- mcp-aws-manager setup --force
177
- mcp-aws-manager doctor
178
- mcp-aws-manager discover --profiles default --no-progress
179
- ```
180
-
181
- Require the agent to include `runId`/`started_at` (live run evidence) in the response. If missing, treat it as file-only/cached summary.
182
-
183
190
  3. Configure AWS auth (SSO recommended):
184
191
 
185
192
  ```bash
@@ -991,5 +998,3 @@ Document status:
991
998
  - `docs/RECORDS_FIELD_REFERENCE_KO.md`: full `records[]` field reference (292 fields)
992
999
  - `docs/RESPONSE_COMPATIBILITY_POLICY.md`: response schema/version compatibility rules
993
1000
  - `schemas/mcp-tool-response.schema.json`: canonical tool response JSON schema
994
-
995
-
package/README_KO.md CHANGED
@@ -4,14 +4,32 @@ SSM 우선(SSM-first) 방식의 AWS 운영 CLI + MCP stdio 서버입니다.
4
4
 
5
5
  이 패키지는 AWS 운영 작업(인벤토리/런타임/조치)을 정규화된 응답 스키마와 `ACTION_REQUIRED` 가이드로 제공합니다. 단순 AWS CLI 래퍼가 아니라, 에이전트 친화적인 운영 워크플로우 실행을 목표로 합니다.
6
6
 
7
- ## 빠른 시작
7
+ ## 에이전트 기준 빠른 시작
8
+
9
+ 에이전트 환경에서는 아래 순서를 기본 플로우로 사용하세요.
8
10
 
9
11
  ```bash
10
12
  npm install -g mcp-aws-manager
11
13
  mcp-aws-manager --version
12
- mcp-aws-manager
14
+ mcp-aws-manager setup --force
13
15
  mcp-aws-manager doctor
14
- mcp-aws-manager discover --profiles default --no-progress
16
+ ```
17
+
18
+ 에이전트 예시 프롬프트:
19
+
20
+ 아래처럼 자연어로 요청하면, 에이전트가 필요 시 조회/분석 단계를 실행합니다.
21
+
22
+ ```text
23
+ AWS 서버 상태 전체를 최신으로 점검해서 요약해주세요.
24
+ ```
25
+
26
+ 이후에는 이렇게 요청해보세요.
27
+
28
+ ```text
29
+ AWS 서버 상태를 지난 점검 결과와 비교해서 달라진 항목만 알려주세요.
30
+ ```
31
+ ```text
32
+ AWS 서버의 상태를 필수 조치 및 주요 경고 사항을 중심으로 우선순위에 따라 요약해주세요.
15
33
  ```
16
34
 
17
35
  ## 무엇을 제공하나요
@@ -168,18 +186,6 @@ mcp-aws-manager doctor
168
186
 
169
187
  기본 동작(`--clients` 없이 `doctor`)은 설치된 클라이언트를 자동 감지하고, 미설치 CLI는 skip 처리합니다.
170
188
 
171
- ### 에이전트 지시용 공통 스니펫
172
-
173
- 에이전트에게 AWS 상태 점검을 지시할 때는 먼저 아래 순서를 그대로 실행하도록 요청하세요.
174
-
175
- ```bash
176
- mcp-aws-manager setup --force
177
- mcp-aws-manager doctor
178
- mcp-aws-manager discover --profiles default --no-progress
179
- ```
180
-
181
- 응답에는 반드시 `runId`/`started_at`(라이브 실행 증거)를 포함하도록 요구하세요. 없으면 파일 기반/캐시 요약으로 간주하세요.
182
-
183
189
  3. AWS 인증 설정(권장: SSO):
184
190
 
185
191
  ```bash
@@ -985,6 +991,3 @@ E2E 러너 검증 항목:
985
991
  - `docs/RECORDS_FIELD_REFERENCE_KO.md`: `records[]` 전수 필드(292개) 레퍼런스
986
992
  - `docs/RESPONSE_COMPATIBILITY_POLICY.md`: 응답 스키마/버전 호환 정책
987
993
  - `schemas/mcp-tool-response.schema.json`: 표준 도구 응답 JSON 스키마
988
-
989
-
990
-
@@ -329,7 +329,8 @@ function pickSectionPayload(section, context) {
329
329
  runFinishedAt: runMeta.runFinishedAt || null,
330
330
  outputHash: runMeta.outputHash || null,
331
331
  recordsHash: runMeta.recordsHash || null,
332
- htmlHash: runMeta.htmlHash || null
332
+ htmlHash: runMeta.htmlHash || null,
333
+ generatedArtifacts: runMeta.generatedArtifacts || null
333
334
  };
334
335
  }
335
336
  return {};
@@ -428,6 +429,12 @@ function renderMarkdown(bundle, templateName = "default") {
428
429
  const fields = bundle && bundle.fields && Array.isArray(bundle.fields.effectiveFields)
429
430
  ? bundle.fields.effectiveFields
430
431
  : ["resourceType", "resourceId", "profile", "region", "state"];
432
+ const runMeta = bundle && bundle.runMeta && typeof bundle.runMeta === "object"
433
+ ? bundle.runMeta
434
+ : {};
435
+ const generatedArtifacts = runMeta.generatedArtifacts && typeof runMeta.generatedArtifacts === "object"
436
+ ? runMeta.generatedArtifacts
437
+ : {};
431
438
 
432
439
  const lines = [];
433
440
  lines.push("# AWS Discovery Output");
@@ -451,6 +458,18 @@ function renderMarkdown(bundle, templateName = "default") {
451
458
  lines.push(`| runtimeSnapshotSuccessCount | ${summary.runtimeSnapshotSuccessCount || 0} |`);
452
459
  lines.push("");
453
460
 
461
+ lines.push("## Generated Artifacts");
462
+ lines.push("");
463
+ lines.push("| artifact | path |");
464
+ lines.push("|---|---|");
465
+ lines.push(`| output | ${generatedArtifacts.outputPath || (generatedArtifacts.outputChannel || "stdout")} |`);
466
+ lines.push(`| htmlReport | ${generatedArtifacts.htmlReportPath || "off"} |`);
467
+ lines.push(`| topology | ${generatedArtifacts.topologyPath || "off"} |`);
468
+ lines.push(`| relationships | ${generatedArtifacts.relationshipsPath || "off"} |`);
469
+ lines.push(`| governanceLog | ${generatedArtifacts.governanceLogPath || "off"} |`);
470
+ lines.push(`| incidentPayload | ${generatedArtifacts.incidentPayloadPath || "off"} |`);
471
+ lines.push("");
472
+
454
473
  if (template === "default" && sections.length) {
455
474
  lines.push("## Sections");
456
475
  lines.push("");
@@ -2311,6 +2311,16 @@ function listLocalAwsProfiles() {
2311
2311
  }
2312
2312
 
2313
2313
  function runCLICommand(cliBin, args, options = {}) {
2314
+ // Cursor 2.x no longer reliably supports `cursor mcp ...` CLI subcommands.
2315
+ // Calling it can open editor tabs named like "mcp", "add", "get".
2316
+ if (String(cliBin).toLowerCase() === "cursor" && Array.isArray(args) && String(args[0] || "").toLowerCase() === "mcp") {
2317
+ return {
2318
+ status: 2,
2319
+ stdout: "",
2320
+ stderr: "cursor mcp subcommand is disabled; use ~/.cursor/mcp.json registration"
2321
+ };
2322
+ }
2323
+
2314
2324
  const execOptions = {
2315
2325
  cwd: process.cwd(),
2316
2326
  env: process.env,
@@ -2351,7 +2361,12 @@ function commandExists(bin, checkArgs) {
2351
2361
  }
2352
2362
 
2353
2363
  function clientHelpAttempts(cliBin) {
2354
- if (cliBin === "cursor" || cliBin === "windsurf" || cliBin === "antigravity") {
2364
+ if (cliBin === "cursor") {
2365
+ return [
2366
+ ["--help"]
2367
+ ];
2368
+ }
2369
+ if (cliBin === "windsurf" || cliBin === "antigravity") {
2355
2370
  return [
2356
2371
  ["mcp", "--help"],
2357
2372
  ["--help"]
@@ -2360,6 +2375,118 @@ function clientHelpAttempts(cliBin) {
2360
2375
  return [["mcp", "--help"]];
2361
2376
  }
2362
2377
 
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
+ function normalizeComparableText(value) {
2387
+ const text = String(value == null ? "" : value).trim();
2388
+ return process.platform === "win32" ? text.toLowerCase() : text;
2389
+ }
2390
+
2391
+ function normalizeComparableArgs(args) {
2392
+ return Array.isArray(args) ? args.map((arg) => normalizeComparableText(arg)) : [];
2393
+ }
2394
+
2395
+ function readCursorMcpConfig() {
2396
+ const filePath = cursorMcpConfigPath();
2397
+ if (!fs.existsSync(filePath)) {
2398
+ return { ok: true, filePath, exists: false, data: { mcpServers: {} } };
2399
+ }
2400
+ 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
+ const parsed = JSON.parse(normalizedRaw);
2407
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2408
+ return { ok: false, filePath, error: "Cursor MCP config must be a JSON object." };
2409
+ }
2410
+ const mcpServers = parsed.mcpServers && typeof parsed.mcpServers === "object" && !Array.isArray(parsed.mcpServers)
2411
+ ? parsed.mcpServers
2412
+ : {};
2413
+ return { ok: true, filePath, exists: true, data: { ...parsed, mcpServers } };
2414
+ } catch (error) {
2415
+ return {
2416
+ ok: false,
2417
+ filePath,
2418
+ error: `Failed to parse Cursor MCP config: ${error && error.message ? error.message : String(error)}`
2419
+ };
2420
+ }
2421
+ }
2422
+
2423
+ function writeCursorMcpConfig(filePath, data) {
2424
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
2425
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
2426
+ }
2427
+
2428
+ function cursorRegistrationMatches(entry, mcpCommand, mcpArgs = []) {
2429
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
2430
+ const entryCommand = normalizeComparableText(entry.command);
2431
+ if (!entryCommand) return false;
2432
+ if (normalizeComparableText(mcpCommand) && entryCommand !== normalizeComparableText(mcpCommand)) {
2433
+ return false;
2434
+ }
2435
+ const expectedArgs = normalizeComparableArgs(mcpArgs);
2436
+ const actualArgs = normalizeComparableArgs(entry.args);
2437
+ if (expectedArgs.length !== actualArgs.length) return false;
2438
+ for (let i = 0; i < expectedArgs.length; i += 1) {
2439
+ if (expectedArgs[i] !== actualArgs[i]) return false;
2440
+ }
2441
+ return true;
2442
+ }
2443
+
2444
+ function ensureCursorRegistered(serverName, mcpCommand, mcpArgs = [], force = false) {
2445
+ const loaded = readCursorMcpConfig();
2446
+ if (!loaded.ok) {
2447
+ return { ok: false, action: "cursor config invalid", detail: `${loaded.error} (${loaded.filePath})` };
2448
+ }
2449
+ const desiredEntry = {
2450
+ command: String(mcpCommand || ""),
2451
+ args: Array.isArray(mcpArgs) ? mcpArgs.map((arg) => String(arg)) : []
2452
+ };
2453
+ const config = loaded.data;
2454
+ const current = config.mcpServers[serverName];
2455
+ if (force || !cursorRegistrationMatches(current, desiredEntry.command, desiredEntry.args)) {
2456
+ config.mcpServers[serverName] = desiredEntry;
2457
+ try {
2458
+ writeCursorMcpConfig(loaded.filePath, config);
2459
+ } catch (error) {
2460
+ return {
2461
+ ok: false,
2462
+ action: "cursor registration failed",
2463
+ detail: `${error && error.message ? error.message : String(error)} (${loaded.filePath})`
2464
+ };
2465
+ }
2466
+ return { ok: true, action: "registered", detail: loaded.filePath };
2467
+ }
2468
+ return { ok: true, action: "already registered", detail: loaded.filePath };
2469
+ }
2470
+
2471
+ function removeCursorRegistration(serverName) {
2472
+ const loaded = readCursorMcpConfig();
2473
+ if (!loaded.ok || !loaded.exists) return;
2474
+ if (!Object.prototype.hasOwnProperty.call(loaded.data.mcpServers, serverName)) return;
2475
+ delete loaded.data.mcpServers[serverName];
2476
+ try {
2477
+ writeCursorMcpConfig(loaded.filePath, loaded.data);
2478
+ } catch (_) {
2479
+ // ignore best-effort cleanup
2480
+ }
2481
+ }
2482
+
2483
+ function isCursorRegistered(serverName, mcpCommand, mcpArgs = []) {
2484
+ const loaded = readCursorMcpConfig();
2485
+ if (!loaded.ok) return false;
2486
+ const entry = loaded.data && loaded.data.mcpServers ? loaded.data.mcpServers[serverName] : null;
2487
+ return cursorRegistrationMatches(entry, mcpCommand, mcpArgs);
2488
+ }
2489
+
2363
2490
  function clientGetAttempts(cliBin, serverName) {
2364
2491
  if (cliBin === "claude") {
2365
2492
  return [
@@ -2427,13 +2554,22 @@ function clientAddAttempts(cliBin, serverName, mcpCommand, mcpArgs = []) {
2427
2554
  }
2428
2555
 
2429
2556
  function removeRegistration(cliBin, serverName) {
2557
+ if (cliBin === "cursor") {
2558
+ removeCursorRegistration(serverName);
2559
+ return;
2560
+ }
2430
2561
  const attempts = clientRemoveAttempts(cliBin, serverName);
2431
2562
  for (const args of attempts) {
2432
2563
  runCLICommand(cliBin, args, { stdio: "ignore" });
2433
2564
  }
2434
2565
  }
2435
2566
 
2436
- function isRegistered(cliBin, serverName) {
2567
+ function isRegistered(cliBin, serverName, target = null) {
2568
+ if (cliBin === "cursor") {
2569
+ const mcpCommand = target && target.mcpCommand ? target.mcpCommand : "";
2570
+ const mcpArgs = target && Array.isArray(target.mcpArgs) ? target.mcpArgs : [];
2571
+ return isCursorRegistered(serverName, mcpCommand, mcpArgs);
2572
+ }
2437
2573
  const attempts = clientGetAttempts(cliBin, serverName);
2438
2574
  for (const args of attempts) {
2439
2575
  const run = runCLICommand(cliBin, args, { stdio: "ignore" });
@@ -2531,11 +2667,36 @@ function runSetupInternal(config, options = {}) {
2531
2667
 
2532
2668
  for (const cliBin of clients) {
2533
2669
  for (const target of activeTargets) {
2534
- const alreadyRegistered = isRegistered(cliBin, target.serverName);
2670
+ const alreadyRegistered = isRegistered(cliBin, target.serverName, target);
2535
2671
  if (config.force || !ensureOnly || alreadyRegistered) {
2536
2672
  removeRegistration(cliBin, target.serverName);
2537
2673
  }
2538
2674
 
2675
+ if (cliBin === "cursor") {
2676
+ const registered = ensureCursorRegistered(target.serverName, target.mcpCommand, target.mcpArgs || [], config.force);
2677
+ if (registered.ok) {
2678
+ const action = ensureOnly
2679
+ ? (alreadyRegistered ? "updated" : "registered")
2680
+ : (alreadyRegistered ? "re-registered" : "registered");
2681
+ results.push({
2682
+ cliBin,
2683
+ serverName: target.serverName,
2684
+ ok: true,
2685
+ action: `${action} (cursor mcp.json)`,
2686
+ detail: registered.detail || ""
2687
+ });
2688
+ } else {
2689
+ results.push({
2690
+ cliBin,
2691
+ serverName: target.serverName,
2692
+ ok: false,
2693
+ action: registered.action || "registration failed",
2694
+ detail: registered.detail || "unknown"
2695
+ });
2696
+ }
2697
+ continue;
2698
+ }
2699
+
2539
2700
  const registered = tryRegister(cliBin, target.serverName, target.mcpCommand, target.mcpArgs || []);
2540
2701
  if (registered.ok) {
2541
2702
  const action = ensureOnly
@@ -2554,7 +2715,7 @@ function runSetupInternal(config, options = {}) {
2554
2715
 
2555
2716
  for (const row of results) {
2556
2717
  process.stdout.write(`${row.cliBin}/${row.serverName}: ${row.action}\n`);
2557
- if (!row.ok && row.detail) {
2718
+ if (row.detail && (!row.ok || /\bcursor mcp\.json\b/i.test(String(row.action || "")))) {
2558
2719
  process.stdout.write(` detail: ${row.detail}\n`);
2559
2720
  }
2560
2721
  }
@@ -2603,7 +2764,7 @@ function runDoctor(config) {
2603
2764
 
2604
2765
  foundClient = true;
2605
2766
  for (const target of targets) {
2606
- const registered = isRegistered(cliBin, target.serverName);
2767
+ const registered = isRegistered(cliBin, target.serverName, target);
2607
2768
  if (registered) {
2608
2769
  process.stdout.write(`${cliBin}: registered (${target.serverName})\n`);
2609
2770
  } else {
@@ -8775,6 +8936,52 @@ function writeJsonArtifact(targetPath, payload, label, warnings, requiredActions
8775
8936
  }
8776
8937
  }
8777
8938
 
8939
+ function buildGeneratedArtifacts(config, state = {}) {
8940
+ return {
8941
+ outputPath: config && config.outPath ? config.outPath : null,
8942
+ outputChannel: config && config.outPath ? "file" : "stdout",
8943
+ htmlReportPath: state.htmlReportPath || null,
8944
+ topologyPath: state.topologyPath || null,
8945
+ relationshipsPath: state.relationshipsPath || null,
8946
+ governanceLogPath: state.governanceLogPath || null,
8947
+ incidentPayloadPath: state.incidentPayloadPath || null
8948
+ };
8949
+ }
8950
+
8951
+ function generatedArtifactsSummary(artifacts) {
8952
+ const item = artifacts && typeof artifacts === "object" ? artifacts : {};
8953
+ return [
8954
+ `output=${item.outputPath || (item.outputChannel || "stdout")}`,
8955
+ `html=${item.htmlReportPath || "off"}`,
8956
+ `topology=${item.topologyPath || "off"}`,
8957
+ `relationships=${item.relationshipsPath || "off"}`,
8958
+ `governance=${item.governanceLogPath || "off"}`,
8959
+ `incident=${item.incidentPayloadPath || "off"}`
8960
+ ].join(", ");
8961
+ }
8962
+
8963
+ function syncRunMetaToPresentationBundle(presentationBundle, runMeta) {
8964
+ if (!presentationBundle || typeof presentationBundle !== "object") return;
8965
+ const meta = runMeta && typeof runMeta === "object" ? { ...runMeta } : {};
8966
+ presentationBundle.runMeta = meta;
8967
+
8968
+ const sections = presentationBundle.sections;
8969
+ const payload = sections && typeof sections === "object" ? sections.payload : null;
8970
+ const governance = payload && typeof payload === "object" ? payload.governance : null;
8971
+ if (!governance || typeof governance !== "object") return;
8972
+
8973
+ payload.governance = {
8974
+ ...governance,
8975
+ runId: meta.runId || null,
8976
+ runStartedAt: meta.runStartedAt || null,
8977
+ runFinishedAt: meta.runFinishedAt || null,
8978
+ outputHash: meta.outputHash || null,
8979
+ recordsHash: meta.recordsHash || null,
8980
+ htmlHash: meta.htmlHash || null,
8981
+ generatedArtifacts: meta.generatedArtifacts || null
8982
+ };
8983
+ }
8984
+
8778
8985
  function readLastGovernanceEntryHash(logPath) {
8779
8986
  if (!logPath || !fs.existsSync(logPath)) return null;
8780
8987
  try {
@@ -11985,6 +12192,14 @@ async function runWorkflow(config) {
11985
12192
  incidentArtifactHash: incidentDispatch.artifactHash || null
11986
12193
  };
11987
12194
  const outputSummary = summarizeOutputRecords(outputRecords);
12195
+ const recordsHash = hashText(JSON.stringify(outputRecords));
12196
+ const runMeta = {
12197
+ ...baseRunMeta,
12198
+ recordsHash,
12199
+ htmlOutPath: null,
12200
+ htmlHash: null,
12201
+ generatedArtifacts: null
12202
+ };
11988
12203
  const presentationBundle = buildOutputBundle({
11989
12204
  rawRecords: outputRecords,
11990
12205
  summary: outputSummary,
@@ -11997,26 +12212,13 @@ async function runWorkflow(config) {
11997
12212
  excludeFields: config.excludeFields,
11998
12213
  sections: config.sections,
11999
12214
  clientProfile: config.clientProfile,
12000
- runMeta: baseRunMeta
12215
+ runMeta
12001
12216
  });
12002
-
12003
- const outputBody = renderOutput(config, presentationBundle);
12004
- const outputHash = hashText(outputBody);
12005
- const recordsHash = hashText(JSON.stringify(outputRecords));
12006
- const runMeta = { ...baseRunMeta, outputHash, recordsHash };
12007
-
12008
- presentationBundle.runMeta = {
12009
- ...(presentationBundle.runMeta && typeof presentationBundle.runMeta === "object" ? presentationBundle.runMeta : {}),
12010
- ...runMeta
12011
- };
12012
- writeOutput(config, outputBody);
12217
+ syncRunMetaToPresentationBundle(presentationBundle, runMeta);
12013
12218
  const htmlPath = writeHtmlReport(config, outputRecords, domainInsights, runMeta, presentationBundle);
12014
12219
  if (htmlPath) {
12015
12220
  runMeta.htmlOutPath = htmlPath;
12016
12221
  runMeta.htmlHash = hashText(fs.readFileSync(htmlPath, "utf8"));
12017
- } else {
12018
- runMeta.htmlOutPath = null;
12019
- runMeta.htmlHash = null;
12020
12222
  }
12021
12223
  if (htmlPath && shouldAutoOpenHtml(config)) {
12022
12224
  const opened = tryOpenFile(htmlPath);
@@ -12027,6 +12229,19 @@ async function runWorkflow(config) {
12027
12229
  }
12028
12230
  }
12029
12231
 
12232
+ runMeta.generatedArtifacts = buildGeneratedArtifacts(config, {
12233
+ htmlReportPath: runMeta.htmlOutPath,
12234
+ topologyPath: topologyWrite.path || null,
12235
+ relationshipsPath: relationshipsWrite.path || null,
12236
+ governanceLogPath: governancePersist.path || null,
12237
+ incidentPayloadPath: incidentDispatch.artifactPath || null
12238
+ });
12239
+ syncRunMetaToPresentationBundle(presentationBundle, runMeta);
12240
+
12241
+ const outputBody = renderOutput(config, presentationBundle);
12242
+ runMeta.outputHash = hashText(outputBody);
12243
+ writeOutput(config, outputBody);
12244
+
12030
12245
  progress(config, 19, "END: emit execution summary and evidence metadata");
12031
12246
  const outputEc2 = outputRecords.filter((r) => r.resourceType === "ec2");
12032
12247
  const outputLambda = outputRecords.filter((r) => r.resourceType === "lambda");
@@ -12078,6 +12293,7 @@ async function runWorkflow(config) {
12078
12293
  const ssmManaged = outputEc2.filter((r) => r.ssmManaged).length;
12079
12294
  const ssmOnline = outputEc2.filter((r) => r.ssmOnline).length;
12080
12295
  const publicCount = outputEc2.filter((r) => Boolean(r.publicIp)).length;
12296
+ eprint(`Artifacts: ${generatedArtifactsSummary(runMeta.generatedArtifacts)}`);
12081
12297
  eprint(`Summary: execution_mode=${INTERNAL_BACKEND_ID}, manual_mode=${config.manualServerListPath ? "on" : "off"}, selected_surface=${selectedSurface}, selected_schema_tier=${selectedSchemaTier}, mode=${routing.modeInfo.mode}, mode_source=${routing.modeInfo.source}, profiles=${stats.profiles}, regions_scanned=${stats.regions}, region_errors=${stats.regionErrors}, ec2_scanned=${stats.instancesScanned}, lambda_scanned=${stats.lambdaFunctionsScanned}, alb_scanned=${stats.loadBalancersScanned}, targetgroup_scanned=${stats.targetGroupsScanned}, asg_scanned=${stats.autoScalingGroupsScanned}, rds_scanned=${stats.rdsInstancesScanned}, elasticache_scanned=${stats.elasticacheClustersScanned}, route53_zone_scanned=${stats.route53ZonesScanned}, vpc_scanned=${stats.vpcsScanned || 0}, subnet_scanned=${stats.subnetsScanned || 0}, security_group_scanned=${stats.securityGroupsScanned || 0}, ecs_cluster_scanned=${stats.ecsClustersScanned || 0}, ecs_service_scanned=${stats.ecsServicesScanned || 0}, s3_bucket_scanned=${stats.s3BucketsScanned || 0}, iam_role_scanned=${stats.iamRolesScanned || 0}, kms_key_scanned=${stats.kmsKeysScanned || 0}, cloudwatch_alarm_scanned=${stats.cloudwatchAlarmsScanned || 0}, cloudtrail_trail_scanned=${stats.cloudtrailTrailsScanned || 0}, config_recorder_scanned=${stats.configRecordersScanned || 0}, secret_scanned=${stats.secretsScanned || 0}, ssm_parameter_scanned=${stats.parametersScanned || 0}, ecr_repository_scanned=${stats.ecrRepositoriesScanned || 0}, dynamodb_table_scanned=${stats.dynamodbTablesScanned || 0}, sns_topic_scanned=${stats.snsTopicsScanned || 0}, eventbridge_bus_scanned=${stats.eventbridgeBusesScanned || 0}, sqs_queue_scanned=${stats.sqsQueuesScanned || 0}, acm_certificate_scanned=${stats.acmCertificatesScanned || 0}, kinesis_stream_scanned=${stats.kinesisStreamsScanned || 0}, msk_cluster_scanned=${stats.mskClustersScanned || 0}, budget_scanned=${stats.budgetsScanned || 0}, cost_anomaly_scanned=${stats.costAnomaliesScanned || 0}, ebs_volume_scanned=${stats.ebsVolumesScanned || 0}, efs_filesystem_scanned=${stats.efsFileSystemsScanned || 0}, eks_cluster_scanned=${stats.eksClustersScanned || 0}, apigateway_rest_api_scanned=${stats.apiGatewayRestApisScanned || 0}, apigatewayv2_api_scanned=${stats.apiGatewayV2ApisScanned || 0}, cloudfront_distribution_scanned=${stats.cloudFrontDistributionsScanned || 0}, waf_web_acl_scanned=${stats.wafWebAclsScanned || 0}, shield_protection_scanned=${stats.shieldProtectionsScanned || 0}, stepfunctions_scanned=${stats.stepFunctionsScanned || 0}, cloudwatch_log_group_scanned=${stats.cloudwatchLogGroupsScanned || 0}, xray_group_scanned=${stats.xrayGroupsScanned || 0}, inspector2_finding_scanned=${stats.inspector2FindingsScanned || 0}, redshift_cluster_scanned=${stats.redshiftClustersScanned || 0}, opensearch_domain_scanned=${stats.opensearchDomainsScanned || 0}, organizations_account_scanned=${stats.organizationsAccountsScanned || 0}, controltower_landing_zone_scanned=${stats.controlTowerLandingZonesScanned || 0}, manual_servers_scanned=${stats.manualServersScanned || 0}, manual_servers_loaded=${stats.manualServersLoaded || 0}, output_records=${outputRecords.length}, output_ec2=${outputEc2.length}, output_lambda=${outputLambda.length}, output_alb=${outputAlb.length}, output_target_group=${outputTargetGroups.length}, output_asg=${outputAsg.length}, output_rds=${outputRds.length}, output_elasticache=${outputElastiCache.length}, output_route53_zone=${outputRoute53.length}, output_vpc=${outputVpc.length}, output_subnet=${outputSubnet.length}, output_security_group=${outputSecurityGroup.length}, output_ecs_cluster=${outputEcsCluster.length}, output_ecs_service=${outputEcsService.length}, output_s3_bucket=${outputS3.length}, output_iam_role=${outputIam.length}, output_kms_key=${outputKms.length}, output_cloudwatch_alarm=${outputCloudwatch.length}, output_cloudtrail_trail=${outputCloudtrail.length}, output_config_recorder=${outputConfig.length}, output_secret=${outputSecret.length}, output_ssm_parameter=${outputParameter.length}, output_ecr_repository=${outputEcr.length}, output_dynamodb_table=${outputDynamodb.length}, output_sns_topic=${outputSns.length}, output_eventbridge_bus=${outputEventbridge.length}, output_sqs_queue=${outputSqs.length}, output_acm_certificate=${outputAcm.length}, output_kinesis_stream=${outputKinesis.length}, output_msk_cluster=${outputMsk.length}, output_budget=${outputBudget.length}, output_cost_anomaly=${outputCostAnomaly.length}, output_ebs_volume=${outputEbs.length}, output_efs_filesystem=${outputEfs.length}, output_eks_cluster=${outputEks.length}, output_apigateway_rest_api=${outputApiGateway.length}, output_apigatewayv2_api=${outputApiGatewayV2.length}, output_cloudfront_distribution=${outputCloudfront.length}, output_waf_web_acl=${outputWaf.length}, output_shield_protection=${outputShield.length}, output_stepfunctions=${outputStepFunctions.length}, output_cloudwatch_log_group=${outputCloudwatchLogs.length}, output_xray_group=${outputXray.length}, output_inspector2_finding=${outputInspector2.length}, output_redshift_cluster=${outputRedshift.length}, output_opensearch_domain=${outputOpensearch.length}, output_organizations_account=${outputOrganizations.length}, output_controltower_landing_zone=${outputControlTower.length}, ec2_public_ip_records=${publicCount}, ec2_ssm_managed=${ssmManaged}, ec2_ssm_online=${ssmOnline}, remediation_attempts=${runtimeStats.remediationAttempts}, remediation_changed=${runtimeStats.remediationChanged}, runtime_snapshot_attempted=${runtimeStats.snapshotAttempted}, runtime_snapshot_succeeded=${runtimeStats.snapshotSucceeded}, runtime_snapshot_ssm_attempted=${runtimeStats.snapshotSsmAttempted}, runtime_snapshot_ssm_succeeded=${runtimeStats.snapshotSsmSucceeded}, runtime_snapshot_ssh_attempted=${runtimeStats.snapshotSshAttempted}, runtime_snapshot_ssh_succeeded=${runtimeStats.snapshotSshSucceeded}, snapshot_profile=${config.snapshotProfile}, output_profile=${presentationBundle.outputProfile || "operator"}, output_profile_source=${presentationBundle.outputProfileSource || "default"}, client_profile=${presentationBundle.clientProfile || "none"}, renderer_template=${config.rendererTemplate || "default"}, topology_path=${topologyWrite.path || "off"}, relationships_path=${relationshipsWrite.path || "off"}, governance_persisted=${governancePersist.persisted ? "on" : "off"}, governance_chain_checked=${governanceVerify.checked ? "on" : "off"}, governance_chain_valid=${governanceVerify.valid == null ? "unknown" : (governanceVerify.valid ? "yes" : "no")}, incident_webhook_dispatched=${incidentDispatch.webhookDispatched ? "on" : "off"}, policy_pack=${integrationDispatch.activePolicyPack || "balanced"}, policy_gate=${policyDecision.allowed !== false ? "allow" : "blocked"}, integration_connectors_enabled=${integrationDispatch.enabledConnectors}, integration_incident_success=${integrationDispatch.incident ? integrationDispatch.incident.successCount : 0}, integration_incident_failure=${integrationDispatch.incident ? integrationDispatch.incident.failureCount : 0}, integration_action_success=${integrationDispatch.actionRequired ? integrationDispatch.actionRequired.successCount : 0}, integration_action_failure=${integrationDispatch.actionRequired ? integrationDispatch.actionRequired.failureCount : 0}, view_sections=${presentationBundle.sections && Array.isArray(presentationBundle.sections.effective) ? presentationBundle.sections.effective.join(",") : "none"}, view_fields=${presentationBundle.fields && Array.isArray(presentationBundle.fields.effectiveFields) ? presentationBundle.fields.effectiveFields.length : 0}, iac_stacks=${domainInsights.iac.scannedStacks || 0}, iac_drifted=${domainInsights.iac.driftedStacks || 0}, cicd_pipelines=${domainInsights.cicd.pipelinesScanned || 0}, cicd_failing=${domainInsights.cicd.pipelinesFailing || 0}, backup_plans=${domainInsights.backupDr.backupPlans || 0}, backup_protected=${domainInsights.backupDr.protectedResources || 0}, security_high_critical=${domainInsights.security.highOrCritical || 0}, finops_cost_7d_usd=${domainInsights.finops.totalCost7dUsd || 0}, app_runtime_coverage_pct=${domainInsights.application.runtimeCoveragePct == null ? "n/a" : domainInsights.application.runtimeCoveragePct}, app_degraded_hosts=${domainInsights.application.degradedHosts || 0}, app_ecs_degraded=${domainInsights.application.ecsServicesDegraded || 0}, app_managed_degraded=${domainInsights.application.managedServicesDegraded || 0}, app_lambda_degraded=${domainInsights.application.lambdaHealth && domainInsights.application.lambdaHealth.degraded ? domainInsights.application.lambdaHealth.degraded : 0}, incident_severity=${domainInsights.incident.severity || "none"}, warnings=${warnings.length}, required_actions=${requiredActions.length}`);
12082
12298
  eprint(`EvidenceMeta: workflow_id=b60f0f18-cc45-4af9-a7c7-217284457759; schema=gesia.orflow.contract.v2; step_count=${TOTAL_STEPS}; execution_mode=${INTERNAL_BACKEND_ID}; run_id=${runId}; output_hash=${runMeta.outputHash}; records_hash=${runMeta.recordsHash}; html_hash=${runMeta.htmlHash || "n/a"}; topology_hash=${runMeta.topologyHash || "n/a"}; relationships_hash=${runMeta.relationshipsHash || "n/a"}; governance_entry_hash=${runMeta.governanceEntryHash || "n/a"}; governance_chain_valid=${runMeta.governanceChainValid == null ? "unknown" : (runMeta.governanceChainValid ? "yes" : "no")}; incident_artifact_hash=${runMeta.incidentArtifactHash || "n/a"}; policy_pack=${integrationDispatch.activePolicyPack || "balanced"}; policy_gate=${policyDecision.allowed !== false ? "allow" : "blocked"}; integration_connectors_enabled=${integrationDispatch.enabledConnectors}; inventory_ops=${operationPlan.inventoryOps.length}; runtime_ops=${operationPlan.runtimeOps.length}; analysis_ops=${operationPlan.analysisOps.length}`);
12083
12299
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-aws-manager",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
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": {