use std::io::{BufRead, BufReader, Read, Write};
use sqlrite::Connection;
use crate::error::ProtocolError;
use crate::protocol::{ServerState, handle};
const MAX_LINE_BYTES: usize = 1024 * 1024;
pub fn run<R: Read, W: Write>(
stdin: R,
mut stdout: W,
conn: Connection,
read_only: bool,
) -> std::io::Result<()> {
let mut reader = BufReader::with_capacity(MAX_LINE_BYTES, stdin);
let mut state = ServerState::new(conn, read_only);
let mut line = String::new();
loop {
line.clear();
let n = match reader.read_line(&mut line) {
Ok(0) => return Ok(()), Ok(n) => n,
Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => return Ok(()),
Err(err) => return Err(err),
};
let trimmed = line.trim_end_matches(['\r', '\n']);
if trimmed.is_empty() {
continue; }
if n > MAX_LINE_BYTES {
write_protocol_error(
&mut stdout,
None,
ProtocolError::parse_error(format!(
"request exceeds maximum size of {} bytes",
MAX_LINE_BYTES
)),
)?;
continue;
}
let response = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handle(trimmed, &mut state)
})) {
Ok(resp) => resp,
Err(panic) => {
let msg = panic_to_string(panic);
eprintln!("[sqlrite-mcp] panic in handler: {msg}");
Some(serde_json::json!({
"jsonrpc": "2.0",
"id": null,
"error": {
"code": crate::error::jsonrpc_codes::INTERNAL_ERROR,
"message": format!("internal server error: {msg}"),
}
}))
}
};
if let Some(resp) = response {
serde_json::to_writer(&mut stdout, &resp)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
}
}
fn write_protocol_error<W: Write>(
mut stdout: W,
id: Option<serde_json::Value>,
err: ProtocolError,
) -> std::io::Result<()> {
let resp = serde_json::json!({
"jsonrpc": "2.0",
"id": id.unwrap_or(serde_json::Value::Null),
"error": {
"code": err.code,
"message": err.message,
}
});
serde_json::to_writer(&mut stdout, &resp)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
Ok(())
}
fn panic_to_string(payload: Box<dyn std::any::Any + Send>) -> String {
if let Some(s) = payload.downcast_ref::<&'static str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"<non-string panic payload>".to_string()
}
}