{
  "markdown": "# Sam's Club Sponsored Ads APIs\n\n[![CI](https://github.com/alyiox/mcp-samsclub-ads/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/alyiox/mcp-samsclub-ads/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/mcp-samsclub-ads.svg)](https://pypi.org/project/mcp-samsclub-ads/)\n[![Python 3.13+](https://img.shields.io/badge/python-3.13%2B-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)\n\n<!-- mcp-name: io.github.alyiox/mcp-samsclub-ads -->\n\nMCP server for [Sam's Club Sponsored Ads APIs](https://developer.samsclub.com/API/overview/).\n\nExposes 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.\n\nForked from [`mcp-walmart-ads`](https://github.com/alyiox/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.\n\n## Features\n\n- **Spec-driven discovery** — list/describe endpoints from a bundled OpenAPI spec, refreshable at runtime\n- **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\n- Multi-region, multi-environment (production + sandbox) via config file\n- Per-request RSA-SHA256 signing with automatic header construction\n- Large responses truncated with full data available via MCP resource URI\n\n## Requirements\n\n- Python 3.13+\n- Sam's Club Partner credentials (consumer ID, RSA key pair, bearer token) — see [Status & blocker](#status--blocker)\n\n## Quick start\n\nSet up your config (see [Configuration](#configuration)), then run the server:\n\n```bash\n# Run directly with uvx (no clone needed)\nnpx -y @modelcontextprotocol/inspector uvx mcp-samsclub-ads\n```\n\n```bash\n# Or run from source\ngit clone https://github.com/alyiox/mcp-samsclub-ads.git\ncd mcp-samsclub-ads\nuv sync\nnpx -y @modelcontextprotocol/inspector uv run mcp-samsclub-ads\n```\n\nThe server speaks MCP over stdio.\n\n## Configuration\n\nThe config file lives under your home directory at `~/.config/mcp-samsclub-ads/config.json`.\n\n> **Windows note:** `~` maps to `%USERPROFILE%` (typically `C:\\Users\\<you>`), so the\n> full path is `%USERPROFILE%\\.config\\mcp-samsclub-ads\\config.json`.\n\n**1. Create the config directory and copy the example**\n\n```bash\n# Unix-like (macOS, Linux, WSL, …)\nmkdir -p ~/.config/mcp-samsclub-ads/keys/us\ncp config.example.json ~/.config/mcp-samsclub-ads/config.json\n```\n\n```powershell\n# Windows (PowerShell)\nNew-Item -ItemType Directory -Force \"$env:USERPROFILE\\.config\\mcp-samsclub-ads\\keys\\us\"\nCopy-Item config.example.json \"$env:USERPROFILE\\.config\\mcp-samsclub-ads\\config.json\"\n```\n\n**2. Edit `~/.config/mcp-samsclub-ads/config.json`**\n\n```json\n{\n  \"response_cache_ttl\": 3600,\n  \"truncate_threshold\": 1024,\n  \"regions\": {\n    \"US\": {\n      \"production\": {\n        \"consumer_id\": \"your-consumer-id\",\n        \"private_key\": \"./keys/us/prod.pem\",\n        \"private_key_version\": \"1\",\n        \"bearer_token\": \"your-bearer-token\",\n        \"base_urls\": {\n          \"sponsored\": \"https://developer.api.us.walmart.com/api-proxy/service/sp/api-sams/v1\"\n        }\n      },\n      \"sandbox\": {\n        \"consumer_id\": \"your-sandbox-consumer-id\",\n        \"private_key\": \"./keys/us/sandbox.pem\",\n        \"private_key_version\": \"1\",\n        \"bearer_token\": \"your-sandbox-bearer-token\",\n        \"base_urls\": {\n          \"sponsored\": \"https://developer.api.us.stg.walmart.com/api-proxy/service/sp/api-sams/v1\"\n        }\n      }\n    }\n  }\n}\n```\n\n**3. Place your RSA private key PEM files in `~/.config/mcp-samsclub-ads/keys/`**\n\nKey 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`.\n\n| Config field | Description |\n|---|---|\n| `response_cache_ttl` | Seconds to keep truncated responses in memory (default `3600`) |\n| `truncate_threshold` | Response byte limit before truncation (default `1024`) |\n| `regions.<R>.<E>.consumer_id` | Your Sam's Club consumer ID |\n| `regions.<R>.<E>.private_key` | Path to RSA private key PEM (relative to config dir or absolute) |\n| `regions.<R>.<E>.private_key_version` | Key version string (default `\"1\"`) |\n| `regions.<R>.<E>.bearer_token` | OAuth bearer token |\n| `regions.<R>.<E>.base_urls.sponsored` | Sponsored Ads API base URL |\n\nEach 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.\n\n### Base URLs\n\n- **Production:** `https://developer.api.us.walmart.com/api-proxy/service/sp/api-sams/v1`\n- **Sandbox:** `https://developer.api.us.stg.walmart.com/api-proxy/service/sp/api-sams/v1`\n\nPaths in the spec begin `/api/v1/...` (or `/api/v2/...`), so the effective URL is `<base_url>/api/v1/<resource>`.\n\n## Tools\n\n### `list_endpoints`\n\nList operations from the bundled OpenAPI spec for an `ad_type`, with optional filters.\n\n| Parameter | Required | Description |\n|---|---|---|\n| `ad_type` | yes | `sponsored` |\n| `query` | no | Case-insensitive substring match on operationId, path, or summary |\n| `tag` | no | Filter to operations whose OpenAPI tags include this value |\n| `method` | no | Filter by HTTP verb |\n\n### `describe_endpoint`\n\nReturn one operation plus the `components.schemas` reachable from it (its `$ref` closure), so request bodies and responses can be built without the full spec.\n\n| Parameter | Required | Description |\n|---|---|---|\n| `ad_type` | yes | `sponsored` |\n| `operation_id` | yes | Spec operation id (from `list_endpoints`) |\n\n### `call_endpoint`\n\nExecute 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.\n\n| Parameter | Required | Description |\n|---|---|---|\n| `region` | yes | e.g. `US` |\n| `env` | yes | `production` or `sandbox` |\n| `ad_type` | yes | `sponsored` |\n| `operation_id` | no* | Spec operation id; resolves `method`+`path` |\n| `method` | no* | `GET`, `POST`, `PUT`, `PATCH`, or `DELETE` |\n| `path` | no* | e.g. `/api/v1/campaigns` |\n| `params` | no | Query string parameters (JSON object) |\n| `body` | no | JSON request body for POST/PUT (object or array) |\n\n\\* Provide either `operation_id`, or both `method` and `path`.\n\n### `refresh_specs`\n\nRe-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.\n\n| Parameter | Required | Description |\n|---|---|---|\n| `spec_id` | no | One spec to refresh (e.g. `sponsored/sponsored-ads`); omit to refresh all |\n\n## MCP resources\n\nEndpoint schemas come from the bundled OpenAPI spec via `list_endpoints` / `describe_endpoint` (see [Tools](#tools)), not from static resources.\n\n### Dynamic resources\n\n| Resource URI | Description |\n|---|---|\n| `sca://config` | Available regions, environments, and ad types from your config |\n| `sca://responses/{request_id}` | Full body of a truncated API response (cached in memory, TTL from config) |\n| `sca://curl/{request_id}` | Reproducible cURL command for a previous API request |\n\n## Spec coverage\n\nThe bundled OpenAPI spec is **hand-authored from the\n[developer.samsclub.com](https://developer.samsclub.com/API/overview/) docs and is partial by\ndesign.** It covers the core ads surface: Campaigns, Statistics, Ad Groups, Ad Items, Catalog\nItem Search, Keywords (+ suggestions/analytics), Negative Keywords, Placements, Bid\nMultipliers, and Snapshot Reports v1/v2 + Latest Report Date. Endpoints not yet modeled (SBA\nprofiles, media, reviews, api-usage, entity/audit snapshots, alerts & recommendations) are\nstill reachable via `call_endpoint` with a raw `method`+`path`.\n\n## Status & blocker\n\n**Partner credentials are the long pole.** Access requires emailing an RSA public key to\n`partner-support@samsclub.com` and receiving a consumer id + auth token + key version.\nWithout sandbox credentials the server runs and its discovery/signing are verified offline,\nbut calls cannot be exercised end-to-end.\n\n## Spec drift detection\n\n[`scripts/build_spec.py`](scripts/build_spec.py) scrapes the docs into a *candidate* spec at\n[`spec-candidate/`](spec-candidate/) — offline, never at runtime. The\n[`Spec drift`](.github/workflows/spec-drift.yml) workflow runs it on a schedule and opens a\nPR when the docs change; a reviewer then ports real changes into the hand-authored canonical\nspec. The candidate is intentionally flat and is never shipped or loaded.\n\n```bash\nuv run --group spec-build python scripts/build_spec.py          # write candidate\nuv run --group spec-build python scripts/build_spec.py --check  # exit 1 on drift\n```\n\nStill deferred: flipping a spec's `auth` flag to fetch the authenticated live Swagger backend\nonce partner credentials exist.\n\n## MCP host examples\n\n### Cursor\n\nAdd to `.cursor/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"samsclub-ads\": {\n      \"command\": \"uvx\",\n      \"args\": [\"mcp-samsclub-ads\"]\n    }\n  }\n}\n```\n\n### Claude Code\n\nAdd to your Claude Code MCP config:\n\n```json\n{\n  \"mcpServers\": {\n    \"samsclub-ads\": {\n      \"command\": \"uvx\",\n      \"args\": [\"mcp-samsclub-ads\"]\n    }\n  }\n}\n```\n\n### Codex\n\n```toml\n[mcp_servers.samsclub-ads]\ncommand = \"uvx\"\nargs = [\"mcp-samsclub-ads\"]\n```\n\n### OpenCode\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"mcp\": {\n    \"samsclub-ads\": {\n      \"type\": \"local\",\n      \"enabled\": true,\n      \"command\": [\"uvx\", \"mcp-samsclub-ads\"]\n    }\n  }\n}\n```\n\n### GitHub Copilot\n\n```json\n{\n  \"inputs\": [],\n  \"servers\": {\n    \"samsclub-ads\": {\n      \"type\": \"stdio\",\n      \"command\": \"uvx\",\n      \"args\": [\"mcp-samsclub-ads\"]\n    }\n  }\n}\n```\n\n## Development\n\n```bash\nuv sync --group dev    # install deps\nuv run pytest          # run tests\nuv run ruff check .    # lint\nuv run ruff format .   # format\nuv run pyright         # type check\n```\n\n## Reference\n\n- API docs: https://developer.samsclub.com/API/overview/\n- Authentication: https://developer.samsclub.com/API/authentication/\n- Base project: [`mcp-walmart-ads`](https://github.com/alyiox/mcp-walmart-ads)\n\n## Contributing\n\nOpen issues or PRs. Follow existing style and add tests where appropriate.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n",
  "bytes": 10803,
  "sha": "4e2691be1dd53541f06e77b72d43eb7f9e7b09ce4b574fb980f6f8f982d69c26",
  "repo_slug": "alyiox/mcp-samsclub-ads",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_alyiox_mcp_samsclub_ads_0b113edb/readme"
}