# Walmart Connect Advertising APIs [Health: Active]

**Category:** 💻 Developer Tools  
**Repository:** https://github.com/alyiox/mcp-walmart-ads  
**GitHub Stars:** 1  
**Views:** 1  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/walmart-connect-advertising-apis

## Description
MCP server for Walmart Connect Ads APIs (Sponsored Search + Display)

## Tools
Capabilities this server exposes over MCP:

- **list_endpoints** — List operations from the bundled OpenAPI spec for an `ad_type`, with optional filters.
- **describe_endpoint** — Return one operation plus the `components.schemas` reachable from it (its `$ref` closure), so request bodies and responses can be built without the full spec.
- **call_endpoint** — Execute any Walmart Connect Ads API endpoint. Identify it by `operation_id`, or by raw `method` + `path`. Raw method+path also reaches alpha/beta/unpublished endpoints that are not in the bundled specs. The server handles RSA-SHA256 signing.
- **refresh_specs** — Re-fetch the bundled OpenAPI specs from ReadMe's public api-registry into a user cache (`~/.cache/mcp-walmart-ads/specs/`) that takes precedence over the bundled copy.
- **download_display_snapshot** — Download a display snapshot file (report or entity). Display snapshot URLs require authenticated requests, so this tool handles the signing automatically. Use the full download URL from the `details` field after polling a display snapshot to `done` status.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "walmart-connect-advertising-apis": {
    "command": "uvx",
    "args": ["mcp-walmart-ads"]
  }
}
```

## Documentation & README

# Walmart & Sam's Club APIs

[![CI](https://github.com/alyiox/mcp-walmart-ads/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/alyiox/mcp-walmart-ads/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/mcp-walmart-ads.svg)](https://pypi.org/project/mcp-walmart-ads/)
[![Python 3.13+](https://img.shields.io/badge/python-3.13%2B-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

<!-- mcp-name: io.github.alyiox/mcp-walmart-ads -->

MCP server for three Walmart Inc. API families, behind one tool surface:

| Platform | APIs | Auth |
|---|---|---|
| `walmart:ads` — [Walmart Connect](https://developer.walmart.com/advertising-partners) | Sponsored Products, Display | RSA-SHA256 signature + bearer token |
| `walmart:marketplace` — [Walmart Marketplace](https://developer.walmart.com/home/us-mp) | 28 domains (orders, items, feeds, reports, …) | OAuth2 `client_credentials` |
| `samsclub:ads` — [Sam's Club](https://developer.samsclub.com) | 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.

## Requirements

- Python 3.13+
- Credentials for whichever platforms you use. Configure only those — an absent platform
  is simply unconfigured, and discovery works with no credentials at all.
  - **Walmart Connect / Sam's Club** — consumer ID, RSA key pair, bearer token
  - **Walmart Marketplace** — client ID + secret, and the advertiser (seller profile) ids
    they serve

## Quick start

Set up your config (see [Configuration](#configuration)), then run the server:

```bash
# Run directly with uvx (no clone needed)
npx -y @modelcontextprotocol/inspector@latest uvx mcp-walmart-ads
```

```bash
# 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
```

## Configuration

`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%` (typically `C:\Users\<you>`), making the full
> path `%USERPROFILE%\.config\mcp-walmart-ads\config.json`.

Create the directory and copy the example:

```bash
# Unix-like (macOS, Linux, WSL, …)
mkdir -p ~/.config/mcp-walmart-ads/keys/walmart-ads
cp config.example.json ~/.config/mcp-walmart-ads/config.json
```

```powershell
# 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"
```

### Shape

```
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`):

```json
{
  "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`):

```json
{
  "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.

### Splitting the config

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/
```

- A drop-in MUST declare only `platforms`; server-wide settings stay in `config.json`.
- A platform declared in two files is an **error naming both** — never silent precedence.
- Only `*.json` directly in `config.d/` is read, so `.bak` and editor swap files are ignored.
- A file that fails to parse costs **only its own platforms**; the rest keep working.
- Relative `private_key` paths resolve against `config.json`'s directory either way, so
  moving a platform into `config.d/` needs no path edits.
- No `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.

### Top-level options

| 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 |

### Market → tenant (`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`.

## Tools

### `list_endpoints`

List 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_endpoint`

One 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_endpoint`

Execute 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_file`

Download 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.

## Specs

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.

### Where they come from

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.

### Keeping them current

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:

```bash
# 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.

### Rebuilding the bundle

A maintainer step, and not the same thing as `--refresh`: that updates your cache, this
updates the copies that ship in the wheel.

```bash
# 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
```

## MCP resources

| 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 |

## MCP host examples

<details>
<summary><b>Cursor</b></summary>

Add to `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "walmart": {
      "command": "uvx",
      "args": ["mcp-walmart-ads"]
    }
  }
}
```

</details>

<details>
<summary><b>Claude Code</b></summary>

Add to your Claude Code MCP config:

```json
{
  "mcpServers": {
    "walmart": {
      "command": "uvx",
      "args": ["mcp-walmart-ads"]
    }
  }
}
```

</details>

<details>
<summary><b>Codex</b></summary>

```toml
[mcp_servers.walmart]
command = "uvx"
args = ["mcp-walmart-ads"]
```

</details>

<details>
<summary><b>OpenCode</b></summary>

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "walmart": {
      "type": "local",
      "enabled": true,
      "command": ["uvx", "mcp-walmart-ads"]
    }
  }
}
```

</details>

<details>
<summary><b>GitHub Copilot</b></summary>

```json
{
  "inputs": [],
  "servers": {
    "walmart": {
      "type": "stdio",
      "command": "uvx",
      "args": ["mcp-walmart-ads"]
    }
  }
}
```

</details>

## Development

```bash
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
```

## Contributing

Issues and pull requests are welcome. Keep changes focused; `ruff check`,
`ruff format --check`, `pyright`, and `pytest` MUST all pass.

## License

[LICENSE](https://github.com/alyiox/mcp-walmart-ads/blob/HEAD/MIT).

