<!-- Generated by api/scripts/gen_api_docs.py — do not edit by hand. -->
# Getting started — MCP

Server URL: `https://silphcoanalytics.xyz/api/v3/mcp/` (trailing slash)

Public MCP version: **1.1.0** (returned as `serverInfo.version` on
`initialize` and echoed on `/health` as `mcp_version`).

The MCP server is the same product surface as `/api/v3` REST, exposed as
typed tool calls for MCP clients (Claude, Cursor, custom agents). Same
data plane, same product jobs, same Bearer key — REST for pipelines and
scripts, MCP for tool-using agents.

Human page: https://silphcoanalytics.xyz/docs/reference/mcp  
Agent skill: https://silphcoanalytics.xyz/skill-api.md  
Keyless REST quote (not MCP): `GET /api/v3/quote?q=42382`

## For agents

1. Create a dashboard API key at https://silphcoanalytics.xyz/dashboard —
   a free `sk_live_...` key works; payment is not required.
2. Point a Streamable-HTTP MCP client at
   `https://silphcoanalytics.xyz/api/v3/mcp/` with
   `Authorization: Bearer <key>` on every request.
3. Call `quote` (`{"ids": ["42382"]}`) — for each result, when `status`
   is `resolved`, read `label` (Midprice or Last) and `price_usd`, and
   check `price_basis` before treating the number as a live market
   price. `ambiguous` → `candidates` (up to 5). `search`
   (`{"q": "base set charizard"}`) resolves a name to ids first.

Missing or invalid credentials return HTTP `401`. There is no anonymous
playground MCP — the keyless path is REST `GET /api/v3/quote`.

## Configure a client

Configuration field names vary by MCP client. A URL-based configuration is:

```json
{
  "mcpServers": {
    "silphco": {
      "server_url": "https://silphcoanalytics.xyz/api/v3/mcp/",
      "headers": {
        "Authorization": "Bearer ${SILPH_API_KEY}"
      }
    }
  }
}
```

Requirements:

- **Streamable HTTP transport** — not legacy SSE-only.
- **Custom request headers** — the Bearer credential rides on every POST.
- **Redirect-following** — the bare `/api/v3/mcp` path 307-redirects to
  the canonical `/api/v3/mcp/` (trailing slash). Clients that do not
  follow redirects must use the trailing-slash URL directly.
- **Session header** — after `initialize`, send the returned
  `mcp-session-id` header on subsequent calls (standard Streamable HTTP;
  handled automatically by SDK clients).

## Python client

With the official SDK (`pip install mcp httpx`, tested on `mcp` 2.x):

```python
import asyncio
import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

URL = "https://silphcoanalytics.xyz/api/v3/mcp/"

async def main() -> None:
    http = httpx.AsyncClient(
        headers={"Authorization": "Bearer YOUR_API_KEY"},
        follow_redirects=True,
    )
    async with http, streamable_http_client(URL, http_client=http) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print([t.name for t in tools.tools])
            res = await session.call_tool("quote", {"ids": ["42382"]})
            print(res.is_error, res.content[0].text)

asyncio.run(main())
```

The SDK manages the `mcp-session-id` header and the
`notifications/initialized` handshake for you. Result objects expose
`is_error` (the wire name is `isError`); content blocks carry the tool's
JSON as `content[0].text`.

## Verify the connection (curl)

The same handshake without an MCP client:

```bash
# 1. initialize — capture the mcp-session-id response header
curl -si -X POST \
  -H "Authorization: Bearer $SILPH_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-03-26","capabilities":{},
                 "clientInfo":{"name":"probe","version":"0"}}}' \
  https://silphcoanalytics.xyz/api/v3/mcp/

# 2. tools/list — reuse the session id
curl -s -X POST \
  -H "Authorization: Bearer $SILPH_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: <session-id>" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  https://silphcoanalytics.xyz/api/v3/mcp/

# 3. tools/call — a real product job
curl -s -X POST \
  -H "Authorization: Bearer $SILPH_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: <session-id>" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"quote","arguments":{"ids":["42382"]}}}' \
  https://silphcoanalytics.xyz/api/v3/mcp/
```

A healthy session answers `initialize` and `tools/list` in tens of
milliseconds; `quote` runs the full grade ladder and lands around
1–2s warm. Tool failures come back as `result.isError: true` (JSON-RPC
200), not HTTP 5xx — check `isError`, not just the status code.

## Tools

Public MCP exposes these product jobs from the canonical ToolSpec manifest:

