use rmcp::ErrorData; use rmcp::ServerHandler; use rmcp::handler::server::router::tool::ToolRouter; use rmcp::handler::server::tool::ToolCallContext; use rmcp::model::{ CallToolRequestParams, CallToolResponse, CallToolResult, ContentBlock, ListToolsResult, PaginatedRequestParams, }; use rmcp::service::RequestContext; use rmcp::{RoleServer, tool_handler}; use crate::auth::Principal; use crate::services::SharedServices; use crate::settings; #[derive(Clone)] pub struct McpServer { pub(crate) services: SharedServices, pub(crate) tool_router: ToolRouter, } impl McpServer { pub fn new(services: SharedServices) -> Self { Self { services, tool_router: Self::tool_router(), } } pub(crate) fn principal( &self, ctx: &RequestContext, ) -> Result { Principal::from_ctx(ctx) } } #[tool_handler( name = "gitdata-mcp", version = "1.0.0", instructions = "GitData 平台的 MCP 工具集。调用结果遵循调用者访问密钥的权限:只读密钥只能调用只读工具,空间/仓库内的操作受成员角色与仓库 ACL 限制。" )] impl ServerHandler for McpServer { async fn list_tools( &self, _request: Option, context: RequestContext, ) -> Result { let principal = self.principal(&context)?; let disabled = settings::read_disabled(&self.services.state).await; let mut tools = self.tool_router.list_all(); tools.retain(|tool| !disabled.contains(tool.name.as_ref())); if !principal.can_write() { tools.retain(|tool| { tool.annotations .as_ref() .and_then(|annotations| annotations.read_only_hint) .unwrap_or(false) }); } Ok(ListToolsResult { tools, ..Default::default() }) } async fn call_tool( &self, request: CallToolRequestParams, context: RequestContext, ) -> Result { self.principal(&context)?; let name = request.name.to_string(); let disabled = settings::read_disabled(&self.services.state).await; if disabled.contains(&name) { return Ok(CallToolResult::error(vec![ContentBlock::text(format!( "工具 {name} 已被管理员停用" ))]) .into()); } let call = ToolCallContext::new(self, request, context); self.tool_router.call(call).await } }