What These Servers Do #
Eight minutes, both servers set up from a clean machine — that is sections 1 to 4 below. The connector failure, the proxy internals and the context cost are written up only, further down.
Amazon runs two MCP servers. Connect one to Claude and you can ask questions about your account in plain English instead of writing API calls:
- Ads — which campaigns are running in the US right now? Show me last month's spend by ad group.
- Selling Partner API — how many orders did I get in the last 7 days? What is my current FBA inventory?
MCP is just the format Amazon uses to describe its tools to an AI. You do not need to know anything about it to use this.
Before you start
You need API credentials Amazon has already approved — a client ID, a client secret and a refresh token, for whichever API you want. Three values. The browser sign-in route does not work; the section near the end explains why.
If you do not have credentials yet, start with the Advertising API onboarding guide — I have written up the direct-advertiser route through it separately. There is no shortcut around that step.
One more thing you need: the proxy script itself, which is the next section — one command.
Get the Proxy #
Make a folder and download the script into it. It is a single file with no dependencies beyond the Python standard library, so there is nothing to install.
mkdir -p ~/amazon-mcp && cd ~/amazon-mcp curl -O https://www.databaaba.com/static/downloads/mcp_proxy.py
Check it runs:
python3 mcp_proxy.py --help
That folder is now your working directory for everything below — the credential files go in it, and the script reads them from alongside itself. Put it wherever you like; the setup commands work out the absolute path on their own, so you never type it.
Read the source first if you would rather see what you are running before you run it — it is about 200 lines, and How the proxy works walks through the two functions that matter.
Windows: use Git Bash or WSL for these commands, or download the file in a browser and cd to wherever it landed. Everything else in this post works unchanged.
Set Up Amazon Ads #
- Save your three credentials
In the folder holding
mcp_proxy.py. The single quotes are not optional — a refresh token contains a pipe character, and unquoted, your shell will treat everything after it as a command.cat > .env.ads <<'EOF' ADS_CLIENT_ID='amzn1.application-oa2-client.YOUR_ID' ADS_CLIENT_SECRET='amzn1.oa2-cs.v1.YOUR_SECRET' ADS_REFRESH_TOKEN='YOUR_REFRESH_TOKEN' EOF chmod 600 .env.ads
- Check Amazon accepts them
Do this before touching Claude, so a bad credential shows up here rather than as a silent failure later.
python3 mcp_proxy.py --server ads --readonly --selftest
upstream : Amazon Ads MCP Server v1.0.0 tools : 110 total, 45 read-only mode : READ-ONLY selftest ok
- Tell Claude about it
Nothing to edit — run this from the same folder. It finds the proxy, finds Claude's config wherever your operating system keeps it, creates that file if it does not exist yet (on a fresh install it usually does not), and backs up anything already there before adding one entry.
python3 - <<'EOF' import json, os, pathlib, shutil, sys SERVER = "ads" NAME = "amazon-" + SERVER EXTRA = ["--readonly"] proxy = pathlib.Path("mcp_proxy.py").resolve() if not proxy.exists(): sys.exit("mcp_proxy.py not found. Run this from the folder you saved it in.") if sys.platform == "darwin": cfg = pathlib.Path.home() / "Library/Application Support/Claude/claude_desktop_config.json" elif sys.platform.startswith("win"): cfg = pathlib.Path(os.environ["APPDATA"]) / "Claude" / "claude_desktop_config.json" else: cfg = pathlib.Path.home() / ".config/Claude/claude_desktop_config.json" cfg.parent.mkdir(parents=True, exist_ok=True) config = {} if cfg.exists() and cfg.read_text().strip(): shutil.copy(cfg, str(cfg) + ".backup") config = json.loads(cfg.read_text()) config.setdefault("mcpServers", {})[NAME] = { "command": sys.executable, "args": [str(proxy), "--server", SERVER] + EXTRA, } cfg.write_text(json.dumps(config, indent=2)) print("proxy :", proxy) print("config :", cfg) print("servers:", ", ".join(config["mcpServers"])) EOFproxy : /Users/you/amazon-mcp/mcp_proxy.py config : /Users/you/Library/Application Support/Claude/claude_desktop_config.json servers: amazon-ads
If it says
mcp_proxy.py not found, you are in the wrong directory —cdback to the folder you downloaded it into. - Quit Claude completely and reopen it
Cmd-Q on macOS, not closing the window. The config is read when the process starts, and a closed window is still the same process.
Click the tools icon. You should see amazon-ads. Ask it something real:
Which of my campaigns are running in the US right now?
Start read-only. Of the 110 tools, 63 change state and 10 delete things — campaigns, ad groups, targets, reports. The --readonly flag exposes only the 45 that read, which costs you nothing useful on day one. It filters on Amazon's own readOnlyHint label, so treat it as an accident-preventer rather than a security control; see the FAQ.
Set Up Seller Central (SP-API) #
Same three steps, with a different set of Seller Central credentials. The server is sellingpartner-ai.amazon.com.
- Save your SP-API credentials
cat > .env.spapi <<'EOF' SPAPI_LWA_CLIENT_ID='amzn1.application-oa2-client.YOUR_ID' SPAPI_LWA_CLIENT_SECRET='amzn1.oa2-cs.v1.YOUR_SECRET' SPAPI_REFRESH_TOKEN='YOUR_REFRESH_TOKEN' EOF chmod 600 .env.spapi
- Check them
python3 mcp_proxy.py --server spapi --selftest
upstream : SpectrumAgenticInterfaceGateway v1.0.0 tools : 3 total selftest ok
Three tools, where Ads had 110. That is not a smaller API — it is a different design, and it turns out to matter a great deal. More on that below.
- Add it next to the Ads entry
The same block with one word changed. It merges, so the Ads server you added earlier survives.
python3 - <<'EOF' import json, os, pathlib, shutil, sys SERVER = "spapi" NAME = "amazon-" + SERVER EXTRA = [] proxy = pathlib.Path("mcp_proxy.py").resolve() if not proxy.exists(): sys.exit("mcp_proxy.py not found. Run this from the folder you saved it in.") if sys.platform == "darwin": cfg = pathlib.Path.home() / "Library/Application Support/Claude/claude_desktop_config.json" elif sys.platform.startswith("win"): cfg = pathlib.Path(os.environ["APPDATA"]) / "Claude" / "claude_desktop_config.json" else: cfg = pathlib.Path.home() / ".config/Claude/claude_desktop_config.json" cfg.parent.mkdir(parents=True, exist_ok=True) config = {} if cfg.exists() and cfg.read_text().strip(): shutil.copy(cfg, str(cfg) + ".backup") config = json.loads(cfg.read_text()) config.setdefault("mcpServers", {})[NAME] = { "command": sys.executable, "args": [str(proxy), "--server", SERVER] + EXTRA, } cfg.write_text(json.dumps(config, indent=2)) print("proxy :", proxy) print("config :", cfg) print("servers:", ", ".join(config["mcpServers"])) EOFservers: amazon-ads, amazon-spapi
Quit and reopen Claude again, then:
How many orders did I get in the last 7 days?
Region note: a North America refresh token worked in testing. A European token against the same endpoint returned {"code":-32001,"message":"Authentication required"}. Whether that is a token-scope issue or genuine regional availability is untested — if you only hold an EU token, verify before building on it.
If Something Does Not Work #
| Symptom | Cause | Fix |
|---|---|---|
| Nothing appears in the tools icon | Claude was closed, not quit | Cmd-Q, then reopen. Closing the window keeps the process alive. |
selftest fails on the token exchange | Wrong credential, or the app is not approved for that API | Confirm all three values, and that the application is approved for the API you are calling |
| Your other MCP servers vanished | The config was overwritten instead of merged | Restore claude_desktop_config.json.backup, sitting next to the config |
| Half your token appears in a shell error | Unquoted refresh token — the pipe was read as a command | Single-quote every value in the env file |
| Config file does not exist | Claude only creates it when a server is added | Nothing to do — the snippet creates it and its parent folder |
To check what is actually in the config:
cat "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
Why the Connector Flow Fails #
The obvious route — Settings, Connectors, Add custom connector, paste the URL — returns this:
400 Bad Request An unknown scope was requested resource=https%3A%2F%2Fadvertising-ai.amazon.com scope=advertising%3A%3Acampaign_management errorMsg=lwa-invalid-parameter-bad-scope
Not your account, not your browser profile, not a typo. It fails for everyone, because two Amazon metadata documents contradict each other.
What the resource says it needs:
curl -s https://advertising-ai.amazon.com/.well-known/oauth-protected-resource
{
"authorization_servers": ["https://lwa.amazon.com"],
"scopes_supported": ["advertising::campaign_management"]
}
What that authorization server says it issues:
curl -s https://lwa.amazon.com/.well-known/oauth-authorization-server
{
"issuer": "https://lwa.amazon.com",
"scopes_supported": ["profile"],
"code_challenge_methods_supported": ["S256"]
}
One scope: profile. Not the advertising scope the resource just demanded. And no registration_endpoint, so a client cannot register itself and ask to be granted one.
That scope is only granted to an LWA application Amazon has manually approved for the Advertising API, and there is no self-service path to becoming one. The browser flow is not an easier route to the same permission — it is the same permission, requested by a client that is not eligible to hold it.
Which is why the setup above brings its own approved application instead.
How the Proxy Works #
If you already call the Advertising API, you hold credentials for an application Amazon approved. Those three values mint an access token the MCP server accepts, with no browser involved.
So the direction flips. Rather than Claude asking Amazon for permission, a small program on your machine holds the approved credentials, mints the token, and passes messages through.
Two functions carry it. access_token() exchanges the refresh token for an access token and caches it until two minutes before expiry. forward() attaches that token as a bearer header, posts the JSON-RPC message upstream, and hands the reply back — unwrapping it first if the server answered as a Server-Sent Events stream, which it sometimes does.
One rule the whole thing depends on: log to stderr, never stdout. Stdout is the MCP channel, and a stray print corrupts the stream in a way that is genuinely annoying to debug.
The full source is here — about 200 lines, standard library only.
What the Connection Costs #
Before a model can use a tool it has to be told the tool exists — the name, what it does, and every field you can pass to it. That list sits in the context window and is re-sent with every message, because the Messages API is stateless. Tool definitions render at the front of the prompt on each request, not once at connect time.
So the size of the list is the cost of the connection. Measured 25 August 2026, tokenised with o200k_base:
| Server | Tools | Tokens | Share of a 200k window |
|---|---|---|---|
| Amazon Ads MCP | 110 | ~96,100 | 48% |
| Amazon SP-API MCP | 3 | ~509 | 0.3% |
About 135 printed pages of tool definitions for Ads, against one. Nearly half the context window spent before the first question — and 94% of it is input schemas, the blank parameter fields, not the names or the descriptions. One tool, campaign_management-create_target, carries 51,595 characters of schema by itself: more than twenty times the entire SP-API catalogue, for a single operation.
This is a known problem across the MCP ecosystem rather than something specific to Amazon. Anthropic wrote about it in Code execution with MCP, and there is an open specification issue on tool-schema overhead. The figure usually cited as the alarming case is GitHub's server at roughly 55,000 tokens. Amazon Ads is close to double it.
Why three tools beats 110
SP-API is not cheaper because it does less — it covers more operations than the Ads server. It exposes three tools and looks the rest up on demand:
| Tool | What it does |
|---|---|
search_tools | Describe what you need in plain language; get ranked matches back |
get_tool_schema | Fetch the parameters for one named tool |
call_tool | Run it |
The schemas still exist, fetched one at a time when needed rather than shipped in advance. call_tool's own schema is two fields — a tool name and an opaque params object — which is why the whole catalogue fits in half a page.
The trade is real in both directions. Ads is immediate, because everything is already known; SP-API pays two extra round trips before it can act, and needs to search well to find the right tool. But the context cost is paid on every turn while the discovery cost is paid once per new tool, so over a real conversation the second trade usually wins.
That is also where the protocol went: MCP added tool search in January 2026 for exactly this reason. If you are building an MCP server, copy this shape. It is the same instinct as choosing the narrowest data source that answers the question — the reasoning in AMS vs AMC applies to tool surfaces as much as to datasets.
Cutting the Cost #
- Keep
--readonly45 tools instead of 110 takes the catalogue from roughly 96,000 tokens to about 17,500 — 48% of the window down to under 9%.
- Hand-pick what you use
A working set of nine read tools — campaigns, ad groups, targets, ads, portfolios, reports, accounts — measured around 6,000 tokens. That is 3% of the window for most reporting work.
- Sort the tool list before handing it over
Prompt caching is a byte-exact prefix match, and tool definitions sit at position zero of the prompt. The Ads server returns the same 110 tools in a different order on each connection — in testing, 22 of 110 positions moved between two calls a second apart, with per-tool JSON identical once sorted by name. Unsorted, every reconnect writes a fresh cache entry instead of reading the existing one.
tools = sorted(response["result"]["tools"], key=lambda t: t["name"])
Worth knowing: caching changes the price of re-sending the catalogue, not the fact of it. Cached reads bill at roughly a tenth of base input rate, so running cost drops sharply after the first message — but the tokens still occupy the window every turn. The 48% figure is about space, not money.
One thing MCP does not change: the underlying API still throttles, and an agent fanning out across tools hits that faster than a person clicking through a console. The rate limits behave the way they always did.
FAQ #
How do I connect Claude to Amazon Ads?
Save your Advertising API client ID, client secret and refresh token to an env file, run a local proxy that mints an access token from them, and register that proxy in Claude's desktop config as an MCP server. The built-in connector flow does not work — it fails with an unknown-scope error before reaching your account.
Why does adding Amazon Ads MCP to Claude fail with an unknown scope error?
The Ads MCP resource requires the scope advertising::campaign_management and names lwa.amazon.com as its authorization server, but that server publishes only the profile scope and offers no dynamic client registration. A generic MCP client asks for a scope it can never be granted, and LWA returns 400 lwa-invalid-parameter-bad-scope.
Where is the Claude desktop config file, and what if it does not exist?
On macOS it is ~/Library/Application Support/Claude/claude_desktop_config.json; on Windows %APPDATA%\Claude\claude_desktop_config.json. Claude only creates it once a server has been added, so on a fresh install it is normal for the file to be missing — the setup snippet creates it and its parent folder.
Can I connect without Amazon API credentials?
No. The advertising scope is only issued to applications Amazon has manually approved, and no browser flow obtains it. You need an approved application and a refresh token first.
How many tokens does the Amazon Ads MCP server use?
Its tool list measured roughly 96,100 tokens in August 2026 — 110 tools, about 404,000 characters, or 48% of a 200,000-token context window, re-sent on every message. Around 94% of that is input schemas rather than names or descriptions. Filtering to the 45 read-only tools reduces it to about 17,500.
Does read-only mode make the connection safe?
It reduces accidents, not risk. The filter relies on Amazon's self-declared readOnlyHint label, and MCP annotations are hints rather than enforced guarantees — a tool can declare itself read-only and still write. Treat it as a convenience and a context-size reduction, not a security boundary.
Related guides
Amazon Ads API
Amazon Ads API Rate Limits Explained (And How to Stop Hitting Them)
A 429 storm from the Amazon Ads API almost always means your retry logic is wrong, not that your limits are too low. Here's how the per-endpoint token buckets actually work, what the response headers tell you, and the request patterns that keep you comfortably under the limit.
Read guide →Amazon Ads API
AMS vs AMC: Which Amazon Ad Data Tool to Use, and When
Amazon Marketing Stream and Amazon Marketing Cloud both start with “Amazon Marketing” and both deal with ad data — so they get mixed up constantly. They actually solve opposite problems. One pushes you hourly data for real-time action; the other runs SQL over years of history for attribution. Here's the difference, side by side, and how to pick.
Read guide →Amazon Ads API
Amazon Marketing Stream Setup: Hourly Ad Data via Firehose in Python
Stop polling the Reports API. Amazon Marketing Stream pushes hourly Sponsored Products, Sponsored Brands, and Sponsored Display data directly into your AWS infrastructure — no cron jobs, no throttling. Here's the full setup: Firehose stream, IAM roles, the subscription API call, and what the data looks like when it lands.
Read guide →Don't want to build this yourself?
I set this up for sellers and agencies every week. Book a free 30-minute audit of your Amazon data & PPC setup — you'll leave with a plan either way, whether we work together or not.