From 8e5f3844cc4eda7a9b23d304738f6bc05d3f9657 Mon Sep 17 00:00:00 2001 From: Senstella Date: Tue, 7 Oct 2025 23:05:36 +0900 Subject: [PATCH] resources mcp --- src/mcp.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/mcp.rs b/src/mcp.rs index d8044e2..b35156c 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -1,5 +1,11 @@ use crate::fossil::{Fossil, FossilManager}; use crate::matcher::match_lines; +use rmcp::model::{ + AnnotateAble, ListResourcesResult, PaginatedRequestParam, RawResource, + ReadResourceRequestParam, ReadResourceResult, ResourceContents, +}; +use rmcp::service::RequestContext; +use rmcp::{ErrorData, RoleServer}; use rmcp::{ ServerHandler, handler::server::{ @@ -9,6 +15,7 @@ use rmcp::{ model::{ServerCapabilities, ServerInfo}, schemars, tool, tool_handler, tool_router, }; +use serde_json::json; #[derive(Debug, serde::Deserialize, schemars::JsonSchema)] pub struct CreateFossilRequest { @@ -181,6 +188,67 @@ impl FossilEditor { #[tool_handler] impl ServerHandler for FossilEditor { + async fn list_resources( + &self, + _request: Option, + _: RequestContext, + ) -> Result { + let fossils = self.state.fossils.lock().unwrap(); + let resources: Vec<_> = fossils + .keys() + .map(|id| { + let uri = format!("fossil://{}", id); + RawResource::new(uri, id).no_annotation() + }) + .collect(); + + Ok(ListResourcesResult { + resources, + next_cursor: None, + }) + } + + async fn read_resource( + &self, + ReadResourceRequestParam { uri }: ReadResourceRequestParam, + _: RequestContext, + ) -> Result { + if let Some(fossil_id) = uri.strip_prefix("fossil://") { + let fossils = self.state.fossils.lock().unwrap(); + if let Some(fossil) = fossils.get(fossil_id) { + if let Some(content) = fossil.latest() { + Ok(ReadResourceResult { + contents: vec![ResourceContents::text(content, uri)], + }) + } else { + Err(ErrorData::resource_not_found( + "fossil_empty", + Some(json!({ + "uri": uri, + "message": "Fossil exists but has no content" + })), + )) + } + } else { + Err(ErrorData::resource_not_found( + "fossil_not_found", + Some(json!({ + "uri": uri, + "message": format!("Fossil '{}' not found", fossil_id) + })), + )) + } + } else { + Err(ErrorData::resource_not_found( + "invalid_uri", + Some(json!({ + "uri": uri, + "message": "URI must start with 'fossil://'" + })), + )) + } + } + fn get_info(&self) -> ServerInfo { ServerInfo { instructions: Some( @@ -197,7 +265,7 @@ impl ServerHandler for FossilEditor { - Use `revert` to undo recent edits on a fossil. Specify `steps` to undo multiple (defaults to 1). This rolls back to previous versions without losing history. Preview with `get_version` first if needed. - Use `delete_fossil` to clean up and remove unused fossils by ID."#.into(), ), - capabilities: ServerCapabilities::builder().enable_tools().build(), + capabilities: ServerCapabilities::builder().enable_tools().enable_resources().build(), ..Default::default() } }