mcp-aws-manager 0.1.0 → 0.2.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 +14 -22
- package/README.md +48 -74
- package/bin/mcp-aws-manager-mcp.js +155 -220
- package/bin/mcp-aws-manager.js +788 -872
- package/package.json +3 -5
|
@@ -20,17 +20,15 @@ function usageText() {
|
|
|
20
20
|
return [
|
|
21
21
|
"mcp-aws-manager-mcp",
|
|
22
22
|
"",
|
|
23
|
-
"MCP stdio wrapper for the mcp-aws-manager CLI.",
|
|
24
|
-
"Starts an MCP server that exposes a discover tool for AI clients.",
|
|
23
|
+
"MCP stdio wrapper for the mcp-aws-manager CLI (SSM-only).",
|
|
25
24
|
"",
|
|
26
25
|
"Usage:",
|
|
27
26
|
" mcp-aws-manager-mcp",
|
|
28
27
|
" mcp-aws-manager-mcp --help",
|
|
29
28
|
"",
|
|
30
29
|
"Notes:",
|
|
31
|
-
" - This process is an MCP stdio server.
|
|
32
|
-
"
|
|
33
|
-
" - The discover tool mirrors the CLI PEM resolution rules (arg/env/cwd auto-discovery).",
|
|
30
|
+
" - This process is an MCP stdio server.",
|
|
31
|
+
" - Exposes SSM inventory/runtime snapshot discovery tools.",
|
|
34
32
|
""
|
|
35
33
|
].join("\n");
|
|
36
34
|
}
|
|
@@ -40,17 +38,13 @@ function shouldShowHelp(argv) {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
function toCsvArg(values) {
|
|
43
|
-
if (!Array.isArray(values) || !values.length)
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
41
|
+
if (!Array.isArray(values) || !values.length) return null;
|
|
46
42
|
return values.map((v) => String(v).trim()).filter(Boolean).join(",");
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
function truncateText(value, maxLength = DEFAULT_STDIO_TEXT_LIMIT) {
|
|
50
46
|
const text = String(value || "");
|
|
51
|
-
if (text.length <= maxLength)
|
|
52
|
-
return text;
|
|
53
|
-
}
|
|
47
|
+
if (text.length <= maxLength) return text;
|
|
54
48
|
const suffix = `\n... [truncated ${text.length - maxLength} chars]`;
|
|
55
49
|
return text.slice(0, maxLength) + suffix;
|
|
56
50
|
}
|
|
@@ -61,60 +55,70 @@ function parseStderr(stderrText) {
|
|
|
61
55
|
.map((line) => line.trim())
|
|
62
56
|
.filter(Boolean);
|
|
63
57
|
|
|
64
|
-
const warnings =
|
|
65
|
-
const
|
|
58
|
+
const warnings = [];
|
|
59
|
+
const requiredActions = [];
|
|
60
|
+
let summaryLine = null;
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
if (line.startsWith("WARNING: ")) {
|
|
64
|
+
warnings.push(line.slice("WARNING: ".length));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (line.startsWith("Summary: ")) {
|
|
68
|
+
summaryLine = line;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (line.startsWith("ACTION_REQUIRED: ")) {
|
|
72
|
+
const payload = line.slice("ACTION_REQUIRED: ".length).trim();
|
|
73
|
+
const m = /^\[(.+?)\]\s*(.*)$/.exec(payload);
|
|
74
|
+
requiredActions.push({
|
|
75
|
+
code: m ? m[1] : "UNKNOWN",
|
|
76
|
+
message: m ? m[2] : payload,
|
|
77
|
+
hint: null
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (line.startsWith("ACTION_HINT: ")) {
|
|
82
|
+
if (requiredActions.length) {
|
|
83
|
+
requiredActions[requiredActions.length - 1].hint = line.slice("ACTION_HINT: ".length);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { lines, warnings, requiredActions, summaryLine };
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
function buildCliArgs(input) {
|
|
75
92
|
const args = [CLI_SCRIPT_PATH, "--format", "json"];
|
|
76
93
|
|
|
77
|
-
const pemPaths = toCsvArg(input.pemPaths);
|
|
78
|
-
if (pemPaths) {
|
|
79
|
-
args.push("--pem-path", pemPaths);
|
|
80
|
-
} else if (input.pemPath) {
|
|
81
|
-
args.push("--pem-path", input.pemPath);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
94
|
const profiles = toCsvArg(input.profiles);
|
|
85
|
-
if (profiles)
|
|
86
|
-
args.push("--profiles", profiles);
|
|
87
|
-
}
|
|
95
|
+
if (profiles) args.push("--profiles", profiles);
|
|
88
96
|
|
|
89
97
|
const regions = toCsvArg(input.regions);
|
|
90
|
-
if (regions)
|
|
91
|
-
args.push("--regions", regions);
|
|
92
|
-
}
|
|
98
|
+
if (regions) args.push("--regions", regions);
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
100
|
+
const instanceIds = toCsvArg(input.instanceIds);
|
|
101
|
+
if (instanceIds) args.push("--instance-ids", instanceIds);
|
|
97
102
|
|
|
98
|
-
if (input.
|
|
99
|
-
|
|
100
|
-
}
|
|
103
|
+
if (input.publicOnly) args.push("--public-only");
|
|
104
|
+
if (input.managedOnly) args.push("--managed-only");
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
if (input.autoRemediateSsm) args.push("--auto-remediate-ssm");
|
|
107
|
+
if (input.ssmInstanceProfileName) args.push("--ssm-instance-profile-name", input.ssmInstanceProfileName);
|
|
108
|
+
if (input.ssmInstanceProfileArn) args.push("--ssm-instance-profile-arn", input.ssmInstanceProfileArn);
|
|
109
|
+
if (input.allowReplaceProfile) args.push("--allow-replace-profile");
|
|
110
|
+
if (input.remediationWaitSec != null) args.push("--remediation-wait", String(input.remediationWaitSec));
|
|
106
111
|
|
|
107
|
-
if (input.
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
if (input.runtimeSnapshot === false) args.push("--no-runtime-snapshot");
|
|
113
|
+
if (input.runtimeSnapshot === true) args.push("--runtime-snapshot");
|
|
114
|
+
if (input.snapshotTimeoutSec != null) args.push("--snapshot-timeout", String(input.snapshotTimeoutSec));
|
|
115
|
+
if (input.snapshotConcurrency != null) args.push("--snapshot-concurrency", String(input.snapshotConcurrency));
|
|
116
|
+
if (input.snapshotMaxKb != null) args.push("--snapshot-max-kb", String(input.snapshotMaxKb));
|
|
110
117
|
|
|
111
|
-
if (input.
|
|
112
|
-
|
|
113
|
-
}
|
|
118
|
+
if (input.autoSsoLogin === false) args.push("--no-auto-sso-login");
|
|
119
|
+
if (input.autoSsoLogin === true) args.push("--auto-sso-login");
|
|
114
120
|
|
|
115
|
-
if (input.noProgress !== false)
|
|
116
|
-
args.push("--no-progress");
|
|
117
|
-
}
|
|
121
|
+
if (input.noProgress !== false) args.push("--no-progress");
|
|
118
122
|
|
|
119
123
|
return args;
|
|
120
124
|
}
|
|
@@ -144,7 +148,6 @@ function runDiscoverCli(input) {
|
|
|
144
148
|
stdout += chunk;
|
|
145
149
|
});
|
|
146
150
|
}
|
|
147
|
-
|
|
148
151
|
if (child.stderr) {
|
|
149
152
|
child.stderr.setEncoding("utf8");
|
|
150
153
|
child.stderr.on("data", (chunk) => {
|
|
@@ -153,18 +156,12 @@ function runDiscoverCli(input) {
|
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
const timer = setTimeout(() => {
|
|
156
|
-
if (exited)
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
+
if (exited) return;
|
|
159
160
|
timedOut = true;
|
|
160
|
-
try {
|
|
161
|
-
child.kill("SIGTERM");
|
|
162
|
-
} catch {}
|
|
161
|
+
try { child.kill("SIGTERM"); } catch {}
|
|
163
162
|
setTimeout(() => {
|
|
164
163
|
if (!exited) {
|
|
165
|
-
try {
|
|
166
|
-
child.kill("SIGKILL");
|
|
167
|
-
} catch {}
|
|
164
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
168
165
|
}
|
|
169
166
|
}, 3000).unref();
|
|
170
167
|
}, timeoutMs);
|
|
@@ -198,7 +195,7 @@ function runDiscoverCli(input) {
|
|
|
198
195
|
});
|
|
199
196
|
});
|
|
200
197
|
});
|
|
201
|
-
}
|
|
198
|
+
}
|
|
202
199
|
|
|
203
200
|
function tryParseJsonArray(text) {
|
|
204
201
|
const trimmed = String(text || "").trim();
|
|
@@ -212,140 +209,87 @@ function tryParseJsonArray(text) {
|
|
|
212
209
|
}
|
|
213
210
|
return { ok: true, value: parsed };
|
|
214
211
|
} catch (error) {
|
|
215
|
-
return {
|
|
216
|
-
ok: false,
|
|
217
|
-
error: error && error.message ? error.message : String(error)
|
|
218
|
-
};
|
|
212
|
+
return { ok: false, error: error && error.message ? error.message : String(error) };
|
|
219
213
|
}
|
|
220
214
|
}
|
|
221
215
|
|
|
222
216
|
function summarizeRecords(records) {
|
|
223
217
|
const summary = {
|
|
224
|
-
totalRecords:
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
218
|
+
totalRecords: 0,
|
|
219
|
+
publicIpRecords: 0,
|
|
220
|
+
ssmManagedCount: 0,
|
|
221
|
+
ssmOnlineCount: 0,
|
|
222
|
+
runtimeSnapshotCount: 0,
|
|
223
|
+
runtimeSnapshotSuccessCount: 0,
|
|
228
224
|
profiles: [],
|
|
229
225
|
regions: []
|
|
230
226
|
};
|
|
231
227
|
|
|
232
|
-
const
|
|
233
|
-
const
|
|
228
|
+
const profileSet = new Set();
|
|
229
|
+
const regionSet = new Set();
|
|
234
230
|
|
|
235
231
|
for (const item of Array.isArray(records) ? records : []) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (item && item.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (item && item.profile) {
|
|
246
|
-
profiles.add(String(item.profile));
|
|
247
|
-
}
|
|
248
|
-
if (item && item.region) {
|
|
249
|
-
regions.add(String(item.region));
|
|
232
|
+
summary.totalRecords += 1;
|
|
233
|
+
if (item && item.publicIp) summary.publicIpRecords += 1;
|
|
234
|
+
if (item && item.ssmManaged === true) summary.ssmManagedCount += 1;
|
|
235
|
+
if (item && item.ssmOnline === true) summary.ssmOnlineCount += 1;
|
|
236
|
+
if (item && item.runtimeSnapshot) {
|
|
237
|
+
summary.runtimeSnapshotCount += 1;
|
|
238
|
+
if (item.runtimeSnapshot.status === "Success") {
|
|
239
|
+
summary.runtimeSnapshotSuccessCount += 1;
|
|
240
|
+
}
|
|
250
241
|
}
|
|
242
|
+
if (item && item.profile) profileSet.add(String(item.profile));
|
|
243
|
+
if (item && item.region) regionSet.add(String(item.region));
|
|
251
244
|
}
|
|
252
245
|
|
|
253
|
-
summary.profiles = Array.from(
|
|
254
|
-
summary.regions = Array.from(
|
|
246
|
+
summary.profiles = Array.from(profileSet).sort();
|
|
247
|
+
summary.regions = Array.from(regionSet).sort();
|
|
255
248
|
return summary;
|
|
256
249
|
}
|
|
257
250
|
|
|
258
251
|
function buildToolTextResponse(payload) {
|
|
259
|
-
|
|
260
|
-
return truncateText(text, DEFAULT_JSON_TEXT_LIMIT);
|
|
252
|
+
return truncateText(JSON.stringify(payload, null, 2), DEFAULT_JSON_TEXT_LIMIT);
|
|
261
253
|
}
|
|
262
254
|
|
|
263
|
-
|
|
255
|
+
function toolSchema() {
|
|
256
|
+
return {
|
|
257
|
+
profiles: z.array(z.string().min(1)).optional().describe("Optional AWS profiles."),
|
|
258
|
+
regions: z.array(z.string().min(1)).optional().describe("Optional AWS regions."),
|
|
259
|
+
instanceIds: z.array(z.string().min(1)).optional().describe("Optional EC2 instance ids."),
|
|
260
|
+
publicOnly: z.boolean().optional().describe("If true, include only public IPv4 instances."),
|
|
261
|
+
managedOnly: z.boolean().optional().describe("If true, include only SSM-managed instances."),
|
|
262
|
+
autoRemediateSsm: z.boolean().optional().describe("If true, try attaching/replacing instance profile for unmanaged instances."),
|
|
263
|
+
ssmInstanceProfileName: z.string().min(1).optional().describe("Instance profile name used for remediation."),
|
|
264
|
+
ssmInstanceProfileArn: z.string().min(1).optional().describe("Instance profile ARN used for remediation."),
|
|
265
|
+
allowReplaceProfile: z.boolean().optional().describe("Allow replacement when instance already has a profile."),
|
|
266
|
+
remediationWaitSec: z.number().positive().optional().describe("Seconds to wait before post-remediation SSM recheck."),
|
|
267
|
+
runtimeSnapshot: z.boolean().optional().describe("Enable/disable runtime snapshot collection."),
|
|
268
|
+
snapshotTimeoutSec: z.number().positive().optional().describe("SSM command timeout in seconds."),
|
|
269
|
+
snapshotConcurrency: z.number().int().positive().optional().describe("Parallel workers for runtime snapshots."),
|
|
270
|
+
snapshotMaxKb: z.number().int().positive().optional().describe("Max runtime snapshot output size per instance in KB."),
|
|
271
|
+
autoSsoLogin: z.boolean().optional().describe("Enable/disable automatic aws sso login retry."),
|
|
272
|
+
noProgress: z.boolean().optional().describe("Suppress CLI progress logs."),
|
|
273
|
+
timeoutSec: z.number().positive().max(3600).optional().describe("Wrapper timeout for CLI process."),
|
|
274
|
+
workingDirectory: z.string().min(1).optional().describe("Optional working directory for subprocess."),
|
|
275
|
+
maxRecords: z.number().int().positive().max(10000).optional().describe("Optional max records in MCP response."),
|
|
276
|
+
includeStderr: z.boolean().optional().describe("Include stderr logs in response.")
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function registerDiscoverTool(server, name, title, description) {
|
|
264
281
|
server.registerTool(
|
|
265
|
-
|
|
282
|
+
name,
|
|
266
283
|
{
|
|
267
|
-
title
|
|
268
|
-
description
|
|
269
|
-
"Runs the local mcp-aws-manager CLI and returns EC2 instances with public IPv4 addresses, including PEM fingerprint matching and optional SSH reachability checks.",
|
|
284
|
+
title,
|
|
285
|
+
description,
|
|
270
286
|
annotations: {
|
|
271
287
|
readOnlyHint: true,
|
|
272
288
|
destructiveHint: false,
|
|
273
289
|
idempotentHint: true,
|
|
274
290
|
openWorldHint: true
|
|
275
291
|
},
|
|
276
|
-
inputSchema:
|
|
277
|
-
pemPaths: z
|
|
278
|
-
.array(z.string().min(1))
|
|
279
|
-
.optional()
|
|
280
|
-
.describe(
|
|
281
|
-
"Optional list of local PEM private key file paths. Preferred when multiple keys should be used."
|
|
282
|
-
),
|
|
283
|
-
pemPath: z
|
|
284
|
-
.string()
|
|
285
|
-
.min(1)
|
|
286
|
-
.optional()
|
|
287
|
-
.describe(
|
|
288
|
-
"Optional single PEM path. Ignored when pemPaths is provided. If omitted, CLI auto-discovers .pem files in workingDirectory/current directory."
|
|
289
|
-
),
|
|
290
|
-
profiles: z
|
|
291
|
-
.array(z.string().min(1))
|
|
292
|
-
.optional()
|
|
293
|
-
.describe("Optional AWS profile names to scan."),
|
|
294
|
-
regions: z
|
|
295
|
-
.array(z.string().min(1))
|
|
296
|
-
.optional()
|
|
297
|
-
.describe("Optional AWS regions to scan."),
|
|
298
|
-
keyName: z
|
|
299
|
-
.string()
|
|
300
|
-
.min(1)
|
|
301
|
-
.optional()
|
|
302
|
-
.describe("Optional EC2 KeyPair name fallback for matching."),
|
|
303
|
-
sshCheck: z
|
|
304
|
-
.boolean()
|
|
305
|
-
.optional()
|
|
306
|
-
.describe("If true, attempt SSH authentication checks."),
|
|
307
|
-
sshUsernames: z
|
|
308
|
-
.array(z.string().min(1))
|
|
309
|
-
.optional()
|
|
310
|
-
.describe("Optional SSH usernames to try when sshCheck=true."),
|
|
311
|
-
sshTimeoutSec: z
|
|
312
|
-
.number()
|
|
313
|
-
.positive()
|
|
314
|
-
.optional()
|
|
315
|
-
.describe("SSH timeout in seconds."),
|
|
316
|
-
matchedOnly: z
|
|
317
|
-
.boolean()
|
|
318
|
-
.optional()
|
|
319
|
-
.describe("If true, return only records matched to the PEM."),
|
|
320
|
-
noProgress: z
|
|
321
|
-
.boolean()
|
|
322
|
-
.optional()
|
|
323
|
-
.describe("Suppress CLI step progress logs (defaults to true in the MCP wrapper)."),
|
|
324
|
-
timeoutSec: z
|
|
325
|
-
.number()
|
|
326
|
-
.positive()
|
|
327
|
-
.max(3600)
|
|
328
|
-
.optional()
|
|
329
|
-
.describe("Wrapper timeout for the CLI process (default 300s)."),
|
|
330
|
-
workingDirectory: z
|
|
331
|
-
.string()
|
|
332
|
-
.min(1)
|
|
333
|
-
.optional()
|
|
334
|
-
.describe(
|
|
335
|
-
"Optional working directory. Used for relative PEM path resolution and auto .pem discovery when pemPath/pemPaths are omitted."
|
|
336
|
-
),
|
|
337
|
-
maxRecords: z
|
|
338
|
-
.number()
|
|
339
|
-
.int()
|
|
340
|
-
.positive()
|
|
341
|
-
.max(10000)
|
|
342
|
-
.optional()
|
|
343
|
-
.describe("Optional max number of records to include in the MCP response."),
|
|
344
|
-
includeStderr: z
|
|
345
|
-
.boolean()
|
|
346
|
-
.optional()
|
|
347
|
-
.describe("If true, include CLI stderr logs in the response (truncated).")
|
|
348
|
-
}
|
|
292
|
+
inputSchema: toolSchema()
|
|
349
293
|
},
|
|
350
294
|
async (args) => {
|
|
351
295
|
const startedAt = new Date().toISOString();
|
|
@@ -354,12 +298,7 @@ async function registerTools(server) {
|
|
|
354
298
|
|
|
355
299
|
if (cliResult.spawnError) {
|
|
356
300
|
return {
|
|
357
|
-
content: [
|
|
358
|
-
{
|
|
359
|
-
type: "text",
|
|
360
|
-
text: `Failed to start CLI subprocess: ${cliResult.spawnError}`
|
|
361
|
-
}
|
|
362
|
-
],
|
|
301
|
+
content: [{ type: "text", text: `Failed to start CLI subprocess: ${cliResult.spawnError}` }],
|
|
363
302
|
isError: true
|
|
364
303
|
};
|
|
365
304
|
}
|
|
@@ -373,8 +312,7 @@ async function registerTools(server) {
|
|
|
373
312
|
ok: false,
|
|
374
313
|
error: "CLI process timed out",
|
|
375
314
|
startedAt,
|
|
376
|
-
timeoutSec:
|
|
377
|
-
typeof args.timeoutSec === "number" ? args.timeoutSec : DEFAULT_TIMEOUT_SEC,
|
|
315
|
+
timeoutSec: typeof args.timeoutSec === "number" ? args.timeoutSec : DEFAULT_TIMEOUT_SEC,
|
|
378
316
|
exitCode: cliResult.exitCode,
|
|
379
317
|
signal: cliResult.signal,
|
|
380
318
|
stderr: truncateText(cliResult.stderr)
|
|
@@ -411,17 +349,19 @@ async function registerTools(server) {
|
|
|
411
349
|
typeof args.maxRecords === "number" && Number.isFinite(args.maxRecords)
|
|
412
350
|
? args.maxRecords
|
|
413
351
|
: null;
|
|
414
|
-
const records =
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
352
|
+
const records = maxRecords && allRecords.length > maxRecords ? allRecords.slice(0, maxRecords) : allRecords;
|
|
353
|
+
|
|
354
|
+
const acceptedExitCodes = new Set([0, 2, 3]);
|
|
355
|
+
const requiresUserAction = logInfo.requiredActions.length > 0 || cliResult.exitCode === 3;
|
|
418
356
|
|
|
419
357
|
const response = {
|
|
420
|
-
ok:
|
|
358
|
+
ok: acceptedExitCodes.has(cliResult.exitCode),
|
|
421
359
|
startedAt,
|
|
422
360
|
finishedAt: new Date().toISOString(),
|
|
423
361
|
exitCode: cliResult.exitCode,
|
|
424
362
|
signal: cliResult.signal,
|
|
363
|
+
requiresUserAction,
|
|
364
|
+
requiredActions: logInfo.requiredActions,
|
|
425
365
|
summary: {
|
|
426
366
|
...summarizeRecords(allRecords),
|
|
427
367
|
returnedRecords: records.length,
|
|
@@ -436,39 +376,41 @@ async function registerTools(server) {
|
|
|
436
376
|
response.stderr = truncateText(cliResult.stderr);
|
|
437
377
|
}
|
|
438
378
|
|
|
439
|
-
if (
|
|
379
|
+
if (!acceptedExitCodes.has(cliResult.exitCode)) {
|
|
440
380
|
return {
|
|
441
|
-
content: [
|
|
442
|
-
{
|
|
443
|
-
type: "text",
|
|
444
|
-
text: buildToolTextResponse({
|
|
445
|
-
...response,
|
|
446
|
-
ok: false,
|
|
447
|
-
stderr: truncateText(cliResult.stderr)
|
|
448
|
-
})
|
|
449
|
-
}
|
|
450
|
-
],
|
|
381
|
+
content: [{ type: "text", text: buildToolTextResponse({ ...response, ok: false, stderr: truncateText(cliResult.stderr) }) }],
|
|
451
382
|
isError: true
|
|
452
383
|
};
|
|
453
384
|
}
|
|
454
385
|
|
|
455
386
|
return {
|
|
456
|
-
content: [
|
|
457
|
-
|
|
458
|
-
type: "text",
|
|
459
|
-
text: buildToolTextResponse(response)
|
|
460
|
-
}
|
|
461
|
-
]
|
|
387
|
+
content: [{ type: "text", text: buildToolTextResponse(response) }],
|
|
388
|
+
isError: false
|
|
462
389
|
};
|
|
463
390
|
}
|
|
464
391
|
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function registerTools(server) {
|
|
395
|
+
registerDiscoverTool(
|
|
396
|
+
server,
|
|
397
|
+
"discover_ec2_with_ssm",
|
|
398
|
+
"Discover EC2 + SSM Inventory",
|
|
399
|
+
"Runs mcp-aws-manager in SSM-only mode and returns EC2 inventory with SSM management/online status and optional runtime snapshots."
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
registerDiscoverTool(
|
|
403
|
+
server,
|
|
404
|
+
"discover_public_ec2_with_pem",
|
|
405
|
+
"Discover EC2 + SSM Inventory (compat alias)",
|
|
406
|
+
"Compatibility alias. Internally runs the same SSM-only discovery flow."
|
|
407
|
+
);
|
|
465
408
|
|
|
466
409
|
server.registerTool(
|
|
467
410
|
"mcp_aws_discover_cli_help",
|
|
468
411
|
{
|
|
469
412
|
title: "Show CLI Help",
|
|
470
|
-
description:
|
|
471
|
-
"Returns the local mcp-aws-manager CLI usage text for reference inside AI clients.",
|
|
413
|
+
description: "Returns the local mcp-aws-manager CLI usage text.",
|
|
472
414
|
annotations: {
|
|
473
415
|
readOnlyHint: true,
|
|
474
416
|
destructiveHint: false,
|
|
@@ -483,30 +425,27 @@ async function registerTools(server) {
|
|
|
483
425
|
env: process.env,
|
|
484
426
|
stdio: ["ignore", "pipe", "pipe"]
|
|
485
427
|
});
|
|
428
|
+
|
|
486
429
|
let stdout = "";
|
|
487
430
|
let stderr = "";
|
|
488
431
|
if (proc.stdout) {
|
|
489
432
|
proc.stdout.setEncoding("utf8");
|
|
490
|
-
proc.stdout.on("data", (c) =>
|
|
433
|
+
proc.stdout.on("data", (c) => { stdout += c; });
|
|
491
434
|
}
|
|
492
435
|
if (proc.stderr) {
|
|
493
436
|
proc.stderr.setEncoding("utf8");
|
|
494
|
-
proc.stderr.on("data", (c) =>
|
|
437
|
+
proc.stderr.on("data", (c) => { stderr += c; });
|
|
495
438
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
stdout,
|
|
504
|
-
stderr: String(error && error.message ? error.message : error)
|
|
505
|
-
})
|
|
506
|
-
);
|
|
439
|
+
|
|
440
|
+
proc.on("close", (code, signal) => {
|
|
441
|
+
resolve({ code: code == null ? null : code, signal, stdout, stderr });
|
|
442
|
+
});
|
|
443
|
+
proc.on("error", (error) => {
|
|
444
|
+
resolve({ code: null, signal: null, stdout, stderr: String(error && error.message ? error.message : error) });
|
|
445
|
+
});
|
|
507
446
|
});
|
|
508
447
|
|
|
509
|
-
const
|
|
448
|
+
const payload = {
|
|
510
449
|
ok: child.code === 0,
|
|
511
450
|
exitCode: child.code,
|
|
512
451
|
signal: child.signal || null,
|
|
@@ -515,12 +454,7 @@ async function registerTools(server) {
|
|
|
515
454
|
};
|
|
516
455
|
|
|
517
456
|
return {
|
|
518
|
-
content: [
|
|
519
|
-
{
|
|
520
|
-
type: "text",
|
|
521
|
-
text: buildToolTextResponse(result)
|
|
522
|
-
}
|
|
523
|
-
],
|
|
457
|
+
content: [{ type: "text", text: buildToolTextResponse(payload) }],
|
|
524
458
|
isError: child.code !== 0
|
|
525
459
|
};
|
|
526
460
|
}
|
|
@@ -539,6 +473,7 @@ async function main() {
|
|
|
539
473
|
});
|
|
540
474
|
|
|
541
475
|
await registerTools(server);
|
|
476
|
+
|
|
542
477
|
const transport = new StdioServerTransport();
|
|
543
478
|
await server.connect(transport);
|
|
544
479
|
}
|
|
@@ -547,4 +482,4 @@ main().catch((error) => {
|
|
|
547
482
|
const message = error && error.stack ? error.stack : String(error);
|
|
548
483
|
process.stderr.write(`mcp-aws-manager-mcp startup error: ${message}\n`);
|
|
549
484
|
process.exitCode = 1;
|
|
550
|
-
});
|
|
485
|
+
});
|