#json-rpc #rpc #async #ipc

jsoncall

A simple asynchronous JSON-RPC 2.0 library leveraging Rust's type system

1 unstable release

new 0.0.1 Mar 8, 2025

#214 in #json-rpc


Used in mcp-attr

MIT/Apache

85KB
2K SLoC

jsoncall

Crates.io Docs.rs Actions Status

A simple asynchronous JSON-RPC 2.0 library leveraging Rust's type system

Overview

jsoncall is a simple asynchronous JSON-RPC 2.0 library that maximizes the use of Rust's type system.

It is specifically designed to facilitate the creation of applications where the client launches the server, such as the Language Server Protocol and Model Context Protocol.

Features

  • Asynchronous support using tokio and async/await
  • Strongly typed requests and responses using serde
    • Easy implementation of JSON Schema-defined RPCs by generating Rust types using typify
  • Built-in support for cancellation handling
  • Comprehensive error handling
    • Provides error types that can store any error like anyhow or Box<dyn Error>, with additional functionality to distinguish between information that should be sent externally and information that should not
  • Bidirectional communication support
  • Notification support
  • Transport layer supports any type implementing tokio's AsyncBufRead and AsyncWrite traits
    • Standard I/O transport is readily available through Session::from_stdio and Session::from_command
  • Small, understandable API set

Installation

Add the following to your Cargo.toml:

[dependencies]
jsoncall = "0.0.1"

Usage

Server Implementation Example

use serde::{Deserialize, Serialize};
use jsoncall::{Handler, Params, RequestContext, Response, Result, Session};

#[derive(Debug, Serialize, Deserialize)]
struct HelloRequest {
    name: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct HelloResponse {
    message: String,
}

struct HelloHandler;

impl Handler for HelloHandler {
    fn request(&mut self, method: &str, params: Params, cx: RequestContext) -> Result<Response> {
        match method {
            "hello" => cx.handle(self.hello(params.to()?)),
            _ => cx.method_not_found(),
        }
    }
}

impl HelloHandler {
    fn hello(&self, r: HelloRequest) -> Result<HelloResponse> {
        Ok(HelloResponse {
            message: format!("Hello, {}!", r.name),
        })
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    // Start server using standard I/O
    Ok(Session::from_stdio(HelloHandler).wait().await?)
}

Client Usage Example

use serde::{Deserialize, Serialize};
use tokio::process::Command;
use jsoncall::{Result, Session};

#[derive(Debug, Serialize, Deserialize)]
struct HelloRequest {
    name: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct HelloResponse {
    message: String,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Launch server process and create session
    let client = Session::from_command(
        (),
        Command::new("cargo").args(["run", "--example", "stdio_server"]),
    )?;

    // Send request
    let response: HelloResponse = client
        .request(
            "hello",
            Some(&HelloRequest {
                name: "world".to_string(),
            }),
        )
        .await?;

    println!("{:?}", response);
    Ok(())
}

Asynchronous Handler Example

use jsoncall::{Handler, Params, RequestContext, Result, Response};

struct ExampleHandler;

impl Handler for ExampleHandler {
    fn request(&mut self, method: &str, params: Params, cx: RequestContext) -> Result<Response> {
        match method {
            "add" => {
                let params: (i32, i32) = params.to()?;
                cx.handle_async(async move {
                    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                    Ok(params.0 + params.1)
                })
            }
            _ => cx.method_not_found(),
        }
    }
}

License

This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-* files for details.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~6–17MB
~212K SLoC