# MCP protocol details

> Transport, protocol versions, authentication, error codes and raw JSON-RPC examples for building your own MCP client.

Source: https://dev.ody.co/mcp/protocol

Most clients handle all of this for you. Read on if you're writing your own client, or debugging one.

## Transport

| | |
|---|---|
| Endpoint | `POST https://api.ody.co/v1/mcp` |
| Transport | Streamable HTTP, **stateless** |
| Request body | One JSON-RPC 2.0 message (arrays are accepted for older clients) |
| Response | One `application/json` JSON-RPC response. Never an SSE stream |
| Notifications | Messages without an `id` get `202 Accepted` with no body |
| Sessions | None. No `Mcp-Session-Id`, so every request stands alone |
| `GET` | `405 Method Not Allowed` (no server-initiated stream) |
| Auth | `Authorization: Bearer ody_live_…` or `ody_test_…` on **every** request |
| Rate limit | Shared with the REST API: 600 requests per minute per key |
| CORS | Any origin |

## Protocol versions

The server supports `2025-06-18` (latest), `2025-03-26` and `2024-11-05`. It answers `initialize` with the version you requested if it supports it, otherwise with `2025-06-18`.

**Capabilities:** `{ "tools": { "listChanged": false } }`. Ody offers tools only, with no resources, prompts, sampling or logging. **Server info:** `{ "name": "ody", "title": "Ody", "version": "0.3.0" }`.

**Methods:** `initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`. Other notifications are accepted and ignored.

## Raw JSON-RPC

initialize:

```bash
curl https://api.ody.co/v1/mcp \
  -H "Authorization: Bearer $ODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
```
tools/list:

```bash
curl https://api.ody.co/v1/mcp \
  -H "Authorization: Bearer $ODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
```
tools/call:

```bash
curl https://api.ody.co/v1/mcp \
  -H "Authorization: Bearer $ODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_conversations","arguments":{"status":"open"}}}'
```

initialize response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": false } },
    "serverInfo": { "name": "ody", "title": "Ody", "version": "0.3.0" }
  }
}
```

tools/call response:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{ "type": "text", "text": "{\"conversations\":[{\"id\":\"c4f1…\",\"status\":\"open\"}]}" }],
    "structuredContent": { "result": { "conversations": [{ "id": "c4f1…", "status": "open" }] } },
    "isError": false
  }
}
```

## Tool definitions

Each `tools/list` entry has `name`, `description`, `inputSchema` (JSON Schema) and `annotations`:

```json
{
  "name": "buy_number",
  "description": "Buy a phone number for the workspace (spends real money — …)",
  "inputSchema": {
    "type": "object",
    "properties": {
      "phoneNumber": { "type": "string", "description": "E.164, e.g. +15125550123" },
      "type": { "type": "string", "enum": ["local", "toll_free"] }
    },
    "required": ["phoneNumber"]
  },
  "annotations": { "title": "Buy a number", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true }
}
```

## Errors

| Situation | What you get |
|---|---|
| Missing or invalid key | HTTP `401`, JSON-RPC error `-32001` "Unauthorized: invalid API key" |
| Body isn't JSON | HTTP `400`, `-32700` "Parse error" |
| Unknown method | `-32601` "Method not found" |
| Missing or unknown tool name | `-32602` |
| Tool not allowed by the key's scopes | A **result** with `isError: true` naming the missing scope |
| Business rule (not found, 10DLC not ready, plan gate…) | A **result** with `isError: true` and a readable message |
| Unexpected server error | A **result** with `isError: true`, "Internal error running tool" |
| Rate limited | HTTP `429` with `Retry-After` (see [Rate limits](https://dev.ody.co/api/rate-limits)) |

Tool failures are returned as results rather than JSON-RPC errors on purpose: the model sees the message and can explain or adjust.
