rust_doctor/mcp/
handler.rs1use rmcp::handler::server::router::prompt::PromptRouter;
2use rmcp::handler::server::tool::ToolRouter;
3use rmcp::model::{
4 AnnotateAble, GetPromptRequestParams, GetPromptResult, ListPromptsResult, ListResourcesResult,
5 PaginatedRequestParams, RawResource, ReadResourceRequestParams, ReadResourceResult, Resource,
6 ResourceContents, ServerCapabilities, ServerInfo,
7};
8use rmcp::service::RequestContext;
9use rmcp::{ErrorData as McpError, RoleServer, prompt_handler};
10
11use super::rules::{get_rule_explanation, rule_docs};
12
13#[derive(Clone)]
18pub struct RustDoctorServer {
19 pub(super) tool_router: ToolRouter<Self>,
20 pub(super) prompt_router: PromptRouter<Self>,
21}
22
23#[rmcp::tool_handler]
28#[prompt_handler(router = self.prompt_router)]
29impl rmcp::handler::server::ServerHandler for RustDoctorServer {
30 fn get_info(&self) -> ServerInfo {
31 ServerInfo::new(
32 ServerCapabilities::builder()
33 .enable_tools()
34 .enable_resources()
35 .enable_prompts()
36 .enable_logging()
37 .build(),
38 )
39 .with_instructions(
40 "rust-doctor is a Rust code health scanner. It analyzes projects for security, \
41 performance, correctness, architecture, and dependency issues.\n\n\
42 ## Recommended workflow\n\
43 1. `scan` a project directory → get diagnostics + score (5-30s)\n\
44 2. `explain_rule` for any rule you want to understand → instant\n\
45 3. `list_rules` to browse all available checks → instant\n\
46 4. `score` for a quick pass/fail without diagnostics (same 5-30s as scan)\n\n\
47 ## Resources\n\
48 - `rule://` — read rule documentation by URI (e.g. `rule://unwrap-in-production`)\n\n\
49 ## Prompts\n\
50 - `deep-audit` — comprehensive expert audit: explores codebase, scans, deep code review, \
51 web research for best practices, synthesis report, then offers to implement all fixes / generate PRD / manual\n\
52 - `health-check` — quick health check with scan + prioritized remediation plan + fix workflow\n\n\
53 ## Tips\n\
54 - Prefer `scan` over `score` — it includes the score plus full diagnostics\n\
55 - Use `diff` parameter in scan to focus on changed files only\n\
56 - All tools are read-only and safe to call repeatedly\n\
57 - `explain_rule` and `list_rules` are instant (no project scanning)",
58 )
59 }
60
61 async fn list_resources(
62 &self,
63 _request: Option<rmcp::model::PaginatedRequestParams>,
64 _context: RequestContext<RoleServer>,
65 ) -> Result<ListResourcesResult, McpError> {
66 let docs = rule_docs();
67 let resources: Vec<Resource> = docs
68 .iter()
69 .map(|doc| {
70 RawResource::new(format!("rule://{}", doc.name), doc.name)
71 .with_description(format!("[{}] {}", doc.severity, doc.description))
72 .with_mime_type("text/markdown")
73 .no_annotation()
74 })
75 .collect();
76
77 Ok(ListResourcesResult {
78 resources,
79 next_cursor: None,
80 meta: None,
81 })
82 }
83
84 async fn read_resource(
85 &self,
86 request: ReadResourceRequestParams,
87 _context: RequestContext<RoleServer>,
88 ) -> Result<ReadResourceResult, McpError> {
89 let uri = request.uri.as_str();
90 let rule_name = uri.strip_prefix("rule://").ok_or_else(|| {
91 McpError::invalid_params(
92 format!("Unknown URI scheme: {uri}. Expected rule://{{rule-name}}"),
93 None,
94 )
95 })?;
96
97 let explanation = get_rule_explanation(rule_name);
98 Ok(ReadResourceResult::new(vec![
99 ResourceContents::text(explanation, uri).with_mime_type("text/markdown"),
100 ]))
101 }
102}