AI & ML
Agent Toolkit for AWS in Practice (1) - Claude Code
Haowen Huang Dev.to (EN Zone)
2 views
Part 1 of the series "Agent Toolkit for AWS in Practice."
Agent Toolkit for AWS gives your coding agent two things it normally lacks: curated knowledge of how AWS services are meant to be used, and a way to actually call them. Setup is one command.
This walkthrough covers the install, two verification tasks against a real account, and three things worth knowing before you rely on it. Every command and output below is from an actual run on macOS with Claude Code.
What you're installing
Two independent pieces. Knowing which is which saves time when something misbehaves.
Piece
Role
Where it lives
Agent Skills
AWS knowledge — service selection, tested procedures, troubleshooting
Files on your disk
AWS MCP Server
Lets the agent call AWS APIs and search current docs
AWS-managed, reached through a local proxy
They work independently. Skills don't require the MCP server, and the MCP server doesn't serve your locally installed skills.
A skill is a directory with a SKILL.md and often a references/ folder:
aws-storage/
├── SKILL.md
├── .aws-skill-metadata # {"version": "v1"}
└── references/
├── s3-general-purpose-knowledge.md
├── ebs-knowledge.md
└── ...
The front matter's description is what the agent uses to decide whether a skill applies — and it spells out what the skill is not for, which keeps irrelevant skills from loading:
---
name: aws-storage
description: >-
Selects, investigates, and compares AWS object, file, and block storage
services... Not applicable for SQL query engines (Athena, Spark, Redshift,
EMR), ETL (Glue), streaming (Kafka, MSK, Kinesis), or managed database
services (RDS, Aurora, DynamoDB).
version: 1
---
The body holds behavioural rules. One from aws-storage explains why the toolkit is worth having: it requires the agent to verify current figures rather than recall them, to cite the pricing page for any cost claim, and to name a value it couldn't verify rather than guess. That pairs directly with the MCP server's documentation tools — the skill demands verification, the server provides the means.
Skills are open source in aws/agent-toolkit-for-aws (Apache-2.0), so you can read them before installing.
Prerequisites
aws --version # need 2.35.0 or later
uv --version # required — the MCP proxy runs through uvx
node --version # v22+ for Claude Code
uv is easy to miss. Nothing in the setup output mentions it, but the MCP server won't connect without it. See the uv install guide.
If your AWS CLI is older than 2.35, upgrade by reinstalling the package — there's no self-update:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /
rm AWSCLIV2.pkg
aws --version
# aws-cli/2.36.40 Python/3.14.6 Darwin/25.6.0 exe/arm64
Install
aws configure agent-toolkit --region us-east-1
Why the region flag? Agent Toolkit is served from us-east-1 only. Official examples omit it, which works if that's already your default. Mine is us-west-2, and without the flag the wizard completes agent detection and the selection screen, then fails at the fetch:
Fetching default AWS skills...
aws: [ERROR]: AgentToolkit is only available in us-east-1
Check aws configure get region first.
The wizard detects installed agents, offers a checklist (everything selected by default), installs skills, and configures the MCP server:
Detecting installed AI coding agents...
✓ Claude Code — ~/.claude/skills
✓ Codex — ~/.agents/skills/
✓ Cursor — ~/.cursor/skills
✓ Kiro — ~/.kiro/skills
Install 23 default AWS skills? [Y/n]: Y
Configure AWS MCP server connection? [Y/n]: Y
Restart your agent afterwards to pick up the config — if Claude Code is already open, exit with /exit and launch it again.
What you got
ls -1 ~/.claude/skills | wc -l
# 23
Those 23 are the catalog's aws-core category — covering IaC, core services, databases, networking, storage, security, observability, messaging, SDKs and cost management. They're installed as a full copy per agent, not symlinks.
The catalog holds more, at finer granularity, which you add yourself:
aws agent-toolkit search-skills --search-query "dynamodb" --region us-east-1
That surfaces single-service skills like amazon-dynamodb — access-pattern enumeration, partition key and GSI selection, single- versus multi-table decisions, cost estimation — well past what the domain-level aws-database covers.
Verify it works
Read path: query your bill
Launch Claude Code from the directory you want to work in:
claude
On first run in a project it asks you to enable the MCP server. Press Enter to accept — pressing Esc rejects it, and the server stays silently disabled with no further prompting.
Once you're at the prompt, check the connection by typing:
/mcp
aws-mcp · ✔ connected · 8 tools
That's success. Now ask for something real:
Get my AWS billing details for July 2026, grouped by service.
Do the sorting and totalling inside the script.
The agent reaches for aws___run_script and shows you the whole script before running it:
resp = await call_boto3(
service_name="ce",
operation_name="GetCostAndUsage",
region_name="us-east-1",
params={
"TimePeriod": {"Start": "2026-07-01", "End": "2026-08-01"},
"Granularity": "MONTHLY",
"Metrics": ["UnblendedCost"],
"GroupBy": [{"Type": "DIMENSION", "Key": "SERVICE"}],
},
)
# Confirm the API actually ran
assert any(c.get("operation_name") == "GetCostAndUsage" for c in resp.get("api_calls", [])), resp
rv = resp["return_value"]
results = rv["ResultsByTime"][0]
groups = results["Groups"]
unit = None
rows = []
for g in groups:
svc = g["Keys"][0]
amt = g["Metrics"]["UnblendedCost"]
unit = amt["Unit"]
rows.append((svc, float(amt["Amount"])))
# Sort descending by cost, inside the script
rows.sort(key=lambda r: r[1], reverse=True)
total = sum(a for _, a in rows)
result = {
"period": "2026-07-01 to 2026-07-31",
"currency": unit,
"estimated": results.get("Estimated"),
"service_count": len(rows),
"total": round(total, 2),
"services": [
{"service": s, "cost": round(a, 2), "pct": round(100 * a / total, 1) if total else 0.0}
for s, a in rows
],
}
result
Reading it is worth the few seconds. One thing is visibly right: the sorting and totalling really do happen in the script, as the prompt asked — those are the rows.sort(...) and sum(...) lines, not something the model does in its reply afterwards.
Two things are wrong, and they share a cause. Both of these assume call_boto3 returns a wrapper around the API response:
assert any(c.get("operation_name") == "GetCostAndUsage" for c in resp.get("api_calls", [])), resp
rv = resp["return_value"]
Neither api_calls nor return_value exists on what call_boto3 hands back, so the assert can never pass and the script fails on the spot. The agent worked it out immediately:
The response is the raw API dict directly. Let me fix that.
Here's the corrected script it submitted next — the version that actually works:
resp = await call_boto3(
service_name="ce",
operation_name="GetCostAndUsage",
region_name="us-east-1",
params={
"TimePeriod": {"Start": "2026-07-01", "End": "2026-08-01"},
"Granularity": "MONTHLY",
"Metrics": ["UnblendedCost"],
"GroupBy": [{"Type": "DIMENSION", "Key": "SERVICE"}],
},
)
results = resp["ResultsByTime"][0]
groups = results["Groups"]
unit = None
rows = []
for g in groups:
svc = g["Keys"][0]
amt = g["Metrics"]["UnblendedCost"]
unit = amt["Unit"]
rows.append((svc, float(amt["Amount"])))
# Sort descending by cost, inside the script
rows.sort(key=lambda r: r[1], reverse=True)
total = sum(a for _, a in rows)
result = {
"period": "2026-07-01 to 2026-07-31",
"currency": unit,
"estimated": results.get("Estimated"),
"service_count": len(rows),
"total": round(total, 2),
"services": [
{"service": s, "cost": round(a, 2), "pct": round(100 * a / total, 1) if total else 0.0}
for s, a in rows
],
}
result
Both wrong assumptions are gone: resp["ResultsByTime"] replaces resp["return_value"]["ResultsByTime"], and the assert on api_calls has been dropped entirely.
call_boto3 hands back the boto3 response directly. A return_value envelope does exist, but one level up — it wraps your script's final value in the MCP tool response, which is a different object. Inside the script, read the API response as-is.
This is a good template to reuse. Take the raw response, aggregate and sort in the script, return a compact dict.
Then it asks permission:
Do you want to proceed?
❯ 1. Yes
2. Yes, and don't ask again for aws-mcp — Aws Run Script commands in <this directory>
3. No
Choose 1, and this is what a working setup produces:
Note what came back beyond the raw numbers: percentages per service, a count of how many services had zero spend, a note that the figures are finalized rather than estimated, and an unprompted takeaway pointing at the two services worth optimising. The skill's guidance is visible in the shape of that answer.
Two things to notice about how it got there.
1 / It's Python in a sandbox, not the AWS CLI. The tool executes code with AWS access through call_boto3 and no other network. call_boto3 takes a service name, a canonical API operation name and a parameter dict. Aggregation happens inside the script rather than by pulling raw data back into context, which keeps token use down.
2 / Ask for the maths to be done in the script. That's why the prompt above says "Do the sorting and totalling inside the script". Left to itself, the model may pull the raw numbers back and add them up in its reply — which in one of my runs produced a total $48 too high, with nothing to indicate anything was wrong. Doing it in Python avoids that.
To check the total the agent gives you, ask Cost Explorer for the same month without grouping. Skip --group-by and the API fills in Total for you, so you get one number to compare against:
aws ce get-cost-and-usage --time-period Start=2026-07-01,End=2026-08-01 \
--granularity MONTHLY --metrics UnblendedCost --region us-east-1 \
--query 'ResultsByTime[0].Total.UnblendedCost.Amount' --output text
# 2116.6859001279
Add --group-by and Total comes back empty — you get per-service groups and have to sum them yourself. That's why the agent does the adding, and why you want it doing that in Python.
Write path: create and delete a bucket
Create an S3 bucket named "example-toolkit-test" in us-east-1.
Then verify from a normal terminal rather than trusting the summary:
aws s3api head-bucket --bucket example-toolkit-test --region us-east-1
aws s3api get-bucket-encryption --bucket example-toolkit-test
aws s3api get-public-access-block --bucket example-toolkit-test
Encryption came back SSE-S3 (AES256) and all four public access blocks enabled — the secure defaults, nothing extra needed. Clean up:
Delete the S3 bucket "example-toolkit-test".
aws s3api head-bucket --bucket example-toolkit-test --region us-east-1
# An error occurred (404) ... Not Found
Read, write and delete all working end to end.
Three things worth knowing
1. The setup command doesn't update skills
Re-running aws configure agent-toolkit installs newly published skills and skips everything already on disk. Not "checks and confirms current" — untouched. Mine sat a month behind without any indication:
ls -l ~/.claude/skills/aws-cdk/SKILL.md
# Aug 6 06:06 ← still the version installed a month earlier
add-skill states the rule outright:
aws-cdk is already installed (v2) at /Users/you/.claude/skills/aws-cdk.
Run "aws agent-toolkit update-skill --skill-name aws-cdk" to update, or remove it first to reinstall.
Updating is a separate verb, and it does compare versions:
aws agent-toolkit update-skill --skill-name aws-cdk --region us-east-1
# Updated aws-cdk (v2) to Claude Code — ~/.claude/skills.
# ... and to the other three agent directories
One command covers every agent copy. To audit what's behind:
aws agent-toolkit list-available-skills --category-filter aws-core --region us-east-1
for s in ~/.claude/skills/*/; do
printf "%-34s %s\n" "$(basename "$s")" "$(cat "$s/.aws-skill-metadata")"
done
Worth putting on a schedule. A stale skill doesn't error; it quietly supplies last month's guidance.
2. The MCP server runs remotely
Selecting the server in /mcp shows how it's wired:
The two lines that matter, in text so you can compare them against your own:
Command: uvx
Args: mcp-proxy-for-aws@latest https://aws-mcp.us-east-1.api.aws/mcp --metadata INSTALL_SOURCE=aws-cli
There's no local server. It's an AWS-managed endpoint, and uvx mcp-proxy-for-aws is a thin local proxy that signs requests with your AWS credential chain. Hence the uv prerequisite and the region in the URL.
This view is also where you go when something isn't working — Reconnect and Disable are right there, and Status tells you whether the problem is the connection or something further along.
Note the @latest. The official examples pin a version and recommend doing so for reproducible behaviour; the CLI writes @latest. Both are defensible — @latest picks up fixes automatically, a pin keeps behaviour stable — so make the choice deliberately rather than inheriting it.
Pick View tools from that menu and Claude Code lists all eight with their annotations:
Six of the eight are read-only lookups, three of those being documentation search and retrieval. Exactly one — aws___run_script, the only one marked destructive — can change your account. Every write goes through it.
Worth scrolling this list once. It's the clearest picture you'll get of what the agent can actually do, and the destructive label tells you which single tool to pay attention to when an approval dialog appears.
3. Approval is coarser than it looks
That's the dialog for a two-line script that returns the string OK. The one for deleting an S3 bucket looks the same — same three options, no extra warning for destructive work. I compared it across five operations.
Which makes option 2 more consequential than it reads. "Don't ask again for aws-mcp — Aws Run Script commands in this directory" sounds narrow, but because reads, writes and deletes all travel through that single tool, it covers every subsequent AWS operation there, deletions included. The grant can't be scoped per operation.
Two practical follow-ups.
1 / Check autoApprove in the configs the wizard touched. On this machine it added "autoApprove": ["aws___run_script"] to ~/.kiro/settings/mcp.json — the one tool that can change your account, pre-approved. Claude Code and Cursor got no such field, and the official example config doesn't include it. Remove it if you'd rather be asked.
2 / Read the code in the dialog, not the comments in it. One delete script arrived with a # Verify empty first comment, computed an object count, never branched on it, and deleted anyway — then reported that it had "verified the bucket was empty". The check the comment promised wasn't there.
Where the real guardrails are
The client is the wrong place to look for granularity. The toolkit's mechanism for this sits on the AWS side: the condition keys aws:ViaAWSMCPService and aws:CalledViaAWSMCP distinguish requests made through the AWS-managed MCP server from direct API calls.
You can use them in IAM policies and SCPs to write rules that apply only to agent-initiated actions — for example, permitting only read-only operations through MCP even where the underlying role can write. Every request also lands in CloudWatch metrics and CloudTrail.
That's the control point worth investing in. Clicking through identical dialogs doesn't scale, and the tool annotation gates nothing on its own.
Command reference
# install / reconfigure
aws configure agent-toolkit --region us-east-1
# skills — add --region unless your default is us-east-1
aws agent-toolkit list-installed-skills
aws agent-toolkit list-available-skills --category-filter aws-core --region us-east-1
aws agent-toolkit search-skills --search-query "dynamodb" --region us-east-1
aws agent-toolkit update-skill --skill-name aws-cdk --region us-east-1
aws agent-toolkit update-skill --skill-name aws-cdk --agent kiro --region us-east-1
aws agent-toolkit add-skill --skill-name amazon-dynamodb --region us-east-1
aws agent-toolkit remove-skill --skill-name aws-cdk --region us-east-1
aws agent-toolkit get-skill-metadata --skill-name aws-serverless --region us-east-1
# local versions at a glance
for s in ~/.claude/skills/*/; do
printf "%-34s %s\n" "$(basename "$s")" "$(cat "$s/.aws-skill-metadata")"
done
list-installed-skills is the one subcommand that works without --region — it only reads local disk.
Verified on
Component
Version
macOS
Darwin 25.6.0 (arm64)
AWS CLI
2.36.40
Node.js
v22.23.2
uv
0.8.18
Claude Code
2.1.261 (via Amazon Bedrock)
AWS skills
23 (aws-core)
Part 2 runs the same walkthrough on Codex — where the config format is TOML rather than JSON, the skills land in a directory shared with three other agents, and the approval model works differently. It also covers what the aws-core plugin installs that the CLI path doesn't.
Whatever your setup looks like, verify it the way this post did: one read, one write, both confirmed from a second terminal. An agent's account of what it did is not evidence that it did it.
References
Agent Toolkit for AWS
Product page — overview, features, per-agent getting started
User guide — setup and reference documentation
AWS CLI integration guide — the configure agent-toolkit wizard and every agent-toolkit subcommand, including update-skill
AWS MCP Server tools reference — the eight tools, authentication, supported regions
Getting started with the AWS MCP Server — configuring the server with agents other than Claude Code, Codex and Cursor
Source and skill contents (Apache-2.0)
aws/agent-toolkit-for-aws — repository
skills/ — every skill's SKILL.md and reference files, readable before you install
rules/ — the recommended project-level rules file, not covered in this post
Prerequisites and related
uv installation — required; the MCP proxy runs through uvx
Installing or updating the AWS CLI — 2.35.0 or later needed
Model Context Protocol — the open standard behind the MCP server, including tool annotations
Claude Code MCP documentation — the /mcp command and server management
Referenced APIs
GetCostAndUsage — Cost Explorer; note the canonical PascalCase operation name expected by call_boto3
A note on method: official documentation and skill contents are paraphrased rather than quoted. Every command and output in this post is from a real run on the environment listed above, and the agent's actions were verified from a separate shell rather than taken from its own reporting.
Read original: https://dev.to/haowen_huang/agent-toolkit-for-aws-in-practice-1-claude-code-pi8
← Previous
RAG vs Memory vs Tools: What Information Should an AI Agent Actually Store?
Next →
The Next RAG Problem Isn’t Retrieval — It’s Knowing When Not to Retrieve
Related
The Next RAG Problem Isn’t Retrieval — It’s Knowing When Not to Retrieve
AI & ML
3
Dev.to (EN Zone)
RAG vs Memory vs Tools: What Information Should an AI Agent Actually Store?
AI & ML
1
Dev.to (EN Zone)
n8n: When AI Writes the Workflow, Who Reviews the Workflow?
AI & ML
1
Dev.to (EN Zone)
n8n Can Now Build Its Own Workflows — What Could Possibly Go Wrong?
AI & ML
1
Dev.to (EN Zone)
Comments0
No comments yet — be the first