# Calling it over MCP

`https://inspired.jetskibay.com/mcp` speaks the Model Context Protocol over the
Streamable HTTP transport, JSON responses only, pinned to revision
`2026-07-28`. It declares one capability — `tools` — and implements three.

## Connecting

Authenticate every request with your Inspired API key, as a bearer token or as
`x-api-key`:

```
POST /mcp HTTP/1.1
Host: inspired.jetskibay.com
Authorization: Bearer isk_…
Content-Type: application/json
```

The credential is checked on every request, including `initialize` and
`tools/list`: there is no anonymous discovery. A key whose allowance is spent can
still list tools and collect a read it already paid for, exactly as polling stays
free over HTTP — only `start_visual_search` is refused.

What the transport does **not** implement, and answers `405` for rather than
pretending: `GET /mcp` (no SSE stream to open), `DELETE /mcp` (no session to
terminate), and JSON-RPC batching. `resources/list` and `prompts/list` answer
`-32601`; only `tools` is declared in `initialize`.

## `list_focus_domains`

No arguments. Free. Returns every subject the pipeline can shop for, with the
aliases each accepts.

```json
{ "name": "list_focus_domains", "arguments": {} }
```

`structuredContent` is `{ defaultDomain, domains: [{ id, label, description,
aliases, example, default }] }`.

Call this first when you do not already know which domain fits the photo. The
default is `fashion`.

## `start_visual_search`

Starts a durable read and returns immediately. **Spends one request** from the
calling key's allowance.

| Argument | Notes |
| --- | --- |
| `image` | The photograph as base64, with or without a `data:` prefix. JPG, PNG, WEBP or GIF, up to 10 MB decoded. |
| `imageMediaType` | Required unless `image` is a `data:` URL that names its own type. |
| `pinterestUrl` | A public Pinterest pin URL, instead of `image`. |
| `domain` | A domain id or alias. Omit for `fashion`. |
| `focus` | Up to 160 characters narrowing what to look for. |

Send exactly one of `image` or `pinterestUrl`. Unknown arguments are refused
rather than ignored, so a misspelling fails loudly.

```json
{
  "name": "start_visual_search",
  "arguments": {
    "image": "/9j/4AAQSkZJRgABAQ…",
    "imageMediaType": "image/jpeg",
    "domain": "furniture"
  }
}
```

`structuredContent` is `{ ok, requestId, status, statusUrl, pollAfterMs,
requestsRemaining }`. Keep the `requestId`: it is the only handle on the read,
and only your key can collect it.

## `get_visual_search`

Collects a read. Free to call, any number of times.

```json
{ "name": "get_visual_search", "arguments": { "requestId": "3f0c8a5e-…" } }
```

While it runs, `structuredContent` is `{ ok, requestId, status: "queued" |
"running", pollAfterMs, progress, items: [{ id, name, status }] }`. Respect
`pollAfterMs`; it is 3000 today.

When it finishes, `structuredContent` is:

```json
{
  "ok": true,
  "requestId": "3f0c8a5e-…",
  "status": "complete",
  "domain": "furniture",
  "focus": "",
  "summary": "Found 3 clearly visible furniture items.",
  "itemCount": 3,
  "matchCount": 24,
  "items": [
    {
      "id": "piece-1",
      "name": "floor lamp",
      "description": "Slim brass floor lamp with a white cone shade",
      "imageUrl": "https://inspired.jetskibay.com/api/inspire/jobs/3f0c8a5e-…/assets/piece-1.webp",
      "candidates": [
        {
          "title": "Brass arc floor lamp",
          "link": "https://shop.example/brass-arc-lamp",
          "price": "€149",
          "imageUrl": "https://images.example/lamp.jpg",
          "source": "example.com"
        }
      ]
    }
  ],
  "moodBoard": { "available": true, "mediaType": "image/webp", "note": "…" }
}
```

A read that failed comes back as a tool error — `isError: true` — carrying the
reason, not as a silent empty result.

### Why the images are links

The HTTP job response embeds every cutout and the mood board as base64 data
URLs, which is right for a client writing files and wrong for a model's context
window. Over MCP each cutout is an HTTPS URL instead, and the mood board is
described rather than inlined. To read the bytes, fetch
`GET /api/v1/jobs/{requestId}` with your key and use `grid.imageDataUrl` and
`items[].imageDataUrl`.

Candidate images are hosted by the retailers and by Google, not by Inspired, and
may expire without notice.

## A whole read, end to end

```
initialize                              → protocolVersion 2026-07-28
notifications/initialized               → 202
tools/list                              → three tools
tools/call list_focus_domains {}        → furniture is what you want
tools/call start_visual_search {image…} → requestId, 9 requests left
tools/call get_visual_search {requestId} → running, poll in 3s
… a few minutes …
tools/call get_visual_search {requestId} → complete, 3 objects, 24 matches
```

The worked shell version of this is in the examples, as `mcp-session.sh`.

## Errors

Transport and protocol problems are JSON-RPC errors:

| Code | Means |
| --- | --- |
| `-32700` | The body was not JSON. |
| `-32600` | Not a JSON-RPC message, a batch, a body over the size ceiling, or an unauthenticated request (HTTP 401). |
| `-32601` | Unknown method, or one of the capabilities this server does not declare. |
| `-32602` | Unknown tool name, or `arguments` that was not an object. |
| `-32603` | The server failed. |

Problems with the work itself are tool results with `isError: true` and a
`structuredContent.code`: `invalid_arguments`, `quota_exhausted`,
`start_failed`, `not_found`, `read_failed`, `internal_error`. This is deliberate
— a model can read a tool error and decide what to do, where a JSON-RPC error is
the runtime's problem and usually never reaches the model.
