cleanpy 0.1.3

Python 3.12+ 最佳实践知识库 - MCP 服务器
Documentation
// MCP protocol request handlers

use anyhow::{Context, Result};
use std::collections::HashMap;

use crate::topics::{get_tips, list_topics};
use crate::types::{
    Capabilities, InitializeResult, JsonRpcError, JsonRpcRequest, JsonRpcResponse, ServerInfo,
    ToolContent, ToolResult, ToolSchema, ToolsListResult,
};

/// Handle initialize request
pub(crate) fn handle_initialize() -> Result<serde_json::Value> {
    let result = InitializeResult {
        protocol_version: "2025-06-18",
        capabilities: Capabilities {
            tools: HashMap::new(),
        },
        server_info: ServerInfo {
            name: "cleanpy-mcp",
            version: "0.1.0",
            _meta: None,
        },
        _meta: None,
    };

    serde_json::to_value(result).context("Failed to serialize initialize result")
}

/// Handle tools/list request
pub(crate) fn handle_tools_list() -> Result<serde_json::Value> {
    let result = ToolsListResult {
        tools: vec![
            ToolSchema {
                name: "list_topics",
                description: "列出所有可用的 Python 最佳实践主题",
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {},
                }),
                title: Some("列出主题"),
                _meta: None,
            },
            ToolSchema {
                name: "get_tips",
                description: "获取特定主题的 Python 最佳实践文档。支持精确匹配和模糊搜索:1) 如果提供精确的 topic 标识符,返回完整文档;2) 如果 topic 不存在,自动进行全局搜索,在所有文档的标题、关键词和内容中查找相关内容,返回按相关度排序的搜索结果",
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "topic": {
                            "type": "string",
                            "description": "主题标识符(如 'async', 'type_hints', 'security')或搜索关键词(如 'async programming', 'error-handling')。支持空格、下划线、连字符分隔的多个关键词"
                        }
                    },
                    "required": ["topic"]
                }),
                title: Some("获取最佳实践"),
                _meta: None,
            },
        ],
    };

    serde_json::to_value(result).context("Failed to serialize tools list")
}

/// Handle tools/call request
pub(crate) fn handle_tools_call(params: Option<serde_json::Value>) -> Result<serde_json::Value> {
    let params = params.context("Missing params in tools/call request")?;

    let name = params
        .get("name")
        .and_then(|v| v.as_str())
        .context("Missing 'name' in tools/call params")?;

    let arguments = params.get("arguments");

    match name {
        "list_topics" => {
            let topics_json = list_topics()?;
            let result = ToolResult {
                content: vec![ToolContent::Text {
                    text: serde_json::to_string_pretty(&topics_json)?,
                    is_error: None,
                }],
                is_error: None,
                _meta: None,
            };
            serde_json::to_value(result).context("Failed to serialize tool result")
        }
        "get_tips" => {
            let topic = arguments
                .and_then(|a| a.get("topic"))
                .and_then(|t| t.as_str())
                .context("Missing 'topic' argument")?;

            let (tips, is_error) = match get_tips(topic) {
                Ok(content) => (content, false),
                Err(e) => (
                    format!(
                        "# ❌ 获取文档失败\n\n无法获取主题 '{}' 的文档。\n\n错误: {}\n",
                        topic, e
                    ),
                    true,
                ),
            };

            let result = ToolResult {
                content: vec![ToolContent::Text {
                    text: tips,
                    is_error: if is_error { Some(true) } else { None },
                }],
                is_error: if is_error { Some(true) } else { None },
                _meta: None,
            };
            serde_json::to_value(result).context("Failed to serialize tool result")
        }
        _ => anyhow::bail!("Unknown tool: {}", name),
    }
}

/// Handle a JSON-RPC request
pub(crate) fn handle_request(request: JsonRpcRequest) -> JsonRpcResponse {
    let result = match request.method.as_str() {
        "initialize" => handle_initialize(),
        "tools/list" => handle_tools_list(),
        "tools/call" => handle_tools_call(request.params),
        "notifications/initialized" => {
            // Notification - no response needed
            return JsonRpcResponse {
                jsonrpc: "2.0",
                id: None,
                result: None,
                error: None,
            };
        }
        _ => Err(anyhow::anyhow!("Unknown method: {}", request.method)),
    };

    match result {
        Ok(value) => JsonRpcResponse {
            jsonrpc: "2.0",
            id: request.id,
            result: Some(value),
            error: None,
        },
        Err(e) => {
            eprintln!("Error handling request: {}", e);
            JsonRpcResponse {
                jsonrpc: "2.0",
                id: request.id,
                result: None,
                error: Some(JsonRpcError {
                    code: -32603,
                    message: e.to_string(),
                }),
            }
        }
    }
}