mcp-aws-manager 0.3.6 → 0.3.8

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
@@ -91,10 +91,16 @@ If AWS auth is not available, use manual fallback:
91
91
  mcp-aws-manager discover --manual-server-list ./servers.csv --pem-paths C:\keys\prod.pem --no-progress
92
92
  ```
93
93
 
94
- Generate GUI report (interactive HTML):
94
+ GUI report is generated by default (auto path: workspace/home `aws-inventory.html`):
95
95
 
96
96
  ```bash
97
- mcp-aws-manager discover --profiles default --html-out ./inventory.html --no-progress
97
+ mcp-aws-manager discover --profiles default --no-progress
98
+ ```
99
+
100
+ Custom path / open behavior:
101
+
102
+ ```bash
103
+ mcp-aws-manager discover --profiles default --html-out ./inventory.html --open-html --no-progress
98
104
  ```
99
105
 
100
106
  ## User Confirmation Required
@@ -228,8 +234,9 @@ aws sts get-caller-identity --profile default
228
234
  - `--ssh-user <name>`
229
235
  - `--ssh-port <port>`
230
236
  - `--ssh-connect-timeout <seconds>`
231
- - `--html-out <path>`
232
- - `--open-html`
237
+ - `--html-out <path>` (default: auto path, workspace/home `aws-inventory.html`)
238
+ - `--open-html` (force open)
239
+ - `--no-open-html` (disable auto-open)
233
240
  - `--auto-sso-login` / `--no-auto-sso-login`
234
241
  - `--format <json|csv>`
235
242
  - `--out <path>`
package/README_KO.md CHANGED
@@ -19,7 +19,7 @@ mcp-aws-manager discover --profiles default --no-progress
19
19
  - SSM 상태 확인: managed/online
20
20
  - 런타임 스냅샷(선택), SSM 완화(선택)
21
21
  - AWS 인증이 안 될 때 수동 모드: JSON/CSV 서버 목록 + PEM SSH
22
- - GUI 리포트 생성: `--html-out` (검색/필터/CSV 다운로드 버튼 포함)
22
+ - GUI 리포트 기본 생성: 워크스페이스/홈 자동 경로 `aws-inventory.html` (검색/필터/CSV 다운로드 버튼 포함)
23
23
  - 사람이 개입해야 할 상황을 `ACTION_REQUIRED`로 표준화
24
24
 
25
25
  ## 바이너리
@@ -70,11 +70,13 @@ mcp-aws-manager discover --manual-server-list ./servers.csv --pem-paths C:\keys\
70
70
  ## GUI 리포트
71
71
 
72
72
  ```bash
