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,
};
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")
}
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")
}
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),
}
}
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" => {
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(),
}),
}
}
}
}