mcp-samsclub-ads

MCP server for Sam's Club Sponsored Ads APIs — spec-driven endpoint discovery and a generic authenticated proxy with automatic RSA-SHA256 request signing.

Documentation

Sam's Club Sponsored Ads APIs

CI PyPI Python 3.13+ License: MIT

MCP server for Sam's Club Sponsored Ads APIs.

Exposes spec-driven discovery (list_endpoints, describe_endpoint), a generic API proxy (call_endpoint), and a runtime spec refresher (refresh_specs). The AI agent discovers endpoints from a bundled OpenAPI spec then calls them; the server handles RSA-SHA256 signing and auth headers automatically.

Forked from mcp-walmart-ads: Sam's Club runs on the same Walmart api-proxy backend and uses the same WM_* signing headers, so the auth, HTTP client, discovery, caching, and tool surface carry over. The differences are a single sponsored ad family (vs Walmart's search/display), the Sam's Club base URLs, and a hand-authored OpenAPI spec.

Features

  • Spec-driven discovery — list/describe endpoints from a bundled OpenAPI spec, refreshable at runtime
  • Any endpoint — call by operation id or raw method+path (raw path reaches endpoints not yet in the spec); no code changes when APIs evolve
  • Multi-region, multi-environment (production + sandbox) via config file
  • Per-request RSA-SHA256 signing with automatic header construction
  • Large responses truncated with full data available via MCP resource URI

Requirements

  • Python 3.13+
  • Sam's Club Partner credentials (consumer ID, RSA key pair, bearer token) — see Status & blocker

Quick start

Set up your config (see Configuration), then run the server:

# Run directly with uvx (no clone needed)
npx -y @modelcontextprotocol/inspector uvx mcp-samsclub-ads
# Or run from source
git clone https://github.com/alyiox/mcp-samsclub-ads.git
cd mcp-samsclub-ads
uv sync
npx -y @modelcontextprotocol/inspector uv run mcp-samsclub-ads

The server speaks MCP over stdio.

Configuration

The config file lives under your home directory at ~/.config/mcp-samsclub-ads/config.json.

Windows note: ~ maps to %USERPROFILE% (typically C:\Users\<you>), so the full path is %USERPROFILE%\.config\mcp-samsclub-ads\config.json.

1. Create the config directory and copy the example

# Unix-like (macOS, Linux, WSL, …)
mkdir -p ~/.config/mcp-samsclub-ads/keys/us
cp config.example.json ~/.config/mcp-samsclub-ads/config.json
# Windows (PowerShell)
New-Item -ItemType Directory -Force "$env:USERPROFILE\.config\mcp-samsclub-ads\keys\us"
Copy-Item config.example.json "$env:USERPROFILE\.config\mcp-samsclub-ads\config.json"

2. Edit ~/.config/mcp-samsclub-ads/config.json

{
  "response_cache_ttl": 3600,
  "truncate_threshold": 51200,
  "regions": {
    "US": {
      "production": {
        "consumer_id": "your-consumer-id",
        "private_key": "./keys/us/prod.pem",
        "private_key_version": "1",
        "bearer_token": "your-bearer-token",
        "base_urls": {
          "sponsored": "https://developer.api.us.walmart.com/api-proxy/service/sp/api-sams/v1"
        }
      },
      "sandbox": {
        "consumer_id": "your-sandbox-consumer-id",
        "private_key": "./keys/us/sandbox.pem",
        "private_key_version": "1",
        "bearer_token": "your-sandbox-bearer-token",
        "base_urls": {
          "sponsored": "https://developer.api.us.stg.walmart.com/api-proxy/service/sp/api-sams/v1"
        }
      }
    }
  }
}

3. Place your RSA private key PEM files in ~/.config/mcp-samsclub-ads/keys/

Key paths in the config are resolved relative to the config directory, so ./keys/us/prod.pem resolves to ~/.config/mcp-samsclub-ads/keys/us/prod.pem.

Config fieldDescription
response_cache_ttlSeconds to keep truncated responses in memory (default 3600)
truncate_thresholdResponse byte limit before truncation (default 51200)
regions.<R>.<E>.consumer_idYour Sam's Club consumer ID
regions.<R>.<E>.private_keyPath to RSA private key PEM (relative to config dir or absolute)
regions.<R>.<E>.private_key_versionKey version string (default "1")
regions.<R>.<E>.bearer_tokenOAuth bearer token
regions.<R>.<E>.base_urls.sponsoredSponsored Ads API base URL

