Aws Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Aws Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A small AWS MCP for AI assistants: one server, one config entry, SSO re-auth baked in, generic CRUD over hundreds of resource types, live docs lookup, server-side scripting for batched workflows.
It's an alternative to AWS's official MCP server, not a complement -- both call any AWS API, so running both just gives the model two redundant tools. Pick one. The honest comparison:
uvx mcp-proxy-for-aws). Strong on AWS-team-curated skills, a server-side Python sandbox (run_script), and days-fresh API coverage. Requires Python + uv, routes through a proxy that bridges IAM SigV4 to OAuth, and assumes your local credentials already work.aws sso login's browser handoff drops (Windows especially), ergonomic CCAPI CRUD with dry-run diffs, multi-region fan-out, pre-flight IAM permission checks, and a JS scripting sandbox. Live AWS docs search + read is built in too -- parity with the official server's search_documentation / read_documentation, no second server needed either way.The one MCP that genuinely pairs with either choice is [`awslabs/mcp`](https://github.com/awslabs/mcp) -- AWS Labs' fleet of typed per-service servers (Lambda invoke, Bedrock retrieval, DynamoDB with type-marshalling). Those are per-service helpers, no overlap with a general AWS-API server.
Five things this server tries to handle well:
aws sso login tries to open a browser from a subprocess -- on Windows (and sometimes elsewhere) that handoff drops silently. You end up context-switching to a terminal, running the command yourself, then coming back. The --no-browser device-code flow fixes this: the assistant surfaces a short URL + code, you click once, done. There's also aws_refresh_if_expiring_soon for proactive top-ups before a long workflow. AWS's hosted server bridges IAM-to-OAuth via a local proxy; it doesn't help with the aws sso login browser-handoff failure.aws_call proxies the aws CLI directly. One tool covers the full API surface -- including services AWS adds tomorrow -- with no SDK bundling and no service-by-service tool sprawl. aws_paginate handles paginated list/describe ops, aws_multi_region fans the same op out across N regions in parallel, and a JMESPath query parameter trims responses server-side (useful when a describe-instances result would otherwise blow past the 5 MB output cap).aws_resource_* (seven tools, including aws_resource_diff for dry-run previews) wraps AWS Cloud Control API, so the same lifecycle -- get / list / create / update / delete / status -- works for any control-plane resource with a CloudFormation schema: Lambda functions, S3 buckets, IAM roles, SSM parameters, RDS instances, and a few hundred more. Pass awaitCompletion: true and the server polls the async create/update/delete through to terminal state for you. CCAPI is control-plane only -- for data-plane ops (S3 reads, Lambda invokes, Bedrock inference, DynamoDB GetItem) drop down to aws_call or use a typed AWS Labs server.aws_docs_search queries the same backend that powers the docs.aws.amazon.com search box; aws_docs_read fetches a doc page and returns it as paginated markdown. Lets the agent discover new services and look up exact parameter names without a second MCP server installed.aws_script runs a short JS snippet inside a constrained node:vm sandbox with aws.call, aws.paginate, aws.paginateAll, aws.resource.*, aws.logsTail, aws.metricsQuery, aws.iamSimulate, aws.multiRegion, aws.assumeRole, and aws.docs.{search,read} available. Best for "list X, fetch Y for each, return Z" pipelines that would otherwise need N tool calls. Same shape as AWS's run_script (Python, sandboxed server-side) -- yours is JS-native and runs locally.One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
For deep work in a single service -- typed lambda_invoke, Bedrock KB retrieval, DynamoDB with type-marshalling -- add the relevant awslabs/mcp server alongside this one. Those are per-service helpers with no tool-name overlap, so they pair cleanly:
{
"mcpServers": {
"aws": {
"command": "npx",
"args": ["-y", "@yawlabs/aws-mcp@latest"]
},
"aws-lambda": {
"command": "uvx",
"args": ["awslabs.lambda-mcp-server@latest"]
}
}
}| Need | Best fit |
|---|---|
| One config entry covering most of AWS | `@yawlabs/aws-mcp` |
| SSO re-login on Windows / broken browser handoff | `@yawlabs/aws-mcp` (aws_login_start device-code flow) |
| Generic CRUD across hundreds of resource types | `@yawlabs/aws-mcp` (aws_resource_*) |
| Dry-run an update before applying it | `@yawlabs/aws-mcp` (aws_resource_diff) |
| Multi-region fan-out in one call | `@yawlabs/aws-mcp` (aws_multi_region) |
| Batch N tool calls into one round-trip (JS) | `@yawlabs/aws-mcp` (aws_script) |
| Check IAM permissions before attempting an op | `@yawlabs/aws-mcp` (aws_iam_simulate) |
| Node/npm-only install (no Python) | `@yawlabs/aws-mcp` |
| Sandboxed Python script execution server-side | AWS MCP Server (run_script) |
| AWS-team-curated best-practice skills | AWS MCP Server (skills) |
| Days-fresh API coverage via hosted endpoint | AWS MCP Server (call_aws) |
| Typed per-service helpers (Lambda invoke, Bedrock KB, DynamoDB type-marshalling, ...) | `awslabs/mcp` (per-service servers) |
@yawlabs/aws-mcp and AWS's official server are an either/or -- pick the one whose tradeoffs fit. awslabs/mcp per-service servers pair cleanly with whichever you pick.
Credit where due -- two features here were shaped by the official AWS MCP Server:
run_script: a sandboxed scripting tool that collapses "list X, fetch Y for each, return Z" pipelines into one round-trip. Theirs is Python, sandboxed server-side; this one is JS-native and runs locally.search_documentation / read_documentation, so you don't need a separate docs MCP regardless of which server you pick.The rest -- SSO device-code re-login, CCAPI CRUD with dry-run diffs, multi-region fan-out, IAM pre-flight checks -- is this server's own.
| Tool | What it does |
|---|---|
aws_whoami | Current identity (account, ARN) + SSO token expiry countdown. Call this first. |
aws_login_start | Start aws sso login --no-browser, returns a verification URL + short code and a sessionId. |
aws_login_complete | Block until the SSO subprocess finishes (you auth in your browser), returns the new identity. |
aws_refresh_if_expiring_soon | Check the cached SSO token and auto-start a refresh when < thresholdMinutes remain (default 10). One round-trip for "am I about to expire? if so, re-login." |
aws_session_set | Set the default profile and/or region for the rest of this MCP session. "Switch to prod," "use us-west-2." |
aws_session_get | Show the current session defaults and where each value came from (session/env/default). |
aws_session_clear | Remove session profile/region overrides so env vars / defaults take over again. No args clears both. |
aws_list_profiles | List profiles configured in ~/.aws/config -- names, regions, and SSO metadata. Use before switching profiles or when an SSO error names one you haven't seen. |
aws_assume_role | Call STS AssumeRole with your current identity and stash the temp creds as a new profile (mcp-<sessionName>) in ~/.aws/credentials. Use for cross-account access. The secret/session token stay on disk -- not returned to the model. Optional timeoutMs (default 120s) for slow SAML / credential_process cold starts. |
aws_call | Run any AWS API operation. service: 's3api', operation: 'list-buckets', optional params (PascalCase JSON), optional query (JMESPath). Returns parsed JSON. |
aws_paginate | Fetch one page of a paginated list/describe operation. Supports query too. Returns nextToken/hasMore; call again with the token to continue. |
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. |
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: [{id, label?, timestamps, values, period?, statusCode?}], periodSeconds, profile, region, nextToken, hasMore, messages?} (full envelope under Stability). |
aws_resource_get | Read an AWS resource via Cloud Control API by typeName + identifier (e.g. AWS::Lambda::Function + function name). Returns parsed Properties. |
aws_resource_list | List resources of a type via CCAPI, paginated. Returns {identifier, properties} per entry plus a nextToken/hasMore. |
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. |
aws_resource_update | Update an AWS resource via CCAPI using RFC 6902 JSON Patch. Same async + awaitCompletion shape as create. |
aws_resource_delete | Delete an AWS resource via CCAPI. Same async + awaitCompletion shape as create. Destructive — verify identifier first. |
aws_resource_status | Poll an async CCAPI request by requestToken. Returns the current state with operationStatus, identifier, errorCode, statusMessage flat-promoted (PENDING / IN_PROGRESS / SUCCESS / FAILED / CANCEL_*). |
aws_resource_diff | Dry-run a CCAPI update: fetches current state, simulates the JSON Patch in memory, returns {before, after, changes[]}. No mutation sent to AWS. Supports the add/remove/replace subset of RFC 6902; add auto-creates missing object parents to match CCAPI's actual update semantics (so patches like /Environment/Variables/NEW_KEY work even when /Environment/Variables doesn't exist yet). changes[i].after reflects what op i produced (not the final post-patch state), so sequential ops on the same path read correctly. Call before aws_resource_update when you want to verify the patch does what you expect. |
aws_multi_region | Run the same AWS operation across N regions in parallel. Same shape as aws_call but takes regions: string[]. Returns {region, ok, data?, error?}[] with okCount/errorCount. Partial failure is expected (services aren't everywhere, perms may be region-scoped). |
aws_script | Run a short JS snippet that orchestrates the other tools and returns a combined result. Sandbox exposes 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}, plus standard JS builtins (JSON, Math, Date, Promise, etc.) and console. No require/import/process/fs/fetch/timers. Best for "list X, fetch Y for each, return Z" pipelines that would otherwise be N round-trips. Use return <value> to surface a result. Not a security sandbox -- treat the same as any other tool the model can call. |
aws_iam_simulate | Simulate IAM permissions for a principal: can principal X do actions Y on resources Z? Wraps iam simulate-principal-policy. Returns one entry per (action, resource) pair with decision (allowed / explicitDeny / implicitDeny), matchedStatementIds (which IAM statements decided), and missingContextValues (context keys the policy needed but you didn't provide). Use BEFORE a risky operation to avoid a 403 -- pairs with the post-failure Suggestion from aws_call. Requires iam:SimulatePrincipalPolicy on the caller. |
aws_docs_search | Search live AWS documentation (the backend behind the docs.aws.amazon.com search box). Returns ranked {title, url, summary, excerpt}. Use to discover the right doc page for a service/API/concept the model may not know -- new services, recently changed APIs, exact parameter names. |
aws_docs_read | Fetch an https://docs.aws.amazon.com/...html page and return it as markdown. Strips nav/cookie-banner/feedback chrome. Long pages paginate via startIndex + maxLength; the response carries hasMore and nextStartIndex. Usually fed a url from aws_docs_search. |
Add to your MCP client config (e.g. .mcp.json):
{
"mcpServers": {
"aws": {
"command": "npx",
"args": ["-y", "@yawlabs/aws-mcp@latest"]
}
}
}The -y flag is what gives you auto-update on each session load: every time your MCP client spawns the server, npx checks the registry for the latest @yawlabs/aws-mcp and downloads it if newer. The first launch in a fresh cache adds ~100-500 ms; subsequent launches use npm's cache (typical metadata-freshness window: 5 min) and add ~50 ms or less. Once the server is up, tool calls have zero auto-update overhead -- the check fires only on (re-)spawn. No separate install step is needed; -y covers both first-time install and ongoing updates.
If you'd rather pin a specific version (no auto-update, but zero startup overhead), install globally and point the config at the installed binary:
npm install -g @yawlabs/aws-mcp{
"mcpServers": {
"aws": {
"command": "aws-mcp"
}
}
}You'll need to npm install -g @yawlabs/aws-mcp@latest manually when you want a newer version.
You ask the assistant to check a staging bucket, but your SSO token just expired. What the assistant does (and what you see):
You: "How many objects are in the staging-artifacts bucket right now?"
Claude: (calls aws_whoami) -> SSO session expired for profile 'staging'.
(calls aws_login_start with profile='staging')
"Your SSO token expired. Open
https://device.sso.us-east-1.amazonaws.com/
and enter code: ABCD-EFGH
I'll wait."
You: *click, authenticate in your browser*
Claude: (calls aws_login_complete with the sessionId)
(calls aws_call with service='s3api', operation='list-objects-v2',
params={ Bucket: 'staging-artifacts' },
query='KeyCount')
"There are 4,182 objects in staging-artifacts."The SSO flow took one click. No "the browser didn't open, let me run it in a terminal" context switch.
For a larger list where the response might exceed the 5 MB output cap, the assistant reaches for aws_paginate:
(calls aws_paginate with service='ec2', operation='describe-instances',
maxItems=50,
query='Reservations[].Instances[].{Id:InstanceId,State:State.Name}')
-> returns one page + a nextToken; Claude calls again until hasMore=falsequery (JMESPath) trims the response server-side -- a typical describe-instances result shrinks from megabytes to kilobytes when you only need two fields.
For "create this resource and tell me when it's ready," aws_resource_create with awaitCompletion: true collapses the usual create-then-poll loop into one tool call:
(calls aws_resource_create with
typeName='AWS::SSM::Parameter',
desiredState={Name: '/my/param', Type: 'String', Value: 'hello'},
awaitCompletion: true)
-> server polls get-resource-request-status until SUCCESS / FAILED / CANCEL_COMPLETE
and returns the terminal ProgressEvent in one callSame shape for aws_resource_update and aws_resource_delete. Drop awaitCompletion (or set it false) for the default fire-and-poll behavior -- useful when you want to kick off a long-running update and check back later.
For "preview the patch before applying":
(calls aws_resource_diff with
typeName='AWS::Lambda::Function',
identifier='my-fn',
patchDocument=[{op: 'replace', path: '/MemorySize', value: 1024}])
-> returns { before: {MemorySize: 256, ...}, after: {MemorySize: 1024, ...},
changes: [{op: 'replace', path: '/MemorySize', before: 256, after: 1024}] }No mutation is sent to AWS; the agent can verify the patch before invoking aws_resource_update.
For batched workflows, aws_script collapses N tool calls into one:
(calls aws_script with code=`
const listed = await aws.resource.list({ typeName: "AWS::Lambda::Function" });
const big = [];
for (const r of listed.resources) {
const cfg = await aws.resource.get({
typeName: "AWS::Lambda::Function", identifier: r.identifier });
if (cfg.properties.MemorySize > 1024) {
big.push({ name: cfg.properties.FunctionName, mem: cfg.properties.MemorySize });
}
}
return big;
`)
-> one round-trip; the agent gets the filtered list without N intermediate tool callsFor multi-region reads:
(calls aws_multi_region with
service='ec2', operation='describe-instances',
regions=['us-east-1','us-west-2','eu-west-1'],
query='Reservations[].Instances[].InstanceId')
-> {okCount: 3, errorCount: 0, results: [{region, ok, data}, ...]}PATH (for aws sso login --no-browser)~/.aws/config| Variable | Default | Purpose |
|---|---|---|
AWS_PROFILE | default | Profile used when a tool call omits profile. |
AWS_REGION / AWS_DEFAULT_REGION | us-east-1 | Region used when a tool call omits region. AWS_REGION wins if both are set. |
If you authenticate via SAML (Okta / Azure AD / ADFS) or a custom credential_process, set AWS_PROFILE to that profile. The server passes --profile through to the AWS CLI, so the CLI's standard credential chain -- credential_process, SSO sessions, role chaining, static keys, IMDS -- resolves as usual.
If neither AWS_PROFILE is set nor aws_session_set has been called and there's no [default] section in ~/.aws/config, tools will fail with ProfileNotFound. Set AWS_PROFILE in your MCP config to your usual working profile.
1. Claude calls aws_login_start({ profile: "prod" })
2. Server spawns: aws sso login --no-browser --profile prod
3. Server parses the URL + code from stdout, returns them to Claude
4. Claude surfaces: "Open https://device.sso.us-east-1.amazonaws.com/ and enter ABCD-EFGH"
5. You click — browser opens in your own user session — auth in ~10 seconds
6. Claude calls aws_login_complete({ sessionId })
7. Tool returns your new identity. Back to work.The token is cached in ~/.aws/sso/cache/<hash>.json the same way a normal aws sso login would, so the AWS CLI, the SDK, and every other tool on your machine pick it up transparently.
SSO tokens live in ~/.aws/sso/cache/ on your device. A remote MCP server can't read them. So this is a stdio server, not a hosted one. That's a constraint of AWS SSO, not a limitation of mcp.hosting.
From 1.0 onward this package follows Semantic Versioning. The 0.x line is the pre-stability tightening phase -- breaking changes are documented in CHANGELOG.md but are not necessarily gated on a major bump.
Stable in 1.x (anything below is a breaking change requiring a major bump):
readOnlyHint, destructiveHint, idempotentHint, openWorldHint. These signal to MCP hosts how to gate calls; flipping them silently would break host UIs.data object on {ok: true, data} responses, specifically:aws_call -> {command, result}aws_paginate -> {command, result, nextToken, hasMore}aws_multi_region -> {service, operation, regionCount, okCount, errorCount, results: [{region, ok, data?, command?, error?, errorKind?}]}aws_whoami -> {account, userId, arn, profile, region, ssoToken: {expiresAt, minutesLeft, startUrl?} | null} (startUrl is omitted when the cached token didn't record one)aws_login_start -> {sessionId, profile, verificationUrl, userCode, instructions, reused?} (reused: true when re-surfacing an in-flight login for the same profile)aws_login_complete -> {loggedIn, account, userId, arn, profile, region, ssoToken} (same ssoToken shape as aws_whoami, including the optional startUrl)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.aws_assume_role -> {profile, credentialsPath, expiration, assumedRoleArn, assumedRoleId, sourceProfile, hint}aws_list_profiles -> {configPath, profiles: [{name, region?, ssoStartUrl?, ssoRegion?, ssoSession?, isSso}]}aws_session_get / aws_session_set / aws_session_clear -> {profile, region, profileSource, regionSource} where *Source is "session" | "env" | "default". All three return the same shape (set/clear return the post-mutation state).aws_resource_get -> {command, typeName, identifier, properties, propertiesRaw?}aws_resource_list -> {command, typeName, resources: [{identifier, properties}], nextToken, hasMore}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 passedaws_resource_diff -> {command, typeName, identifier, before, after, changes, changeCount}aws_logs_tail -> {command, logGroupName, since, eventCount, events}aws_metrics_query -> {command, profile, region, startTime, endTime, periodSeconds, series: [{id, label?, timestamps, values, period?, statusCode?}], nextToken, hasMore, messages?: [{code?, value?}]} (messages is omitted when empty; per-series label / period / statusCode are present when CloudWatch returns them or the query specifies/inherits a period; nextToken is null and hasMore false unless CloudWatch truncated the response)aws_iam_simulate -> {command, principalArn, summary: {allowed, denied, total}, results, evaluationResults}aws_script -> {result, logs, truncatedLogs, durationMs} where result is whatever the script returned (any JSON-serializable value, including undefined)aws_docs_search -> {query, count, results: [{title, url, summary?, excerpt?}]} (summary / excerpt are present only when the upstream search backend returns them)aws_docs_read -> {url, cached, content, startIndex, endIndex, totalLength, hasMore, nextStartIndex}{ok: false, error: string, rawBody?: string}. The error string is human-readable; its wording is best-effort (see below)."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.Best-effort (may change in a minor or patch):
errorKind (for aws_multi_region) or the structured envelope, not on regex-matching error text.aws CLI for diagnostic purposes. Format follows whatever the CLI emits in your installed version.<redacted len=N>) may shift.Deprecation policy: breaking a stable shape requires a major bump. A deprecation lands first in a minor (the old shape continues to work and the new shape becomes available alongside it), with a removal scheduled for the next major. Both the deprecation and the removal show up in CHANGELOG.md.
npm test runs both unit tests and integration tests. The integration suites spawn a local fake-aws subprocess that stubs the AWS CLI -- no AWS credentials or network access required. The only tests that need real AWS credentials are the live tests gated behind the AWS_MCP_LIVE_TESTS environment variable, which are skipped in a standard npm test run.
MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.