Skip to content

protocol_transport_core

Universal transport foundation for multiple protocols

  • A2A (Agent-to-Agent)
  • MCP (Model Context Protocol)
  • Redpanda PubSub
  • REST (GET/PUT/POST)
  • Protocol Agnostic: Transport independent of protocol details
  • WASM-Optimized: SpinKube-first design
  • Zero-Copy: Minimal serialization overhead
  • Composable: Mix and match transports and protocols
const JSONRPC_VERSION: &str = ;

JSON-RPC 2.0 version constant

const RPC_REQUEST_TIMEOUT: Duration = ;

Per-request RPC timeout (wall-clock). Applied to non-streaming calls.

type ProtocolResult = Result<T, ProtocolError>;
type TransportResult = Result<T, TransportError>;

Result types

type JsonRpcId = serde_json::Value;

JSON-RPC 2.0 request ID type

Can be a string, number, or null according to the JSON-RPC 2.0 specification. The ID is used to correlate requests with responses and must be echoed back in the response exactly as received.

  • String: "request-123", "abc-def-456"
  • Number: 1, 42, 1234567890
  • Null: null (though discouraged for traceability)
type ProtocolHandlerFn = Box<dyn Fn + Send + Sync>;

Protocol Handler Function - Function-based approach for WASM compatibility

Protocol Handler Trait - Implement for each protocol

Associated Types

  • type Request
  • type Response
  • type Error

Required / Provided Methods

fn protocol_name(&self) -> &''static str

Protocol name (e.g., “A2A”, “MCP”, “REST”)

fn encode_request(&self, request: &<Self as ?>::Request) -> Result<UniversalRequest, <Self as ?>::Error>

Serialize protocol request to universal format

fn decode_request(&self, universal: &UniversalRequest) -> Result<<Self as ?>::Request, <Self as ?>::Error>

Deserialize universal request to protocol format

fn encode_response(&self, response: &<Self as ?>::Response) -> Result<UniversalResponse, <Self as ?>::Error>

Serialize protocol response to universal format

fn decode_response(&self, universal: &UniversalResponse) -> Result<<Self as ?>::Response, <Self as ?>::Error>

Deserialize universal response to protocol format

Transport Trait - HTTP, SSE, etc.

Uses async fn in trait without Send bounds because WASM targets (Spin SDK) do not produce Send futures.

Required / Provided Methods

async fn send(&self, request: UniversalRequest) -> Result<UniversalResponse, TransportError>
async fn health_check(&self) -> Result<(), TransportError>

Async Protocol Handler - Spin SDK async compatible

Required / Provided Methods

fn protocol_name(&self) -> &''static str

Protocol name (e.g., “A2A”, “MCP”, “REST”)

fn handle_request_sync(&self, request: UniversalRequest) -> Result<UniversalResponse, ProtocolError>

Handle request synchronously (async operations handled internally)

Methods

fn new(headers: HashMap<String, String>) -> Self
fn as_map(&self) -> &HashMap<String, String>
fn into_map(self) -> HashMap<String, String>

Common Protocol Headers

Fields

FieldTypeDescription
protocolString
versionString
correlation_idString
client_agent_idOption&lt;String&gt;
trace_idOption&lt;String&gt;

Methods

fn from_headers(headers: &HashMap<String, String>) -> Self

Extract protocol headers from raw headers

fn to_headers(&self) -> HashMap<String, String>

Convert to raw headers HashMap

JSON-RPC 2.0 Error Object structure

Represents detailed error information in JSON-RPC 2.0 error responses. Includes a standard error code, human-readable message, and optional additional data for debugging and error handling.

Fields

FieldTypeDescription
codei64Error code (standard or application-defined)
messageStringError message
dataOption&lt;serde_json::Value&gt;Optional additional error data

Methods

fn new(code: i64, message: String) -> Self

Create a new JSON-RPC 2.0 error

fn with_data(code: i64, message: String, data: serde_json::Value) -> Self

Create a new JSON-RPC 2.0 error with additional data

JSON-RPC 2.0 Notification structure

Represents a JSON-RPC 2.0 notification message that does not expect a response. Notifications are “fire-and-forget” messages used for operations where the caller doesn’t need to know the result.

Fields

FieldTypeDescription
jsonrpcStringJSON-RPC version (must be “2.0”)
methodStringMethod name to invoke
paramsserde_json::ValueMethod parameters (optional, defaults to null)

Methods

fn new(method: String, params: serde_json::Value) -> Self

Create a new JSON-RPC 2.0 notification

fn is_valid(&self) -> bool

Check if this is a valid JSON-RPC 2.0 notification

JSON-RPC 2.0 Request structure

Represents a JSON-RPC 2.0 request message that expects a response. All requests must include the jsonrpc, id, and method fields. The params field is optional and defaults to null.

Fields

