Skip to content

Wire format (JSON-RPC)

Tesseron's app ↔ MCP gateway hop is JSON-RPC 2.0, one JSON object per WebSocket text frame. No batching, no binary, no compression - one message, one parse.

REQUEST id + method expects response jsonrpc: "2.0" NOTIFICATION method only fire-and-forget no id SUCCESS echoes request id carries result result: R ERROR echoes request id carries error object error: {...}
Four envelope shapes. Every message on the wire is exactly one of these.
{
"jsonrpc": "2.0",
"id": 42,
"method": "actions/invoke",
"params": { /* method-specific */ }
}
{
"jsonrpc": "2.0",
"method": "actions/progress",
"params": { "invocationId": "inv_1", "percent": 40 }
}
{
"jsonrpc": "2.0",
"id": 42,
"result": { /* method-specific payload */ }
}
{
"jsonrpc": "2.0",
"id": 42,
"error": { "code": -32004, "message": "Invalid input", "data": [/* issues */] }
}

id can be a string, number, or null. The SDK uses monotonically incrementing integers per connection; any JSON-RPC-compliant peer is welcome to do otherwise.

MethodKindPurpose
tesseron/hellorequestRegister app, actions, resources, capabilities. First message.
actions/progressnotificationStreaming update during an invocation.
actions/list_changednotificationApp (re)registered / removed an action after hello.
resources/updatednotificationPush a new value to a subscriber.
resources/list_changednotificationApp (re)registered / removed a resource after hello.
sampling/requestrequestAsk the agent to run an LLM step.
elicitation/requestrequestAsk the user (confirm or elicit) via the agent UI.
lognotificationStructured log forwarded to MCP logging.

Plus: the response for any actions/invoke, resources/read, resources/subscribe, resources/unsubscribe the gateway sent you.

MethodKindPurpose
actions/invokerequestAgent called an action. Respond with result or error.
actions/cancelnotificationAgent cancelled an in-flight invocation.
resources/readrequestAgent requested current resource value.
resources/subscriberequestAgent subscribed to future updates.
resources/unsubscriberequestAgent unsubscribed.

And the response to the tesseron/hello you sent.

  • A peer that issues a request assigns the id. The other peer echoes the exact same id in the response.
  • The SDK keeps a Map<id, { resolve, reject, timeoutHandle }> of pending outbound requests. On response it looks up the id, clears the timer, and settles the promise.
  • On transport close, every pending request is rejected with TransportClosedError. There is no resumable queue; reconnect means re-send.
  • Notifications have no id - they never fail visibly and never get a response. Don't send data you care about as a notification.
  • Each JSON-RPC object is serialized with JSON.stringify and sent as one text frame.
  • Binary frames sent by the peer are coerced to text and parsed - tolerated but not idiomatic.
  • There is no length prefix and no framing header. WebSocket gives us message boundaries for free.
  • There is no batching. Every message is self-contained.

tesseron/hello includes protocolVersion: "1.0.0". The gateway parses it as major.minor: a major mismatch is rejected with -32000 ProtocolMismatch and the WebSocket is closed, a minor mismatch is accepted with a stderr warning (newer fields may be silently dropped), an exact match is silent. See Handshake.

Next: how that WebSocket gets established - Transport.