diff --git a/Cargo.lock b/Cargo.lock index fa8e88a..5610500 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" + [[package]] name = "bit-set" version = "0.8.0" @@ -3924,12 +3930,12 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" [[package]] name = "rmcp" -version = "2.2.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14db48ee17a9ba61810ab1a9c1beb7d06d8136ae39ac25a1137f10d357af01af" +checksum = "3797d226787327b35b0b6f9e878fe55f3af2bb4daf916c0411079885454042f0" dependencies = [ "async-trait", - "base64 0.22.1", + "base64 0.23.0", "chrono", "futures", "pastey", @@ -3944,13 +3950,14 @@ dependencies = [ "tokio-stream", "tokio-util", "tracing", + "uuid", ] [[package]] name = "rmcp-macros" -version = "2.2.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783d787bf21813b285f13019adc49e11af501c658890c1e519f31f937c68b7e3" +checksum = "41bc748630c2be2a71b614c2f40d27bc0df0060696d224e1692c72345b7e0b79" dependencies = [ "darling", "proc-macro2", diff --git a/crates/dowse/Cargo.toml b/crates/dowse/Cargo.toml index 6c3ab14..6396fbb 100644 --- a/crates/dowse/Cargo.toml +++ b/crates/dowse/Cargo.toml @@ -45,12 +45,12 @@ zip = { version = "8.6.0", default-features = false, features = ["deflate"] } clap = { version = "4.6.1", features = ["derive"], optional = true } ctrlc = { version = "3.5.2", optional = true } directories = { version = "6.0.0", optional = true } -rmcp = { version = "2.2.0", features = ["server", "macros", "transport-io", "schemars"], optional = true } +rmcp = { version = "3.0.1", features = ["server", "macros", "transport-io", "schemars"], optional = true } schemars = { version = "1.2.1", optional = true } tokio = { version = "1.52.3", features = ["rt-multi-thread", "macros", "io-std"], optional = true } [dev-dependencies] -rmcp = { version = "2.2.0", features = ["client", "transport-child-process"] } +rmcp = { version = "3.0.1", features = ["client", "transport-child-process"] } tempfile = "3.27.0" # MCP 集成测试要起编译好的 dowse 二进制走 stdio 握手,依赖 `cli` feature 下才存在的 diff --git a/crates/dowse/src/bin/dowse/mcp.rs b/crates/dowse/src/bin/dowse/mcp.rs index 3938dc6..6d5d678 100644 --- a/crates/dowse/src/bin/dowse/mcp.rs +++ b/crates/dowse/src/bin/dowse/mcp.rs @@ -13,7 +13,7 @@ use dowse::{ }; use rmcp::handler::server::tool::IntoCallToolResult; use rmcp::handler::server::wrapper::{Json, Parameters}; -use rmcp::model::{CallToolResult, ServerCapabilities, ServerInfo}; +use rmcp::model::{CallToolResponse, CallToolResult, ServerCapabilities, ServerInfo}; use rmcp::{ErrorData as McpError, ServerHandler, tool, tool_handler, tool_router}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -215,7 +215,8 @@ fn index_unavailable_result(err: &anyhow::Error) -> CallToolResult { /// 常驻一个 Searcher 会读到过期数据(见 docs/DESIGN-M5-MCP.md 第二节的并发约束)。 /// /// 返回 `Err(CallToolResult)` 而不是 `Err(McpError)`:索引不可用是工具级错误, -/// 调用方直接 `return Ok(e)` 短路成功分支即可,见上面 index_unavailable_result 的说明。 +/// 调用方直接 `return Ok(e)` 短路成功分支即可, +/// 见上面 index_unavailable_result 的说明。 fn open_and_reload(index_dir: &std::path::Path) -> Result { let searcher = Searcher::open(index_dir).map_err(|e| index_unavailable_result(&e))?; searcher @@ -224,6 +225,23 @@ fn open_and_reload(index_dir: &std::path::Path) -> Result` 的转换结果也因此 +/// 从 `CallToolResult` 变成了 `CallToolResponse`。这里的三个工具都是同步完成的 +/// 只读工具,因此只接受 `Complete` 分支,并保持工具路由的返回类型为 +/// `CallToolResult`。 +fn json_tool_result(value: T) -> Result +where + T: Serialize + JsonSchema + 'static, +{ + match Json(value).into_call_tool_result()? { + CallToolResponse::Complete(result) => Ok(result), + _ => Err(McpError::internal_error( + "JSON 工具结果不应产生 MRTR 中间响应", + None, + )), + } +} + #[derive(Clone)] pub struct DowseMcpServer { index_dir: PathBuf, @@ -289,7 +307,7 @@ impl DowseMcpServer { // 只有相关性排序下 BM25 分数才有意义;mtime/size 排序时不带 score。 let include_score = sort_mode == SortMode::Relevance; - Json(SearchOutput { + json_tool_result(SearchOutput { hits: page .hits .into_iter() @@ -298,7 +316,6 @@ impl DowseMcpServer { total_hits: page.total, total_docs: searcher.num_docs(), }) - .into_call_tool_result() } #[tool( @@ -325,7 +342,7 @@ impl DowseMcpServer { .map_err(|e| McpError::invalid_params(format!("查询语法有问题:{e}"), None))?; match hit { - Some(hit) => Json(to_preview_output(hit)).into_call_tool_result(), + Some(hit) => json_tool_result(to_preview_output(hit)), // 路径本身没问题(不是参数错误),是索引里当下就是找不到这篇—— // 文件可能已被删除、或改名后索引还没重新收录。跟"索引不可用"一样, // 是运行时状态问题,agent 应该看得到,所以也是工具级错误。 @@ -343,7 +360,7 @@ impl DowseMcpServer { )] async fn index_status(&self) -> Result { match core_index_status(&self.index_dir) { - Ok(status) => Json(to_index_status_output(status)).into_call_tool_result(), + Ok(status) => json_tool_result(to_index_status_output(status)), Err(e) => Ok(index_unavailable_result(&e)), } }