{
  "markdown": "# ebay-mcp\n\n<!-- mcp-name: io.github.cunicopia-dev/ebay-mcp -->\n\n![Python](https://img.shields.io/badge/python-3.12%2B-3776AB?logo=python&logoColor=white)\n![License: MIT](https://img.shields.io/badge/license-MIT-green)\n![MCP](https://img.shields.io/badge/MCP-ready-FF6F00)\n![eBay Browse API](https://img.shields.io/badge/eBay-Browse_API-0064D2?logo=ebay&logoColor=white)\n![tests: passing](https://img.shields.io/badge/tests-25_passing-brightgreen)\n\n**Give an AI agent real, live market prices — straight from the largest\nsecondhand marketplace on the internet.**\n\neBay is a continuously-updating ledger of what physical things actually cost\nright now. This wraps its [Browse API](https://developer.ebay.com/api-docs/buy/browse/overview.html)\nas an [MCP](https://modelcontextprotocol.io) server with three tools, so an agent\ncan search listings, pull a single item, and — the useful one — get an aggregated\n**price landscape** for anything: min, median, max, broken down by condition.\n\n> Python 3.12+ · MIT · MCP server · app-token auth, no user login\n\nSetup is one free eBay app keyset — no user login, no OAuth consent screen to\nclick through. Point an agent at it and ask \"what does an RTX 5080 actually go\nfor?\" — one call back comes a grounded answer, split by condition, with the\ncheapest listings attached.\n\n---\n\n## Contents\n\n- [The three tools](#the-three-tools)\n- [Architecture: one client, three tools](#architecture-one-client-three-tools)\n- [Authentication: client-credentials, cached](#authentication-client-credentials-cached)\n- [`ebay_price_check`: how the landscape is built](#ebay_price_check-how-the-landscape-is-built)\n- [Install](#install)\n- [Configuration](#configuration)\n- [Sandbox vs. production](#sandbox-vs-production)\n- [Project layout](#project-layout)\n- [License](#license)\n\n---\n\n## The three tools\n\n| Tool | What it does |\n| ---- | ------------ |\n| **`ebay_price_check`** | Aggregated price landscape for a query — count, min/median/max, a breakdown by condition, and the cheapest listings. The headline tool. |\n| **`ebay_search`** | Listing search with sorting and filtering — returns clean `{itemId, title, price, condition, seller, itemWebUrl}` rows. |\n| **`ebay_get_item`** | Full detail for one item by ID. |\n\n```jsonc\n// ebay_price_check  ·  query: \"RTX 5080\", exclude: [\"laptop\", \"notebook\"]\n{\n  \"count\": 47, \"currency\": \"USD\",\n  \"min\": 899, \"median\": 1099, \"max\": 2200,\n  \"by_condition\": {\n    \"New\":  { \"count\": 18, \"min\": 1099, \"median\": 1199, \"max\": 1634 },\n    \"Used\": { \"count\": 21, \"min\": 899,  \"median\": 1050, \"max\": 1499 }\n  },\n  \"cheapest\": [ { \"price\": 899, \"condition\": \"Used\", \"title\": \"…\", \"itemWebUrl\": \"…\" } ]\n}\n```\n\n---\n\n## Architecture: one client, three tools\n\nEvery tool flows through a single `EbayBrowseClient`, which owns the token and\ntalks to eBay. There's one place credentials are read, one place a token is\ncached, one place HTTP happens — nothing to drift.\n\n```mermaid\nflowchart LR\n    Agent([\"AI agent / Claude\"])\n    subgraph server[\"ebay-mcp · stdio server\"]\n        Tools[\"ebay_search<br/>ebay_get_item<br/>ebay_price_check\"]\n        Client[\"EbayBrowseClient\"]\n        Cache[(\"OAuth token<br/>in-memory, auto-refresh\")]\n    end\n    Cfg[\"~/.ebay-mcp.toml<br/>or env vars\"]\n    eBay[\"eBay Browse API\"]\n\n    Agent -->|\"MCP tool call\"| Tools\n    Tools -->|\"search / get_item\"| Client\n    Client <-->|\"reuse or mint token\"| Cache\n    Client -->|\"Bearer token + query\"| eBay\n    eBay -->|\"listings JSON\"| Client\n    Cfg -.->|\"keyset + active env\"| Client\n```\n\nThe server is async; the client is plain synchronous `requests`, run in a thread\n(`asyncio.to_thread`) so a slow eBay call never blocks the event loop.\n`ebay_price_check` is the one tool that does more than pass through — it runs a\nsearch and then aggregates the result (see [below](#ebay_price_check-how-the-landscape-is-built)).\n\n---\n\n## Authentication: client-credentials, cached\n\neBay's Browse API uses an **application token** (the OAuth client-credentials\ngrant) — no user is involved. The client mints one on first use, caches it in\nmemory, and silently refreshes when it's about to expire. You never think about\nit.\n\n```mermaid\nsequenceDiagram\n    participant T as Tool call\n    participant C as EbayBrowseClient\n    participant O as eBay OAuth\n    participant B as Browse API\n\n    T->>C: search(\"RTX 5080\")\n    alt token missing or expired\n        C->>O: POST /identity/v1/oauth2/token<br/>Basic(app_id:cert_id), grant=client_credentials\n        O-->>C: access_token + expires_in\n        Note over C: cache until (expires_in − 60s)\n    end\n    C->>B: GET /item_summary/search<br/>Authorization: Bearer …\n    B-->>C: listings JSON\n    C-->>T: parsed results\n```\n\nThe 60-second buffer means a token is treated as expired slightly early, so a\ncall never races a token that dies mid-flight. Tokens live ~2 hours; in practice\none fetch covers a long session.\n\n---\n\n## `ebay_price_check`: how the landscape is built\n\nThe other two tools are thin wrappers. This one is the reason the project\nexists: it turns a pile of raw listings into a number you can reason about.\n\n```mermaid\nflowchart LR\n    Q[\"query<br/>+ exclude[]\"] --> S[\"search<br/>(up to 50 listings)\"]\n    S --> F[\"drop excluded titles<br/>+ unpriced listings\"]\n    F --> G[\"group by condition\"]\n    G --> A[\"aggregate<br/>min · median · max\"]\n    G --> H[\"cheapest N<br/>(the tail)\"]\n    A --> R([\"{ count, min, median, max,<br/>by_condition, cheapest }\"])\n    H --> R\n```\n\n`exclude` is what makes the number honest — a search for `\"RTX 5080\"` is full of\nlaptops and prebuilt PCs, and `exclude: [\"laptop\", \"notebook\", \"prebuilt\"]`\nstrips them so the median reflects the actual card. The `by_condition` split\nmatters just as much: a \"median\" that blends new-in-box with used-and-abused is\nnoise; split by condition and each tier tells the truth.\n\n> One honest limitation worth knowing: the Browse API returns **active asking\n> prices**, not completed sales. Treat the floor as \"best currently advertised,\"\n> not \"what it sold for.\"\n\n---\n\n## Install\n\n```bash\ngit clone https://github.com/cunicopia-dev/ebay-mcp\ncd ebay-mcp\npython3.12 -m venv .venv && source .venv/bin/activate\npip install -e .\n```\n\nYou need a (free) eBay developer **application keyset** — see\n[docs/SETUP.md](docs/SETUP.md) for the five-minute walkthrough. Then wire it into\nyour MCP client:\n\n```json\n{\n  \"mcpServers\": {\n    \"ebay\": { \"command\": \"/path/to/ebay-mcp/.venv/bin/ebay-mcp\" }\n  }\n}\n```\n\n---\n\n## Configuration\n\nCredentials come from **environment variables** (highest priority) or a\n**`~/.ebay-mcp.toml`** file. The active `env` selects the keyset *and* the API\nbase URL together — so production creds can never accidentally point at the\nsandbox, or vice versa.\n\n```mermaid\nflowchart TD\n    Start([\"load_config()\"]) --> Env{\"EBAY_ENV /<br/>EBAY_*_APP_ID<br/>in environment?\"}\n    Env -->|\"set\"| UseEnv[\"take keyset<br/>from env vars\"]\n    Env -->|\"unset\"| Toml{\"~/.ebay-mcp.toml<br/>present?\"}\n    Toml -->|\"yes\"| UseToml[\"take keyset<br/>from TOML\"]\n    Toml -->|\"no\"| Default[\"default env = production<br/>(error if creds missing)\"]\n    UseEnv --> Pick[\"env → keyset + base URL<br/>(locked together)\"]\n    UseToml --> Pick\n    Default --> Pick\n```\n\n```toml\n# ~/.ebay-mcp.toml   (chmod 600)\nenv = \"production\"\n\n[production]\napp_id  = \"YourApp-PRD-...\"\ncert_id = \"PRD-...\"\n\n[sandbox]\napp_id  = \"YourApp-SBX-...\"\ncert_id = \"SBX-...\"\n```\n\nCheck what's active any time — credentials are masked in the output:\n\n```bash\nebay-mcp-config\n# env:      production\n# app_id:   Keit****87dd\n# cert_id:  PRD-****0914\n# api_base: https://api.ebay.com\n# OK — configuration is valid.\n```\n\n---\n\n## Sandbox vs. production\n\nFlip `env` between `sandbox` and `production` to switch environments — same code,\ndifferent endpoints and keyset. The sandbox is good for proving the auth flow\nwires up; its inventory is sparse and seeded, so for real prices you want a\nproduction keyset.\n\n---\n\n## Project layout\n\n```\nsrc/ebay_mcp/\n  config.py    # env + TOML loader; ebay-mcp-config CLI\n  browse.py    # EbayBrowseClient — OAuth cache + search / get_item\n  server.py    # MCP server: list_tools / call_tool / main\ntests/         # config precedence, aggregation, tool listing (no network)\ndocs/SETUP.md  # getting an eBay keyset\n```\n\n---\n\n## License\n\nMIT\n",
  "bytes": 8291,
  "sha": "0f3550476859751a1644a5048b5cc2828ef033d0897ca3e107d7e51b4dc6a99f",
  "repo_slug": "cunicopia-dev/ebay-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_cunicopia_dev_ebay_mcp_9f13c254/readme"
}