1pub mod handlers;
9pub mod protocol;
10pub mod response;
11pub mod server;
12pub mod tools;
13
14pub use handlers::{handle_method, handle_notification};
15pub use response::{mcp_error_response, mcp_text_response};
16pub use server::run_server;
17
18#[cfg(test)]
19mod tests {
20 use super::handlers::{handle_method, handle_notification};
21 use super::protocol::{JsonRpcResponse, PROTOCOL_VERSION, SERVER_NAME};
22 use serde_json::json;
23
24 #[test]
25 fn test_handle_initialize() {
26 let result = handle_method("initialize", None).unwrap();
27 assert_eq!(result["protocolVersion"], PROTOCOL_VERSION);
28 assert_eq!(result["serverInfo"]["name"], SERVER_NAME);
29 }
30
31 #[test]
32 fn test_handle_tools_list() {
33 let result = handle_method("tools/list", None).unwrap();
34 let tools = result["tools"].as_array().unwrap();
35 assert_eq!(tools.len(), 23);
36 assert_eq!(tools[0]["name"], "chant_spec_list");
38 assert_eq!(tools[1]["name"], "chant_spec_get");
39 assert_eq!(tools[2]["name"], "chant_ready");
40 assert_eq!(tools[3]["name"], "chant_status");
41 assert_eq!(tools[4]["name"], "chant_log");
42 assert_eq!(tools[5]["name"], "chant_search");
43 assert_eq!(tools[6]["name"], "chant_diagnose");
44 assert_eq!(tools[7]["name"], "chant_lint");
45 assert_eq!(tools[8]["name"], "chant_spec_update");
47 assert_eq!(tools[9]["name"], "chant_add");
48 assert_eq!(tools[10]["name"], "chant_finalize");
49 assert_eq!(tools[11]["name"], "chant_reset");
50 assert_eq!(tools[12]["name"], "chant_cancel");
51 assert_eq!(tools[13]["name"], "chant_archive");
52 assert_eq!(tools[14]["name"], "chant_verify");
53 assert_eq!(tools[15]["name"], "chant_work_start");
54 assert_eq!(tools[16]["name"], "chant_work_list");
55 assert_eq!(tools[17]["name"], "chant_pause");
56 assert_eq!(tools[18]["name"], "chant_takeover");
57 }
58
59 #[test]
60 fn test_json_rpc_response_success() {
61 let resp = JsonRpcResponse::success(json!(1), json!({"test": true}));
62 assert_eq!(resp.jsonrpc, "2.0");
63 assert!(resp.result.is_some());
64 assert!(resp.error.is_none());
65 }
66
67 #[test]
68 fn test_json_rpc_response_error() {
69 let resp = JsonRpcResponse::error(json!(1), -32600, "Invalid request");
70 assert_eq!(resp.jsonrpc, "2.0");
71 assert!(resp.result.is_none());
72 assert!(resp.error.is_some());
73 assert_eq!(resp.error.as_ref().unwrap().code, -32600);
74 }
75
76 #[test]
77 fn test_chant_status_schema_has_brief_and_activity() {
78 let result = handle_method("tools/list", None).unwrap();
79 let tools = result["tools"].as_array().unwrap();
80 let status_tool = tools.iter().find(|t| t["name"] == "chant_status").unwrap();
81
82 let props = &status_tool["inputSchema"]["properties"];
83 assert!(
84 props.get("brief").is_some(),
85 "chant_status should have 'brief' property"
86 );
87 assert!(
88 props.get("include_activity").is_some(),
89 "chant_status should have 'include_activity' property"
90 );
91
92 assert!(props["brief"]["description"]
94 .as_str()
95 .unwrap()
96 .contains("single-line"));
97 assert!(props["include_activity"]["description"]
98 .as_str()
99 .unwrap()
100 .contains("activity"));
101 }
102
103 #[test]
104 fn test_chant_ready_has_limit_param() {
105 let result = handle_method("tools/list", None).unwrap();
106 let tools = result["tools"].as_array().unwrap();
107 let ready_tool = tools.iter().find(|t| t["name"] == "chant_ready").unwrap();
108
109 let props = &ready_tool["inputSchema"]["properties"];
110 assert!(
111 props.get("limit").is_some(),
112 "chant_ready should have 'limit' property"
113 );
114 assert_eq!(props["limit"]["type"], "integer");
115 assert!(props["limit"]["description"]
116 .as_str()
117 .unwrap()
118 .contains("50"));
119 }
120
121 #[test]
122 fn test_chant_spec_list_has_limit_param() {
123 let result = handle_method("tools/list", None).unwrap();
124 let tools = result["tools"].as_array().unwrap();
125 let list_tool = tools
126 .iter()
127 .find(|t| t["name"] == "chant_spec_list")
128 .unwrap();
129
130 let props = &list_tool["inputSchema"]["properties"];
131 assert!(
132 props.get("limit").is_some(),
133 "chant_spec_list should have 'limit' property"
134 );
135 assert_eq!(props["limit"]["type"], "integer");
136 assert!(props["limit"]["description"]
137 .as_str()
138 .unwrap()
139 .contains("50"));
140 }
141
142 #[test]
143 fn test_handle_notification() {
144 handle_notification("notifications/initialized", None);
146 handle_notification("unknown_notification", None);
147 }
148}