ebay-mcp
by deepujain
README.md
# eBay MCP Connector
An MCP server (streamable HTTP) exposing eBay's **Buy APIs** as tools:
search listings, inspect items, watch for price drops, bid on auctions, and
buy fixed-price items via guest checkout.
## Works with
This is a standard [Model Context Protocol](https://modelcontextprotocol.io/)
server — nothing in it is tied to any single assistant. Run it yourself
(below) and point any MCP-compatible client at `http://127.0.0.1:8000/mcp`:
- **Meta Muse**, **Claude / Claude Code** (Anthropic), **ChatGPT** (OpenAI)
- **Cursor**, **Windsurf**, **Cline**, and other MCP-capable coding assistants
- Any custom agent built on an MCP SDK (Python, TypeScript, …)
## Tools
| Tool | Effect | Approval |
|---|---|---|
| `search_listings` | Search active listings (keyword, price range, condition, format, sort) | immediate (read-only) |
| `get_item_details` | Full details for one listing | immediate (read-only) |
| `watch_item` | Add listing to the server-side watchlist, optional target price | immediate (read-only) |
| `list_watches` | Re-check all watches: price drops, target hits, stock-outs, ended | immediate (read-only) |
| `remove_watch` | Stop watching a listing | immediate (read-only) |
| `get_order_status` | Look up a guest order placed through this connector | immediate (read-only) |
| `place_bid` | Proxy (max) bid on an **auction** item | **two-phase** |
| `buy_now` | Buy a fixed-price item via **guest checkout** | **two-phase** |
### Two-phase approval contract
`place_bid` and `buy_now` never spend on the first call. The first call
(`confirm=false`, the default) fetches a fresh quote and returns
`{"status": "pending_confirmation", ...}` with a `confirmation_token` that is
HMAC-SHA256-signed (server's client secret), binds item id + price +
quantity/max-bid, and expires after 15 minutes.
The second call (`confirm=true` + token) re-fetches the listing and aborts —
placing nothing — if the price moved (`PriceChangedError`), the item sold out
or ended (`ItemNotAvailableError`), or the token is expired/tampered
(`ConfirmationError`). Only then does it call eBay.
## Required OAuth scopes
| Scope | Used for | Token type |
|---|---|---|
| `https://api.ebay.com/oauth/api_scope` | Browse API: search, item details | application (client credentials) |
| `https://api.ebay.com/oauth/api_scope/buy.guest.order` | Order API v2: guest checkout | application (client credentials) |
| `https://api.ebay.com/oauth/api_scope/buy.offer.auction` | Offer API: `place_proxy_bid` | **user** (authorization code grant) |
Everything except auction bidding works with the application token alone
(no user login). Bidding needs one browser consent per user.
## Setup
1. **eBay developer account** (free, ~5 min, not scriptable — requires
accepting the developer agreement as yourself):
- Sign up at https://developer.ebay.com and verify your email.
- New accounts go through a review before keys work — allow at least one
business day.
- Go to **My Keys** → create a keyset → note the **App ID** (client_id)
and **Cert ID** (client_secret). Create both **Sandbox** and
**Production** keysets.
- Under the keyset, add a **RuName** (redirect URI name) — needed only for
the optional bidding consent flow.
2. **Configure:**
```bash
cp .env.example .env # then fill in EBAY_CLIENT_ID / EBAY_CLIENT_SECRET
```
Start with `EBAY_ENV=sandbox` (test data, free, no real money moves).
3. **Install & run** (always start with `EBAY_ENV=sandbox` — test data, no
real money moves):
Local Python:
```bash
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m ebay_mcp.server
# listening on 127.0.0.1:8000 by default (EBAY_HOST / EBAY_PORT)
```
Or Docker (the image bakes in a `/healthz` liveness probe; the watchlist
lives in `/data`, so mount a volume to keep watches across restarts):
```bash
cp .env.example .env # then fill in your keys
docker build -t ebay-mcp .
docker run --env-file .env -p 8000:8000 -v ebay-data:/data ebay-mcp
```
4. **User consent for bidding** (only needed for `place_bid`): print the
consent URL, open it in a browser, approve, then exchange the returned
`?code=...` for tokens:
```bash
.venv/bin/python - <<'EOF'
from ebay_mcp.config import get_config
from ebay_mcp.auth import build_authorize_url, exchange_code_for_tokens
config = get_config()
print(build_authorize_url(config)) # open this in a browser and approve
# then:
# tokens = exchange_code_for_tokens(config, code="<code from redirect>")
# print(tokens["access_token"], tokens["refresh_token"])
EOF
```
Set `EBAY_USER_ACCESS_TOKEN` / `EBAY_USER_REFRESH_TOKEN` in `.env` from the
exchange output. Everything except auction bidding works with the
application token alone (no browser step).
5. **Connect a client:** point any MCP-compatible client at
`http://<host>:<port>/mcp`. eBay credentials stay in this server's
environment — the client never sees them.
## Running tests
```bash
.venv/bin/python -m pytest -q # unit tests (mocked HTTP)
.venv/bin/python -m pytest -q -m integration # live sandbox tests (needs keys)
```
Integration tests are skipped without `EBAY_CLIENT_ID`/`EBAY_CLIENT_SECRET`.
The one live mutation test additionally requires `EBAY_RUN_LIVE_MUTATIONS=1`
**and** `EBAY_ENV=sandbox`; it only opens a guest checkout session, never
places an order.
## Example prompts
1. "Find me a used ThinkPad X1 Carbon under $600 with free shipping."
2. "Watch this auction for the vintage Rolex and alert me if it drops below $2,000."
3. "What's the cheapest Buy-It-Now listing for AirPods Pro 2 in new condition?"
4. "Bid up to $450 on that auction ending today — but confirm the price with me first."
5. "Buy two of those fixed-price phone cases and tell me the order total before you check out."
## Layout
```
src/ebay_mcp/
config.py env-var config (no secrets in code)
errors.py exception hierarchy
auth.py OAuth: app token (cached), user-token helpers, scope constants
client.py REST client: Browse / Offer / Order v2 + response normalization
watchlist.py server-side watchlist (JSON file)
tools.py MCP tool logic incl. the two-phase approval contract
server.py FastMCP entrypoint (streamable HTTP)
tests/
test_config.py / test_auth.py / test_client.py / test_tools.py
test_integration.py (skipped without sandbox credentials)
```
## Unverified details
Verified against official eBay docs at build time: Browse search/getItem
paths and params, Offer `GET /bidding/{id}` and `POST
/bidding/{id}/place_proxy_bid`, OAuth token/authorize URLs, and all scope
strings. **Not verifiable from public docs** (flagged `UNVERIFIED` in code):
the Order API v2 guest-checkout REST paths
(`/buy/order/v2/guest_checkout_session/...`), the `place_proxy_bid` request
body shape (`{"maxAmount": {"value", "currency"}}`), the guest-checkout
initiate body shape, and the `estimatedAvailableQuantity` field name. The
sandbox integration tests are the verification vehicle — run them with real
sandbox keys before production use.
Notes:
- Pins `mcp>=1.9,<2` (v1 FastMCP API). v2 renamed FastMCP → MCPServer;
migrate deliberately, not accidentally.
- eBay's Buy APIs expose no watchlist endpoint, so `watch_item` is
server-side by design. There is also no REST Buy endpoint for Best Offer
negotiation — `place_bid` covers auctions only.
- Browse API default quota is 5,000 calls/day (free tier).
## Troubleshooting
- **Keys return auth errors right after signup:** new eBay developer accounts
are reviewed before keys activate (at least one business day). Sandbox and
production keysets are separate — approval for one does not imply the other.
- **"Item not available" in sandbox:** sandbox listings are test data with
short lifespans. Search fresh (`search_listings`) rather than reusing item
IDs from an earlier session.
- **`place_bid` fails but search works:** bidding needs the *user* token
(Authorization Code grant) with the `buy.offer.auction` scope — the
application token alone is not enough. Re-run Setup step 4.
- **Watches disappear after a container restart:** the watchlist is a plain
JSON file at `EBAY_WATCHLIST_PATH`. In Docker it lives in `/data` — mount
a volume (see Setup step 3) or the file dies with the container.
- **Guest checkout vs eBay account:** `buy_now` uses eBay's guest checkout —
no eBay buyer account is needed. The order confirmation email goes to the
address you supply at confirm time.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues