# Protocol

## Transport

PorchQ serves MCP over [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) at `https://porchq.com/mcp`.

- Send every request as `POST` with a JSON-RPC 2.0 body and `Content-Type: application/json`. PorchQ answers with a single JSON response, never a stream.
- `GET` and `DELETE` answer `405 Method Not Allowed` with `Allow: POST`. PorchQ opens no server-sent event stream and keeps no sessions, so there is no `Mcp-Session-Id`.
- A notification (no `id`) is accepted with `202 Accepted` and no body.
- Batches (a JSON array) are refused with `-32600`.
- Every request needs an access token; see [Sign-in and tokens](/developers/mcp/authorization).

## Revisions

| Revision | How a client talks to the server |
|---|---|
| `2026-07-28` | Stateless: each request carries its version and client capabilities in `params._meta`; no `initialize`. |
| `2025-11-25` | Opens with `initialize`, then `notifications/initialized`. |
| `2025-06-18` | Opens with `initialize`, then `notifications/initialized`. |
| `2025-03-26` | Opens with `initialize`, then `notifications/initialized`. |

A request that carries `io.modelcontextprotocol/protocolVersion` in `params._meta` is served as `2026-07-28`. Anything else is served as one of the earlier revisions.

## Revision 2026-07-28

Each request stands alone: there is no `initialize`. It names its protocol version and declares the client's capabilities in `params._meta`, and three HTTP headers repeat what the body says.

```http
POST /mcp
Authorization: Bearer kV3n…
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: community_overview

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "community_overview",
    "arguments": { "property_id": "prop_web4k2m9q7x1v8c3n5b6z0a2s4d" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}
```

- `MCP-Protocol-Version` and `Mcp-Method` are required on every request, `Mcp-Name` on `tools/call`. Each must equal the body's value, or PorchQ answers `400` with `-32020`. A header may be sent as `=?base64?…?=` when the value isn't plain ASCII.
- `io.modelcontextprotocol/clientCapabilities` is required (an empty object is fine), or PorchQ answers `400` with `-32602`.
- An unsupported version is answered `400` with `-32022` and the supported versions in `error.data.supported`.
- `initialize` and `ping` don't exist in this revision and answer `404` with `-32601`.

### server/discover

`server/discover` tells a client what PorchQ supports before it does anything else:

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "supportedVersions": ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26"],
    "capabilities": { "tools": {} },
    "ttlMs": 3600000,
    "cacheScope": "public",
    "resultType": "complete",
    "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "PorchQ MCP Server", "version": "1.5.0" } }
  }
}
```

Every result says `"resultType": "complete"` and, in this revision, carries the server's name and version in `_meta`.

## Revisions 2025-03-26 to 2025-11-25

Open with `initialize`, then send `notifications/initialized`:

```json
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": { "name": "ExampleClient", "version": "1.0.0" } } }
```

PorchQ answers with your `protocolVersion` if it supports it, otherwise with `2025-11-25`, and with `capabilities: { "tools": {} }` and its `serverInfo`. From `2025-06-18` on, send the negotiated version in the `MCP-Protocol-Version` header on later requests. An unsupported value there is answered `400` with `-32022`.

`ping` answers an empty result. `server/discover` works in these revisions too.

## Tools

PorchQ offers tools only: no resources, prompts or sampling yet. `tools/list` returns all 14 with their input and output schemas, annotations and `securitySchemes`, and may be cached for 60 minutes (`ttlMs`, `cacheScope: "public"`): the list is the same for everyone, and each call is checked on its own.

`tools/call` answers with:

- `content`: a short sentence for the person;
- `structuredContent`: the data, described on each tool's page;
- `isError: true` when the tool couldn't do what was asked, with the reason in `content`.

An unknown tool name is a protocol error, `-32602`.

## Errors

| Code | HTTP | When |
|---|---|---|
| `-32700` | 200 | The body isn't valid JSON. |
| `-32600` | 200 | Not a JSON-RPC 2.0 request, or a batch. |
| `-32601` | 404 (`2026-07-28`), 200 (earlier) | Unknown method. |
| `-32602` | 400 or 200 | Missing `_meta` fields, or an unknown tool. |
| `-32020` | 400 | A header doesn't match the body. |
| `-32022` | 400 | Unsupported protocol version. |
| `-32000` | 401 | No token, or a token that isn't valid. |

Sign-in and permission answers (`401`, `403`) carry a `WWW-Authenticate` header; see [Sign-in and tokens](/developers/mcp/authorization). Rate limits answer `429`; see [Errors and limits](/developers/mcp/errors-and-limits).

## Browser origins

A request from a web page (one with an `Origin` header) is accepted only from PorchQ, `https://chatgpt.com`, `https://claude.ai` or a loopback address; anything else gets `403`. This protects people against DNS-rebinding attacks. Requests from servers carry no `Origin` and are unaffected.
