Published on

JSON-RPC Protocol Analysis [Part 1]

Authors

Introduction

Having heard and read quite a bit about JSON-RPC being used in several places like crypto and mcp servers, I thought it was time to do some analysis on the protocol.

JSON-RPC (JavaScript Object Notation Remote Procedure Call) is a protocol designed for remote procedure calls (RPC) that utilizes the JSON format for encoding messages, simple. At its core, RPC allows a program running on one computer to execute a procedure or subroutine within a different address space, such as another computer on a network, as if it were a local call. The requesting program typically pauses until the results are returned from the remote system. JSON-RPC provides a standardized structure for these interactions, leveraging JSON's lightweight, text-based format which is easily readable by humans and readily parsed by machines.

The protocol itself is intentionally simple and lightweight, especially when compared to older, more complex protocols like SOAP (Simple Object Access Protocol). It defines a minimal set of data types (based on standard JSON types) and commands necessary for remote invocation. This simplicity facilitates communication between disparate software applications, making it a common choice in web development and distributed systems for enabling interaction between different services or components over a network.

Some Historical Background

JSON-RPC emerged as a simpler, JSON-based alternative to the established XML-RPC protocol. Introduced around the mid-2000s , its development coincided with the rise of JSON as a preferred data interchange format, particularly in web environments.

The protocol has evolved, most notably from version 1.0 to the current version 2.0. This evolution addressed ambiguities and added features based on practical usage while aiming to retain the protocol's fundamental simplicity. Key differences between JSON-RPC 1.0 and 2.0 include:

  • Version Specification: JSON-RPC 2.0 mandates a "jsonrpc": "2.0" member in request objects, whereas versioning was optional in 1.0.
  • Error Format: Version 1.0 often returned errors as simple strings. Version 2.0 standardized a structured Error Object containing a numeric code, a string message, and optional data.
  • Data Format: While 1.0 sometimes supported other formats like XML, version 2.0 explicitly standardizes JSON as the sole data format.
  • Batch Calls: Version 1.0 lacked a formal specification for batching requests. Version 2.0 introduced the ability to send an array of request objects, allowing multiple calls to be processed potentially in parallel or sequence by the server, enhancing efficiency.
  • Notifications: Version 2.0 formally defined "Notifications" which are basically requests that do not require a response from the server.
  • Named Parameters: While early versions primarily used positional parameters (arrays), the ability to use named parameters (params as a JSON object) was clarified and standardized, often associated with version 2.0 or intermediate drafts like 1.1.

Core Concepts and Structure

The JSON-RPC Message Format

Communication in JSON-RPC revolves around the exchange of JSON objects representing requests, responses, and notifications.

1. Request Object: A client initiates a remote procedure call by sending a Request Object. It typically contains three members:

  • method: a required string specifying the name of the method to be invoked on the remote server. Method names starting with rpc are reserved for internal protocol use.
  • params: a optional structured value (either an Array for positional parameters or an Object for named parameters) containing the arguments for the method. This flexibility allows developers to choose the parameter style best suited for their method definition.
  • id: an identifier established by the client, which must be a string, number, or null (though non-fractional numbers and non-null values are recommended). This ID is crucial for correlating responses with their corresponding requests, especially in asynchronous communication scenarios where multiple requests might be in flight concurrently. If the id is ommitted, the request is treated as a notification.

2. Response Object: For every request object received (that is not a notification), the server must reply with a Response Object. It contains:

  • result: This member is required on success and must contain the data returned by the invoked method. Its value is determined by the server-side procedure. Crucially, this member must not exist if an error occurred during the method invocation. (note: some older specifications suggested result should be null on error, but the JSON-RPC 2.0 specification mandates its absence in case of an error)
  • error: This member is required if an error occurred during the invocation and must not exist if the call was successful. Its value must be an Error Object detailing the failure.
  • id: This member is required and must be the same value as the id from the Request Object it is responding to. This allows the client to match the response to the original request. Essentially works like a correlation id.

3. Error Object: The Error Object is a structured value that provides detailed information about an error that occurred during the execution of a request. It consists of the following members:

  • code: An integer indicating the type of error that occurred. The specification defines standard codes for protocol-level errors:
