1use crate::mock::{MockPrompt, MockResource, MockTool};
6use mcpkit_core::types::{Prompt, PromptArgument, Resource, Tool, ToolAnnotations, ToolOutput};
7
8#[must_use]
16pub fn sample_tools() -> Vec<MockTool> {
17 vec![
18 MockTool::new("echo")
19 .description("Echo back the input")
20 .input_schema(serde_json::json!({
21 "type": "object",
22 "properties": {
23 "message": { "type": "string" }
24 },
25 "required": ["message"]
26 }))
27 .handler(|args| {
28 let message = args
29 .get("message")
30 .and_then(|v| v.as_str())
31 .unwrap_or("(no message)");
32 Ok(ToolOutput::text(message))
33 }),
34 MockTool::new("add")
35 .description("Add two numbers")
36 .input_schema(serde_json::json!({
37 "type": "object",
38 "properties": {
39 "a": { "type": "number" },
40 "b": { "type": "number" }
41 },
42 "required": ["a", "b"]
43 }))
44 .annotations(ToolAnnotations::read_only())
45 .handler(|args| {
46 let a = args
47 .get("a")
48 .and_then(serde_json::Value::as_f64)
49 .unwrap_or(0.0);
50 let b = args
51 .get("b")
52 .and_then(serde_json::Value::as_f64)
53 .unwrap_or(0.0);
54 Ok(ToolOutput::text(format!("{}", a + b)))
55 }),
56 MockTool::new("multiply")
57 .description("Multiply two numbers")
58 .input_schema(serde_json::json!({
59 "type": "object",
60 "properties": {
61 "a": { "type": "number" },
62 "b": { "type": "number" }
63 },
64 "required": ["a", "b"]
65 }))
66 .annotations(ToolAnnotations::read_only())
67 .handler(|args| {
68 let a = args
69 .get("a")
70 .and_then(serde_json::Value::as_f64)
71 .unwrap_or(0.0);
72 let b = args
73 .get("b")
74 .and_then(serde_json::Value::as_f64)
75 .unwrap_or(0.0);
76 Ok(ToolOutput::text(format!("{}", a * b)))
77 }),
78 MockTool::new("fail")
79 .description("Always fails")
80 .returns_error("This tool always fails"),
81 ]
82}
83
84#[must_use]
91pub fn sample_resources() -> Vec<MockResource> {
92 vec![
93 MockResource::new("test://readme", "README")
94 .description("A sample README file")
95 .mime_type("text/markdown")
96 .content("# Test Server\n\nThis is a test server."),
97 MockResource::new("test://config", "Configuration")
98 .description("Server configuration")
99 .mime_type("application/json")
100 .content(r#"{"debug": true, "port": 8080}"#),
101 MockResource::new("test://data", "Sample Data")
102 .description("Sample data for testing")
103 .mime_type("application/json")
104 .content(r#"{"items": [1, 2, 3], "count": 3}"#),
105 ]
106}
107
108#[must_use]
114pub fn sample_prompts() -> Vec<MockPrompt> {
115 vec![
116 MockPrompt::new("summarize")
117 .description("Summarize the given text")
118 .template("Please summarize the following text:\n\n{{text}}"),
119 MockPrompt::new("translate")
120 .description("Translate text to another language")
121 .template("Translate the following text to {{language}}:\n\n{{text}}"),
122 ]
123}
124
125#[must_use]
127pub fn tool_definition(name: &str, description: &str) -> Tool {
128 Tool::new(name).description(description)
129}
130
131#[must_use]
133pub fn resource_definition(uri: &str, name: &str) -> Resource {
134 Resource::new(uri, name)
135}
136
137#[must_use]
139pub fn prompt_definition(name: &str) -> Prompt {
140 Prompt::new(name)
141}
142
143#[must_use]
145pub fn prompt_with_args(name: &str, description: &str, args: Vec<(&str, &str, bool)>) -> Prompt {
146 let mut prompt = Prompt::new(name).description(description);
147 for (arg_name, arg_desc, required) in args {
148 let arg = if required {
149 PromptArgument::required(arg_name, arg_desc)
150 } else {
151 PromptArgument::optional(arg_name, arg_desc)
152 };
153 prompt = prompt.argument(arg);
154 }
155 prompt
156}
157
158#[must_use]
162pub fn calculator_tools() -> Vec<MockTool> {
163 vec![
164 MockTool::new("add")
165 .description("Add two numbers together")
166 .input_schema(serde_json::json!({
167 "type": "object",
168 "properties": {
169 "a": { "type": "number", "description": "First number" },
170 "b": { "type": "number", "description": "Second number" }
171 },
172 "required": ["a", "b"]
173 }))
174 .handler(|args| {
175 let a = args
176 .get("a")
177 .and_then(serde_json::Value::as_f64)
178 .unwrap_or(0.0);
179 let b = args
180 .get("b")
181 .and_then(serde_json::Value::as_f64)
182 .unwrap_or(0.0);
183 Ok(ToolOutput::text(format!("{}", a + b)))
184 }),
185 MockTool::new("subtract")
186 .description("Subtract two numbers")
187 .handler(|args| {
188 let a = args
189 .get("a")
190 .and_then(serde_json::Value::as_f64)
191 .unwrap_or(0.0);
192 let b = args
193 .get("b")
194 .and_then(serde_json::Value::as_f64)
195 .unwrap_or(0.0);
196 Ok(ToolOutput::text(format!("{}", a - b)))
197 }),
198 MockTool::new("multiply")
199 .description("Multiply two numbers")
200 .handler(|args| {
201 let a = args
202 .get("a")
203 .and_then(serde_json::Value::as_f64)
204 .unwrap_or(0.0);
205 let b = args
206 .get("b")
207 .and_then(serde_json::Value::as_f64)
208 .unwrap_or(0.0);
209 Ok(ToolOutput::text(format!("{}", a * b)))
210 }),
211 MockTool::new("divide")
212 .description("Divide two numbers")
213 .handler(|args| {
214 let a = args
215 .get("a")
216 .and_then(serde_json::Value::as_f64)
217 .unwrap_or(0.0);
218 let b = args
219 .get("b")
220 .and_then(serde_json::Value::as_f64)
221 .unwrap_or(0.0);
222 if b == 0.0 {
223 Ok(ToolOutput::error("Cannot divide by zero"))
224 } else {
225 Ok(ToolOutput::text(format!("{}", a / b)))
226 }
227 }),
228 ]
229}
230
231#[must_use]
235pub fn filesystem_resources() -> Vec<MockResource> {
236 vec![
237 MockResource::new("file:///project/src/main.rs", "main.rs")
238 .mime_type("text/x-rust")
239 .content("fn main() {\n println!(\"Hello, world!\");\n}"),
240 MockResource::new("file:///project/Cargo.toml", "Cargo.toml")
241 .mime_type("text/x-toml")
242 .content("[package]\nname = \"test\"\nversion = \"0.1.0\""),
243 MockResource::new("file:///project/README.md", "README.md")
244 .mime_type("text/markdown")
245 .content("# Test Project\n\nA test project."),
246 ]
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_sample_tools() {
255 let tools = sample_tools();
256 assert_eq!(tools.len(), 4);
257 assert!(tools.iter().any(|t| t.name == "echo"));
258 assert!(tools.iter().any(|t| t.name == "add"));
259 }
260
261 #[test]
262 fn test_sample_resources() {
263 let resources = sample_resources();
264 assert_eq!(resources.len(), 3);
265 assert!(resources.iter().any(|r| r.uri == "test://readme"));
266 }
267
268 #[test]
269 fn test_calculator_tools() -> Result<(), Box<dyn std::error::Error>> {
270 let tools = calculator_tools();
271 assert_eq!(tools.len(), 4);
272
273 let add = tools
275 .iter()
276 .find(|t| t.name == "add")
277 .ok_or("add tool not found")?;
278 let result = add.call(serde_json::json!({"a": 5, "b": 3}))?;
279 match result {
280 ToolOutput::Success(r) => {
281 if let mcpkit_core::types::Content::Text(tc) = &r.content[0] {
282 assert_eq!(tc.text, "8");
283 }
284 }
285 _ => panic!("Expected success"),
286 }
287
288 let divide = tools
290 .iter()
291 .find(|t| t.name == "divide")
292 .ok_or("divide tool not found")?;
293 let result = divide.call(serde_json::json!({"a": 5, "b": 0}))?;
294 match result {
295 ToolOutput::RecoverableError { message, .. } => {
296 assert!(message.contains("zero"));
297 }
298 _ => panic!("Expected error"),
299 }
300 Ok(())
301 }
302
303 #[test]
304 fn test_prompt_with_args() -> Result<(), Box<dyn std::error::Error>> {
305 let prompt = prompt_with_args(
306 "test",
307 "A test prompt",
308 vec![
309 ("required_arg", "A required argument", true),
310 ("optional_arg", "An optional argument", false),
311 ],
312 );
313
314 assert_eq!(prompt.name, "test");
315 let args = prompt.arguments.ok_or("arguments not found")?;
316 assert_eq!(args.len(), 2);
317 assert!(args[0].required.ok_or("required field not found")?);
318 assert!(!args[1].required.ok_or("required field not found")?);
319 Ok(())
320 }
321}