mcp-aws-manager 0.2.0 → 0.3.0
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/MCP_CLIENT_SETUP.md +36 -9
- package/README.md +31 -7
- package/bin/mcp-aws-manager.js +366 -7
- package/package.json +1 -1
package/MCP_CLIENT_SETUP.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# MCP Client Setup (stdio)
|
|
2
2
|
|
|
3
3
|
This project provides an MCP stdio wrapper around the SSM-only CLI.
|
|
4
4
|
|
|
@@ -12,7 +12,38 @@ Exposed MCP tools:
|
|
|
12
12
|
- `discover_public_ec2_with_pem` (compatibility alias, same behavior)
|
|
13
13
|
- `mcp_aws_discover_cli_help`
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Recommended (Install Once)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g mcp-aws-manager
|
|
19
|
+
mcp-aws-manager
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`mcp-aws-manager` (no args) runs bootstrap and registers the MCP server for detected clients (`codex`, `claude`).
|
|
23
|
+
|
|
24
|
+
Verification:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
mcp-aws-manager doctor
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Explicit Registration
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
mcp-aws-manager setup
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Custom name/command:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
mcp-aws-manager setup --name mcp-aws-manager --mcp-command mcp-aws-manager-mcp --clients codex,claude
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Manual Configuration (Fallback)
|
|
43
|
+
|
|
44
|
+
Use only when automatic registration is unavailable in your environment.
|
|
45
|
+
|
|
46
|
+
### 1) Local Repo (development)
|
|
16
47
|
|
|
17
48
|
```json
|
|
18
49
|
{
|
|
@@ -28,11 +59,7 @@ Exposed MCP tools:
|
|
|
28
59
|
}
|
|
29
60
|
```
|
|
30
61
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm install -g mcp-aws-manager
|
|
35
|
-
```
|
|
62
|
+
### 2) Global npm Install
|
|
36
63
|
|
|
37
64
|
```json
|
|
38
65
|
{
|
|
@@ -44,7 +71,7 @@ npm install -g mcp-aws-manager
|
|
|
44
71
|
}
|
|
45
72
|
```
|
|
46
73
|
|
|
47
|
-
|
|
74
|
+
### 3) npx (no global install)
|
|
48
75
|
|
|
49
76
|
```json
|
|
50
77
|
{
|
|
@@ -67,4 +94,4 @@ npm install -g mcp-aws-manager
|
|
|
67
94
|
- Discovery is SSM-only; PEM path arguments are no longer required.
|
|
68
95
|
- Keep AWS credentials/profiles available on the host running MCP.
|
|
69
96
|
- When `requiresUserAction=true` is returned, surface `requiredActions` to the user and retry after intervention.
|
|
70
|
-
- For auto remediation, pass `autoRemediateSsm` and an instance profile name/arn.
|
|
97
|
+
- For auto remediation, pass `autoRemediateSsm` and an instance profile name/arn.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# mcp-aws-manager
|
|
2
2
|
|
|
3
3
|
AWS operations CLI and MCP server package (SSM-only mode).
|
|
4
4
|
|
|
@@ -15,6 +15,7 @@ Current implementation focuses on:
|
|
|
15
15
|
- Optional SSM auto-remediation (instance profile association)
|
|
16
16
|
- Human-in-the-loop guidance via `ACTION_REQUIRED` messages
|
|
17
17
|
- JSON/CSV output (CLI)
|
|
18
|
+
- Codex/Claude MCP registration bootstrap helpers
|
|
18
19
|
|
|
19
20
|
## Install
|
|
20
21
|
|
|
@@ -22,6 +23,16 @@ Current implementation focuses on:
|
|
|
22
23
|
npm install -g mcp-aws-manager
|
|
23
24
|
```
|
|
24
25
|
|
|
26
|
+
## One-Time Bootstrap (Recommended)
|
|
27
|
+
|
|
28
|
+
After install, run once:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
mcp-aws-manager
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This ensures `mcp-aws-manager` is registered in detected clients (`codex`, `claude`).
|
|
35
|
+
|
|
25
36
|
## Prerequisites
|
|
26
37
|
|
|
27
38
|
- Node.js `>=18`
|
|
@@ -31,28 +42,36 @@ npm install -g mcp-aws-manager
|
|
|
31
42
|
|
|
32
43
|
## Quick Start
|
|
33
44
|
|
|
45
|
+
Bootstrap / setup / doctor:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
mcp-aws-manager # bootstrap (default command)
|
|
49
|
+
mcp-aws-manager setup # register/re-register MCP server
|
|
50
|
+
mcp-aws-manager doctor # verify install + registration
|
|
51
|
+
```
|
|
52
|
+
|
|
34
53
|
Basic discovery:
|
|
35
54
|
|
|
36
55
|
```bash
|
|
37
|
-
mcp-aws-manager --profiles default
|
|
56
|
+
mcp-aws-manager discover --profiles default
|
|
38
57
|
```
|
|
39
58
|
|
|
40
59
|
Only public IP instances:
|
|
41
60
|
|
|
42
61
|
```bash
|
|
43
|
-
mcp-aws-manager --profiles default --public-only
|
|
62
|
+
mcp-aws-manager discover --profiles default --public-only
|
|
44
63
|
```
|
|
45
64
|
|
|
46
65
|
Collect runtime snapshots:
|
|
47
66
|
|
|
48
67
|
```bash
|
|
49
|
-
mcp-aws-manager --profiles default --runtime-snapshot
|
|
68
|
+
mcp-aws-manager discover --profiles default --runtime-snapshot
|
|
50
69
|
```
|
|
51
70
|
|
|
52
71
|
Try automatic remediation for unmanaged instances:
|
|
53
72
|
|
|
54
73
|
```bash
|
|
55
|
-
mcp-aws-manager \
|
|
74
|
+
mcp-aws-manager discover \
|
|
56
75
|
--profiles default \
|
|
57
76
|
--auto-remediate-ssm \
|
|
58
77
|
--ssm-instance-profile-name MySsmInstanceProfile
|
|
@@ -61,9 +80,14 @@ mcp-aws-manager \
|
|
|
61
80
|
Output CSV file:
|
|
62
81
|
|
|
63
82
|
```bash
|
|
64
|
-
mcp-aws-manager --profiles default --format csv --out ./inventory.csv
|
|
83
|
+
mcp-aws-manager discover --profiles default --format csv --out ./inventory.csv
|
|
65
84
|
```
|
|
66
85
|
|
|
86
|
+
Compatibility note:
|
|
87
|
+
|
|
88
|
+
- Legacy invocation without subcommand still works for discovery when options are passed.
|
|
89
|
+
- Example: `mcp-aws-manager --profiles default --public-only`
|
|
90
|
+
|
|
67
91
|
## MCP (LLM Tool) Usage
|
|
68
92
|
|
|
69
93
|
Run as an MCP stdio server:
|
|
@@ -111,4 +135,4 @@ The MCP wrapper surfaces these in a structured `requiredActions` list.
|
|
|
111
135
|
These legacy commands are still available:
|
|
112
136
|
|
|
113
137
|
- `mcp-aws-discover`
|
|
114
|
-
- `mcp-aws-discover-mcp`
|
|
138
|
+
- `mcp-aws-discover-mcp`
|
package/bin/mcp-aws-manager.js
CHANGED
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
const fs = require("node:fs");
|
|
5
5
|
const os = require("node:os");
|
|
6
6
|
const path = require("node:path");
|
|
7
|
-
const { spawn } = require("node:child_process");
|
|
7
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
8
8
|
|
|
9
9
|
const TOTAL_STEPS = 9;
|
|
10
10
|
const DEFAULT_SNAPSHOT_CONCURRENCY = 3;
|
|
11
11
|
const MAX_SSM_FILTER_IDS = 50;
|
|
12
|
+
const DEFAULT_SERVER_NAME = "mcp-aws-manager";
|
|
13
|
+
const DEFAULT_MCP_COMMAND = "mcp-aws-manager-mcp";
|
|
14
|
+
const SUPPORTED_CLIENTS = new Set(["codex", "claude"]);
|
|
12
15
|
|
|
13
16
|
function eprint(msg) {
|
|
14
17
|
process.stderr.write(String(msg) + "\n");
|
|
@@ -63,11 +66,30 @@ function expandHome(input) {
|
|
|
63
66
|
|
|
64
67
|
function usageText() {
|
|
65
68
|
return [
|
|
66
|
-
"Usage:
|
|
69
|
+
"Usage:",
|
|
70
|
+
" mcp-aws-manager",
|
|
71
|
+
" mcp-aws-manager bootstrap [options]",
|
|
72
|
+
" mcp-aws-manager setup [options]",
|
|
73
|
+
" mcp-aws-manager doctor [options]",
|
|
74
|
+
" mcp-aws-manager discover [discover-options]",
|
|
75
|
+
" mcp-aws-manager [discover-options]",
|
|
67
76
|
"",
|
|
68
|
-
"SSM-only AWS EC2 inventory
|
|
77
|
+
"SSM-only AWS EC2 inventory/runtime collector plus MCP client setup helper.",
|
|
69
78
|
"",
|
|
70
|
-
"
|
|
79
|
+
"Commands:",
|
|
80
|
+
" bootstrap Ensure mcp-aws-manager MCP server is registered (default command)",
|
|
81
|
+
" setup Register/re-register MCP server for Codex/Claude",
|
|
82
|
+
" doctor Check install and registration health",
|
|
83
|
+
" discover Run EC2+SSM inventory workflow",
|
|
84
|
+
"",
|
|
85
|
+
"Setup/Bootstrap/Doctor options:",
|
|
86
|
+
" --name <server-name> (default: mcp-aws-manager)",
|
|
87
|
+
" --mcp-command <command> (default: mcp-aws-manager-mcp)",
|
|
88
|
+
" --clients <codex,claude> (default: codex,claude)",
|
|
89
|
+
" --force (setup/bootstrap only; always remove then add)",
|
|
90
|
+
" -h, --help",
|
|
91
|
+
"",
|
|
92
|
+
"Discover options:",
|
|
71
93
|
" --profiles <a,b,c>",
|
|
72
94
|
" --regions <a,b,c>",
|
|
73
95
|
" --instance-ids <id1,id2>",
|
|
@@ -100,7 +122,131 @@ function usageText() {
|
|
|
100
122
|
].join("\n");
|
|
101
123
|
}
|
|
102
124
|
|
|
103
|
-
function
|
|
125
|
+
function parseClients(raw) {
|
|
126
|
+
const values = parseCsv(raw) || [];
|
|
127
|
+
if (!values.length) {
|
|
128
|
+
throw new Error("--clients must include at least one of: codex, claude");
|
|
129
|
+
}
|
|
130
|
+
const out = [];
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
for (const value of values) {
|
|
133
|
+
const name = String(value).trim().toLowerCase();
|
|
134
|
+
if (!SUPPORTED_CLIENTS.has(name)) {
|
|
135
|
+
throw new Error(`Unsupported client '${value}'. Supported: codex, claude`);
|
|
136
|
+
}
|
|
137
|
+
if (!seen.has(name)) {
|
|
138
|
+
seen.add(name);
|
|
139
|
+
out.push(name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function parseCommand(argv) {
|
|
146
|
+
const args = Array.from(argv || []);
|
|
147
|
+
if (!args.length) {
|
|
148
|
+
return { command: "bootstrap", args: [] };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const first = String(args[0] || "");
|
|
152
|
+
if (first === "-h" || first === "--help") {
|
|
153
|
+
return { command: "help", args: [] };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (first === "bootstrap" || first === "init") {
|
|
157
|
+
return { command: "bootstrap", args: args.slice(1) };
|
|
158
|
+
}
|
|
159
|
+
if (first === "setup" || first === "doctor" || first === "discover") {
|
|
160
|
+
return { command: first, args: args.slice(1) };
|
|
161
|
+
}
|
|
162
|
+
if (first.startsWith("-")) {
|
|
163
|
+
return { command: "discover", args };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
throw new Error(`Unknown command '${first}'. Run --help for usage.`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseRegistrationArgs(argv, opts = {}) {
|
|
170
|
+
const allowForce = opts.allowForce === true;
|
|
171
|
+
const options = {
|
|
172
|
+
serverName: null,
|
|
173
|
+
mcpCommand: null,
|
|
174
|
+
clients: null,
|
|
175
|
+
force: false,
|
|
176
|
+
help: false
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
function setKV(key, value) {
|
|
180
|
+
switch (key) {
|
|
181
|
+
case "name":
|
|
182
|
+
options.serverName = String(value || "").trim();
|
|
183
|
+
break;
|
|
184
|
+
case "mcp-command":
|
|
185
|
+
options.mcpCommand = String(value || "").trim();
|
|
186
|
+
break;
|
|
187
|
+
case "clients":
|
|
188
|
+
options.clients = String(value || "").trim();
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
throw new Error(`Unknown option --${key}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const args = Array.from(argv || []);
|
|
196
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
197
|
+
const arg = String(args[i] || "");
|
|
198
|
+
if (arg === "-h" || arg === "--help") {
|
|
199
|
+
options.help = true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (arg === "--force") {
|
|
203
|
+
if (!allowForce) {
|
|
204
|
+
throw new Error("--force is only supported by setup/bootstrap");
|
|
205
|
+
}
|
|
206
|
+
options.force = true;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (!arg.startsWith("--")) {
|
|
210
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const eq = arg.indexOf("=");
|
|
214
|
+
if (eq >= 0) {
|
|
215
|
+
const key = arg.slice(2, eq);
|
|
216
|
+
const value = arg.slice(eq + 1);
|
|
217
|
+
if (!value) throw new Error(`Missing value for --${key}`);
|
|
218
|
+
setKV(key, value);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const key = arg.slice(2);
|
|
223
|
+
const next = args[i + 1];
|
|
224
|
+
if (!next || String(next).startsWith("--")) {
|
|
225
|
+
throw new Error(`Missing value for --${key}`);
|
|
226
|
+
}
|
|
227
|
+
setKV(key, next);
|
|
228
|
+
i += 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (options.help) {
|
|
232
|
+
return { help: true };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const serverName = options.serverName || DEFAULT_SERVER_NAME;
|
|
236
|
+
const mcpCommand = options.mcpCommand || DEFAULT_MCP_COMMAND;
|
|
237
|
+
if (!serverName) throw new Error("--name cannot be empty");
|
|
238
|
+
if (!mcpCommand) throw new Error("--mcp-command cannot be empty");
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
help: false,
|
|
242
|
+
serverName,
|
|
243
|
+
mcpCommand,
|
|
244
|
+
clients: options.clients ? parseClients(options.clients) : ["codex", "claude"],
|
|
245
|
+
force: options.force
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function parseDiscoverArgs(argv) {
|
|
104
250
|
const options = {
|
|
105
251
|
profiles: null,
|
|
106
252
|
regions: null,
|
|
@@ -136,7 +282,7 @@ function parseArgs(argv) {
|
|
|
136
282
|
case "snapshot-max-kb": options.snapshotMaxKb = value; break;
|
|
137
283
|
case "format": options.format = value; break;
|
|
138
284
|
case "out": options.outPath = value; break;
|
|
139
|
-
default: throw new Error(`Unknown option --${key}`);
|
|
285
|
+
default: throw new Error(`Unknown discover option --${key}`);
|
|
140
286
|
}
|
|
141
287
|
}
|
|
142
288
|
|
|
@@ -254,6 +400,179 @@ function listLocalAwsProfiles() {
|
|
|
254
400
|
return Array.from(found).filter(Boolean).sort();
|
|
255
401
|
}
|
|
256
402
|
|
|
403
|
+
function runCLICommand(cliBin, args, options = {}) {
|
|
404
|
+
const execOptions = {
|
|
405
|
+
cwd: process.cwd(),
|
|
406
|
+
env: process.env,
|
|
407
|
+
stdio: options.stdio || "pipe",
|
|
408
|
+
encoding: "utf8",
|
|
409
|
+
shell: false
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const direct = spawnSync(cliBin, args, execOptions);
|
|
413
|
+
if (process.platform !== "win32") {
|
|
414
|
+
return direct;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const errCode = String(direct && direct.error && direct.error.code ? direct.error.code : "");
|
|
418
|
+
if (!direct.error || (errCode !== "ENOENT" && errCode !== "EINVAL")) {
|
|
419
|
+
return direct;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// On Windows, global npm CLIs are often .cmd wrappers and can fail direct lookup.
|
|
423
|
+
return spawnSync("cmd.exe", ["/d", "/s", "/c", cliBin, ...args], execOptions);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function runStatusLabel(run) {
|
|
427
|
+
if (run && typeof run.status === "number") {
|
|
428
|
+
return `exit ${run.status}`;
|
|
429
|
+
}
|
|
430
|
+
if (run && run.error) {
|
|
431
|
+
return run.error.message || String(run.error);
|
|
432
|
+
}
|
|
433
|
+
return "unknown";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function commandExists(bin, checkArgs) {
|
|
437
|
+
const run = runCLICommand(bin, Array.isArray(checkArgs) ? checkArgs : ["--help"], { stdio: "ignore" });
|
|
438
|
+
return run && run.status === 0;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function removeRegistration(cliBin, serverName) {
|
|
442
|
+
if (cliBin === "claude") {
|
|
443
|
+
runCLICommand(cliBin, ["mcp", "remove", serverName, "-s", "user"], { stdio: "ignore" });
|
|
444
|
+
runCLICommand(cliBin, ["mcp", "remove", serverName, "-s", "local"], { stdio: "ignore" });
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
runCLICommand(cliBin, ["mcp", "remove", serverName], { stdio: "ignore" });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function isRegistered(cliBin, serverName) {
|
|
451
|
+
const args = cliBin === "claude"
|
|
452
|
+
? ["mcp", "get", serverName]
|
|
453
|
+
: ["mcp", "get", serverName, "--json"];
|
|
454
|
+
const run = runCLICommand(cliBin, args, { stdio: "ignore" });
|
|
455
|
+
return run && run.status === 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function registrationAttempts(cliBin, serverName, mcpCommand) {
|
|
459
|
+
if (cliBin === "claude") {
|
|
460
|
+
return [
|
|
461
|
+
["mcp", "add", "--scope", "user", serverName, "--", mcpCommand],
|
|
462
|
+
["mcp", "add", "--scope", "user", serverName, mcpCommand]
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
return [
|
|
466
|
+
["mcp", "add", serverName, "--", mcpCommand],
|
|
467
|
+
["mcp", "add", serverName, mcpCommand]
|
|
468
|
+
];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function tryRegister(cliBin, serverName, mcpCommand) {
|
|
472
|
+
const attempts = registrationAttempts(cliBin, serverName, mcpCommand);
|
|
473
|
+
let lastRun = null;
|
|
474
|
+
let lastArgs = null;
|
|
475
|
+
for (const args of attempts) {
|
|
476
|
+
const run = runCLICommand(cliBin, args, { stdio: "pipe" });
|
|
477
|
+
if (run && run.status === 0) {
|
|
478
|
+
return { ok: true, args, run };
|
|
479
|
+
}
|
|
480
|
+
lastRun = run;
|
|
481
|
+
lastArgs = args;
|
|
482
|
+
}
|
|
483
|
+
return { ok: false, args: lastArgs, run: lastRun };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function runSetupInternal(config, options = {}) {
|
|
487
|
+
const ensureOnly = options.ensureOnly === true;
|
|
488
|
+
const clients = config.clients.filter((cli) => commandExists(cli, ["mcp", "--help"]));
|
|
489
|
+
const results = [];
|
|
490
|
+
|
|
491
|
+
process.stdout.write(ensureOnly ? "Bootstrap start.\n" : "Setup start.\n");
|
|
492
|
+
process.stdout.write(`Server: ${config.serverName}\n`);
|
|
493
|
+
process.stdout.write(`Command: ${config.mcpCommand}\n`);
|
|
494
|
+
process.stdout.write(`Clients: ${config.clients.join(",")}\n`);
|
|
495
|
+
|
|
496
|
+
if (!clients.length) {
|
|
497
|
+
process.stdout.write("No codex/claude CLI found. Registration skipped.\n");
|
|
498
|
+
return 2;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
for (const cliBin of clients) {
|
|
502
|
+
const alreadyRegistered = isRegistered(cliBin, config.serverName);
|
|
503
|
+
if (config.force || !ensureOnly || alreadyRegistered) {
|
|
504
|
+
removeRegistration(cliBin, config.serverName);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const registered = tryRegister(cliBin, config.serverName, config.mcpCommand);
|
|
508
|
+
if (registered.ok) {
|
|
509
|
+
const action = ensureOnly
|
|
510
|
+
? (alreadyRegistered ? "updated" : "registered")
|
|
511
|
+
: (alreadyRegistered ? "re-registered" : "registered");
|
|
512
|
+
results.push({ cliBin, ok: true, action, detail: "" });
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const stderr = String(registered.run && registered.run.stderr ? registered.run.stderr : "").trim();
|
|
517
|
+
const stdout = String(registered.run && registered.run.stdout ? registered.run.stdout : "").trim();
|
|
518
|
+
const detail = stderr || stdout || runStatusLabel(registered.run);
|
|
519
|
+
results.push({ cliBin, ok: false, action: "registration failed", detail });
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
for (const row of results) {
|
|
523
|
+
process.stdout.write(`${row.cliBin}: ${row.action}\n`);
|
|
524
|
+
if (!row.ok && row.detail) {
|
|
525
|
+
process.stdout.write(` detail: ${row.detail}\n`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
530
|
+
process.stdout.write(failed ? "Setup finished with failures.\n" : "Setup finished successfully.\n");
|
|
531
|
+
return failed ? 2 : 0;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function runDoctor(config) {
|
|
535
|
+
process.stdout.write("Doctor start.\n");
|
|
536
|
+
let hasIssue = false;
|
|
537
|
+
let foundClient = false;
|
|
538
|
+
|
|
539
|
+
const mcpCommandRun = runCLICommand(config.mcpCommand, ["--help"], { stdio: "pipe" });
|
|
540
|
+
if (mcpCommandRun && mcpCommandRun.status === 0) {
|
|
541
|
+
process.stdout.write(`mcp-command: ok (${config.mcpCommand})\n`);
|
|
542
|
+
} else {
|
|
543
|
+
hasIssue = true;
|
|
544
|
+
const detail = String(mcpCommandRun && (mcpCommandRun.stderr || mcpCommandRun.stdout) ? (mcpCommandRun.stderr || mcpCommandRun.stdout) : "").trim();
|
|
545
|
+
process.stdout.write(`mcp-command: fail (${config.mcpCommand})\n`);
|
|
546
|
+
if (detail) process.stdout.write(` detail: ${detail}\n`);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (const cliBin of config.clients) {
|
|
550
|
+
const exists = commandExists(cliBin, ["mcp", "--help"]);
|
|
551
|
+
if (!exists) {
|
|
552
|
+
hasIssue = true;
|
|
553
|
+
process.stdout.write(`${cliBin}: not installed or not available in PATH\n`);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
foundClient = true;
|
|
558
|
+
const registered = isRegistered(cliBin, config.serverName);
|
|
559
|
+
if (registered) {
|
|
560
|
+
process.stdout.write(`${cliBin}: registered (${config.serverName})\n`);
|
|
561
|
+
} else {
|
|
562
|
+
hasIssue = true;
|
|
563
|
+
process.stdout.write(`${cliBin}: missing registration (${config.serverName})\n`);
|
|
564
|
+
process.stdout.write(` action: mcp-aws-manager setup --name ${config.serverName} --mcp-command ${config.mcpCommand} --clients ${cliBin}\n`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (!foundClient) {
|
|
569
|
+
process.stdout.write("No requested clients detected. Install Codex or Claude Code first.\n");
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
process.stdout.write(hasIssue ? "Doctor result: issues found.\n" : "Doctor result: healthy.\n");
|
|
573
|
+
return hasIssue ? 2 : 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
257
576
|
let awsModulesCache = null;
|
|
258
577
|
function loadAwsModules() {
|
|
259
578
|
if (awsModulesCache) return awsModulesCache;
|
|
@@ -1058,7 +1377,47 @@ async function runWorkflow(config) {
|
|
|
1058
1377
|
|
|
1059
1378
|
async function main() {
|
|
1060
1379
|
try {
|
|
1061
|
-
const
|
|
1380
|
+
const parsed = parseCommand(process.argv.slice(2));
|
|
1381
|
+
if (parsed.command === "help") {
|
|
1382
|
+
process.stdout.write(usageText());
|
|
1383
|
+
process.exitCode = 0;
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (parsed.command === "setup") {
|
|
1388
|
+
const config = parseRegistrationArgs(parsed.args, { allowForce: true });
|
|
1389
|
+
if (config.help) {
|
|
1390
|
+
process.stdout.write(usageText());
|
|
1391
|
+
process.exitCode = 0;
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
process.exitCode = runSetupInternal(config, { ensureOnly: false });
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (parsed.command === "bootstrap") {
|
|
1399
|
+
const config = parseRegistrationArgs(parsed.args, { allowForce: true });
|
|
1400
|
+
if (config.help) {
|
|
1401
|
+
process.stdout.write(usageText());
|
|
1402
|
+
process.exitCode = 0;
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
process.exitCode = runSetupInternal(config, { ensureOnly: true });
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (parsed.command === "doctor") {
|
|
1410
|
+
const config = parseRegistrationArgs(parsed.args, { allowForce: false });
|
|
1411
|
+
if (config.help) {
|
|
1412
|
+
process.stdout.write(usageText());
|
|
1413
|
+
process.exitCode = 0;
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
process.exitCode = runDoctor(config);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
const config = parseDiscoverArgs(parsed.args);
|
|
1062
1421
|
if (config.help) {
|
|
1063
1422
|
process.stdout.write(usageText());
|
|
1064
1423
|
process.exitCode = 0;
|