Skip to content

Links and protocol / MCP endpoint

MCP endpoint

What hosts see at the person's URL: Streamable HTTP without sessions, tools from the cached list, and calls over SSE.

POST/v1/mcp/{urlToken}
Auth The URL token in the path. No Authorization header.

Each link's MCP URL is an MCP server over the Streamable HTTP transport. Hosts talk to it; your app never does. Every request is one JSON-RPC message in a POST, and every POST stands on its own: Relay never sends an Mcp-Session-Id and ignores one if it gets it.

Protocol versions#

  • Relay speaks MCP 2025-03-26, 2025-06-18, 2025-11-25.
  • A request without an MCP-Protocol-Version header is treated as 2025-03-26. A version outside the list answers 400, naming the supported versions.
  • initialize echoes the client's protocolVersion when Relay supports it, and answers 2025-11-25 otherwise.

initialize#

Answered at once as JSON, whether or not the app is running. serverInfo.name is the project's name. instructions are the app's, from its last hello; before the app has ever connected they ask the model to have the person open it: "Notes has not connected yet; open it on your device and its tools appear." Relay keeps the client's clientInfo.name on the link, and the dashboard shows it as the last client.

{  "jsonrpc": "2.0",  "id": 1,  "method": "initialize",  "params": {    "protocolVersion": "2025-11-25",    "capabilities": {},    "clientInfo": {      "name": "example-host",      "version": "1.0.0"    }  }}

tools/list#

Answered at once as JSON from the tool list the app registered in its last hello, every field unchanged. The list is stored on the link, so a host can be set up and list tools while the app is closed. Before the app's first hello, the list is empty.

{  "jsonrpc": "2.0",  "id": 2,  "method": "tools/list"}

tools/call#

Relay checks the call, sends it to the app as a `call` frame and answers with a Server-Sent Events stream that opens at once. While the app works, a : keep-alive comment arrives every 15 seconds so proxies keep the connection open. When the app answers, the JSON-RPC response arrives as one message event and the stream closes.

curl https://api.meslzy.com/relay/v1/mcp/ut_8Hq3Lm2Vx7Tc9Rb4Nw1Ks6Jd5Yf0Pg2E \  -H "Content-Type: application/json" \  -H "Accept: application/json, text/event-stream" \  -H "MCP-Protocol-Version: 2025-11-25" \  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_notes","arguments":{"query":"invoices"}}}'
  • If the app does not answer within 180 seconds, the stream ends with a tool error saying so, and Relay sends the app cancel.
  • If the host sends notifications/cancelled for the call, or closes the stream, Relay sends the app cancel.
  • If the app's socket closes during the call, the stream ends with a tool error saying the connection was lost and the tool may or may not have completed.

Notifications and ping#

  • Notifications such as notifications/initialized answer 202 Accepted with an empty body.
  • ping answers an empty result, {}.
  • Relay offers tools only: no resources, prompts, sampling or elicitation.

When the app cannot answer#

These answers are tool results with isError: true, so the model can tell the person what happened. Notes stands for the app's name from its last hello, or the project's name before the app has connected. Branch on isError, never on the wording. When the call never reached the app, the answer comes back at once as JSON; otherwise it ends the SSE stream.

⫻

200 OK

HTTP/1.1 200 OKContent-Type: application/jsonX-Request-Id: req_0199c4e25b7a7d318f2e6a1c9b3d4e58
{  "jsonrpc": "2.0",  "id": 3,  "result": {    "content": [      {        "type": "text",        "text": "Notes is not running on your device. Open it and try again."      }    ],    "isError": true  }}
SituationTextCounted
Over the link's calls per minuteNotes is rate limited: too many calls this minute. Try again in 12 seconds.No
The link is pausedNotes is paused in the app. Resume it there and try again.No
The app is not connectedNotes is not running on your device. Open it and try again.No
8 calls are already in flight on the linkNotes is busy with other calls. Try again in a moment.No
The call would pass the project's spend capNotes is unavailable: spend cap reached for this month. The developer can raise it.No
The free allowance is used up and the balance cannot payNotes is unavailable: allowance used up; the developer needs to add credits.No
The app answered with errorThe app's messageYes
The app's answer broke the result rulesThe app sent an answer the relay could not accept: <reason>Yes
No answer within 180 secondsNotes did not answer within 180 seconds.Yes
The app's socket closed during the callThe relay lost the connection to Notes while running the tool; it may or may not have completed.No, taken off the count

JSON-RPC errors#

A request that is wrong in itself answers with a JSON-RPC error instead. It never reaches the app and never counts.

⫻

200 OK

HTTP/1.1 200 OKContent-Type: application/jsonX-Request-Id: req_0199c4e25b7a7d318f2e6a1c9b3d4e58
{  "jsonrpc": "2.0",  "id": 3,  "error": {    "code": -32602,    "message": "Invalid arguments for tool search_notes: arguments must have required property 'query'"  }}
CodeMessageWhen
-32602Unknown tool: <name>The tool is not in the app's list.
-32602Invalid arguments for tool <name>: …The arguments do not match the tool's inputSchema, or are over 64 KiB as JSON.
-32601Method not found: <method>A method Relay does not offer, such as resources/list.
-32000This connector is disabledThe project is suspended.

HTTP rules#

Errors that stop a request before any method runs carry a JSON-RPC error body too.

RequestAnswer
An unknown, rotated or revoked URL token404. Never 401, so hosts do not start an OAuth sign-in.
GET or DELETE405 with Allow: POST and a short plain-text note. There is no server stream to open and no session to end.
A body that is not JSON400 with -32700.
A JSON array (a batch)400. Send one message per request.
A body over 80 KiB413.
An unsupported MCP-Protocol-Version400, naming the supported versions.
An Origin header that is not https:403.
A JSON-RPC response sent by the client202 with an empty body.
More than 300 initialize, tools/list or ping requests a minute on one link, or 120 unknown tokens a minute from one IP address429 with Retry-After in seconds.

Responses allow any origin, so MCP clients that run in a browser work.

NoteRelay has no OAuth yet. The URL token is the credential, so treat the URL like a password. See Security and privacy.