What it does
The server scans the account behind your AWS profile, prices what it finds, and hands the result to the assistant you already use. It never changes anything in AWS. When you ask how to fix something, it prints the commands as text for you to review and run yourself.
Savings from the MCP server are estimates, so it says "about" and never "verified". Verified savings come from the ledger in the full product, which is not in the hosted service yet.
| Tool | What it returns |
|---|---|
list_findings | Findings, largest monthly saving first. Ask "what are my top 5 savings?" |
explain_finding | Evidence, the cost and where that number comes from, the risk tier, and the exact AWS CLI commands to fix it |
get_costs | Billed cost from Cost Explorer by service, account, region or usage type |
bedrock_usage | Bedrock tokens by model, billed cost by usage type, and which Lambda functions and ECS services can call Bedrock |
Install
Not yet published. spendsaw-mcp is not on PyPI yet, so uvx spendsaw-mcp and pipx install spendsaw-mcp will not find it. The source repository is not public yet either. Until one of them is, the server can only be installed from a checkout; write to hello@spendsaw.com for early access.
You need Python 3.10 or newer and working AWS credentials.
From a checkout of the repository, run this in the repository root:
pip install ./spendsaw_mcp
To build a wheel you can install on another machine, build the wheel only. A source distribution cannot be built, because the package bundles the scanner code that sits beside it in the repository.
uv build spendsaw_mcp --wheel
Once the package is on PyPI:
uvx spendsaw-mcp # or: pipx install spendsaw-mcp
Either way the command on your path is spendsaw-mcp.
Connect your assistant
The examples use uvx spendsaw-mcp, which needs the published package. If you installed from a checkout, use spendsaw-mcp as the command with no arguments.
Claude Code
claude mcp add spendsaw --env AWS_PROFILE=default --env AWS_REGIONS=us-east-1 -- uvx spendsaw-mcp
Or as .mcp.json in a project:
{
"mcpServers": {
"spendsaw": {
"type": "stdio",
"command": "uvx",
"args": ["spendsaw-mcp"],
"env": { "AWS_PROFILE": "default" }
}
}
}
Claude Desktop and Cursor
Claude Desktop (claude_desktop_config.json) and Cursor (~/.cursor/mcp.json or .cursor/mcp.json) use the same block:
{
"mcpServers": {
"spendsaw": {
"command": "uvx",
"args": ["spendsaw-mcp"],
"env": {
"AWS_PROFILE": "default",
"AWS_REGIONS": "us-east-1,us-west-2"
}
}
}
}
First run
- Ask: "What are my top 5 savings?"
- The first call scans the account. With every enabled region this takes a few minutes; set
AWS_REGIONSto make it faster. - Ask the assistant to explain a finding by its id to see the evidence and the commands.
Settings
The server reads the standard AWS credential chain.
| Variable | Default | Meaning |
|---|---|---|
AWS_PROFILE | default chain | Which credentials to scan with |
AWS_REGIONS | all enabled | Regions to scan, comma-separated |
SPENDSAW_MCP_CACHE_TTL_SECONDS | 21600 | How long a scan is reused, in seconds |
SPENDSAW_MCP_CACHE_DIR | ~/.cache/spendsaw-mcp | Where scans and prices are cached |
If you used the earlier package name, the old COST_MCP_CACHE_TTL_SECONDS and COST_MCP_CACHE_DIR variables still work; the new names win when both are set.
Caching and cost
- A scan is cached on disk per account for six hours. Pass
refresh: trueto a tool to scan again. - The cache holds resource ids, names and tags, and stays on your machine.
get_costsandbedrock_usagecall Cost Explorer, which AWS bills at $0.01 per request.
IAM policy
The scanner only reads. This is the full set of actions it calls. Attach it to the profile you run the server with. A scanner whose permission is missing is skipped and the rest still run, so you can start narrower.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "SpendsawMcpReadOnly",
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity",
"ce:GetCostAndUsage",
"ce:GetDimensionValues",
"pricing:GetProducts",
"tag:GetResources",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"cloudtrail:LookupEvents",
"ec2:DescribeRegions",
"ec2:DescribeInstances",
"ec2:DescribeVolumes",
"ec2:DescribeSnapshots",
"ec2:DescribeImages",
"ec2:DescribeAddresses",
"ec2:DescribeNatGateways",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"rds:DescribeDBInstances",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"s3:GetLifecycleConfiguration",
"s3:GetIntelligentTieringConfiguration",
"logs:DescribeLogGroups",
"lambda:ListFunctions",
"elasticache:DescribeCacheClusters",
"es:ListDomainNames",
"es:DescribeDomain",
"dynamodb:ListTables",
"dynamodb:DescribeTable",
"redshift:DescribeClusters",
"ecs:ListClusters",
"ecs:ListServices",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"kinesis:ListStreams",
"kinesis:DescribeStreamSummary",
"glue:GetJobs",
"glue:GetJobRuns",
"sagemaker:ListEndpoints",
"sagemaker:ListNotebookInstances",
"bedrock:ListProvisionedModelThroughputs",
"bedrock:ListCustomModels",
"bedrock:ListModelCustomizationJobs",
"bedrock:ListGuardrails",
"bedrock:ListAgents",
"bedrock:ListKnowledgeBases",
"cloudfront:ListDistributions",
"apigateway:GET",
"route53:ListHostedZones",
"secretsmanager:ListSecrets",
"iam:ListAttachedRolePolicies",
"iam:ListRolePolicies",
"iam:GetRolePolicy"
],
"Resource": "*"
}]
}
What the assistant sees
Resource names, tags and descriptions are written by whoever can create resources in your account. Before they leave the server they are flattened, stripped of control characters and truncated, and the server tells the client to treat them as data. The results go to the assistant you connected, so that assistant's provider sees them. Review any command before you run it.