#[cfg(feature = "daemon")]
pub(crate) mod daemon;
mod dispatch;
pub(crate) mod protocol;
pub(crate) mod tools;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use anyhow::Result;
use serde_json::Value;
use protocol::{
InitializeResult, JsonRpcRequest, JsonRpcResponse, ServerCapabilities, ServerInfo,
ToolCallParams, ToolResult, ToolsCapability, INVALID_PARAMS, INVALID_REQUEST, METHOD_NOT_FOUND,
PARSE_ERROR,
};
const PROTOCOL_VERSION: &str = "2024-11-05";
pub fn serve() -> Result<()> {
let tools = tools::build_tools();
let stdin = io::stdin().lock();
let stdout = io::stdout().lock();
let mut reader = BufReader::new(stdin);
let mut writer = BufWriter::new(stdout);
loop {
let mut line = String::new();
if reader.read_line(&mut line)? == 0 {
break; }
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let value: Value = match serde_json::from_str(trimmed) {
Ok(v) => v,
Err(e) => {
write_response(
&mut writer,
&JsonRpcResponse::error(Value::Null, PARSE_ERROR, format!("parse error: {e}")),
)?;
continue;
}
};
let raw_id = value.get("id").cloned();
let request: JsonRpcRequest = match serde_json::from_value(value) {
Ok(r) => r,
Err(e) => {
write_response(
&mut writer,
&JsonRpcResponse::error(
raw_id.unwrap_or(Value::Null),
INVALID_REQUEST,
format!("invalid request: {e}"),
),
)?;
continue;
}
};
if request.jsonrpc != "2.0" {
if raw_id.is_some() {
write_response(
&mut writer,
&JsonRpcResponse::error(
raw_id.unwrap_or(Value::Null),
INVALID_REQUEST,
format!("unsupported jsonrpc version: {:?}", request.jsonrpc),
),
)?;
}
continue;
}
if let Some(ref id) = raw_id {
match id {
Value::String(_) | Value::Number(_) | Value::Null => {}
_ => {
write_response(
&mut writer,
&JsonRpcResponse::error(
Value::Null, INVALID_REQUEST,
"id must be a string, number, or null",
),
)?;
continue;
}
}
}
match &request.params {
Value::Object(_) | Value::Array(_) | Value::Null => {}
_ => {
if raw_id.is_some() {
write_response(
&mut writer,
&JsonRpcResponse::error(
raw_id.unwrap_or(Value::Null),
INVALID_REQUEST,
"params must be a structured value (object or array)",
),
)?;
}
continue;
}
}
let id = match raw_id {
Some(id) => id,
None => continue,
};
let response = handle_request(&request.method, &request.params, id, &tools);
write_response(&mut writer, &response)?;
}
Ok(())
}
pub(crate) fn handle_request(
method: &str,
params: &Value,
id: Value,
tools: &[protocol::Tool],
) -> JsonRpcResponse {
match method {
"initialize" => {
let result = InitializeResult {
protocol_version: PROTOCOL_VERSION,
capabilities: ServerCapabilities {
tools: ToolsCapability {},
},
server_info: ServerInfo {
name: "ccd",
version: env!("CARGO_PKG_VERSION"),
},
};
JsonRpcResponse::success(id, serde_json::to_value(result).unwrap())
}
"ping" => JsonRpcResponse::success(id, serde_json::json!({})),
"tools/list" => {
let list = serde_json::json!({ "tools": tools });
JsonRpcResponse::success(id, list)
}
"tools/call" => handle_tool_call(params, id),
_ => JsonRpcResponse::error(id, METHOD_NOT_FOUND, format!("method not found: {method}")),
}
}
fn handle_tool_call(params: &Value, id: Value) -> JsonRpcResponse {
let call: ToolCallParams = match serde_json::from_value(params.clone()) {
Ok(c) => c,
Err(e) => {
return JsonRpcResponse::error(
id,
INVALID_PARAMS,
format!("invalid tool call params: {e}"),
);
}
};
match dispatch::dispatch(&call.name, &call.arguments) {
Ok(report_value) => {
let text = serde_json::to_string(&report_value).unwrap_or_default();
let result = ToolResult::text(text);
JsonRpcResponse::success(id, serde_json::to_value(result).unwrap())
}
Err(e) => {
let result = ToolResult::error(format!("{e:#}"));
JsonRpcResponse::success(id, serde_json::to_value(result).unwrap())
}
}
}
fn write_response(
writer: &mut BufWriter<io::StdoutLock<'_>>,
response: &JsonRpcResponse,
) -> Result<()> {
serde_json::to_writer(&mut *writer, response)?;
writer.write_all(b"\n")?;
writer.flush()?;
Ok(())
}