@yawlabs/aws-mcp 1.0.1 → 1.1.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/README.md +6 -4
- package/dist/index.js +221 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ The rest -- SSO device-code re-login, CCAPI CRUD with dry-run diffs, multi-regio
|
|
|
84
84
|
| `aws_call` | Run any AWS API operation. `service: 's3api', operation: 'list-buckets'`, optional `params` (PascalCase JSON), optional `query` (JMESPath). Returns parsed JSON. |
|
|
85
85
|
| `aws_paginate` | Fetch one page of a paginated list/describe operation. Supports `query` too. Returns `nextToken`/`hasMore`; call again with the token to continue. |
|
|
86
86
|
| `aws_logs_tail` | Fetch recent CloudWatch Logs events for a log group. Wraps `aws logs tail --format json` with `since`, `filterPattern`, and stream-name filters; returns events as a parsed array. |
|
|
87
|
+
| `aws_metrics_query` | Query CloudWatch metrics via GetMetricData (the modern multi-metric / expression-capable API). Pass `queries: [{id, namespace, metricName, dimensions?, statistic?, period?}]` or expression-based queries; `startTime`/`endTime` accept ISO 8601 or relative shorthand (`'15m'`, `'1h'`, `'1d'`). Period auto-picks from the time range. Returns `{series, periodSeconds, messages?}`. |
|
|
87
88
|
| `aws_resource_get` | Read an AWS resource via Cloud Control API by `typeName` + `identifier` (e.g. `AWS::Lambda::Function` + function name). Returns parsed Properties. |
|
|
88
89
|
| `aws_resource_list` | List resources of a type via CCAPI, paginated. Returns `{identifier, properties}` per entry plus a `nextToken`/`hasMore`. |
|
|
89
90
|
| `aws_resource_create` | Create an AWS resource via CCAPI. Async — returns top-level `requestToken` + `operationStatus`. Pass `awaitCompletion: true` to have the server poll to terminal state in one call. |
|
|
@@ -247,16 +248,16 @@ From 1.0 onward this package follows [Semantic Versioning](https://semver.org/sp
|
|
|
247
248
|
|
|
248
249
|
**Stable in 1.x (anything below is a breaking change requiring a major bump):**
|
|
249
250
|
|
|
250
|
-
- **Tool names** -- the
|
|
251
|
+
- **Tool names** -- the 25 tool names listed in the Tools table above will not be renamed or removed.
|
|
251
252
|
- **Tool annotations** -- `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`. These signal to MCP hosts how to gate calls; flipping them silently would break host UIs.
|
|
252
253
|
- **Required input fields** -- the required fields per tool will not change shape or be removed. New *optional* fields may be added.
|
|
253
254
|
- **Success envelope shape per tool** -- the `data` object on `{ok: true, data}` responses, specifically:
|
|
254
255
|
- `aws_call` -> `{command, result}`
|
|
255
256
|
- `aws_paginate` -> `{command, result, nextToken, hasMore}`
|
|
256
257
|
- `aws_multi_region` -> `{service, operation, regionCount, okCount, errorCount, results: [{region, ok, data?, command?, error?, errorKind?}]}`
|
|
257
|
-
- `aws_whoami` -> `{account, userId, arn, profile, region, ssoToken: {expiresAt, minutesLeft, startUrl} | null}`
|
|
258
|
+
- `aws_whoami` -> `{account, userId, arn, profile, region, ssoToken: {expiresAt, minutesLeft, startUrl?} | null}` (`startUrl` is omitted when the cached token didn't record one)
|
|
258
259
|
- `aws_login_start` -> `{sessionId, profile, verificationUrl, userCode, instructions, reused?}` (`reused: true` when re-surfacing an in-flight login for the same profile)
|
|
259
|
-
- `aws_login_complete` -> `{loggedIn, account, userId, arn, profile, region, ssoToken}` (same `ssoToken` shape as `aws_whoami`)
|
|
260
|
+
- `aws_login_complete` -> `{loggedIn, account, userId, arn, profile, region, ssoToken}` (same `ssoToken` shape as `aws_whoami`, including the optional `startUrl`)
|
|
260
261
|
- `aws_refresh_if_expiring_soon` -> **one of two shapes by branch:** `{status: "ok", minutesLeft, expiresAt, profile}` when the cached token has more than `thresholdMinutes` left, or `{status: "refreshing", reason, sessionId, profile, verificationUrl, userCode, reused?, instructions}` when a refresh is in flight. Discriminate on `status`.
|
|
261
262
|
- `aws_assume_role` -> `{profile, credentialsPath, expiration, assumedRoleArn, assumedRoleId, sourceProfile, hint}`
|
|
262
263
|
- `aws_list_profiles` -> `{configPath, profiles: [{name, region?, ssoStartUrl?, ssoRegion?, ssoSession?, isSso}]}`
|
|
@@ -266,9 +267,10 @@ From 1.0 onward this package follows [Semantic Versioning](https://semver.org/sp
|
|
|
266
267
|
- `aws_resource_create` / `_update` / `_delete` / `_status` -> flat-promoted `{command, requestToken, operationStatus, identifier, errorCode, statusMessage, retryAfter, progressEvent}` plus an `awaited: {attempts, elapsedMs}` block when `awaitCompletion: true` was passed
|
|
267
268
|
- `aws_resource_diff` -> `{command, typeName, identifier, before, after, changes, changeCount}`
|
|
268
269
|
- `aws_logs_tail` -> `{command, logGroupName, since, eventCount, events}`
|
|
270
|
+
- `aws_metrics_query` -> `{command, startTime, endTime, periodSeconds, series: [{id, label?, timestamps, values, statusCode?}], messages?: [{code?, value?}]}` (`messages` is omitted when empty; per-series `label` / `statusCode` are present when CloudWatch returns them)
|
|
269
271
|
- `aws_iam_simulate` -> `{command, principalArn, summary: {allowed, denied, total}, results, evaluationResults}`
|
|
270
272
|
- `aws_script` -> `{result, logs, truncatedLogs, durationMs}` where `result` is whatever the script `return`ed (any JSON-serializable value, including `undefined`)
|
|
271
|
-
- `aws_docs_search` -> `{query, count, results: [{title, url, summary
|
|
273
|
+
- `aws_docs_search` -> `{query, count, results: [{title, url, summary?, excerpt?}]}` (`summary` / `excerpt` are present only when the upstream search backend returns them)
|
|
272
274
|
- `aws_docs_read` -> `{url, cached, content, startIndex, endIndex, totalLength, hasMore, nextStartIndex}`
|
|
273
275
|
- **Error envelope** -- `{ok: false, error: string, rawBody?: string}`. The `error` string is human-readable; its *wording* is best-effort (see below).
|
|
274
276
|
- **`errorKind` enum on `aws_multi_region`** -- `"sso_expired" | "no_creds" | "bad_input" | "spawn_failure" | "timeout" | "output_too_large" | "nonzero_exit"`. New variants may be added (additive); existing ones won't be renamed or repurposed.
|
package/dist/index.js
CHANGED
|
@@ -55448,6 +55448,225 @@ var logsTools = [
|
|
|
55448
55448
|
}
|
|
55449
55449
|
];
|
|
55450
55450
|
|
|
55451
|
+
// src/tools/metrics.ts
|
|
55452
|
+
var SIMPLE_STATS = ["Average", "Sum", "Maximum", "Minimum", "SampleCount"];
|
|
55453
|
+
var EXTENDED_STAT_RE = /^(p|tm|tc|wm|pr|ts|iqm)(\d{1,3}(\.\d{1,3})?)?$/i;
|
|
55454
|
+
function isValidStatistic(s) {
|
|
55455
|
+
if (SIMPLE_STATS.includes(s)) return true;
|
|
55456
|
+
return EXTENDED_STAT_RE.test(s);
|
|
55457
|
+
}
|
|
55458
|
+
var QUERY_ID_RE = /^[a-z][A-Za-z0-9_]*$/;
|
|
55459
|
+
var MAX_QUERIES = 100;
|
|
55460
|
+
var PERIOD_3H_MS = 3 * 60 * 60 * 1e3;
|
|
55461
|
+
var PERIOD_24H_MS = 24 * 60 * 60 * 1e3;
|
|
55462
|
+
var PERIOD_15D_MS = 15 * 24 * 60 * 60 * 1e3;
|
|
55463
|
+
function pickAutoPeriodSeconds(startMs, endMs) {
|
|
55464
|
+
const rangeMs = Math.max(0, endMs - startMs);
|
|
55465
|
+
if (rangeMs <= PERIOD_3H_MS) return 60;
|
|
55466
|
+
if (rangeMs <= PERIOD_24H_MS) return 300;
|
|
55467
|
+
if (rangeMs <= PERIOD_15D_MS) return 900;
|
|
55468
|
+
return 3600;
|
|
55469
|
+
}
|
|
55470
|
+
var RELATIVE_TIME_RE = /^\d+[smhdw]$/;
|
|
55471
|
+
var UNIT_MS = {
|
|
55472
|
+
s: 1e3,
|
|
55473
|
+
m: 60 * 1e3,
|
|
55474
|
+
h: 60 * 60 * 1e3,
|
|
55475
|
+
d: 24 * 60 * 60 * 1e3,
|
|
55476
|
+
w: 7 * 24 * 60 * 60 * 1e3
|
|
55477
|
+
};
|
|
55478
|
+
function resolveTime(input, now) {
|
|
55479
|
+
if (input === "now") return new Date(now);
|
|
55480
|
+
const rel = input.match(RELATIVE_TIME_RE);
|
|
55481
|
+
if (rel) {
|
|
55482
|
+
const num = Number(input.slice(0, -1));
|
|
55483
|
+
const unit = input.slice(-1);
|
|
55484
|
+
const ms = UNIT_MS[unit];
|
|
55485
|
+
if (!ms || !Number.isFinite(num)) return null;
|
|
55486
|
+
return new Date(now - num * ms);
|
|
55487
|
+
}
|
|
55488
|
+
const t = new Date(input);
|
|
55489
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
55490
|
+
return t;
|
|
55491
|
+
}
|
|
55492
|
+
function buildMetricDataQueries(inputs, autoPeriod) {
|
|
55493
|
+
return inputs.map((q) => {
|
|
55494
|
+
const base = { Id: q.id };
|
|
55495
|
+
if (q.label !== void 0) base.Label = q.label;
|
|
55496
|
+
if (q.returnData !== void 0) base.ReturnData = q.returnData;
|
|
55497
|
+
if (q.expression !== void 0) {
|
|
55498
|
+
base.Expression = q.expression;
|
|
55499
|
+
if (q.period !== void 0) base.Period = q.period;
|
|
55500
|
+
return base;
|
|
55501
|
+
}
|
|
55502
|
+
const dimensions = q.dimensions ? Object.entries(q.dimensions).map(([Name, Value]) => ({ Name, Value })) : void 0;
|
|
55503
|
+
const stat = {
|
|
55504
|
+
Metric: {
|
|
55505
|
+
Namespace: q.namespace,
|
|
55506
|
+
MetricName: q.metricName,
|
|
55507
|
+
...dimensions ? { Dimensions: dimensions } : {}
|
|
55508
|
+
},
|
|
55509
|
+
Period: q.period ?? autoPeriod,
|
|
55510
|
+
Stat: q.statistic ?? "Average"
|
|
55511
|
+
};
|
|
55512
|
+
if (q.unit !== void 0) stat.Unit = q.unit;
|
|
55513
|
+
base.MetricStat = stat;
|
|
55514
|
+
return base;
|
|
55515
|
+
});
|
|
55516
|
+
}
|
|
55517
|
+
var metricsTools = [
|
|
55518
|
+
{
|
|
55519
|
+
name: "aws_metrics_query",
|
|
55520
|
+
description: "Query CloudWatch metrics via GetMetricData (the modern multi-metric / expression-capable API, not the legacy get-metric-statistics). Pass `queries` as a flat array of {id, namespace, metricName, dimensions?, statistic?, period?, expression?, label?}; the tool shapes them into MetricDataQueries for you. `startTime`/`endTime` accept ISO 8601 or relative shorthand ('15m', '1h', '1d', '1w'); endTime defaults to 'now'. Period is auto-picked from the time range when omitted (60s for <=3h, 300s for <=24h, 900s for <=15d, 3600s otherwise) to stay under CloudWatch's ~100,800-datapoint response cap. Returns {series: [{id, label?, timestamps, values, statusCode?}], messages?, periodSeconds}. Use for 'show me the CPU on this instance for the last hour', 'sum lambda invocations across these 3 functions', or expression-based 'p99 latency divided by average latency' lookups.",
|
|
55521
|
+
annotations: {
|
|
55522
|
+
title: "Query CloudWatch metrics (GetMetricData)",
|
|
55523
|
+
readOnlyHint: true,
|
|
55524
|
+
destructiveHint: false,
|
|
55525
|
+
idempotentHint: true,
|
|
55526
|
+
openWorldHint: true
|
|
55527
|
+
},
|
|
55528
|
+
inputSchema: external_exports3.object({
|
|
55529
|
+
queries: external_exports3.array(
|
|
55530
|
+
external_exports3.object({
|
|
55531
|
+
id: external_exports3.string().regex(QUERY_ID_RE, "id must match /^[a-z][A-Za-z0-9_]*$/ (CloudWatch's MetricDataQuery.Id contract)"),
|
|
55532
|
+
namespace: external_exports3.string().min(1).optional().describe("AWS metric namespace, e.g. 'AWS/Lambda', 'AWS/EC2'. Required unless `expression` is set."),
|
|
55533
|
+
metricName: external_exports3.string().min(1).optional().describe("Metric name, e.g. 'Invocations', 'CPUUtilization'. Required unless `expression` is set."),
|
|
55534
|
+
dimensions: external_exports3.record(external_exports3.string(), external_exports3.string()).optional().describe("Dimension Name -> Value map, e.g. {FunctionName: 'my-fn'}."),
|
|
55535
|
+
statistic: external_exports3.string().optional().describe(
|
|
55536
|
+
"Statistic: Average | Sum | Maximum | Minimum | SampleCount, or an extended stat like 'p99', 'p99.9', 'tm95'. Default 'Average'."
|
|
55537
|
+
),
|
|
55538
|
+
period: external_exports3.number().int().positive().optional().describe("Period in seconds. Defaults to an auto-pick from the time range (60s/300s/900s/3600s)."),
|
|
55539
|
+
expression: external_exports3.string().min(1).optional().describe(
|
|
55540
|
+
`CloudWatch metric math expression, e.g. 'SUM([m1, m2])' or 'AVG(METRICS("AWS/Lambda"))'. Mutually exclusive with namespace/metricName/dimensions.`
|
|
55541
|
+
),
|
|
55542
|
+
label: external_exports3.string().optional().describe("Human-readable label for the series in the response."),
|
|
55543
|
+
returnData: external_exports3.boolean().optional().describe(
|
|
55544
|
+
"Set false to compute this query but not return its data (useful for intermediate values in expressions). Default true."
|
|
55545
|
+
),
|
|
55546
|
+
unit: external_exports3.string().optional().describe(
|
|
55547
|
+
"Restrict to a specific Unit (e.g. 'Seconds', 'Bytes'). Default: no filter. Only meaningful on metric-stat queries."
|
|
55548
|
+
)
|
|
55549
|
+
})
|
|
55550
|
+
).min(1).max(MAX_QUERIES).describe(`1-${MAX_QUERIES} queries. Each is either a metric-stat (namespace + metricName) or an expression.`),
|
|
55551
|
+
startTime: external_exports3.string().optional().describe("ISO 8601 timestamp or relative shorthand ('15m', '1h', '1d', '1w'). Default '1h' (one hour ago)."),
|
|
55552
|
+
endTime: external_exports3.string().optional().describe("ISO 8601 timestamp or relative shorthand. Default 'now'."),
|
|
55553
|
+
scanBy: external_exports3.enum(["TimestampAscending", "TimestampDescending"]).optional().describe("Sort order for returned datapoints. Default 'TimestampDescending' (matches CloudWatch's default)."),
|
|
55554
|
+
maxDataPoints: external_exports3.number().int().positive().optional().describe(
|
|
55555
|
+
"Soft cap on returned datapoints across all queries. CloudWatch's hard cap is ~100,800; lower this to keep response sizes manageable."
|
|
55556
|
+
),
|
|
55557
|
+
profile: external_exports3.string().optional().describe("Override session profile for this call."),
|
|
55558
|
+
region: external_exports3.string().optional().describe("Override session region for this call."),
|
|
55559
|
+
timeoutMs: external_exports3.number().int().positive().optional().describe("Timeout in milliseconds. Default 60000 (60s).")
|
|
55560
|
+
}),
|
|
55561
|
+
handler: async (input) => {
|
|
55562
|
+
const i = input;
|
|
55563
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
55564
|
+
for (const q of i.queries) {
|
|
55565
|
+
if (seenIds.has(q.id)) {
|
|
55566
|
+
return {
|
|
55567
|
+
ok: false,
|
|
55568
|
+
error: `Duplicate query id '${q.id}'. Each MetricDataQuery.Id must be unique in a batch.`
|
|
55569
|
+
};
|
|
55570
|
+
}
|
|
55571
|
+
seenIds.add(q.id);
|
|
55572
|
+
const hasMetricStat = q.namespace !== void 0 || q.metricName !== void 0 || q.dimensions !== void 0;
|
|
55573
|
+
const hasExpression = q.expression !== void 0;
|
|
55574
|
+
if (hasMetricStat && hasExpression) {
|
|
55575
|
+
return {
|
|
55576
|
+
ok: false,
|
|
55577
|
+
error: `Query '${q.id}' mixes metric-stat fields (namespace/metricName/dimensions) with 'expression'. Pick one shape per query.`
|
|
55578
|
+
};
|
|
55579
|
+
}
|
|
55580
|
+
if (!hasMetricStat && !hasExpression) {
|
|
55581
|
+
return {
|
|
55582
|
+
ok: false,
|
|
55583
|
+
error: `Query '${q.id}' has neither metric-stat (namespace+metricName) nor 'expression'. One is required.`
|
|
55584
|
+
};
|
|
55585
|
+
}
|
|
55586
|
+
if (hasMetricStat && (q.namespace === void 0 || q.metricName === void 0)) {
|
|
55587
|
+
return {
|
|
55588
|
+
ok: false,
|
|
55589
|
+
error: `Query '${q.id}' must include BOTH 'namespace' and 'metricName' (or use 'expression' instead).`
|
|
55590
|
+
};
|
|
55591
|
+
}
|
|
55592
|
+
if (q.statistic !== void 0 && !isValidStatistic(q.statistic)) {
|
|
55593
|
+
return {
|
|
55594
|
+
ok: false,
|
|
55595
|
+
error: `Query '${q.id}' has invalid statistic '${q.statistic}'. Use Average | Sum | Maximum | Minimum | SampleCount, or an extended stat like p99 / p99.9 / tm95.`
|
|
55596
|
+
};
|
|
55597
|
+
}
|
|
55598
|
+
}
|
|
55599
|
+
const now = Date.now();
|
|
55600
|
+
const startStr = i.startTime ?? "1h";
|
|
55601
|
+
const endStr = i.endTime ?? "now";
|
|
55602
|
+
const startDate = resolveTime(startStr, now);
|
|
55603
|
+
const endDate = resolveTime(endStr, now);
|
|
55604
|
+
if (!startDate) {
|
|
55605
|
+
return {
|
|
55606
|
+
ok: false,
|
|
55607
|
+
error: `Invalid startTime '${startStr}'. Use ISO 8601 (e.g. '2026-05-16T10:00:00Z') or relative shorthand (e.g. '1h', '15m', '1d').`
|
|
55608
|
+
};
|
|
55609
|
+
}
|
|
55610
|
+
if (!endDate) {
|
|
55611
|
+
return {
|
|
55612
|
+
ok: false,
|
|
55613
|
+
error: `Invalid endTime '${endStr}'. Use ISO 8601 or relative shorthand, or 'now' for the current moment.`
|
|
55614
|
+
};
|
|
55615
|
+
}
|
|
55616
|
+
if (endDate.getTime() <= startDate.getTime()) {
|
|
55617
|
+
return {
|
|
55618
|
+
ok: false,
|
|
55619
|
+
error: `endTime (${endDate.toISOString()}) must be after startTime (${startDate.toISOString()}).`
|
|
55620
|
+
};
|
|
55621
|
+
}
|
|
55622
|
+
const periodSeconds = pickAutoPeriodSeconds(startDate.getTime(), endDate.getTime());
|
|
55623
|
+
const metricDataQueries = buildMetricDataQueries(i.queries, periodSeconds);
|
|
55624
|
+
const params = {
|
|
55625
|
+
MetricDataQueries: metricDataQueries,
|
|
55626
|
+
StartTime: startDate.toISOString(),
|
|
55627
|
+
EndTime: endDate.toISOString(),
|
|
55628
|
+
ScanBy: i.scanBy ?? "TimestampDescending"
|
|
55629
|
+
};
|
|
55630
|
+
if (i.maxDataPoints !== void 0) params.MaxDatapoints = i.maxDataPoints;
|
|
55631
|
+
const result = await runAwsCall({
|
|
55632
|
+
service: "cloudwatch",
|
|
55633
|
+
operation: "get-metric-data",
|
|
55634
|
+
profile: i.profile,
|
|
55635
|
+
region: i.region,
|
|
55636
|
+
timeoutMs: i.timeoutMs,
|
|
55637
|
+
outputFormat: "json",
|
|
55638
|
+
params
|
|
55639
|
+
});
|
|
55640
|
+
if (!result.ok) {
|
|
55641
|
+
return { ok: false, error: result.error, rawBody: result.rawStderr ?? result.rawStdout };
|
|
55642
|
+
}
|
|
55643
|
+
const raw = result.data ?? {};
|
|
55644
|
+
const series = (raw.MetricDataResults ?? []).map((r) => ({
|
|
55645
|
+
id: r.Id ?? "",
|
|
55646
|
+
...r.Label !== void 0 ? { label: r.Label } : {},
|
|
55647
|
+
timestamps: r.Timestamps ?? [],
|
|
55648
|
+
values: r.Values ?? [],
|
|
55649
|
+
...r.StatusCode !== void 0 ? { statusCode: r.StatusCode } : {}
|
|
55650
|
+
}));
|
|
55651
|
+
const messages = raw.Messages?.filter((m) => m.Code || m.Value).map((m) => ({
|
|
55652
|
+
code: m.Code,
|
|
55653
|
+
value: m.Value
|
|
55654
|
+
}));
|
|
55655
|
+
return {
|
|
55656
|
+
ok: true,
|
|
55657
|
+
data: {
|
|
55658
|
+
command: result.command,
|
|
55659
|
+
startTime: startDate.toISOString(),
|
|
55660
|
+
endTime: endDate.toISOString(),
|
|
55661
|
+
periodSeconds,
|
|
55662
|
+
series,
|
|
55663
|
+
...messages && messages.length > 0 ? { messages } : {}
|
|
55664
|
+
}
|
|
55665
|
+
};
|
|
55666
|
+
}
|
|
55667
|
+
}
|
|
55668
|
+
];
|
|
55669
|
+
|
|
55451
55670
|
// src/tools/multi-region.ts
|
|
55452
55671
|
var DEFAULT_CONCURRENCY = 8;
|
|
55453
55672
|
var MAX_CONCURRENCY = 32;
|
|
@@ -56678,7 +56897,7 @@ var sessionTools = [
|
|
|
56678
56897
|
];
|
|
56679
56898
|
|
|
56680
56899
|
// src/index.ts
|
|
56681
|
-
var version2 = true ? "1.0
|
|
56900
|
+
var version2 = true ? "1.1.0" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
56682
56901
|
var subcommand = process.argv[2];
|
|
56683
56902
|
if (subcommand === "version" || subcommand === "--version") {
|
|
56684
56903
|
console.log(version2);
|
|
@@ -56692,6 +56911,7 @@ var allTools = [
|
|
|
56692
56911
|
...paginateTools,
|
|
56693
56912
|
...assumeTools,
|
|
56694
56913
|
...logsTools,
|
|
56914
|
+
...metricsTools,
|
|
56695
56915
|
...resourceTools,
|
|
56696
56916
|
...multiRegionTools,
|
|
56697
56917
|
...iamSimulateTools,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/aws-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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",
|