FieldTypeDescription
jsonrpcStringJSON-RPC version (must be “2.0”)
idJsonRpcIdRequest ID (string, number, or null)
methodStringMethod name to invoke
paramsserde_json::ValueMethod parameters (optional, defaults to null)

Methods

fn new(id: JsonRpcId, method: String, params: serde_json::Value) -> Self

Create a new JSON-RPC 2.0 request

fn is_valid(&self) -> bool

Check if this is a valid JSON-RPC 2.0 request

JSON-RPC 2.0 Response structure

Represents a JSON-RPC 2.0 response message sent back to the client. A response must contain either a result (for success) or an error (for failure), but never both. The id field must match the request ID.

Fields

FieldTypeDescription
jsonrpcStringJSON-RPC version (always “2.0”)
resultOption&lt;serde_json::Value&gt;Successful result (mutually exclusive with error)
errorOption&lt;JsonRpcError&gt;Error object (mutually exclusive with result)
idJsonRpcIdRequest ID (copied from request)

Methods

fn success(id: JsonRpcId, result: serde_json::Value) -> Self

Create a successful JSON-RPC 2.0 response

fn error(id: JsonRpcId, code: i64, message: String) -> Self

Create an error JSON-RPC 2.0 response

fn error_with_data(id: JsonRpcId, code: i64, message: String, data: serde_json::Value) -> Self

Create an error JSON-RPC 2.0 response with additional data

fn is_success(&self) -> bool

Check if this is a success response

fn is_error(&self) -> bool

Check if this is an error response

Stream wrapper that terminates when no item arrives within idle_timeout.

The timer resets on every yielded item — a stream emitting one token every 500ms runs indefinitely. A stream that goes silent for idle_timeout is terminated with None (clean EOF).

Methods

fn new(inner: S, idle_timeout: Duration) -> Self

Three-clock streaming timeout configuration.

Unconditional: compiled for both WASM and native targets. On WASM, values are stored for config parity but not enforced until WASI 0.3 streaming support is available.

Fields

FieldTypeDescription
connect_msu64TCP + TLS handshake timeout. Default: 10s.
first_byte_msu64Time until first data chunk arrives (after headers). Default: 45s.
idle_msu64Max silence between consecutive chunks — resets on each chunk. Default: 90s.

Methods

fn connect_timeout(&self) -> Duration
fn first_byte_timeout(&self) -> Duration
fn idle_timeout(&self) -> Duration

Universal Request - Protocol-agnostic request container

Fields

FieldTypeDescription
methodString
uriString
headersHashMap&lt;String, String&gt;
bodyVec&lt;u8&gt;
protocolString
correlation_idString

Universal Response - Protocol-agnostic response container

Fields

FieldTypeDescription
statusu16
headersHashMap&lt;String, String&gt;
bodyVec&lt;u8&gt;
protocolString
correlation_idString

Server Router - Route requests to appropriate protocol handlers (Spin async compatible)

Methods

fn new() -> Self
fn register_fn<F>(&mut self, protocol: &str, handler: F)
where
F: Fn + Send + Sync + ?

Register protocol handler function

fn register<H>(&mut self, protocol: &str, handler: H)
where
H: AsyncProtocolHandler + ?

Register async protocol handler

fn route_request(&self, request: UniversalRequest) -> Result<UniversalResponse, ProtocolError>

Route request to appropriate handler

fn list_protocols(&self) -> Vec<String>

List available protocols

Universal Protocol Error

Variants

VariantDescription
UnsupportedProtocol(String)
ProtocolNotFound(String)
Validation(String)
Parsing(String)
Transport(TransportError)
Serialization(serde_json::Error)
Internal(String)

Methods

fn internal_error(msg: &str) -> Self

Create internal error

fn is_retryable(&self) -> bool

Check if error is retryable

Universal Transport Error

Variants

VariantDescription
Network(_)
Serialization(String)
Timeout(String)
Http { ... }
Configuration(String)
Authentication(String)

Methods

fn is_retryable(&self) -> bool

Check if error is retryable

fn is_auth_error(&self) -> bool

Check if error is due to authentication

Incoming JSON-RPC message types

Represents all possible incoming JSON-RPC 2.0 messages that can be received and parsed. The enum uses serde’s untagged feature to automatically detect the message type based on the presence of the id field.

  • Request: Has id field - expects a response
  • Notification: No id field - fire-and-forget
  • Batch: Array of requests/notifications (optional feature)

Variants

VariantDescription
Request(JsonRpcRequest)Standard JSON-RPC 2.0 request with ID
Notification(JsonRpcNotification)JSON-RPC 2.0 notification (no ID, no response expected)
fn sanitize_header_map(headers: &http::HeaderMap) -> ForwardedHeaders
fn sanitize_headers(headers: &HashMap<String, String>) -> ForwardedHeaders

SERVER MACRO - Create universal server with protocol registration

The key insight: Spin http_component functions are sync, but can call async operations internally