MCP endpoint
What hosts see at the person's URL: Streamable HTTP without sessions, tools from the cached list, and calls over SSE.
/v1/mcp/{urlToken}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-Versionheader is treated as2025-03-26. A version outside the list answers400, naming the supported versions. initializeechoes the client'sprotocolVersionwhen Relay supports it, and answers2025-11-25otherwise.
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.
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.
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.
- 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/cancelledfor the call, or closes the stream, Relay sends the appcancel. - 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/initializedanswer202 Acceptedwith an empty body. pinganswers 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
| Situation | Text | Counted |
|---|---|---|
| Over the link's calls per minute | Notes is rate limited: too many calls this minute. Try again in 12 seconds. | No |
| The link is paused | Notes is paused in the app. Resume it there and try again. | No |
| The app is not connected | Notes is not running on your device. Open it and try again. | No |
| 8 calls are already in flight on the link | Notes is busy with other calls. Try again in a moment. | No |
| The call would pass the project's spend cap | Notes is unavailable: spend cap reached for this month. The developer can raise it. | No |
| The free allowance is used up and the balance cannot pay | Notes is unavailable: allowance used up; the developer needs to add credits. | No |
The app answered with error | The app's message | Yes |
| The app's answer broke the result rules | The app sent an answer the relay could not accept: <reason> | Yes |
| No answer within 180 seconds | Notes did not answer within 180 seconds. | Yes |
| The app's socket closed during the call | The 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
| Code | Message | When |
|---|---|---|
| -32602 | Unknown tool: <name> | The tool is not in the app's list. |
| -32602 | Invalid arguments for tool <name>: … | The arguments do not match the tool's inputSchema, or are over 64 KiB as JSON. |
| -32601 | Method not found: <method> | A method Relay does not offer, such as resources/list. |
| -32000 | This connector is disabled | The project is suspended. |
HTTP rules#
Errors that stop a request before any method runs carry a JSON-RPC error body too.
| Request | Answer |
|---|---|
| An unknown, rotated or revoked URL token | 404. Never 401, so hosts do not start an OAuth sign-in. |
GET or DELETE | 405 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 JSON | 400 with -32700. |
| A JSON array (a batch) | 400. Send one message per request. |
| A body over 80 KiB | 413. |
An unsupported MCP-Protocol-Version | 400, naming the supported versions. |
An Origin header that is not https: | 403. |
| A JSON-RPC response sent by the client | 202 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 address | 429 with Retry-After in seconds. |
Responses allow any origin, so MCP clients that run in a browser work.