73
- mcp-aws-manager discover --profiles default --html-out ./inventory.html --open-html --no-progress
73
+ mcp-aws-manager discover --profiles default --no-progress
74
74
  ```
75
75
 
76
- - `--html-out <path>`: 인터랙티브 HTML 리포트 생성
77
- - `--open-html`: 생성 브라우저 자동 오픈 시도
76
+ - 기본 경로: 워크스페이스/홈 자동 선택(`aws-inventory.html`)
77
+ - `--html-out <path>`: 리포트 경로 변경
78
+ - `--open-html`: 생성 후 브라우저 오픈 강제
79
+ - `--no-open-html`: 자동 오픈 비활성화
78
80
  - GUI에서 현재 뷰 기준 CSV 다운로드 가능
79
81
 
80
82
  ## MCP 도구
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
4
  const path = require("node:path");
@@ -11,6 +11,7 @@ const DEFAULT_SNAPSHOT_CONCURRENCY = 3;
11
11
  const MAX_SSM_FILTER_IDS = 50;
12
12
  const DEFAULT_SERVER_NAME = "mcp-aws-manager";
13
13
  const DEFAULT_MCP_COMMAND = "mcp-aws-manager-mcp";
14
+ const DEFAULT_HTML_REPORT_NAME = "aws-inventory.html";
14
15
  const SUPPORTED_CLIENTS = new Set(["codex", "claude", "cursor", "windsurf", "antigravity"]);
15
16
  const INTERNAL_BACKEND_ID = "internal";
16
17
 
@@ -206,6 +207,64 @@ function expandHome(input) {
206
207
  return value;
207
208
  }
208
209
 
210
+ function isWritableDirectory(dirPath) {
211
+ try {
212
+ const resolved = path.resolve(String(dirPath || ""));
213
+ fs.mkdirSync(resolved, { recursive: true });
214
+ fs.accessSync(resolved, fs.constants.W_OK);
215
+ return true;
216
+ } catch (_error) {
217
+ return false;
218
+ }
219
+ }
220
+
221
+ function isLikelyProtectedInstallDir(dirPath) {
222
+ const resolved = path.resolve(String(dirPath || ""));
223
+ if (!resolved) return false;
224
+ const normalized = resolved.replace(/\//g, "\\").toLowerCase();
225
+ if (process.platform === "win32") {
226
+ if (normalized.includes("\\program files\\")) return true;
227
+ if (/^[a-z]:\\windows(\\|$)/.test(normalized)) return true;
228
+ } else {
229
+ if (normalized === "\\usr" || normalized.startsWith("\\usr\\")) return true;
230
+ if (normalized === "\\opt" || normalized.startsWith("\\opt\\")) return true;
231
+ if (normalized === "\\system" || normalized.startsWith("\\system\\")) return true;
232
+ }
233
+ return false;
234
+ }
235
+
236
+ function resolveDefaultHtmlOutPath() {
237
+ const explicit = envText("MCP_AWS_WORKSPACE_DIR");
238
+ if (explicit) {
239
+ const dir = path.resolve(expandHome(explicit));
240
+ if (isWritableDirectory(dir)) {
241
+ return path.join(dir, DEFAULT_HTML_REPORT_NAME);
242
+ }
243
+ }
244
+
245
+ const candidates = [
246
+ envText("PWD"),
247
+ envText("INIT_CWD"),
248
+ envText("WORKSPACE_DIR"),
249
+ envText("PROJECT_ROOT")
250
+ ];
251
+ for (const raw of candidates) {
252
+ if (!raw) continue;
253
+ const dir = path.resolve(expandHome(raw));
254
+ if (isLikelyProtectedInstallDir(dir)) continue;
255
+ if (isWritableDirectory(dir)) {
256
+ return path.join(dir, DEFAULT_HTML_REPORT_NAME);
257
+ }
258
+ }
259
+
260
+ const home = os.homedir();
261
+ if (isWritableDirectory(home)) {
262
+ return path.join(home, DEFAULT_HTML_REPORT_NAME);
263
+ }
264
+
265
+ return path.resolve(process.cwd(), DEFAULT_HTML_REPORT_NAME);
266
+ }
267
+
209
268
  function usageText() {
210
269
  const clientList = Array.from(SUPPORTED_CLIENTS).join(",");
211
270
  return [
@@ -266,8 +325,9 @@ function usageText() {
266
325
  " --ssh-user <name> (default: ec2-user)",
267
326
  " --ssh-port <port> (default: 22)",
268
327
  " --ssh-connect-timeout <seconds> (default: 8)",
269
- " --html-out <path> (optional interactive HTML report output path)",
270
- " --open-html (try opening HTML report after write)",
328
+ " --html-out <path> (default: auto -> workspace/home aws-inventory.html)",
329
+ " --open-html (force opening HTML report after write)",
330
+ " --no-open-html (disable auto-open of HTML report)",
271
331
  " --auto-sso-login / --no-auto-sso-login",
272
332
  " --format <json|csv> (default: json)",
273
333
  " --out <path>",
@@ -285,7 +345,7 @@ function usageText() {
285
345
  " MCP_AWS_SNAPSHOT_MAX_KB, MCP_AWS_AUTO_SSO_LOGIN",
286
346
  " MCP_AWS_MANUAL_SERVER_LIST, MCP_AWS_PEM_PATHS, MCP_AWS_SSH_USER, MCP_AWS_SSH_PORT",
287
347
  " MCP_AWS_SSH_CONNECT_TIMEOUT",
288
- " MCP_AWS_HTML_OUT, MCP_AWS_OPEN_HTML",
348
+ " MCP_AWS_HTML_OUT, MCP_AWS_OPEN_HTML, MCP_AWS_HEADLESS, MCP_AWS_WORKSPACE_DIR",
289
349
  " MCP_AWS_FORMAT, MCP_AWS_OUT, MCP_AWS_NO_PROGRESS",
290
350
  ""
291
351
  ].join("\n");
@@ -502,6 +562,7 @@ function parseDiscoverArgs(argv) {
502
562
  if (arg === "--auto-sso-login") { options.autoSsoLogin = true; continue; }
503
563
  if (arg === "--no-auto-sso-login") { options.autoSsoLogin = false; continue; }
504
564
  if (arg === "--open-html") { options.openHtml = true; continue; }
565
+ if (arg === "--no-open-html") { options.openHtml = false; continue; }
505
566
  if (arg === "--progress") { options.noProgress = false; continue; }
506
567
  if (arg === "--no-progress") { options.noProgress = true; continue; }
507
568
  if (!arg.startsWith("--")) throw new Error(`Unexpected argument: ${arg}`);
@@ -529,6 +590,8 @@ function parseDiscoverArgs(argv) {
529
590
 
530
591
  const envNoProgress = envText("MCP_AWS_NO_PROGRESS");
531
592
 
593
+ const envOpenHtml = envText("MCP_AWS_OPEN_HTML");
594
+
532
595
  return {
533
596
  help: false,
534
597
  profiles: parseCsv(options.profiles) || parseCsv(envText("MCP_AWS_PROFILES")),
@@ -561,8 +624,8 @@ function parseDiscoverArgs(argv) {
561
624
  sshConnectTimeoutSec: Math.round(toPosNum(options.sshConnectTimeoutSec || envText("MCP_AWS_SSH_CONNECT_TIMEOUT") || "8", "--ssh-connect-timeout", 8)),
562
625
  htmlOutPath: options.htmlOutPath
563
626
  ? path.resolve(expandHome(options.htmlOutPath))
564
- : (envText("MCP_AWS_HTML_OUT") ? path.resolve(expandHome(envText("MCP_AWS_HTML_OUT"))) : null),
565
- openHtml: options.openHtml != null ? options.openHtml : envBool("MCP_AWS_OPEN_HTML"),
627
+ : (envText("MCP_AWS_HTML_OUT") ? path.resolve(expandHome(envText("MCP_AWS_HTML_OUT"))) : resolveDefaultHtmlOutPath()),
628
+ openHtml: options.openHtml != null ? options.openHtml : (envOpenHtml != null ? envBool("MCP_AWS_OPEN_HTML") : null),
566
629
  autoSsoLogin: options.autoSsoLogin != null ? options.autoSsoLogin : (envText("MCP_AWS_AUTO_SSO_LOGIN") != null ? envBool("MCP_AWS_AUTO_SSO_LOGIN") : true),
567
630
  format,
568
631
  outPath: options.outPath ? path.resolve(expandHome(options.outPath)) : (envText("MCP_AWS_OUT") ? path.resolve(expandHome(envText("MCP_AWS_OUT"))) : null),
@@ -2934,9 +2997,25 @@ function renderHtmlReport(records) {
2934
2997
  function writeHtmlReport(config, records) {
2935
2998
  if (!config.htmlOutPath) return null;
2936
2999
  const html = renderHtmlReport(records);
2937
- fs.writeFileSync(config.htmlOutPath, html, "utf8");
2938
- eprint(`Wrote HTML report to ${config.htmlOutPath}`);
2939
- return config.htmlOutPath;
3000
+ try {
3001
+ fs.writeFileSync(config.htmlOutPath, html, "utf8");
3002
+ eprint(`Wrote HTML report to ${config.htmlOutPath}`);
3003
+ return config.htmlOutPath;
3004
+ } catch (error) {
3005
+ const code = String(error && error.code ? error.code : "");
3006
+ if (!["EACCES", "EPERM", "EROFS"].includes(code)) {
3007
+ throw error;
3008
+ }
3009
+
3010
+ const fallbackPath = path.join(os.homedir(), path.basename(config.htmlOutPath) || DEFAULT_HTML_REPORT_NAME);
3011
+ if (path.resolve(fallbackPath) === path.resolve(config.htmlOutPath)) {
3012
+ throw error;
3013
+ }
3014
+ fs.mkdirSync(path.dirname(fallbackPath), { recursive: true });
3015
+ fs.writeFileSync(fallbackPath, html, "utf8");
3016
+ eprint(`WARNING: failed to write HTML report to ${config.htmlOutPath} (${code}); wrote fallback to ${fallbackPath}`);
3017
+ return fallbackPath;
3018
+ }
2940
3019
  }
2941
3020
 
2942
3021
  function tryOpenFile(targetPath) {
@@ -2961,6 +3040,19 @@ function tryOpenFile(targetPath) {
2961
3040
  }
2962
3041
  }
2963
3042
 
3043
+ function shouldAutoOpenHtml(config) {
3044
+ if (config.openHtml === true) return true;
3045
+ if (config.openHtml === false) return false;
3046
+
3047
+ // Auto mode: only try in likely local desktop sessions.
3048
+ if (envBool("MCP_AWS_HEADLESS")) return false;
3049
+ if (envBool("CI")) return false;
3050
+ if (envText("SSH_CONNECTION") || envText("SSH_CLIENT") || envText("SSH_TTY")) return false;
3051
+ if (!(process.stdout && process.stdout.isTTY)) return false;
3052
+ if (process.platform === "linux" && !envText("DISPLAY") && !envText("WAYLAND_DISPLAY")) return false;
3053
+ return true;
3054
+ }
3055
+
2964
3056
  function writeOutput(config, content) {
2965
3057
  if (config.outPath) {
2966
3058
  fs.writeFileSync(config.outPath, content, "utf8");
@@ -2976,7 +3068,7 @@ async function runWorkflow(config) {
2976
3068
  const requiredActions = [];
2977
3069
 
2978
3070
  progress(config, 1, "orchestrator: parse user request and operation scope");
2979
- eprint(`Inputs: execution_mode=${INTERNAL_BACKEND_ID}, profiles=${config.profiles ? config.profiles.join(",") : "auto"}, regions=${config.regions ? config.regions.join(",") : "auto"}, include_ec2=${config.includeEc2 ? "on" : "off"}, include_lambda=${config.includeLambda ? "on" : "off"}, include_alb=${config.includeAlb ? "on" : "off"}, include_asg=${config.includeAsg ? "on" : "off"}, include_rds=${config.includeRds ? "on" : "off"}, include_elasticache=${config.includeElastiCache ? "on" : "off"}, include_route53=${config.includeRoute53 ? "on" : "off"}, public_only=${config.publicOnly ? "on" : "off"}, managed_only=${config.managedOnly ? "on" : "off"}, auto_remediate_ssm=${config.autoRemediateSsm ? "on" : "off"}, runtime_snapshot=${config.runtimeSnapshot ? "on" : "off"}, manual_server_list=${config.manualServerListPath || "off"}, pem_paths=${config.pemPaths && config.pemPaths.length ? config.pemPaths.length : 0}, ssh_user=${config.sshUser}, ssh_port=${config.sshPort}, html_out=${config.htmlOutPath || "off"}, open_html=${config.openHtml ? "on" : "off"}, auto_sso_login=${config.autoSsoLogin ? "on" : "off"}`);
3071
+ eprint(`Inputs: execution_mode=${INTERNAL_BACKEND_ID}, profiles=${config.profiles ? config.profiles.join(",") : "auto"}, regions=${config.regions ? config.regions.join(",") : "auto"}, include_ec2=${config.includeEc2 ? "on" : "off"}, include_lambda=${config.includeLambda ? "on" : "off"}, include_alb=${config.includeAlb ? "on" : "off"}, include_asg=${config.includeAsg ? "on" : "off"}, include_rds=${config.includeRds ? "on" : "off"}, include_elasticache=${config.includeElastiCache ? "on" : "off"}, include_route53=${config.includeRoute53 ? "on" : "off"}, public_only=${config.publicOnly ? "on" : "off"}, managed_only=${config.managedOnly ? "on" : "off"}, auto_remediate_ssm=${config.autoRemediateSsm ? "on" : "off"}, runtime_snapshot=${config.runtimeSnapshot ? "on" : "off"}, manual_server_list=${config.manualServerListPath || "off"}, pem_paths=${config.pemPaths && config.pemPaths.length ? config.pemPaths.length : 0}, ssh_user=${config.sshUser}, ssh_port=${config.sshPort}, html_out=${config.htmlOutPath || "off"}, open_html=${config.openHtml == null ? "auto" : (config.openHtml ? "on" : "off")}, auto_sso_login=${config.autoSsoLogin ? "on" : "off"}`);
2980
3072
 
2981
3073
  progress(config, 2, "config_validator: validate settings and output path");
2982
3074
  validateConfig(config, warnings, requiredActions);
@@ -3039,7 +3131,7 @@ async function runWorkflow(config) {
3039
3131
  progress(config, 8, "cli_output_formatter: render JSON/CSV and guidance payload");
3040
3132
  writeOutput(config, renderOutput(config, outputRecords));
3041
3133
  const htmlPath = writeHtmlReport(config, outputRecords);
3042
- if (htmlPath && config.openHtml) {
3134
+ if (htmlPath && shouldAutoOpenHtml(config)) {
3043
3135
  const opened = tryOpenFile(htmlPath);
3044
3136
  if (!opened.ok) {
3045
3137
  warnings.push(`Failed to auto-open HTML report: ${opened.detail}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-aws-manager",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
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": {