Skip to main content

trusty_analyzer_mcp/
lib.rs

1//! trusty-analyzer-mcp — MCP tool definitions for the analysis daemon.
2//!
3//! Why: parity with `trusty-search-mcp`. Exposes analysis tools backed by the
4//! analyzer's HTTP daemon. The JSON-RPC stdio loop and HTTP/SSE transports
5//! land in a follow-up; for now this crate publishes the tool *schemas* so
6//! both the daemon and external clients (Claude Code, MCP debuggers) can
7//! reference a single authoritative list.
8//!
9//! What: a `tool_definitions()` function returning the static tool catalogue
10//! (`name`, `description`, `input_schema`, upstream HTTP route). Three
11//! analysis tools are exposed today: `cluster_concepts`, `ner_extract`,
12//! `ingest_scip`. Plus the existing complexity / smells / quality tools the
13//! analyzer already serves (added so the catalogue is complete).
14//!
15//! Test: see `#[cfg(test)]` — round-trips the catalogue through `serde_json`
16//! and asserts the expected tool names are present.
17
18use serde::{Deserialize, Serialize};
19
20/// A single MCP tool definition. Mirrors the shape used by `trusty-search-mcp`
21/// so future transport code can ship the catalogue verbatim.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ToolDefinition {
24    /// Tool name as exposed to the MCP client.
25    pub name: &'static str,
26    /// Short user-facing description (≤200 chars).
27    pub description: &'static str,
28    /// JSON-schema describing the tool's input payload.
29    pub input_schema: serde_json::Value,
30    /// HTTP route this tool proxies to on the analyzer daemon.
31    pub http_route: &'static str,
32    /// HTTP method (`"GET"` or `"POST"`).
33    pub http_method: &'static str,
34}
35
36/// Static catalogue of MCP tools served by the analyzer daemon.
37///
38/// Why: callers (transport code, tests, doc generators) all need the same
39/// list. Centralising it here prevents drift between the daemon's actual
40/// routes and the catalogue advertised to MCP clients.
41/// What: returns a `Vec<ToolDefinition>` covering the three new analysis
42/// tools plus the existing complexity / smells / quality / facts routes.
43/// Test: `tool_definitions_includes_new_analysis_tools`.
44pub fn tool_definitions() -> Vec<ToolDefinition> {
45    vec![
46        ToolDefinition {
47            name: "cluster_concepts",
48            description:
49                "Cluster doc-comment themes from a set of source contents. Returns ConceptCluster entities labelled by nearest vocab word.",
50            input_schema: serde_json::json!({
51                "type": "object",
52                "properties": {
53                    "contents": {
54                        "type": "array",
55                        "items": { "type": "string" },
56                        "description": "Raw chunk source contents."
57                    },
58                    "file": {
59                        "type": "string",
60                        "description": "Anchor filename for emitted entity ids."
61                    }
62                },
63                "required": ["contents"]
64            }),
65            http_route: "/analyze/concept-cluster",
66            http_method: "POST",
67        },
68        ToolDefinition {
69            name: "ner_extract",
70            description:
71                "Extract NaturalLanguagePhrase entities from doc-comment text via the optional ONNX NER model. Returns an empty list when the model is not installed.",
72            input_schema: serde_json::json!({
73                "type": "object",
74                "properties": {
75                    "text": { "type": "string", "description": "Source text or pre-extracted doc text." },
76                    "file": { "type": "string" },
77                    "extract_doc_comments_first": {
78                        "type": "boolean",
79                        "description": "When true, pull /// and //! lines from `text` before running NER."
80                    }
81                },
82                "required": ["text"]
83            }),
84            http_route: "/analyze/ner",
85            http_method: "POST",
86        },
87        ToolDefinition {
88            name: "ingest_scip",
89            description:
90                "Ingest SCIP-derived entity references and edges. Returns the materialised RawEntity list and edge tuples.",
91            input_schema: serde_json::json!({
92                "type": "object",
93                "properties": {
94                    "refs": { "type": "array", "description": "ScipEntityRef objects." },
95                    "edges": { "type": "array", "description": "ScipEdge objects." }
96                }
97            }),
98            http_route: "/analyze/scip-ingest",
99            http_method: "POST",
100        },
101        ToolDefinition {
102            name: "complexity_hotspots",
103            description: "Return the top-N chunks by cyclomatic complexity for an index.",
104            input_schema: serde_json::json!({
105                "type": "object",
106                "properties": {
107                    "index_id": { "type": "string" },
108                    "top_n": { "type": "integer", "default": 20 }
109                },
110                "required": ["index_id"]
111            }),
112            http_route: "/analyze/{index_id}/complexity_hotspots",
113            http_method: "GET",
114        },
115        ToolDefinition {
116            name: "find_smells",
117            description: "List chunks with one or more code-smell findings.",
118            input_schema: serde_json::json!({
119                "type": "object",
120                "properties": { "index_id": { "type": "string" } },
121                "required": ["index_id"]
122            }),
123            http_route: "/analyze/{index_id}/smells",
124            http_method: "GET",
125        },
126        ToolDefinition {
127            name: "analyze_quality",
128            description: "Aggregate quality stats for an index (avg cyclomatic, %A grade, smell count).",
129            input_schema: serde_json::json!({
130                "type": "object",
131                "properties": { "index_id": { "type": "string" } },
132                "required": ["index_id"]
133            }),
134            http_route: "/analyze/{index_id}/quality",
135            http_method: "GET",
136        },
137    ]
138}
139
140/// Marker for downstream callers that the MCP transport layer is reserved but
141/// not yet wired up. Replace with `McpServer` + transports when implementing.
142pub fn placeholder() -> &'static str {
143    "trusty-analyzer-mcp: transport layer pending; tool definitions available via tool_definitions()"
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn tool_definitions_includes_new_analysis_tools() {
152        let defs = tool_definitions();
153        let names: Vec<&str> = defs.iter().map(|d| d.name).collect();
154        assert!(names.contains(&"cluster_concepts"));
155        assert!(names.contains(&"ner_extract"));
156        assert!(names.contains(&"ingest_scip"));
157    }
158
159    #[test]
160    fn tool_definitions_serialise() {
161        let defs = tool_definitions();
162        let json = serde_json::to_string(&defs).expect("serialise");
163        assert!(json.contains("cluster_concepts"));
164    }
165
166    #[test]
167    fn tool_definitions_have_non_empty_descriptions() {
168        for def in tool_definitions() {
169            assert!(
170                !def.description.is_empty(),
171                "{} missing description",
172                def.name
173            );
174            assert!(
175                !def.http_route.is_empty(),
176                "{} missing http_route",
177                def.name
178            );
179        }
180    }
181}