rustango 0.58.0

Batteries-included web framework for Rust: ORM + migrations + auto-admin + multi-tenancy + audit log + auth (sessions, JWT, OAuth2/OIDC, HMAC) + APIs (ViewSet, OpenAPI auto-derive, JSON:API) + jobs (in-mem + Postgres) + email + media (S3 / R2 / B2 / MinIO + presigned uploads + collections + tags) + production middleware (CSRF, CSP, rate-limiting, compression, idempotency, etc.).
Documentation
//! The JSON-RPC 2.0 envelope, its error codes, and the shape of the
//! MCP `initialize` result.

use serde::{Deserialize, Serialize};
use serde_json::Value;

/// The MCP revision this server speaks. `initialize` returns it, and
/// the client decides whether to go on.
pub const PROTOCOL_VERSION: &str = "2025-06-18";

/// The `"2.0"` literal every JSON-RPC message must carry.
pub const JSONRPC_VERSION: &str = "2.0";

// ----------------------------------------------------------------- envelope

/// An inbound JSON-RPC message: a request, or a notification when it
/// has no `id`.
#[derive(Debug, Clone, Deserialize)]
pub struct JsonRpcRequest {
    #[allow(dead_code)] // the transport checks this, not this module
    #[serde(default)]
    pub jsonrpc: String,
    /// A request has one and must be answered. A notification has
    /// none and must not be.
    #[serde(default)]
    pub id: Option<Value>,
    pub method: String,
    #[serde(default)]
    pub params: Option<Value>,
}

impl JsonRpcRequest {
    /// A message with no `id` is a notification: handle it, but send
    /// nothing back.
    #[must_use]
    pub fn is_notification(&self) -> bool {
        self.id.is_none()
    }
}

/// A JSON-RPC response. Exactly one of `result` and `error` is set.
#[derive(Debug, Clone, Serialize)]
pub struct JsonRpcResponse {
    pub jsonrpc: &'static str,
    pub id: Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<JsonRpcError>,
}

impl JsonRpcResponse {
    /// A success response for request `id`.
    #[must_use]
    pub fn success(id: Value, result: Value) -> Self {
        Self {
            jsonrpc: JSONRPC_VERSION,
            id,
            result: Some(result),
            error: None,
        }
    }

    /// An error response. Pass `Value::Null` as `id` when the request
    /// could not be parsed far enough to find its own.
    #[must_use]
    pub fn failure(id: Value, error: JsonRpcError) -> Self {
        Self {
            jsonrpc: JSONRPC_VERSION,
            id,
            result: None,
            error: Some(error),
        }
    }
}

/// A JSON-RPC error. `code` comes from [`codes`].
#[derive(Debug, Clone, Serialize)]
pub struct JsonRpcError {
    pub code: i64,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<Value>,
}

impl JsonRpcError {
    #[must_use]
    pub fn new(code: i64, message: impl Into<String>) -> Self {
        Self {
            code,
            message: message.into(),
            data: None,
        }
    }

    #[must_use]
    pub fn parse_error() -> Self {
        Self::new(codes::PARSE_ERROR, "parse error: invalid JSON")
    }

    #[must_use]
    pub fn invalid_request(detail: impl Into<String>) -> Self {
        Self::new(codes::INVALID_REQUEST, detail)
    }

    #[must_use]
    pub fn method_not_found(method: &str) -> Self {
        Self::new(
            codes::METHOD_NOT_FOUND,
            format!("method not found: {method}"),
        )
    }

    #[must_use]
    pub fn invalid_params(detail: impl Into<String>) -> Self {
        Self::new(codes::INVALID_PARAMS, detail)
    }
}

/// The error codes JSON-RPC 2.0 reserves, plus the two this server
/// defines in the range left for servers.
pub mod codes {
    pub const PARSE_ERROR: i64 = -32700;
    pub const INVALID_REQUEST: i64 = -32600;
    pub const METHOD_NOT_FOUND: i64 = -32601;
    pub const INVALID_PARAMS: i64 = -32602;
    pub const INTERNAL_ERROR: i64 = -32603;
    /// `tools/call` named a tool that is not registered.
    pub const TOOL_NOT_FOUND: i64 = -32002;
    /// `tools/call` named a tool the agent may not use.
    pub const TOOL_FORBIDDEN: i64 = -32003;
}

// ------------------------------------------------------------- initialize

/// Result of the `initialize` handshake.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResult {
    pub protocol_version: &'static str,
    pub capabilities: ServerCapabilities,
    pub server_info: Implementation,
}

/// What the server tells the client it supports. A `None` field is
/// left out, so a server that supports nothing serializes to `{}`.
#[derive(Debug, Clone, Default, Serialize)]
pub struct ServerCapabilities {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tools: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prompts: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resources: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub logging: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completions: Option<Value>,
}

/// The name and version of a server or client.
#[derive(Debug, Clone, Serialize)]
pub struct Implementation {
    pub name: &'static str,
    pub version: &'static str,
}