Skip to main content

devboy_mcp/
tools.rs

1//! MCP tool definitions.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Tool {
7    pub name: String,
8    pub description: String,
9    pub parameters: serde_json::Value,
10}
11
12/// Available MCP tools.
13pub fn available_tools() -> Vec<Tool> {
14    vec![
15        Tool {
16            name: "get_issues".to_string(),
17            description: "Get issues from configured git providers".to_string(),
18            parameters: serde_json::json!({
19                "type": "object",
20                "properties": {
21                    "state": {
22                        "type": "string",
23                        "enum": ["open", "closed", "all"],
24                        "description": "Filter by issue state"
25                    }
26                }
27            }),
28        },
29        Tool {
30            name: "get_merge_requests".to_string(),
31            description: "Get merge requests / pull requests from configured git providers"
32                .to_string(),
33            parameters: serde_json::json!({
34                "type": "object",
35                "properties": {
36                    "state": {
37                        "type": "string",
38                        "enum": ["open", "closed", "merged", "all"],
39                        "description": "Filter by MR/PR state"
40                    }
41                }
42            }),
43        },
44    ]
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn test_available_tools_not_empty() {
53        let tools = available_tools();
54        assert!(!tools.is_empty(), "should have at least one tool");
55    }
56
57    #[test]
58    fn test_available_tools_contain_expected_names() {
59        let tools = available_tools();
60        let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
61        assert!(names.contains(&"get_issues"), "missing get_issues tool");
62        assert!(
63            names.contains(&"get_merge_requests"),
64            "missing get_merge_requests tool"
65        );
66    }
67
68    #[test]
69    fn test_available_tools_have_descriptions() {
70        let tools = available_tools();
71        for tool in &tools {
72            assert!(
73                !tool.description.is_empty(),
74                "tool '{}' has empty description",
75                tool.name
76            );
77        }
78    }
79
80    #[test]
81    fn test_available_tools_have_valid_parameters() {
82        let tools = available_tools();
83        for tool in &tools {
84            assert_eq!(
85                tool.parameters["type"], "object",
86                "tool '{}' parameters should be object type",
87                tool.name
88            );
89            assert!(
90                tool.parameters["properties"].is_object(),
91                "tool '{}' should have properties object",
92                tool.name
93            );
94        }
95    }
96
97    #[test]
98    fn test_tool_serialization_roundtrip() {
99        let tools = available_tools();
100        let json = serde_json::to_string(&tools).unwrap();
101        let parsed: Vec<Tool> = serde_json::from_str(&json).unwrap();
102        assert_eq!(parsed.len(), tools.len());
103        let parsed_names: Vec<&str> = parsed.iter().map(|t| t.name.as_str()).collect();
104        for tool in &tools {
105            assert!(parsed_names.contains(&tool.name.as_str()));
106        }
107    }
108}