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.
@@ -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 (SSM-only).",
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 SSM inventory/runtime snapshot discovery tools.",
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 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."
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 EC2 + SSM Inventory (compat alias)",
406
- "Compatibility alias. Internally runs the same SSM-only discovery flow."
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(