MCP protocol details
Transport, protocol versions, authentication, error codes and raw JSON-RPC examples for building your own MCP client.
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
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"}}}'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"}'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"}}}'{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": false } },
"serverInfo": { "name": "ody", "title": "Ody", "version": "0.3.0" }
}
}{
"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:
{
"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) |
Tool failures are returned as results rather than JSON-RPC errors on purpose: the model sees the message and can explain or adjust.