chabeau 0.7.1

A full-screen terminal chat interface that connects to various AI APIs for real-time conversations
Documentation
use super::{format_rpc_error, format_unexpected_server_message};
use crate::core::config::data::McpServerConfig;
use rust_mcp_schema::schema_utils::ServerMessage;
use rust_mcp_schema::{
    CallToolResult, GetPromptResult, InitializeResult, ListPromptsResult,
    ListResourceTemplatesResult, ListResourcesResult, ListToolsResult, ReadResourceResult,
    LATEST_PROTOCOL_VERSION,
};
use serde_json::Value;

pub(crate) fn requested_protocol_version(config: &McpServerConfig) -> String {
    config
        .protocol_version
        .clone()
        .unwrap_or_else(|| LATEST_PROTOCOL_VERSION.to_string())
}

pub(crate) fn effective_protocol_version(
    config: &McpServerConfig,
    negotiated_version: Option<&str>,
) -> String {
    match negotiated_version {
        Some(version) if !version.trim().is_empty() => version.to_string(),
        _ => requested_protocol_version(config),
    }
}

pub(crate) fn parse_initialize_result(message: ServerMessage) -> Result<InitializeResult, String> {
    let value = parse_response_value(message)?;
    let result =
        serde_json::from_value::<InitializeResult>(value).map_err(|err| err.to_string())?;
    if result.protocol_version.trim().is_empty() {
        return Err("Unexpected initialize response.".to_string());
    }
    Ok(result)
}

pub(crate) fn parse_list_tools(message: ServerMessage) -> Result<ListToolsResult, String> {
    parse_response(message)
}

pub(crate) fn parse_list_resources(message: ServerMessage) -> Result<ListResourcesResult, String> {
    parse_response(message)
}

pub(crate) fn parse_list_resource_templates(
    message: ServerMessage,
) -> Result<ListResourceTemplatesResult, String> {
    parse_response(message)
}

pub(crate) fn parse_list_prompts(message: ServerMessage) -> Result<ListPromptsResult, String> {
    parse_response(message)
}

pub(crate) fn parse_get_prompt(message: ServerMessage) -> Result<GetPromptResult, String> {
    parse_response(message)
}

pub(crate) fn parse_read_resource(message: ServerMessage) -> Result<ReadResourceResult, String> {
    parse_response(message)
}

pub(crate) fn parse_call_tool(message: ServerMessage) -> Result<CallToolResult, String> {
    parse_response(message)
}

fn parse_response<T: serde::de::DeserializeOwned>(message: ServerMessage) -> Result<T, String> {
    let value = parse_response_value(message)?;
    serde_json::from_value::<T>(value).map_err(|err| err.to_string())
}

pub(crate) fn parse_response_value(message: ServerMessage) -> Result<Value, String> {
    match message {
        ServerMessage::Response(response) => {
            serde_json::to_value(&response.result).map_err(|err| err.to_string())
        }
        ServerMessage::Error(error) => Err(format_rpc_error(&error.error)),
        other => Err(format_unexpected_server_message(&other)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::config::data::McpServerConfig;

    #[test]
    fn parse_initialize_rejects_blank_protocol_version() {
        let message = serde_json::from_value(serde_json::json!({
            "jsonrpc": "2.0",
            "id": 1,
            "result": {
            "capabilities": {},
            "protocolVersion": " ",
            "serverInfo": {"name": "x", "version": "1.0.0"}
            }
        }))
        .expect("message should parse");

        assert!(parse_initialize_result(message).is_err());
    }

    #[test]
    fn effective_protocol_prefers_negotiated() {
        let config = McpServerConfig {
            id: "alpha".to_string(),
            display_name: "Alpha".to_string(),
            base_url: None,
            command: None,
            args: None,
            env: None,
            headers: None,
            transport: None,
            allowed_tools: None,
            protocol_version: Some("2025-01-01".to_string()),
            enabled: Some(true),
            tool_payloads: None,
            tool_payload_window: None,
            yolo: None,
        };

        assert_eq!(
            effective_protocol_version(&config, Some("2025-11-25")),
            "2025-11-25"
        );
        assert_eq!(effective_protocol_version(&config, None), "2025-01-01");
    }
}