mcp-aws-manager 0.3.0 → 0.3.2
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/AGENT_GUIDANCE_LOOP_TEMPLATE_KO.md +68 -0
- package/IMPLEMENTATION_INTEGRATIONS.md +91 -0
- package/MCP_CLIENT_SETUP.md +30 -5
- package/MCP_DIFFERENTIATION.md +39 -0
- package/README.md +60 -7
- package/bin/mcp-aws-manager-mcp.js +264 -6
- package/bin/mcp-aws-manager.js +888 -132
- package/package.json +13 -3
|
@@ -20,7 +20,7 @@ function usageText() {
|
|
|
20
20
|
return [
|
|
21
21
|
"mcp-aws-manager-mcp",
|
|
22
22
|
"",
|
|
23
|
-
"MCP stdio wrapper for the mcp-aws-manager CLI
|
|
23
|
+
"MCP stdio wrapper for the mcp-aws-manager CLI.",
|
|
24
24
|
"",
|
|
25
25
|
"Usage:",
|
|
26
26
|
" mcp-aws-manager-mcp",
|
|
@@ -28,7 +28,7 @@ function usageText() {
|
|
|
28
28
|
"",
|
|
29
29
|
"Notes:",
|
|
30
30
|
" - This process is an MCP stdio server.",
|
|
31
|
-
" - Exposes
|
|
31
|
+
" - Exposes multi-service AWS inventory and optional runtime tools.",
|
|
32
32
|
""
|
|
33
33
|
].join("\n");
|
|
34
34
|
}
|
|
@@ -100,6 +100,21 @@ function buildCliArgs(input) {
|
|
|
100
100
|
const instanceIds = toCsvArg(input.instanceIds);
|
|
101
101
|
if (instanceIds) args.push("--instance-ids", instanceIds);
|
|
102
102
|
|
|
103
|
+
if (input.includeLambda === true) args.push("--include-lambda");
|
|
104
|
+
if (input.includeLambda === false) args.push("--no-include-lambda");
|
|
105
|
+
if (input.includeEc2 === true) args.push("--include-ec2");
|
|
106
|
+
if (input.includeEc2 === false) args.push("--no-ec2");
|
|
107
|
+
if (input.includeAlb === true) args.push("--include-alb");
|
|
108
|
+
if (input.includeAlb === false) args.push("--no-include-alb");
|
|
109
|
+
if (input.includeAsg === true) args.push("--include-asg");
|
|
110
|
+
if (input.includeAsg === false) args.push("--no-include-asg");
|
|
111
|
+
if (input.includeRds === true) args.push("--include-rds");
|
|
112
|
+
if (input.includeRds === false) args.push("--no-include-rds");
|
|
113
|
+
if (input.includeElastiCache === true) args.push("--include-elasticache");
|
|
114
|
+
if (input.includeElastiCache === false) args.push("--no-include-elasticache");
|
|
115
|
+
if (input.includeRoute53 === true) args.push("--include-route53");
|
|
116
|
+
if (input.includeRoute53 === false) args.push("--no-include-route53");
|
|
117
|
+
|
|
103
118
|
if (input.publicOnly) args.push("--public-only");
|
|
104
119
|
if (input.managedOnly) args.push("--managed-only");
|
|
105
120
|
|
|
@@ -216,6 +231,14 @@ function tryParseJsonArray(text) {
|
|
|
216
231
|
function summarizeRecords(records) {
|
|
217
232
|
const summary = {
|
|
218
233
|
totalRecords: 0,
|
|
234
|
+
ec2Records: 0,
|
|
235
|
+
lambdaRecords: 0,
|
|
236
|
+
albRecords: 0,
|
|
237
|
+
targetGroupRecords: 0,
|
|
238
|
+
asgRecords: 0,
|
|
239
|
+
rdsRecords: 0,
|
|
240
|
+
elasticacheRecords: 0,
|
|
241
|
+
route53ZoneRecords: 0,
|
|
219
242
|
publicIpRecords: 0,
|
|
220
243
|
ssmManagedCount: 0,
|
|
221
244
|
ssmOnlineCount: 0,
|
|
@@ -230,6 +253,15 @@ function summarizeRecords(records) {
|
|
|
230
253
|
|
|
231
254
|
for (const item of Array.isArray(records) ? records : []) {
|
|
232
255
|
summary.totalRecords += 1;
|
|
256
|
+
const resourceType = item && item.resourceType ? String(item.resourceType).toLowerCase() : null;
|
|
257
|
+
if (resourceType === "ec2") summary.ec2Records += 1;
|
|
258
|
+
if (resourceType === "lambda") summary.lambdaRecords += 1;
|
|
259
|
+
if (resourceType === "alb") summary.albRecords += 1;
|
|
260
|
+
if (resourceType === "target_group") summary.targetGroupRecords += 1;
|
|
261
|
+
if (resourceType === "asg") summary.asgRecords += 1;
|
|
262
|
+
if (resourceType === "rds") summary.rdsRecords += 1;
|
|
263
|
+
if (resourceType === "elasticache") summary.elasticacheRecords += 1;
|
|
264
|
+
if (resourceType === "route53_zone") summary.route53ZoneRecords += 1;
|
|
233
265
|
if (item && item.publicIp) summary.publicIpRecords += 1;
|
|
234
266
|
if (item && item.ssmManaged === true) summary.ssmManagedCount += 1;
|
|
235
267
|
if (item && item.ssmOnline === true) summary.ssmOnlineCount += 1;
|
|
@@ -248,6 +280,223 @@ function summarizeRecords(records) {
|
|
|
248
280
|
return summary;
|
|
249
281
|
}
|
|
250
282
|
|
|
283
|
+
function firstProfileArg(args) {
|
|
284
|
+
if (args && Array.isArray(args.profiles) && args.profiles.length > 0) {
|
|
285
|
+
return String(args.profiles[0]);
|
|
286
|
+
}
|
|
287
|
+
return "default";
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function inferSsoLoginCommand(action, args) {
|
|
291
|
+
const hint = action && action.hint ? String(action.hint) : "";
|
|
292
|
+
const matched = /aws sso login --profile\s+[^\s'"]+/i.exec(hint);
|
|
293
|
+
if (matched) {
|
|
294
|
+
return matched[0];
|
|
295
|
+
}
|
|
296
|
+
return `aws sso login --profile ${firstProfileArg(args)}`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function guidanceForAction(action, args) {
|
|
300
|
+
const code = action && action.code ? String(action.code) : "UNKNOWN";
|
|
301
|
+
const defaultItem = {
|
|
302
|
+
code,
|
|
303
|
+
title: "Manual action required",
|
|
304
|
+
steps: [
|
|
305
|
+
action && action.message ? action.message : "A manual action is required.",
|
|
306
|
+
action && action.hint ? action.hint : "After completing the action, reply '?熬곣뫁?? to continue."
|
|
307
|
+
],
|
|
308
|
+
confirmText: "?브퀗??洹쏆쾸? ?熬곣뫁???濡?듆 '?熬곣뫁?????┑?????면썒??닔??? ?띠룇?? ??븐슙???怨쀬Ŧ ???吏?????熬곥굥由?뇦猿뗭쪠????덈펲."
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
switch (code) {
|
|
312
|
+
case "SSO_LOGIN_NEEDED":
|
|
313
|
+
case "SSO_REAUTH_REQUIRED": {
|
|
314
|
+
const cmd = inferSsoLoginCommand(action, args);
|
|
315
|
+
return {
|
|
316
|
+
code,
|
|
317
|
+
title: "AWS SSO login required",
|
|
318
|
+
steps: [
|
|
319
|
+
`????????????깅쾳 嶺뚮ㅏ援앲??????덈뺄??琉얠돪?? ${cmd}`,
|
|
320
|
+
"??곗뒧???? ?筌뤾쑴理?MFA???熬곣뫁???琉얠돪??",
|
|
321
|
+
"?熬곣뫁????'?熬곣뫁?????┑?????면썒??닔???"
|
|
322
|
+
],
|
|
323
|
+
confirmText: "SSO ?β돦裕??筌뤾쑴逾???硫명뀬???좊듆 '?熬곣뫁?????┑?????면썒??닔???"
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
case "AWS_CREDENTIALS_REQUIRED":
|
|
327
|
+
return {
|
|
328
|
+
code,
|
|
329
|
+
title: "AWS credentials required",
|
|
330
|
+
steps: [
|
|
331
|
+
"??????熬곣뫁夷?熬곣뫗踰????遊꾤춯?밸퉾筌?????깆젧??琉얠돪??(SSO ???裕?access key).",
|
|
332
|
+
"SSO??寃밸듆 'aws configure sso --profile <profile>' ???β돦裕??筌뤿굝由?筌뤾쑴??",
|
|
333
|
+
"?熬곣뫁????'?熬곣뫁?????┑?????면썒??닔???"
|
|
334
|
+
],
|
|
335
|
+
confirmText: "???遊꾤춯?밸퉾筌????깆젧/?β돦裕??筌뤾쑴逾???硫명뀬???좊듆 '?熬곣뫁?????┑?????면썒??닔???"
|
|
336
|
+
};
|
|
337
|
+
case "SET_SSM_INSTANCE_PROFILE":
|
|
338
|
+
return {
|
|
339
|
+
code,
|
|
340
|
+
title: "SSM remediation target missing",
|
|
341
|
+
steps: [
|
|
342
|
+
"???吏??곌랜踰?袁ㅻご??????濡?졎嶺?instance profile ???藥????裕?ARN??嶺뚯솘??筌먐삵돵????紐껊퉵??",
|
|
343
|
+
"???깅쾳 ?????繞???濡る룎????節띾쐾 ?熬곣뫀堉??琉얠돪?? --ssm-instance-profile-name ???裕?--ssm-instance-profile-arn",
|
|
344
|
+
"?熬곣뫁????'?熬곣뫁?????┑?????면썒??닔???"
|
|
345
|
+
],
|
|
346
|
+
confirmText: "?熬곣뫁夷???逾?????⑤챷諭?嶺뚯솘??筌먐삳빳???좊듆 '?熬곣뫁?????┑?????면썒??닔???"
|
|
347
|
+
};
|
|
348
|
+
case "SSM_ROLE_OR_AGENT_REQUIRED":
|
|
349
|
+
return {
|
|
350
|
+
code,
|
|
351
|
+
title: "Instance is not SSM managed",
|
|
352
|
+
steps: [
|
|
353
|
+
"?筌뤾쑬裕??怨룸츩 ?????AmazonSSMManagedInstanceCore???????琉얠돪??",
|
|
354
|
+
"SSM Agent?? ???덈콦??怨뚯씩(SSM endpoint/?筌뤿굛????롪퍔?δ빳??띠럾? ?筌먦끆留?筌? ?筌먦끉逾??琉얠돪??",
|
|
355
|
+
"?熬곣뫁????'?熬곣뫁?????┑?????면썒??닔???"
|
|
356
|
+
],
|
|
357
|
+
confirmText: "SSM ??㉱????⑤객臾???브퀗?????덈펲嶺?'?熬곣뫁?????┑?????면썒??닔???"
|
|
358
|
+
};
|
|
359
|
+
case "INSTANCE_HAS_PROFILE":
|
|
360
|
+
return {
|
|
361
|
+
code,
|
|
362
|
+
title: "Existing instance profile detected",
|
|
363
|
+
steps: [
|
|
364
|
+
"?リ옇????筌뤾쑬裕??怨룸츩 ?熬곣뫁夷???逾?????곕????덈펲.",
|
|
365
|
+
"??ルㅎ臾?1: ?リ옇????????筌먦끉???SSM 雅?굝??뇡???怨뺣뼺???紐껊퉵??",
|
|
366
|
+
"??ルㅎ臾?2: ???吏???흮?우뮁紐???믨퀡由?춯?allowReplaceProfile=true ??????熬곥굥????덈펲."
|
|
367
|
+
],
|
|
368
|
+
confirmText: "??⑤챷????꾩렮維뽬떋???筌먐삳빳???좊듆 '?熬곣뫁?????┑?????면썒??닔???"
|
|
369
|
+
};
|
|
370
|
+
case "IAM_PROFILE_ASSOCIATION_FAILED":
|
|
371
|
+
case "IAM_PROFILE_REPLACE_FAILED":
|
|
372
|
+
return {
|
|
373
|
+
code,
|
|
374
|
+
title: "Missing IAM permission for remediation",
|
|
375
|
+
steps: [
|
|
376
|
+
"???덈뺄 ?낅슣?섊뙼??EC2 ?筌뤾쑬裕??怨룸츩 ?熬곣뫁夷???逾???⑤슡????흮??雅?굝??뇡???遊붋????筌뤾쑴??",
|
|
377
|
+
"?熬곣뫗??雅?굝??뇡? ec2:AssociateIamInstanceProfile, ec2:ReplaceIamInstanceProfileAssociation(??흮????, iam:PassRole",
|
|
378
|
+
"?熬곣뫁????'?熬곣뫁?????┑?????면썒??닔???"
|
|
379
|
+
],
|
|
380
|
+
confirmText: "IAM 雅?굝??뇡??꾩룇瑗?????硫명뀬???좊듆 '?熬곣뫁?????┑?????면썒??닔???"
|
|
381
|
+
};
|
|
382
|
+
case "SSM_RUNCOMMAND_PERMISSION_REQUIRED":
|
|
383
|
+
return {
|
|
384
|
+
code,
|
|
385
|
+
title: "Missing SSM RunCommand permission",
|
|
386
|
+
steps: [
|
|
387
|
+
"???덈뺄 ?낅슣?섊뙼??SSM 嶺뚮ㅏ援앲??雅?굝??뇡???遊붋????筌뤾쑴??",
|
|
388
|
+
"?熬곣뫗??雅?굝??뇡? ssm:SendCommand, ssm:GetCommandInvocation",
|
|
389
|
+
"?熬곣뫁????'?熬곣뫁?????┑?????면썒??닔???"
|
|
390
|
+
],
|
|
391
|
+
confirmText: "SSM 雅?굝??뇡??꾩룇瑗?????硫명뀬???좊듆 '?熬곣뫁?????┑?????면썒??닔???"
|
|
392
|
+
};
|
|
393
|
+
case "LAMBDA_LIST_PERMISSION_REQUIRED":
|
|
394
|
+
return {
|
|
395
|
+
code,
|
|
396
|
+
title: "Missing Lambda list permission",
|
|
397
|
+
steps: [
|
|
398
|
+
"??쎈뻬 雅뚯눘猿??Lambda 鈺곌퀬??亦낅슦釉???봔鈺곌퉲鍮??덈뼄.",
|
|
399
|
+
"?袁⑹뒄 亦낅슦釉? lambda:ListFunctions",
|
|
400
|
+
"亦낅슦釉?獄쏆꼷????'??袁⑥┷'??⑦????젻雅뚯눘苑??"
|
|
401
|
+
],
|
|
402
|
+
confirmText: "Lambda 亦낅슦釉?獄쏆꼷?????멸돌筌?'??袁⑥┷'??⑦????젻雅뚯눘苑??"
|
|
403
|
+
};
|
|
404
|
+
case "ELBV2_LIST_PERMISSION_REQUIRED":
|
|
405
|
+
return {
|
|
406
|
+
code,
|
|
407
|
+
title: "Missing ELBv2 list permission",
|
|
408
|
+
steps: [
|
|
409
|
+
"Grant permissions to list load balancers and target groups.",
|
|
410
|
+
"Required: elasticloadbalancing:DescribeLoadBalancers and elasticloadbalancing:DescribeTargetGroups.",
|
|
411
|
+
"Retry after permission update."
|
|
412
|
+
],
|
|
413
|
+
confirmText: "After ELBv2 permission update, reply 'completed' and retry."
|
|
414
|
+
};
|
|
415
|
+
case "ASG_LIST_PERMISSION_REQUIRED":
|
|
416
|
+
return {
|
|
417
|
+
code,
|
|
418
|
+
title: "Missing Auto Scaling list permission",
|
|
419
|
+
steps: [
|
|
420
|
+
"Grant permission to read Auto Scaling Groups.",
|
|
421
|
+
"Required: autoscaling:DescribeAutoScalingGroups.",
|
|
422
|
+
"Retry after permission update."
|
|
423
|
+
],
|
|
424
|
+
confirmText: "After Auto Scaling permission update, reply 'completed' and retry."
|
|
425
|
+
};
|
|
426
|
+
case "RDS_LIST_PERMISSION_REQUIRED":
|
|
427
|
+
return {
|
|
428
|
+
code,
|
|
429
|
+
title: "Missing RDS list permission",
|
|
430
|
+
steps: [
|
|
431
|
+
"Grant permission to list RDS DB instances.",
|
|
432
|
+
"Required: rds:DescribeDBInstances.",
|
|
433
|
+
"Retry after permission update."
|
|
434
|
+
],
|
|
435
|
+
confirmText: "After RDS permission update, reply 'completed' and retry."
|
|
436
|
+
};
|
|
437
|
+
case "ELASTICACHE_LIST_PERMISSION_REQUIRED":
|
|
438
|
+
return {
|
|
439
|
+
code,
|
|
440
|
+
title: "Missing ElastiCache list permission",
|
|
441
|
+
steps: [
|
|
442
|
+
"Grant permission to list ElastiCache clusters.",
|
|
443
|
+
"Required: elasticache:DescribeCacheClusters.",
|
|
444
|
+
"Retry after permission update."
|
|
445
|
+
],
|
|
446
|
+
confirmText: "After ElastiCache permission update, reply 'completed' and retry."
|
|
447
|
+
};
|
|
448
|
+
case "ROUTE53_LIST_PERMISSION_REQUIRED":
|
|
449
|
+
return {
|
|
450
|
+
code,
|
|
451
|
+
title: "Missing Route53 list permission",
|
|
452
|
+
steps: [
|
|
453
|
+
"Grant permission to list Route53 hosted zones.",
|
|
454
|
+
"Required: route53:ListHostedZones (and route53:ListResourceRecordSets for record counts).",
|
|
455
|
+
"Retry after permission update."
|
|
456
|
+
],
|
|
457
|
+
confirmText: "After Route53 permission update, reply 'completed' and retry."
|
|
458
|
+
};
|
|
459
|
+
default:
|
|
460
|
+
return defaultItem;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function buildAgentGuidance(requiredActions, toolName, args) {
|
|
465
|
+
const items = Array.isArray(requiredActions)
|
|
466
|
+
? requiredActions.map((action) => guidanceForAction(action, args))
|
|
467
|
+
: [];
|
|
468
|
+
|
|
469
|
+
if (!items.length) {
|
|
470
|
+
return {
|
|
471
|
+
mode: "none",
|
|
472
|
+
autoRetryRecommended: false,
|
|
473
|
+
retryTool: toolName,
|
|
474
|
+
retryArgs: args,
|
|
475
|
+
userChecklist: [],
|
|
476
|
+
assistantMessageTemplate: "상태 조회가 완료되었습니다."
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const firstItem = items[0];
|
|
481
|
+
const lines = [];
|
|
482
|
+
lines.push("AWS 상태 조회를 계속하려면 아래 조치가 필요합니다.");
|
|
483
|
+
lines.push(`1. [${firstItem.code}] ${firstItem.title}`);
|
|
484
|
+
for (let i = 0; i < firstItem.steps.length; i += 1) {
|
|
485
|
+
lines.push(`${i + 1}. ${firstItem.steps[i]}`);
|
|
486
|
+
}
|
|
487
|
+
lines.push(firstItem.confirmText);
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
mode: "human_in_the_loop",
|
|
491
|
+
autoRetryRecommended: true,
|
|
492
|
+
retryTool: toolName,
|
|
493
|
+
retryArgs: args,
|
|
494
|
+
completionTrigger: "사용자가 '완료' 또는 조치 완료 의사를 전달하면 같은 입력으로 재시도",
|
|
495
|
+
userChecklist: items,
|
|
496
|
+
assistantMessageTemplate: lines.join("\n")
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
251
500
|
function buildToolTextResponse(payload) {
|
|
252
501
|
return truncateText(JSON.stringify(payload, null, 2), DEFAULT_JSON_TEXT_LIMIT);
|
|
253
502
|
}
|
|
@@ -257,6 +506,13 @@ function toolSchema() {
|
|
|
257
506
|
profiles: z.array(z.string().min(1)).optional().describe("Optional AWS profiles."),
|
|
258
507
|
regions: z.array(z.string().min(1)).optional().describe("Optional AWS regions."),
|
|
259
508
|
instanceIds: z.array(z.string().min(1)).optional().describe("Optional EC2 instance ids."),
|
|
509
|
+
includeLambda: z.boolean().optional().describe("If true, include Lambda inventory."),
|
|
510
|
+
includeEc2: z.boolean().optional().describe("If false, skip EC2 inventory."),
|
|
511
|
+
includeAlb: z.boolean().optional().describe("If true, include ALB/NLB and target group inventory."),
|
|
512
|
+
includeAsg: z.boolean().optional().describe("If true, include Auto Scaling Group inventory."),
|
|
513
|
+
includeRds: z.boolean().optional().describe("If true, include RDS DB instance inventory."),
|
|
514
|
+
includeElastiCache: z.boolean().optional().describe("If true, include ElastiCache cluster inventory."),
|
|
515
|
+
includeRoute53: z.boolean().optional().describe("If true, include Route53 hosted zone inventory."),
|
|
260
516
|
publicOnly: z.boolean().optional().describe("If true, include only public IPv4 instances."),
|
|
261
517
|
managedOnly: z.boolean().optional().describe("If true, include only SSM-managed instances."),
|
|
262
518
|
autoRemediateSsm: z.boolean().optional().describe("If true, try attaching/replacing instance profile for unmanaged instances."),
|
|
@@ -353,6 +609,7 @@ function registerDiscoverTool(server, name, title, description) {
|
|
|
353
609
|
|
|
354
610
|
const acceptedExitCodes = new Set([0, 2, 3]);
|
|
355
611
|
const requiresUserAction = logInfo.requiredActions.length > 0 || cliResult.exitCode === 3;
|
|
612
|
+
const guidance = buildAgentGuidance(logInfo.requiredActions, name, args);
|
|
356
613
|
|
|
357
614
|
const response = {
|
|
358
615
|
ok: acceptedExitCodes.has(cliResult.exitCode),
|
|
@@ -362,6 +619,7 @@ function registerDiscoverTool(server, name, title, description) {
|
|
|
362
619
|
signal: cliResult.signal,
|
|
363
620
|
requiresUserAction,
|
|
364
621
|
requiredActions: logInfo.requiredActions,
|
|
622
|
+
guidance,
|
|
365
623
|
summary: {
|
|
366
624
|
...summarizeRecords(allRecords),
|
|
367
625
|
returnedRecords: records.length,
|
|
@@ -395,15 +653,15 @@ async function registerTools(server) {
|
|
|
395
653
|
registerDiscoverTool(
|
|
396
654
|
server,
|
|
397
655
|
"discover_ec2_with_ssm",
|
|
398
|
-
"Discover
|
|
399
|
-
"Runs mcp-aws-manager
|
|
656
|
+
"Discover AWS Inventory (multi-service + SSM runtime)",
|
|
657
|
+
"Runs mcp-aws-manager and returns inventory across EC2/Lambda/ALB/ASG/RDS/ElastiCache/Route53 with optional SSM runtime snapshots."
|
|
400
658
|
);
|
|
401
659
|
|
|
402
660
|
registerDiscoverTool(
|
|
403
661
|
server,
|
|
404
662
|
"discover_public_ec2_with_pem",
|
|
405
|
-
"Discover
|
|
406
|
-
"Compatibility alias. Internally runs the same
|
|
663
|
+
"Discover AWS Inventory (compat alias)",
|
|
664
|
+
"Compatibility alias. Internally runs the same multi-service discovery flow."
|
|
407
665
|
);
|
|
408
666
|
|
|
409
667
|
server.registerTool(
|