ccd-cli 1.0.0-beta.1

Bootstrap and validate Continuous Context Development repositories
use serde::{Deserialize, Serialize};
use serde_json::Value;

// ---------------------------------------------------------------------------
// JSON-RPC 2.0 envelope
// ---------------------------------------------------------------------------

#[derive(Deserialize)]
pub struct JsonRpcRequest {
    /// Must be `"2.0"`. Defaulted so missing-field still deserializes
    /// (caught by the version check in the server loop, not serde).
    #[serde(default)]
    pub jsonrpc: String,
    pub method: String,
    #[serde(default)]
    pub params: Value,
    /// Present in the struct for serde completeness; the server loop uses the
    /// pre-deserialization `raw_id` extracted from the raw `Value` instead, so
    /// that `"id": null` (request) is correctly distinguished from absent id
    /// (notification).
    #[serde(rename = "id")]
    pub _id: Option<Value>,
}

#[derive(Serialize)]
pub struct JsonRpcResponse {
    pub jsonrpc: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<JsonRpcError>,
    pub id: Value,
}

#[derive(Serialize)]
pub struct JsonRpcError {
    pub code: i32,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<Value>,
}

// Standard JSON-RPC error codes
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
#[cfg(feature = "daemon")]
pub const INTERNAL_ERROR: i32 = -32603;

// ---------------------------------------------------------------------------
// MCP types
// ---------------------------------------------------------------------------

/// Returned as `result` for `initialize`.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResult {
    pub protocol_version: &'static str,
    pub capabilities: ServerCapabilities,
    pub server_info: ServerInfo,
}

#[derive(Serialize)]
pub struct ServerCapabilities {
    pub tools: ToolsCapability,
}

#[derive(Serialize)]
pub struct ToolsCapability {}

#[derive(Serialize)]
pub struct ServerInfo {
    pub name: &'static str,
    pub version: &'static str,
}

#[derive(Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Tool {
    pub name: String,
    pub description: String,
    pub input_schema: Value,
}

/// Extracted from `tools/call` params.
#[derive(Deserialize)]
pub struct ToolCallParams {
    pub name: String,
    #[serde(default)]
    pub arguments: Value,
}

/// Returned as `result` for `tools/call`.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolResult {
    pub content: Vec<Content>,
    #[serde(skip_serializing_if = "std::ops::Not::not")]
    pub is_error: bool,
}

#[derive(Serialize)]
pub struct Content {
    #[serde(rename = "type")]
    pub type_: &'static str,
    pub text: String,
}

// ---------------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------------

impl JsonRpcResponse {
    pub fn success(id: Value, result: Value) -> Self {
        Self {
            jsonrpc: "2.0",
            result: Some(result),
            error: None,
            id,
        }
    }

    pub fn error(id: Value, code: i32, message: impl Into<String>) -> Self {
        Self {
            jsonrpc: "2.0",
            result: None,
            error: Some(JsonRpcError {
                code,
                message: message.into(),
                data: None,
            }),
            id,
        }
    }
}

impl ToolResult {
    pub fn text(text: String) -> Self {
        Self {
            content: vec![Content {
                type_: "text",
                text,
            }],
            is_error: false,
        }
    }

    pub fn error(message: String) -> Self {
        Self {
            content: vec![Content {
                type_: "text",
                text: message,
            }],
            is_error: true,
        }
    }
}