Error CodeDescription
-32700Parse error
-32600Invalid Request
-32601Method not found
-32602Invalid params
-32603Internal error
-32000 - -32099App-specific errors
  • message: A string providing a short description of the error. The message should be limited to a single sentence.
  • data: A primitive or structured value that provides additional information about the error. This is optional and may be omitted.

4. Notification: As stated above, notification is a Request Object that does not require a response from the server. It is used to send events or messages from the server to the client. Notifications are not expected to generate a response and are typically used for one-way communication. This makes notifications a "fire-and-forget" mechanism, suitable for sending events or commands where confirmation is not required. However, the lack of a response means the client receives no confirmation of success or notification of potential errors (like invalid parameters or internal server issues).

Key Operations

Conceptually, JSON-RPC operations can eb categorized based on the message types:

  • rpc.call: A request to invoke a method on the server
  • rpc.notify: A notification to the server that an event has occurred

While not universally implemented, the protocol also suggests potential auxiliary methods that servers could expose such as:

  • discover: to retrieve a list of available methods on the server
  • status: to get status information about the rpc servers
  • ping: simple check for liveness
  • cancel: to cancel an outstanding request (usually if supported by the server logic)

Transport Agnosticism

JSON-RPC is designed to be transport agnostic, meaning it can be used over any reliable stream-oriented protocol that supports the exchange of JSON objects. The core specification only defines the structure of the JSON messages and the rules for processing them, but it does not dictate how these messages should be transmitted between the participating client and server.

JSON-RPC messages can be exchanged over various underlying transport protocols, including:

  • HTTP/S
  • WebSockets
  • Raw TCP/IP Sockets
  • Unix Domain Sockets
  • Pipes
  • Message Passing Environments (e.g., ZeroMQ, AMQP)
  • Other protocols like XMPP, Telnet, SSH

This flexibility allows developers to choose the transport layer that best suits their application's requirements regarding performance, network topology, security needs, or existing infrastructure. For instance, using raw TCP sockets can eliminate HTTP overhead for performance-critical internal communication, while HTTP/S provides wide compatibility and leverages existing web infrastructure.

However, this transport independence comes with some costs and implications. Because the core JSON-RPC specification does not cover transport-level concerns, aspects like connection management, transport-specific error handling (e.g., connection timeouts, network failures), authentication, and security mechanisms are not defined by JSON-RPC itself. These critical functions must be handled by the chosen transport protocol implementation or by layers built on top of the basic JSON-RPC exchange. For example, when using HTTP, authentication might be handled via standard HTTP headers (Basic Auth, Bearer Tokens) or cookies , and security via TLS/SSL (HTTPS). For Unix sockets, file permissions might provide authentication. This separation contrasts with protocols like REST, which are tightly coupled with HTTP and leverage its features more directly. Consequently, implementing a robust JSON-RPC service requires careful consideration and configuration of the underlying transport in addition to handling the JSON-RPC message logic.

Real-World Applications

JSON-RPC has found adoption in a diverse range of applications, demonstrating its versatility and utility across different domains:

Blockchain and Cryptocurrency: Ethereum and many other blockchain platforms expose their node APIs via JSON-RPC. This allows developers to query blockchain state, submit transactions, and interact with smart contracts using a standardized interface. The simplicity and language-agnostic nature of JSON-RPC makes it ideal for blockchain ecosystems where multiple client implementations exist across different programming languages.

IDE Extensions and Development Tools: Visual Studio Code and other modern IDEs use JSON-RPC for communication between the editor and extensions. The Language Server Protocol (LSP), which powers features like code completion, diagnostics, and refactoring across multiple programming languages, is built on top of JSON-RPC. This architecture allows language-specific intelligence to be developed once and reused across multiple editors that support the protocol.

Remote Development Environments: Many cloud-based development environments and coding platforms use JSON-RPC to facilitate communication between client-side interfaces and server-side code execution environments.

IoT and Embedded Systems: In resource-constrained environments, JSON-RPC provides a lightweight mechanism for device-to-server communication, allowing IoT devices to expose functionality via a standardized interface.

Internal Microservices: Organizations often use JSON-RPC for internal service-to-service communication, particularly when direct method invocation semantics are more natural than REST's resource-oriented approach.

The protocol's simplicity, transport flexibility, and straightforward implementation requirements have contributed to its adoption in these diverse contexts, particularly where lightweight RPC functionality is needed without the overhead of more complex alternatives.