MCP server for Walmart Connect Ads, Sam's Club Sponsored Ads, and Walmart Marketplace APIs
Runs on your machine from its package; the steps are below.
At most: creates or changes data: records, files, settings.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx mcp-walmart-ads
MCP client config
{
"mcpServers": {
"walmart-sam-s-club-apis": {
"command": "uvx",
"args": [
"mcp-walmart-ads"
]
}
}
}MCP server for three Walmart Inc. API families, behind one tool surface:
| Platform | APIs | Auth |
|---|---|---|
walmart:ads — Walmart Connect | Sponsored Products, Display | RSA-SHA256 signature + bearer token |
walmart:marketplace — Walmart Marketplace | 28 domains (orders, items, feeds, reports, …) | OAuth2 client_credentials |
samsclub:ads — Sam's Club | Sponsored Products | RSA-SHA256 signature + bearer token |
Four tools over 31 apis and 424 operations — discovery, a generic proxy, and a downloader. An agent finds endpoints in the bundled OpenAPI specs and calls them; the server signs, acquires tokens, and builds headers.
walmart:ads:sponsored-products:SBAProfileUpdateV2
└─ retailer ─┘└ line ┘└─── api name ───┘└── operationId ──┘
└────────── platform ─────────┘ credentials attach here
An operation id alone resolves to a host and an auth model. A matching <line>:<name>
suffix means the same surface for another retailer — 13 shared operation ids of 90.
Set up your config (see Configuration), then run the server:
# Run directly with uvx (no clone needed)
npx -y @modelcontextprotocol/inspector@latest uvx mcp-walmart-ads
# Or run from source
git clone https://github.com/alyiox/mcp-walmart-ads.git
cd mcp-walmart-ads
uv sync
npx -y @modelcontextprotocol/inspector@latest uv run mcp-walmart-ads
config.json MUST live at ~/.config/mcp-walmart-ads/config.json. The server reads it
once at startup, so a corrected file REQUIRES a restart.
Windows:
~maps to%USERPROFILE%(typicallyC:\Users\<you>), making the full path%USERPROFILE%\.config\mcp-walmart-ads\config.json.
Create the directory and copy the example:
# Unix-like (macOS, Linux, WSL, …)
mkdir -p ~/.config/mcp-walmart-ads/keys/walmart-ads
cp config.example.json ~/.config/mcp-walmart-ads/config.json
# Windows (PowerShell)
New-Item -ItemType Directory -Force "$env:USERPROFILE\.config\mcp-walmart-ads\keys\walmart-ads"
Copy-Item config.example.json "$env:USERPROFILE\.config\mcp-walmart-ads\config.json"
platforms.<platform>.regions.<region>.<environment> = <auth block>
<platform> is the two-segment prefix an api id starts with, so a config key is literally
the value you pass as the platform tool parameter — nothing to translate.
The auth block's shape follows the platform's auth model. There is exactly one shape per platform, so no discriminator field is needed.
Signature platforms (walmart:ads, samsclub:ads):
{
"platforms": {
"walmart:ads": {
"regions": {
"us": {
"production": {
"consumer_id": "your-consumer-id",
"private_key": "./keys/walmart-ads/us-prod.pem",
"private_key_version": "1",
"bearer_token": "your-bearer-token",
"base_urls": {
"sponsored-products": "https://developer.api.walmart.com/api-proxy/service/WPA/Api/v1",
"display": "https://developer.api.walmart.com/api-proxy/service/display/api/v1"
}
}
}
}
}
}
}
| Field | Notes |
|---|---|
consumer_id | Partner Network consumer ID |
private_key | Path to the RSA private key (PEM). Relative paths resolve against the config directory |
private_key_version | Key version string (default "1") |
bearer_token | OAuth bearer token |
base_urls.<api> | One per api in the platform's discovery surface. Keys MAY be bare (sponsored-products) or fully qualified (walmart:ads:sponsored-products); extra keys are allowed for the auxiliary specs reached by raw method+path |
Environment names are free-form here — Walmart may issue a tenant only production, or
production and staging.
OAuth2 platform (walmart:marketplace):
{
"platforms": {
"walmart:marketplace": {
"regions": {
"us": {
"production": {
"credentials": [
{
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"advertisers": [
{ "id": 7060158, "partner_id": "10001234" },
{ "id": 7060159 }
]
}
]
}
}
}
}
}
}
environment MUST be production or sandbox; base URLs are fixed by the server and
absent from the file. Advertiser ids nest under the credential that serves them, so a
secret appears exactly once and a dangling advertiser reference is structurally
impossible. partner_id is per-seller because two payments operations require it as
WM_PARTNER_ID; an all-zero value reads as absent, since that is what a generated config
writes for a seller without one, and scripts/backfill_partner_ids.py fills the gaps from
Walmart.
Regions are a namespace, not a route — every walmart:marketplace region reaches the same
hosts. The level exists because advertiser ids are unique only within a region.
A populated walmart:marketplace block runs to tens of kilobytes of credentials, 88% of
the file here, and a stray comma while editing it takes down every platform: a parse
failure precedes per-platform validation. Platforms MAY therefore live in drop-in files
under config.d/, merged over the base:
~/.config/mcp-walmart-ads/
├── config.json # server-wide settings, and any platforms you like
├── config.d/
│ ├── walmart-marketplace.json # only a "platforms" object
│ └── samsclub-ads.json
└── keys/
platforms; server-wide settings stay in config.json.*.json directly in config.d/ is read, so .bak and editor swap files are ignored.private_key paths resolve against config.json's directory either way, so
moving a platform into config.d/ needs no path edits.config.d/ directory means no change in behavior.A malformed block for one platform MUST NOT stop the others loading. Read
wmt://platforms to see which loaded and what regions and environments they declare: one
that failed has no regions, and reading one of its environments returns the loader's own
message — which file, which fields, and that a fix needs a restart.
| Field | Default | Notes |
|---|---|---|
response_cache_ttl | 3600 | Seconds a truncated body or download stays readable at its resource URI |
truncate_threshold | 2048 | Response bytes returned inline before truncating to a preview |
spec_refresh | {"auto": true, "interval": 7} | Background spec refresh. auto turns the sweep on or off; interval is days between sweeps, and MAY be fractional |
wap-tenant-id)Pass tenant on call_endpoint and download_file for non-US walmart:ads markets
(WMT_CA, WMT_MX, WBD_OD, …). Omit it for US and for walmart:marketplace.
list_endpointsList operations across every api, with optional filters.
| Parameter | Notes |
|---|---|
query | Case-insensitive substring on operation id, path, or summary |
api | Limit to one api, e.g. walmart:marketplace:order-management |
platform | Limit to one platform — walmart:ads, walmart:marketplace, samsclub:ads (schema enum) |
tag | Filter by OpenAPI tag |
method | Filter by HTTP verb — GET, POST, PUT, PATCH, DELETE (schema enum) |
Returned operation ids are qualified (api:operationId) and pass straight to
describe_endpoint or call_endpoint.
describe_endpointOne operation plus every components.schemas entry reachable from it, so a request body
can be built without the full spec. Server-managed auth and QoS headers are omitted.
| Parameter | Notes |
|---|---|
operation_id | Qualified (api:operationId), or bare when unambiguous |
api | Api to resolve a bare id in, e.g. walmart:ads:sponsored-products |
call_endpointExecute an authenticated request against any configured platform. Signing, token acquisition with per-credential caching and single-flight refresh, and one retry after a 401 all happen server-side.
| Parameter | Notes |
|---|---|
region, environment | Required. Src: config |
operation_id | Qualified or bare. Resolves api, platform, method, path, and required headers |
api | Required with raw method + path; otherwise inferred from operation_id. Accepts the two auxiliary walmart:ads specs |
method, path | Raw route, reaching alpha/beta/unpublished endpoints absent from the specs |
path_params | Values for {placeholders} in the path |
params, body | Query string and JSON body |
file_path | Send the file as multipart/form-data — Marketplace feed uploads. Pair with the feedType query parameter |
advertiser_id | MUST be given on walmart:marketplace, where it selects the credential. Optional on the ads platforms, where it is sent as X-Advertiser-ID |
tenant | WAP tenant for non-US walmart:ads regions |
A response larger than truncate_threshold is previewed inline, with the full body at
wmt://responses/{request_id} and a reproducible cURL at wmt://curl/{request_id} —
bearer tokens, access tokens, and signatures replaced with placeholders.
download_fileDownload a report, label, or snapshot from an authenticated endpoint. Give a full url
(the details URL from a display snapshot poll, say), or operation_id, or api with
method + path. platform is required only for a bare url.
With dest_path the bytes are written there. Without it they are gunzipped when gzipped
and cached, and the result carries cached_at — a binary payload with no dest_path asks
for one instead. Redirects are followed, keeping auth headers on a relative or same-host
Location and dropping credentials cross-host; the result includes urls, the hop path.
33 OpenAPI documents ship in the wheel. A refresh writes updated copies to
~/.cache/mcp-walmart-ads/specs/, which outranks the bundle on read. The bundle is never
written at runtime, so it stays the floor a damaged or missing cache falls back to.
Walmart publishes no OpenAPI files, but each ReadMe reference page hydrates its HTML with
the registry UUIDs of its documents, and https://dash.readme.com/api/v1/api-registry/<uuid>
serves the full spec unauthenticated. That covers Walmart Connect and all 28 Marketplace
domains. Sam's Club publishes neither, so its spec is hand-authored from the developer
docs: scripts/build_samsclub_spec.py regenerates a candidate from those docs, and the
scheduled spec drift workflow opens a PR when they change, as a human review gate. The
candidate is never shipped and never loaded at runtime.
Documents are stored verbatim as upstream served them, so a refresh diff shows exactly
what changed. Oversized inline examples and x-readme metadata are stripped on load
rather than on disk, which keeps that reduction retunable without re-downloading anything.
Specs refresh in the background: once at startup, then every spec_refresh.interval days
(7 by default). Set auto to false to stop the sweep — the interval is remembered for
whenever you turn it back on, and the manual refresh still works.
To refresh now, having hit an endpoint the bundled spec does not have:
# Installed with uvx (no clone)
uvx mcp-walmart-ads --refresh
# Or from a source checkout
uv run mcp-walmart-ads --refresh
Either form sweeps every spec regardless of the interval and writes the same user cache,
printing one row per document: written, unchanged, or error.
There is deliberately no tool for this. A stale spec is indistinguishable from a current one from inside a session — it simply lacks an endpoint — so an agent asked to decide would either never refresh or refresh superstitiously after an unrelated failure. The trigger belongs outside the session.
A document MUST yield at least one operation before it is installed, so an upstream
answering 200 with an error body cannot poison the cache, and a byte-identical document
is left alone. A cached file that will not load is discarded and the read falls back to
the bundle. Servers coordinate through spec-state.json at the cache root, which records
when each spec was last tried and holds a lease so one process sweeps at a time — every
client session runs its own server process, and without it each would re-download all 33.
A maintainer step, and not the same thing as --refresh: that updates your cache, this
updates the copies that ship in the wheel.
# Registry-sourced specs only, by default
uv run python scripts/fetch_specs.py
uv run python scripts/fetch_specs.py walmart:ads:sponsored-products walmart:marketplace:order-management
# Regenerate the Sam's Club candidate spec for review
uv run --group spec-build python scripts/build_samsclub_spec.py
| Resource URI | Description |
|---|---|
wmt://platforms | Every platform, its auth model, and the regions and environments it declares |
wmt://platforms/{platform}/apis | That platform's api ids |
wmt://platforms/{platform}/apis/{name} | One api: title, version, operation count, and its tags with a count each — the legal list_endpoints(tag=…) values |
wmt://platforms/walmart:marketplace/regions/{region}/{environment}/advertisers | Advertiser ids mapped to their Walmart Partner ID (null when unset) |
wmt://platforms/{platform}/regions/{region}/{environment}/hosts | Api ids mapped to the base URL a call reaches, or * for every api whose host the server owns |
wmt://responses/{request_id} | Full body of a truncated response or a cached download (in memory, TTL from config) |
wmt://curl/{request_id} | Reproducible cURL for a previous request, credentials replaced with placeholders |
Add to .cursor/mcp.json:
{
"mcpServers": {
"walmart": {
"command": "uvx",
"args": ["mcp-walmart-ads"]
}
}
}
Add to your Claude Code MCP config:
{
"mcpServers": {
"walmart": {
"command": "uvx",
"args": ["mcp-walmart-ads"]
}
}
}
[mcp_servers.walmart]
command = "uvx"
args = ["mcp-walmart-ads"]
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"walmart": {
"type": "local",
"enabled": true,
"command": ["uvx", "mcp-walmart-ads"]
}
}
}
{
"inputs": [],
"servers": {
"walmart": {
"type": "stdio",
"command": "uvx",
"args": ["mcp-walmart-ads"]
}
}
}
uv sync --group dev
uv run ruff check src/ tests/ scripts/
uv run ruff format --check src/ tests/ scripts/
uv run pyright
uv run pytest tests/ -v
Issues and pull requests are welcome. Keep changes focused; ruff check,
ruff format --check, pyright, and pytest MUST all pass.