Files
DevFlow/crates/df-mcp/src/protocol.rs
绝尘 f217e39bfe 新增: MCP Server(df-mcp crate + stdio 协议 + CLI 子命令)
依据 F-260619-02 设计,对外暴露数据层接入 Claude Code/Cursor:
- 新建 df-mcp crate:JSON-RPC 2.0 over stdio(initialize/tools/list/tools/call)
- 暴露 19 数据层工具(项目/任务/灵感/工作流/回收站),复用 df-storage Repo 零重复 CRUD
- 三层安全降级:High 风险(delete/purge/run_workflow)tools/list 不暴露+dispatch 拒+handler 二次拒;Medium 允许+审计日志;--read-only 仅 Low
- 不暴露文件系统工具(防绕过应用内路径校验)
- Tauri CLI 子命令 devflow mcp-server [--db/--read-only/--print-config]
- SQLite WAL 已启用(复用 Database::open),应用与 MCP server 并发写安全

自验: df-mcp 8 passed + workspace EXIT 0
2026-06-19 19:24:02 +08:00

202 lines
6.1 KiB
Rust

//! JSON-RPC 2.0 over stdio 协议层
//!
//! 职责:
//! - 按行读 stdin,解析 JSON-RPC Request/Notification
//! - 构造 Response / Error 对象并序列化为单行 JSON 写 stdout
//! - MCP 三类方法路由(initialize / tools/list / tools/call)由上层 dispatch
//!
//! 不解析 batch 请求(MCP 客户端实测逐条发,单条足够)。
//! 错误码遵循 JSON-RPC 2.0 + MCP 自定义段(-32xxx 协议层 / 0xffff 工具层)。
use serde::{Deserialize, Serialize};
use serde_json::Value;
// ============================================================
// JSON-RPC 2.0 错误码
// ============================================================
/// 协议层错误(JSON-RPC 2.0 预留段 -32000..-32099)
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;
pub const INTERNAL_ERROR: i32 = -32603;
// ============================================================
// 请求 / 响应数据结构
// ============================================================
/// JSON-RPC 2.0 请求(请求与通知共用,Notification 时 id=None)
#[derive(Debug, Clone, Deserialize)]
pub struct Request {
pub jsonrpc: Option<String>,
pub id: Option<Value>,
pub method: String,
#[serde(default)]
pub params: Value,
}
/// JSON-RPC 2.0 错误对象
#[derive(Debug, Clone, Serialize)]
pub struct ErrorObject {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
/// JSON-RPC 2.0 响应
#[derive(Debug, Clone, Serialize)]
pub struct Response {
pub jsonrpc: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ErrorObject>,
}
impl Response {
/// 成功响应
pub fn ok(id: Option<Value>, result: Value) -> Self {
Self {
jsonrpc: "2.0",
id,
result: Some(result),
error: None,
}
}
/// 错误响应
pub fn err(id: Option<Value>, code: i32, message: impl Into<String>, data: Option<Value>) -> Self {
Self {
jsonrpc: "2.0",
id,
result: None,
error: Some(ErrorObject {
code,
message: message.into(),
data,
}),
}
}
}
// ============================================================
// MCP 方法路由 — 暴露给上层 dispatch 的 enum
// ============================================================
/// MCP 协议层方法(已剥离 JSON-RPC 包装)。未知方法返回 `Unknown`,由 dispatch 回 METHOD_NOT_FOUND。
#[derive(Debug, Clone)]
pub enum McpMethod {
/// initialize 握手:返 server info + capabilities
Initialize { params: Value },
/// initialized 通知:客户端确认握手完成,无需响应
Initialized,
/// tools/list:列出所有工具(MCP Tool schema)
ToolsList,
/// tools/call:调用某工具
ToolsCall { name: String, arguments: Value },
/// ping 心跳(MCP 2025 规范),回空 result
Ping,
/// 未识别的方法(回 METHOD_NOT_FOUND)
Unknown(String),
}
impl McpMethod {
/// 从 Request 路由出 MCP 语义方法。
pub fn from_request(req: &Request) -> Self {
match req.method.as_str() {
"initialize" => McpMethod::Initialize { params: req.params.clone() },
"notifications/initialized" => McpMethod::Initialized,
"tools/list" => McpMethod::ToolsList,
"tools/call" => {
let name = req
.params
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_owned();
let arguments = req.params.get("arguments").cloned().unwrap_or(Value::Null);
McpMethod::ToolsCall { name, arguments }
}
"ping" => McpMethod::Ping,
other => McpMethod::Unknown(other.to_owned()),
}
}
}
// ============================================================
// MCP 协议数据结构(initialize 握手 / Tool schema / CallToolResult)
// ============================================================
/// MCP initialize 握手响应结构(server info + capabilities)
#[derive(Debug, Clone, Serialize)]
pub struct InitializeResult {
#[serde(rename = "protocolVersion")]
pub protocol_version: &'static str,
pub capabilities: Capabilities,
#[serde(rename = "serverInfo")]
pub server_info: ServerInfo,
}
#[derive(Debug, Clone, Serialize)]
pub struct Capabilities {
pub tools: ToolsCapability,
}
#[derive(Debug, Clone, Serialize)]
pub struct ToolsCapability {
#[serde(rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct ServerInfo {
pub name: &'static str,
pub version: &'static str,
}
/// MCP Tool 描述符(tools/list 返回数组元素)
#[derive(Debug, Clone, Serialize)]
pub struct Tool {
pub name: &'static str,
pub description: &'static str,
#[serde(rename = "inputSchema")]
pub input_schema: Value,
}
/// MCP tools/call 成功响应(CallToolResult)
#[derive(Debug, Clone, Serialize)]
pub struct CallToolResult {
pub content: Vec<ContentBlock>,
#[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
}
/// MCP 内容块(本 server 仅用 text)
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ContentBlock {
Text { text: String },
}
impl CallToolResult {
/// 文本成功响应
pub fn text(text: impl Into<String>) -> Self {
Self {
content: vec![ContentBlock::Text { text: text.into() }],
is_error: None,
}
}
/// 文本错误响应(is_error=true 让客户端区分「工具业务错」与「协议错」)
pub fn error(text: impl Into<String>) -> Self {
Self {
content: vec![ContentBlock::Text { text: text.into() }],
is_error: Some(true),
}
}
}