Skip to main content

chant/mcp/
server.rs

1//! MCP server main loop and request handling.
2
3use anyhow::{Context, Result};
4use serde_json::Value;
5use std::io::{BufRead, BufReader, Write};
6
7use super::handlers::{handle_method, handle_notification};
8use super::protocol::{JsonRpcRequest, JsonRpcResponse};
9
10/// Run the MCP server, reading from stdin and writing to stdout.
11pub fn run_server() -> Result<()> {
12    let stdin = std::io::stdin();
13    let mut stdout = std::io::stdout();
14    let reader = BufReader::new(stdin.lock());
15
16    for line in reader.lines() {
17        let line = line.context("Failed to read from stdin")?;
18
19        if line.trim().is_empty() {
20            continue;
21        }
22
23        let response = handle_request(&line);
24
25        if let Some(resp) = response {
26            let output = serde_json::to_string(&resp)?;
27            writeln!(stdout, "{}", output)?;
28            stdout.flush()?;
29        }
30    }
31
32    Ok(())
33}
34
35/// Handle a single JSON-RPC request line.
36///
37/// # Request Processing
38///
39/// 1. Parse JSON-RPC 2.0 request from the line
40/// 2. Validate `jsonrpc` field is `"2.0"`
41/// 3. Dispatch to appropriate handler based on `method`
42/// 4. Return response or `None` for notifications
43///
44/// # Error Handling
45///
46/// - **Parse Error (-32700)**: JSON is invalid or malformed
47/// - **Invalid Version (-32600)**: `jsonrpc` field is not `"2.0"`
48/// - **Server Error (-32603)**: Handler function returns `Err`
49/// - **No Response**: Notifications (requests without `id`) are handled silently
50///
51/// # Returns
52///
53/// - `Some(response)`: For requests (with `id`)
54/// - `None`: For notifications (without `id`)
55pub fn handle_request(line: &str) -> Option<JsonRpcResponse> {
56    let request: JsonRpcRequest = match serde_json::from_str(line) {
57        Ok(req) => req,
58        Err(e) => {
59            return Some(JsonRpcResponse::error(
60                Value::Null,
61                -32700,
62                &format!("Parse error: {}", e),
63            ));
64        }
65    };
66
67    // Validate jsonrpc version
68    if request.jsonrpc != "2.0" {
69        return Some(JsonRpcResponse::error(
70            request.id.unwrap_or(Value::Null),
71            -32600,
72            "Invalid JSON-RPC version",
73        ));
74    }
75
76    // Notifications (no id) don't get responses
77    let id = match request.id {
78        Some(id) => id,
79        None => {
80            // Handle notification (no response needed)
81            handle_notification(&request.method, request.params.as_ref());
82            return None;
83        }
84    };
85
86    let result = handle_method(&request.method, request.params.as_ref());
87
88    match result {
89        Ok(value) => Some(JsonRpcResponse::success(id, value)),
90        Err(e) => Some(JsonRpcResponse::error(id, -32603, &e.to_string())),
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_handle_request_parse_error() {
100        let response = handle_request("{invalid json}");
101        assert!(response.is_some());
102        let resp = response.unwrap();
103        assert!(resp.error.is_some());
104        assert_eq!(resp.error.as_ref().unwrap().code, -32700);
105    }
106
107    #[test]
108    fn test_handle_request_invalid_version() {
109        let response = handle_request(r#"{"jsonrpc":"1.0","method":"test","id":1}"#);
110        assert!(response.is_some());
111        let resp = response.unwrap();
112        assert!(resp.error.is_some());
113        assert_eq!(resp.error.as_ref().unwrap().code, -32600);
114    }
115
116    #[test]
117    fn test_handle_request_notification() {
118        let response = handle_request(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#);
119        assert!(response.is_none());
120    }
121}