Each request is signed with WM_CONSUMER.ID, WM_SEC.AUTH_SIGNATURE (Base64 RSA-SHA256), WM_SEC.KEY_VERSION, and WM_CONSUMER.INTIMESTAMP (Unix epoch ms), plus Authorization: Bearer <token> — all injected automatically by the client.

Base URLs

  • Production: https://developer.api.us.walmart.com/api-proxy/service/sp/api-sams/v1
  • Sandbox: https://developer.api.us.stg.walmart.com/api-proxy/service/sp/api-sams/v1

Paths in the spec begin /api/v1/... (or /api/v2/...), so the effective URL is <base_url>/api/v1/<resource>.

Tools

list_endpoints

List operations from the bundled OpenAPI spec for an ad_type, with optional filters.

ParameterRequiredDescription
ad_typeyessponsored
querynoCase-insensitive substring match on operationId, path, or summary
tagnoFilter to operations whose OpenAPI tags include this value
methodnoFilter by HTTP verb

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.

ParameterRequiredDescription
ad_typeyessponsored
operation_idyesSpec operation id (from list_endpoints)

call_endpoint

Execute any Sam's Club Sponsored Ads API endpoint. Identify it by operation_id, or by raw method + path. Raw method+path also reaches endpoints not yet in the bundled spec. The server handles RSA-SHA256 signing.

ParameterRequiredDescription
regionyese.g. US
envyesproduction or sandbox
ad_typeyessponsored
operation_idno*Spec operation id; resolves method+path
methodno*GET, POST, PUT, PATCH, or DELETE
pathno*e.g. /api/v1/campaigns
paramsnoQuery string parameters (JSON object)
bodynoJSON request body for POST/PUT (object or array)

* Provide either operation_id, or both method and path.

refresh_specs

Re-fetch the bundled OpenAPI spec from its committed source_url into a user cache (~/.cache/mcp-samsclub-ads/specs/) that takes precedence over the bundled copy — ship fixes without a release.

ParameterRequiredDescription
spec_idnoOne spec to refresh (e.g. sponsored/sponsored-ads); omit to refresh all

MCP resources

Endpoint schemas come from the bundled OpenAPI spec via list_endpoints / describe_endpoint (see Tools), not from static resources.

Dynamic resources

Resource URIDescription
sca://configAvailable regions, environments, and ad types from your config
sca://responses/{request_id}Full body of a truncated API response (cached in memory, TTL from config)
sca://curl/{request_id}Reproducible cURL command for a previous API request

Spec coverage

The bundled OpenAPI spec is hand-authored from the developer.samsclub.com docs and is partial by design. It covers the core ads surface: Campaigns, Statistics, Ad Groups, Ad Items, Catalog Item Search, Keywords (+ suggestions/analytics), Negative Keywords, Placements, Bid Multipliers, and Snapshot Reports v1/v2 + Latest Report Date. Endpoints not yet modeled (SBA profiles, media, reviews, api-usage, entity/audit snapshots, alerts & recommendations) are still reachable via call_endpoint with a raw method+path.

Status & blocker

Partner credentials are the long pole. Access requires emailing an RSA public key to partner-support@samsclub.com and receiving a consumer id + auth token + key version. Without sandbox credentials the server runs and its discovery/signing are verified offline, but calls cannot be exercised end-to-end.

Spec drift detection

scripts/build_spec.py scrapes the docs into a candidate spec at spec-candidate/ — offline, never at runtime. The Spec drift workflow runs it on a schedule and opens a PR when the docs change; a reviewer then ports real changes into the hand-authored canonical spec. The candidate is intentionally flat and is never shipped or loaded.

uv run --group spec-build python scripts/build_spec.py          # write candidate
uv run --group spec-build python scripts/build_spec.py --check  # exit 1 on drift

Still deferred: flipping a spec's auth flag to fetch the authenticated live Swagger backend once partner credentials exist.

MCP host examples

Cursor

Add to .cursor/mcp.json:

{
  "mcpServers": {
    "samsclub-ads": {
      "command": "uvx",
      "args": ["mcp-samsclub-ads"]
    }
  }
}

Claude Code

Add to your Claude Code MCP config:

{
  "mcpServers": {
    "samsclub-ads": {
      "command": "uvx",
      "args": ["mcp-samsclub-ads"]
    }
  }
}

Codex

[mcp_servers.samsclub-ads]
command = "uvx"
args = ["mcp-samsclub-ads"]

OpenCode

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

GitHub Copilot

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

Development

uv sync --group dev    # install deps
uv run pytest          # run tests
uv run ruff check .    # lint
uv run ruff format .   # format
uv run pyright         # type check

Reference

Contributing

Open issues or PRs. Follow existing style and add tests where appropriate.

License

MIT. See LICENSE.