@yawlabs/aws-mcp 1.3.3 → 1.4.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.
Files changed (2) hide show
  1. package/dist/index.js +219 -147
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30054,6 +30054,9 @@ var require_turndown_cjs = __commonJS({
30054
30054
  }
30055
30055
  });
30056
30056
 
30057
+ // src/index.ts
30058
+ import { pathToFileURL } from "node:url";
30059
+
30057
30060
  // node_modules/zod/v3/helpers/util.js
30058
30061
  var util;
30059
30062
  (function(util2) {
@@ -50924,11 +50927,11 @@ var Protocol = class {
50924
50927
  *
50925
50928
  * The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
50926
50929
  */
50927
- async connect(transport2) {
50930
+ async connect(transport) {
50928
50931
  if (this._transport) {
50929
50932
  throw new Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");
50930
50933
  }
50931
- this._transport = transport2;
50934
+ this._transport = transport;
50932
50935
  const _onclose = this.transport?.onclose;
50933
50936
  this._transport.onclose = () => {
50934
50937
  _onclose?.();
@@ -52518,8 +52521,8 @@ var McpServer = class {
52518
52521
  *
52519
52522
  * The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
52520
52523
  */
52521
- async connect(transport2) {
52522
- return await this.server.connect(transport2);
52524
+ async connect(transport) {
52525
+ return await this.server.connect(transport);
52523
52526
  }
52524
52527
  /**
52525
52528
  * Closes the connection.
@@ -54207,6 +54210,8 @@ function doStartSsoLogin(profile, opts) {
54207
54210
  sessionId,
54208
54211
  verificationUrl: urlSeen,
54209
54212
  userCode: codeSeen,
54213
+ // `|| "default"` is vestigial -- see the empty-profile note above;
54214
+ // `profile` is always non-empty here (startSsoLogin rejects "").
54210
54215
  profile: profile || "default"
54211
54216
  });
54212
54217
  }
@@ -54307,7 +54312,7 @@ async function waitForLogin(sessionId) {
54307
54312
  return {
54308
54313
  ok: false,
54309
54314
  exitCode: null,
54310
- error: `No active login session with id '${sessionId}'. Call aws_login_start first.`
54315
+ error: `No active login session with id '${sessionId}'. It may have already completed (waitForLogin is fire-once -- the session is dropped after the first call resolves) or it may never have started. If a prior aws_login_complete already returned success for this id, the login is done; run aws_whoami to confirm rather than starting over. Otherwise call aws_login_start first.`
54311
54316
  };
54312
54317
  }
54313
54318
  try {
@@ -54431,6 +54436,7 @@ var profilesTools = [
54431
54436
 
54432
54437
  // src/tools/auth.ts
54433
54438
  var MAX_SSO_CACHE_FILE_BYTES = 64 * 1024;
54439
+ var _startSsoLoginImpl = (profile) => startSsoLogin(profile);
54434
54440
  function findCachedSsoToken(cacheDir = join3(homedir3(), ".aws", "sso", "cache"), opts = {}) {
54435
54441
  try {
54436
54442
  const files = readdirSync(cacheDir).filter((f) => f.endsWith(".json"));
@@ -54462,6 +54468,14 @@ function findCachedSsoToken(cacheDir = join3(homedir3(), ".aws", "sso", "cache")
54462
54468
  }
54463
54469
  return null;
54464
54470
  }
54471
+ function projectSsoToken(cachedToken) {
54472
+ if (!cachedToken) return null;
54473
+ return {
54474
+ expiresAt: cachedToken.expiresAt,
54475
+ minutesLeft: cachedToken.minutesLeft,
54476
+ startUrl: cachedToken.startUrl
54477
+ };
54478
+ }
54465
54479
  function startUrlForProfile(profile) {
54466
54480
  try {
54467
54481
  const text = readFileSync3(join3(homedir3(), ".aws", "config"), "utf-8");
@@ -54526,11 +54540,7 @@ var authTools = [
54526
54540
  arn: identity.arn,
54527
54541
  profile: useProfile,
54528
54542
  region: useRegion,
54529
- ssoToken: cachedToken ? {
54530
- expiresAt: cachedToken.expiresAt,
54531
- minutesLeft: cachedToken.minutesLeft,
54532
- startUrl: cachedToken.startUrl
54533
- } : null
54543
+ ssoToken: projectSsoToken(cachedToken)
54534
54544
  }
54535
54545
  };
54536
54546
  }
@@ -54565,7 +54575,7 @@ var authTools = [
54565
54575
  }
54566
54576
  };
54567
54577
  }
54568
- const result = await startSsoLogin(useProfile);
54578
+ const result = await _startSsoLoginImpl(useProfile);
54569
54579
  if (!result.ok) {
54570
54580
  return {
54571
54581
  ok: false,
@@ -54630,7 +54640,7 @@ var authTools = [
54630
54640
  arn: identity.arn,
54631
54641
  profile: useProfile,
54632
54642
  region: useRegion,
54633
- ssoToken: cachedToken
54643
+ ssoToken: projectSsoToken(cachedToken)
54634
54644
  }
54635
54645
  };
54636
54646
  }
@@ -54682,7 +54692,7 @@ var authTools = [
54682
54692
  }
54683
54693
  };
54684
54694
  }
54685
- const loginResult = await startSsoLogin(useProfile);
54695
+ const loginResult = await _startSsoLoginImpl(useProfile);
54686
54696
  if (!loginResult.ok) {
54687
54697
  return { ok: false, error: loginResult.error, rawBody: loginResult.rawOutput };
54688
54698
  }
@@ -54750,7 +54760,14 @@ var callTools = [
54750
54760
  return {
54751
54761
  ok: false,
54752
54762
  error: result.error,
54753
- rawBody: result.rawStderr ?? result.rawStdout
54763
+ // Treat an empty-string rawStderr as "no stderr" so a nonzero exit
54764
+ // that wrote its diagnostic to stdout (rare but observed: some
54765
+ // `aws` operations route through stdout when stderr is closed or
54766
+ // when a wrapper script swallows stderr) still surfaces the
54767
+ // stdout body. Pinned by call.test.ts -- the failure-shape
54768
+ // contract is "diagnostic text first, stdout second" rather
54769
+ // than "stderr always wins even when empty".
54770
+ rawBody: result.rawStderr ? result.rawStderr : result.rawStdout
54754
54771
  };
54755
54772
  }
54756
54773
  return {
@@ -55321,6 +55338,12 @@ var logsTools = [
55321
55338
  }
55322
55339
  }
55323
55340
  }
55341
+ if (i.logStreamNamePrefix && !isValidLogStreamName(i.logStreamNamePrefix)) {
55342
+ return {
55343
+ ok: false,
55344
+ error: `Invalid logStreamNamePrefix '${i.logStreamNamePrefix}'. Must be 1-512 chars, not start with '-', and contain no ':', '*', or control characters.`
55345
+ };
55346
+ }
55324
55347
  const extraFlags = [i.logGroupName, "--format", "json", "--since", i.since ?? "10m"];
55325
55348
  if (i.filterPattern) extraFlags.push("--filter-pattern", i.filterPattern);
55326
55349
  if (i.logStreamNames && i.logStreamNames.length > 0) {
@@ -55358,6 +55381,87 @@ var logsTools = [
55358
55381
  }
55359
55382
  ];
55360
55383
 
55384
+ // src/tools/paginate.ts
55385
+ function extractNextToken(data) {
55386
+ if (data && typeof data === "object" && "NextToken" in data) {
55387
+ const token = data.NextToken;
55388
+ if (typeof token === "string" && token.length > 0) return token;
55389
+ }
55390
+ return null;
55391
+ }
55392
+ function wrapQueryForPagination(userQuery) {
55393
+ return `{NextToken: NextToken, items: ${userQuery}}`;
55394
+ }
55395
+ var paginateTools = [
55396
+ {
55397
+ name: "aws_paginate",
55398
+ description: "Fetch one page of a paginated AWS list/describe operation. Identical to aws_call plus `maxItems` (page size) and `startingToken` (resume cursor). Returns the parsed response, a `nextToken` (null when the list is exhausted), and `hasMore`. Call again with the returned nextToken as startingToken until hasMore is false. Use this instead of aws_call for operations that might exceed the 5 MB stdout cap: list-objects-v2, describe-instances, describe-log-streams, list-roles, etc.",
55399
+ annotations: {
55400
+ title: "Fetch one page of a paginated AWS operation",
55401
+ readOnlyHint: true,
55402
+ destructiveHint: false,
55403
+ idempotentHint: true,
55404
+ openWorldHint: true
55405
+ },
55406
+ inputSchema: external_exports3.object({
55407
+ service: external_exports3.string().describe("AWS service in kebab-case: 's3api', 'ec2', 'iam', 'logs', etc."),
55408
+ operation: external_exports3.string().describe("Paginated operation: 'list-objects-v2', 'describe-instances', 'list-roles', etc."),
55409
+ params: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Operation parameters (PascalCase keys) passed via --cli-input-json."),
55410
+ query: external_exports3.string().optional().describe(
55411
+ "JMESPath expression to extract fields from each page (--query). The query is wrapped server-side as {NextToken, items: <query>} so pagination still works even when the projection drops NextToken; the handler unwraps `items` before returning."
55412
+ ),
55413
+ maxItems: external_exports3.number().int().positive().optional().describe("Items per page. Default 100. Lower this if hitting the 5 MB output cap."),
55414
+ startingToken: external_exports3.string().optional().describe("Resume cursor from the previous call's `nextToken`. Omit for the first page."),
55415
+ profile: external_exports3.string().optional().describe("Override session profile for this call."),
55416
+ region: external_exports3.string().optional().describe("Override session region for this call."),
55417
+ timeoutMs: external_exports3.number().int().positive().optional().describe("Timeout in milliseconds. Default 60000.")
55418
+ }),
55419
+ handler: async (input) => {
55420
+ const i = input;
55421
+ const maxItems = i.maxItems ?? 100;
55422
+ const extraFlags = ["--max-items", String(maxItems)];
55423
+ if (i.startingToken) {
55424
+ extraFlags.push("--starting-token", i.startingToken);
55425
+ }
55426
+ const userQuery = i.query?.trim();
55427
+ const queryWrapped = userQuery ? wrapQueryForPagination(userQuery) : void 0;
55428
+ const result = await runAwsCall({
55429
+ service: i.service,
55430
+ operation: i.operation,
55431
+ params: i.params,
55432
+ query: queryWrapped,
55433
+ profile: i.profile,
55434
+ region: i.region,
55435
+ outputFormat: "json",
55436
+ timeoutMs: i.timeoutMs,
55437
+ extraFlags
55438
+ });
55439
+ if (!result.ok) {
55440
+ return { ok: false, error: result.error, rawBody: result.rawStderr ?? result.rawStdout };
55441
+ }
55442
+ let resultBody;
55443
+ let nextToken;
55444
+ if (queryWrapped) {
55445
+ const wrapped = result.data ?? {};
55446
+ nextToken = extractNextToken(wrapped);
55447
+ resultBody = wrapped.items ?? null;
55448
+ } else {
55449
+ nextToken = extractNextToken(result.data);
55450
+ resultBody = result.data;
55451
+ }
55452
+ return {
55453
+ ok: true,
55454
+ data: {
55455
+ command: result.command,
55456
+ result: resultBody,
55457
+ nextToken,
55458
+ hasMore: nextToken !== null
55459
+ }
55460
+ };
55461
+ }
55462
+ }
55463
+ ];
55464
+
55361
55465
  // src/tools/metrics.ts
55362
55466
  var SIMPLE_STATS = ["Average", "Sum", "Maximum", "Minimum", "SampleCount"];
55363
55467
  var EXTENDED_STAT_RE = /^(p|tm|tc|wm|pr|ts|iqm)(\d{1,3}(\.\d{1,3})?)?$/i;
@@ -55376,6 +55480,7 @@ function canonicalizeStatistic(s) {
55376
55480
  }
55377
55481
  var QUERY_ID_RE = /^[a-z][A-Za-z0-9_]*$/;
55378
55482
  var MAX_QUERIES = 100;
55483
+ var CLOUDWATCH_MAX_DATAPOINTS = 100800;
55379
55484
  var PERIOD_3H_MS = 3 * 60 * 60 * 1e3;
55380
55485
  var PERIOD_24H_MS = 24 * 60 * 60 * 1e3;
55381
55486
  var PERIOD_15D_MS = 15 * 24 * 60 * 60 * 1e3;
@@ -55471,7 +55576,7 @@ var metricsTools = [
55471
55576
  endTime: external_exports3.string().optional().describe("ISO 8601 timestamp or relative shorthand. Default 'now'."),
55472
55577
  scanBy: external_exports3.enum(["TimestampAscending", "TimestampDescending"]).optional().describe("Sort order for returned datapoints. Default 'TimestampDescending' (matches CloudWatch's default)."),
55473
55578
  maxDataPoints: external_exports3.number().int().positive().optional().describe(
55474
- "Soft cap on returned datapoints across all queries. CloudWatch's hard cap is ~100,800; lower this to keep response sizes manageable. Forwarded as CloudWatch's MaxDatapoints (single 'p') field; the camelCase schema name follows this server's convention."
55579
+ "Target datapoint count. CloudWatch does not truncate to the first N points -- it widens (coarsens) the period server-side so the series aggregates down to fit this many points. CloudWatch's own ceiling is ~100,800; lower this to make CloudWatch return a coarser, smaller series. Forwarded as CloudWatch's MaxDatapoints (single 'p') field; the camelCase schema name follows this server's convention."
55475
55580
  ),
55476
55581
  nextToken: external_exports3.string().optional().describe(
55477
55582
  "Resume cursor from a previous call's `nextToken`. Omit for the first page. Forwarded as CloudWatch's NextToken; only meaningful when a prior call returned `hasMore: true`."
@@ -55543,6 +55648,23 @@ var metricsTools = [
55543
55648
  error: `endTime (${endDate.toISOString()}) must be after startTime (${startDate.toISOString()}).`
55544
55649
  };
55545
55650
  }
55651
+ const rangeSeconds = (endDate.getTime() - startDate.getTime()) / 1e3;
55652
+ for (const q of i.queries) {
55653
+ if (q.period === void 0) continue;
55654
+ if (q.period <= 0 || q.period % 60 !== 0) {
55655
+ return {
55656
+ ok: false,
55657
+ error: `Query '${q.id}' has invalid period ${q.period}. CloudWatch requires period to be a positive multiple of 60 (seconds).`
55658
+ };
55659
+ }
55660
+ const datapoints = Math.ceil(rangeSeconds / q.period);
55661
+ if (datapoints > CLOUDWATCH_MAX_DATAPOINTS) {
55662
+ return {
55663
+ ok: false,
55664
+ error: `Query '${q.id}' with period ${q.period}s over the requested range (${startDate.toISOString()} to ${endDate.toISOString()}) would request ${datapoints} datapoints, exceeding CloudWatch's per-request cap of ${CLOUDWATCH_MAX_DATAPOINTS}. Widen the period or narrow the time range.`
55665
+ };
55666
+ }
55667
+ }
55546
55668
  const periodSeconds = pickAutoPeriodSeconds(startDate.getTime(), endDate.getTime());
55547
55669
  const metricDataQueries = buildMetricDataQueries(i.queries, periodSeconds);
55548
55670
  const params = {
@@ -55579,7 +55701,7 @@ var metricsTools = [
55579
55701
  code: m.Code,
55580
55702
  value: m.Value
55581
55703
  }));
55582
- const nextToken = typeof raw.NextToken === "string" && raw.NextToken.length > 0 ? raw.NextToken : null;
55704
+ const nextToken = extractNextToken(raw);
55583
55705
  return {
55584
55706
  ok: true,
55585
55707
  data: {
@@ -55634,7 +55756,7 @@ var multiRegionTools = [
55634
55756
  service: external_exports3.string().describe("AWS service in kebab-case: 's3api', 'ec2', 'iam', etc."),
55635
55757
  operation: external_exports3.string().describe("Operation in kebab-case: 'describe-instances', 'list-buckets', etc."),
55636
55758
  regions: external_exports3.array(external_exports3.string().min(1)).min(1).max(MAX_REGIONS).describe(
55637
- `Region IDs (e.g. ['us-east-1','us-west-2','eu-west-1']). 1-${MAX_REGIONS}. Validated for argv-safety; bad region names fail per-region rather than poisoning the batch.`
55759
+ `Region IDs (e.g. ['us-east-1','us-west-2','eu-west-1']). 1-${MAX_REGIONS}. Validated for argv-safety; a bad region name yields a clear per-region error and skips its CLI spawn (per-region isolation comes from each region being a separate call, not from this pre-check).`
55638
55760
  ),
55639
55761
  params: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Operation parameters (PascalCase keys) -- same shape as aws_call."),
55640
55762
  query: external_exports3.string().optional().describe("JMESPath expression for --query (server-side trimming per region)."),
@@ -55701,87 +55823,6 @@ var multiRegionTools = [
55701
55823
  }
55702
55824
  ];
55703
55825
 
55704
- // src/tools/paginate.ts
55705
- function extractNextToken(data) {
55706
- if (data && typeof data === "object" && "NextToken" in data) {
55707
- const token = data.NextToken;
55708
- if (typeof token === "string" && token.length > 0) return token;
55709
- }
55710
- return null;
55711
- }
55712
- function wrapQueryForPagination(userQuery) {
55713
- return `{NextToken: NextToken, items: ${userQuery}}`;
55714
- }
55715
- var paginateTools = [
55716
- {
55717
- name: "aws_paginate",
55718
- description: "Fetch one page of a paginated AWS list/describe operation. Identical to aws_call plus `maxItems` (page size) and `startingToken` (resume cursor). Returns the parsed response, a `nextToken` (null when the list is exhausted), and `hasMore`. Call again with the returned nextToken as startingToken until hasMore is false. Use this instead of aws_call for operations that might exceed the 5 MB stdout cap: list-objects-v2, describe-instances, describe-log-streams, list-roles, etc.",
55719
- annotations: {
55720
- title: "Fetch one page of a paginated AWS operation",
55721
- readOnlyHint: true,
55722
- destructiveHint: false,
55723
- idempotentHint: true,
55724
- openWorldHint: true
55725
- },
55726
- inputSchema: external_exports3.object({
55727
- service: external_exports3.string().describe("AWS service in kebab-case: 's3api', 'ec2', 'iam', 'logs', etc."),
55728
- operation: external_exports3.string().describe("Paginated operation: 'list-objects-v2', 'describe-instances', 'list-roles', etc."),
55729
- params: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Operation parameters (PascalCase keys) passed via --cli-input-json."),
55730
- query: external_exports3.string().optional().describe(
55731
- "JMESPath expression to extract fields from each page (--query). The query is wrapped server-side as {NextToken, items: <query>} so pagination still works even when the projection drops NextToken; the handler unwraps `items` before returning."
55732
- ),
55733
- maxItems: external_exports3.number().int().positive().optional().describe("Items per page. Default 100. Lower this if hitting the 5 MB output cap."),
55734
- startingToken: external_exports3.string().optional().describe("Resume cursor from the previous call's `nextToken`. Omit for the first page."),
55735
- profile: external_exports3.string().optional().describe("Override session profile for this call."),
55736
- region: external_exports3.string().optional().describe("Override session region for this call."),
55737
- timeoutMs: external_exports3.number().int().positive().optional().describe("Timeout in milliseconds. Default 60000.")
55738
- }),
55739
- handler: async (input) => {
55740
- const i = input;
55741
- const maxItems = i.maxItems ?? 100;
55742
- const extraFlags = ["--max-items", String(maxItems)];
55743
- if (i.startingToken) {
55744
- extraFlags.push("--starting-token", i.startingToken);
55745
- }
55746
- const userQuery = i.query?.trim();
55747
- const queryWrapped = userQuery ? wrapQueryForPagination(userQuery) : void 0;
55748
- const result = await runAwsCall({
55749
- service: i.service,
55750
- operation: i.operation,
55751
- params: i.params,
55752
- query: queryWrapped,
55753
- profile: i.profile,
55754
- region: i.region,
55755
- outputFormat: "json",
55756
- timeoutMs: i.timeoutMs,
55757
- extraFlags
55758
- });
55759
- if (!result.ok) {
55760
- return { ok: false, error: result.error, rawBody: result.rawStderr ?? result.rawStdout };
55761
- }
55762
- let resultBody;
55763
- let nextToken;
55764
- if (queryWrapped) {
55765
- const wrapped = result.data ?? {};
55766
- nextToken = typeof wrapped.NextToken === "string" && wrapped.NextToken.length > 0 ? wrapped.NextToken : null;
55767
- resultBody = wrapped.items ?? null;
55768
- } else {
55769
- nextToken = extractNextToken(result.data);
55770
- resultBody = result.data;
55771
- }
55772
- return {
55773
- ok: true,
55774
- data: {
55775
- command: result.command,
55776
- result: resultBody,
55777
- nextToken,
55778
- hasMore: nextToken !== null
55779
- }
55780
- };
55781
- }
55782
- }
55783
- ];
55784
-
55785
55826
  // src/tools/resource.ts
55786
55827
  var TYPE_NAME_RE = /^[A-Z][A-Za-z0-9]*::[A-Z][A-Za-z0-9]*::[A-Z][A-Za-z0-9]*$/;
55787
55828
  function isValidIdentifier(id) {
@@ -56065,7 +56106,7 @@ var resourceTools = [
56065
56106
  const p = parseResourceProperties(d);
56066
56107
  return { identifier: p.Identifier, properties: p.Properties };
56067
56108
  });
56068
- const nextToken = typeof raw?.NextToken === "string" && raw.NextToken.length > 0 ? raw.NextToken : null;
56109
+ const nextToken = extractNextToken(raw);
56069
56110
  return {
56070
56111
  ok: true,
56071
56112
  data: {
@@ -56755,10 +56796,10 @@ var scriptTools = [
56755
56796
  },
56756
56797
  inputSchema: external_exports3.object({
56757
56798
  code: external_exports3.string().min(1).describe(
56758
- "JavaScript snippet evaluated inside `(async () => { ... })()`. Use `return <value>` to surface a result. Bound globals: aws.call, aws.paginate, aws.paginateAll, aws.resource.{get,list,create,update,delete,status}, aws.logsTail, aws.metricsQuery, aws.iamSimulate, aws.multiRegion, aws.assumeRole, aws.docs.{search,read}, console (capture), JSON, Math, Date, Promise, Array, Object, String, Number, Boolean, Error, Intl, Atomics, SharedArrayBuffer, WebAssembly (compile blocked). Intentionally NOT bound (call as sibling MCP tools instead): aws_list_profiles, the auth/session tools, and aws_script itself. Shadowed (undefined): require, import, process, fs, fetch + family, BroadcastChannel, setTimeout/Interval, queueMicrotask, Buffer, global, globalThis. NOT available (ReferenceError if used): URL, URLSearchParams, TextEncoder, TextDecoder, crypto, structuredClone, EventTarget, MessageChannel, performance. eval/Function are disabled (codeGeneration off). Tool helpers throw on failure -- wrap in try/catch when you want to handle errors per-call."
56799
+ "JavaScript snippet evaluated inside `(async () => { ... })()`. Use `return <value>` to surface a result. Bound globals: aws.call, aws.paginate, aws.paginateAll, aws.resource.{get,list,create,update,delete,status}, aws.logsTail, aws.metricsQuery, aws.iamSimulate, aws.multiRegion, aws.assumeRole, aws.docs.{search,read}, console (capture), JSON, Math, Date, Promise, Array, Object, String, Number, Boolean, Error, Intl, Atomics, SharedArrayBuffer, WebAssembly (compile blocked). Intentionally NOT bound (call as sibling MCP tools instead): aws_list_profiles, the auth/session tools, and aws_script itself. Shadowed (undefined): require, process, fetch + family, BroadcastChannel, setTimeout/Interval, queueMicrotask, Buffer, global, globalThis. NOT available (ReferenceError if used): URL, URLSearchParams, TextEncoder, TextDecoder, crypto, structuredClone, EventTarget, MessageChannel, performance, fs, import. eval/Function are disabled (codeGeneration off). Tool helpers throw on failure -- wrap in try/catch when you want to handle errors per-call."
56759
56800
  ),
56760
56801
  timeoutMs: external_exports3.number().int().positive().max(MAX_TIMEOUT_MS).optional().describe(
56761
- `Wall-clock timeout in milliseconds. Default ${DEFAULT_TIMEOUT_MS2}; max ${MAX_TIMEOUT_MS}. Covers evaluation plus every awaited aws.* call. On timeout the script stops being awaited and the tool returns an error, but any aws.* call already in flight is NOT cancelled -- it continues until its own per-call timeout (default 60s). Plan retries accordingly: a script that timed out mid 'resource.delete' may have completed the delete; re-issuing the same script can double-mutate.`
56802
+ `Wall-clock timeout in milliseconds. Default ${DEFAULT_TIMEOUT_MS2}; max ${MAX_TIMEOUT_MS}. Best-effort across evaluation plus awaited aws.* calls -- it fires on synchronous spin before the first await and on async wall-clock once the script has yielded, but a synchronous infinite loop BETWEEN awaits can outrun the timer and is not guaranteed to be interrupted. On timeout the script stops being awaited and the tool returns an error, but any aws.* call already in flight is NOT cancelled -- it continues until its own per-call timeout (default 60s). Plan retries accordingly: a script that timed out mid 'resource.delete' may have completed the delete; re-issuing the same script can double-mutate.`
56762
56803
  )
56763
56804
  }),
56764
56805
  handler: async (input) => {
@@ -56807,12 +56848,32 @@ var sessionTools = [
56807
56848
  error: "Nothing to set \u2014 pass at least one of 'profile' or 'region'. Use aws_session_get to read current values."
56808
56849
  };
56809
56850
  }
56810
- try {
56811
- if (profile !== void 0) setProfile(profile);
56812
- if (region !== void 0) setRegion(region);
56813
- } catch (err) {
56814
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
56851
+ if (profile !== void 0) {
56852
+ const trimmed = profile.trim();
56853
+ if (!trimmed) {
56854
+ return { ok: false, error: "Profile name cannot be empty" };
56855
+ }
56856
+ if (!isValidProfileName(trimmed)) {
56857
+ return {
56858
+ ok: false,
56859
+ error: `Invalid profile name '${trimmed}'. Must be 1-128 chars from [A-Za-z0-9_+=,.@:-], must not start with '-' or '=', no whitespace or shell metacharacters.`
56860
+ };
56861
+ }
56862
+ }
56863
+ if (region !== void 0) {
56864
+ const trimmed = region.trim();
56865
+ if (!trimmed) {
56866
+ return { ok: false, error: "Region cannot be empty" };
56867
+ }
56868
+ if (!isValidRegionName(trimmed)) {
56869
+ return {
56870
+ ok: false,
56871
+ error: `Invalid region '${trimmed}'. Must match /^[a-z][a-z0-9-]{2,30}$/ (e.g. 'us-east-1', 'eu-west-3').`
56872
+ };
56873
+ }
56815
56874
  }
56875
+ if (profile !== void 0) setProfile(profile);
56876
+ if (region !== void 0) setRegion(region);
56816
56877
  return { ok: true, data: getSessionState() };
56817
56878
  }
56818
56879
  },
@@ -56856,9 +56917,40 @@ var sessionTools = [
56856
56917
  ];
56857
56918
 
56858
56919
  // src/index.ts
56859
- var version2 = true ? "1.3.3" : (await null).createRequire(import.meta.url)("../package.json").version;
56920
+ function toMcpResult(response) {
56921
+ if (!response.ok) {
56922
+ const baseError = `Error: ${response.error || "Unknown error"}`;
56923
+ const errorText = response.rawBody ? `${baseError}
56924
+
56925
+ ${response.rawBody}` : baseError;
56926
+ return {
56927
+ content: [{ type: "text", text: errorText }],
56928
+ isError: true
56929
+ };
56930
+ }
56931
+ const text = response.rawBody ?? JSON.stringify(response.data ?? { success: true }, null, 2);
56932
+ return {
56933
+ content: [{ type: "text", text }]
56934
+ };
56935
+ }
56936
+ function errorToMcpResult(err, toolName) {
56937
+ const message = err instanceof Error ? err.message : String(err);
56938
+ const stack = err instanceof Error ? err.stack : void 0;
56939
+ console.error(`[aws-mcp] handler '${toolName}' threw: ${message}`);
56940
+ if (stack) console.error(stack);
56941
+ return {
56942
+ content: [{ type: "text", text: `Error: ${message}` }],
56943
+ isError: true
56944
+ };
56945
+ }
56946
+ var version2 = true ? "1.4.0" : (await null).createRequire(import.meta.url)("../package.json").version;
56947
+ var isEntryPoint = (() => {
56948
+ const entry = process.argv[1];
56949
+ if (!entry) return false;
56950
+ return import.meta.url === pathToFileURL(entry).href;
56951
+ })();
56860
56952
  var subcommand = process.argv[2];
56861
- if (subcommand === "version" || subcommand === "--version") {
56953
+ if (isEntryPoint && (subcommand === "version" || subcommand === "--version")) {
56862
56954
  console.log(version2);
56863
56955
  process.exit(0);
56864
56956
  }
@@ -56877,49 +56969,29 @@ var allTools = [
56877
56969
  ...docsTools,
56878
56970
  ...scriptTools
56879
56971
  ];
56880
- var server = new McpServer({
56881
- name: "@yawlabs/aws-mcp",
56882
- version: version2
56883
- });
56884
- for (const tool of allTools) {
56885
- server.tool(
56886
- tool.name,
56887
- tool.description,
56888
- tool.inputSchema.shape,
56889
- tool.annotations,
56890
- async (input) => {
56972
+ if (isEntryPoint) {
56973
+ const server = new McpServer({
56974
+ name: "@yawlabs/aws-mcp",
56975
+ version: version2
56976
+ });
56977
+ for (const tool of allTools) {
56978
+ server.tool(tool.name, tool.description, tool.inputSchema.shape, tool.annotations, async (input) => {
56891
56979
  try {
56892
- const response = await tool.handler(input);
56893
- if (!response.ok) {
56894
- const baseError = `Error: ${response.error || "Unknown error"}`;
56895
- const errorText = response.rawBody ? `${baseError}
56896
-
56897
- ${response.rawBody}` : baseError;
56898
- return {
56899
- content: [{ type: "text", text: errorText }],
56900
- isError: true
56901
- };
56902
- }
56903
- const text = response.rawBody ?? JSON.stringify(response.data ?? { success: true }, null, 2);
56904
- return {
56905
- content: [{ type: "text", text }]
56906
- };
56980
+ return toMcpResult(await tool.handler(input));
56907
56981
  } catch (err) {
56908
- const message = err instanceof Error ? err.message : String(err);
56909
- const stack = err instanceof Error ? err.stack : void 0;
56910
- console.error(`[aws-mcp] handler '${tool.name}' threw: ${message}`);
56911
- if (stack) console.error(stack);
56912
- return {
56913
- content: [{ type: "text", text: `Error: ${message}` }],
56914
- isError: true
56915
- };
56982
+ return errorToMcpResult(err, tool.name);
56916
56983
  }
56917
- }
56918
- );
56984
+ });
56985
+ }
56986
+ const transport = new StdioServerTransport();
56987
+ await server.connect(transport);
56988
+ console.error(`@yawlabs/aws-mcp v${version2} ready (${allTools.length} tools)`);
56919
56989
  }
56920
- var transport = new StdioServerTransport();
56921
- await server.connect(transport);
56922
- console.error(`@yawlabs/aws-mcp v${version2} ready (${allTools.length} tools)`);
56990
+ export {
56991
+ allTools,
56992
+ errorToMcpResult,
56993
+ toMcpResult
56994
+ };
56923
56995
  /*! Bundled license information:
56924
56996
 
56925
56997
  he/he.js:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/aws-mcp",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "mcpName": "io.github.YawLabs/aws-mcp",
5
5
  "description": "AWS MCP server — call any AWS API from AI assistants, with first-class SSO re-login (no more 'browser won't open' dead ends)",
6
6
  "license": "MIT",