| Tool | Purpose |
|------|---------|
| `search` | Card lookup: pass q for a name/id/productId single lookup, or ids to batch-resolve mixed tcg_card_id and TCGPlayer productId tokens. |
| `quote` | Card-hero quotes for a batch of ids: per grade, Midprice when usable (ok/thin and midprice_usd > 0), else Last, else Ask. Ungraded tries PSA:10 then raw NM. Same overlay_midprice gate as the card page. Not Fair, not nm_price_usd, not unit_marks. |
| `get_prices` | Warehouse historical price ledger. Not the card chart. |
| `get_sales` | Return public recent sale records or daily aggregates for canonical card ids. |
| `get_listings` | Return public current listings or per-finish book summaries for card ids. |
| `batch_grade_ladder` | PRIMARY multi-card grade job: Last + cheapest ask + multiples. Accepts ids, queries (inline resolve), or set_id. Default compact rows. dry_run previews cost. |
| `compare_set_finish` | Set cohort grade ladder for one finish (e.g. ex15 δ reverse holo PSA 8/9/10). One call — expands set, batch ladder, compact rows. Returns cohort_handle. |
| `pin_cohort` | Pin the working card cohort for this conversation — name + ids, set, or a prior result_handle. Follow-ups use cohort_handle; tool results alone are not auto-pinned. |
| `get_cohort` | Read the active pinned cohort (or session context pins) for follow-up planning. |
| `rank_set_sales` | Flat top-N graded sales for a set or cohort_handle (YTD / 90d / 365d windows). Prefer over flattening get_sales buckets. |

### Batch first

Every public tool is one call per job — `quote`, `get_prices`,
`get_sales`, `get_listings`, `batch_grade_ladder`, `compare_set_finish`,
and `rank_set_sales` take `ids`/cohort inputs; `search` takes `q` for a
name/id lookup or `ids` for batch resolve. Same cost (unique resolved
ids) as looping, fewer round trips — never loop single-id calls.

- `batch_grade_ladder` — Last sale, cheapest ask, and ask/last multiples
  for many cards and PSA grades in one call (preferred for grade-spread work).
- `pin_cohort`/`get_cohort` — name and reuse a resolved cohort across calls.
- `get_prices` with no `limit`/`since` returns each id's full series —
  hundreds of KB for a small id list. Always bound it (`limit`,
  `since`/`until`, `pick`).

### Agent patterns

**Identity, then finish.** `search` resolves the card; pass `finish` on
downstream tools — not inside `search.q`.

```json
{"name":"search","arguments":{"q":"Nidoking"}}
{"name":"quote","arguments":{"ids":["<tcg_card_id>"],"finish":"reverse_holo"}}
```

**δ-era commons (ex11+ex14+ex15).** One call expands the era — not set
`ex11` alone:

```json
{"name":"batch_grade_ladder","arguments":{
  "era":"delta_species","rarity":"Common","finish":"reverse_holo",
  "grades":"PSA:8,PSA:9,PSA:10","compact":true
}}
```

Or pin first: `pin_cohort(name=…, era=delta_species, rarity=Common)` →
`batch_grade_ladder(cohort_handle=…)`.

**Gem-rate / pop-style questions.** Use `batch_grade_ladder` on the cohort
(or `pin_cohort` then `cohort_handle`) — there is no separate gem-rank public
tool.

These are product jobs, not direct warehouse access. The MCP server uses
the same loopback `lor_client` and product-job data plane as the REST
product; it does not maintain or query a second warehouse.

## Tool audiences

The server selects the tool audience from the Bearer credential:

- Dashboard API keys receive the coarse public product jobs above.
- Chat user JWTs can also receive chat product jobs such as `show_cards`,
  `price_card`, and list operations, subject to the user's product access.
- `execute_sql` is not available through public MCP or chat MCP.
- `describe_schema` and `query_gold_sql` are not public MCP tools. They stay
  off dashboard keys and off chat MCP until they are a named product job
  (ADR 0007). `POST /api/v3/sql` is internal-only (user-allowlisted via
  `SILPH_SQL_ALLOWED_USERS`), not part of public MCP or public REST.

## MCP, REST, and web chat

- MCP is the tool protocol for external agents and authenticated chat
  clients — it complements REST, not replaces it.
- REST remains the primary direct data-integration surface at `/api/v3`.
  `GET`/`POST /prices` share `prices.v1`. `source` is `silph|tcgplayer|observed`
  (never blended; `all` is silph+tcgplayer only). Keyed POST/MCP JSON cap is
  1000 ids.
- The web chat UI still streams from `POST /api/v3/agent/chat` over SSE; it
  does not use the MCP URL as its chat transport.

The equivalent REST request:

```bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://silphcoanalytics.xyz/api/v3/cards/base1-4"
```

## Status codes

| Status | Meaning |
|--------|---------|
| 200 | JSON-RPC envelope returned — check `result.isError` for tool-level failures. |
| 202 | Notification accepted (`notifications/initialized`). |
| 307 | Bare `/api/v3/mcp` redirects to the trailing-slash URL. |
| 401 | Missing or invalid Bearer credential. |
| 429 | Rate or quota limit exceeded; see `Retry-After`. |

See [API getting started](../api/getting-started.md) for key creation,
quotas, and the REST product jobs, and the public
[agent skill](https://silphcoanalytics.xyz/skill-api.md). Public skill:
`/skill-api.md`.

`POST /api/v3/sql` is not part of the public API. It is an internal,
user-allowlisted read-only SQL passthrough (see
[endpoint-access-matrix](../api/endpoint-access-matrix.md)) — non-allowlisted
callers get HTTP `403`. Use the typed MCP product jobs or documented REST
endpoints instead.
