1use std::collections::HashMap;
8use std::sync::Arc;
9
10use serde_json::{json, Value};
11use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
12
13use anvil_core::Application;
14
15use crate::log_capture::LogBuffer;
16use crate::protocol::{
17 CallToolParams, CallToolResult, ContentBlock, InitializeResult, JsonRpcRequest,
18 JsonRpcResponse, ListToolsResult, ServerCapabilities, ServerInfo, ToolDescriptor,
19 ToolsCapability, PROTOCOL_VERSION,
20};
21use crate::tool::{Context, Tool};
22
23pub struct Server {
24 ctx: Arc<Context>,
25 tools: HashMap<&'static str, Arc<dyn Tool>>,
26}
27
28impl Server {
29 pub fn with_defaults(app: &Application, log_buffer: Arc<LogBuffer>) -> Self {
31 let ctx = Arc::new(Context::from_app(app, log_buffer));
32 let mut tools: HashMap<&'static str, Arc<dyn Tool>> = HashMap::new();
33 for tool in crate::tools::all() {
34 tools.insert(tool.name(), tool);
35 }
36 Self { ctx, tools }
37 }
38
39 pub fn register(&mut self, tool: Arc<dyn Tool>) {
40 self.tools.insert(tool.name(), tool);
41 }
42
43 pub async fn serve_stdio(self) -> std::io::Result<()> {
45 let stdin = tokio::io::stdin();
46 let mut stdout = tokio::io::stdout();
47 let mut reader = BufReader::new(stdin).lines();
48
49 while let Some(line) = reader.next_line().await? {
50 if line.trim().is_empty() {
51 continue;
52 }
53 let response = self.handle_line(&line).await;
54 if let Some(resp) = response {
55 let bytes = serde_json::to_vec(&resp).unwrap_or_else(|_| b"{}".to_vec());
56 stdout.write_all(&bytes).await?;
57 stdout.write_all(b"\n").await?;
58 stdout.flush().await?;
59 }
60 }
61 Ok(())
62 }
63
64 async fn handle_line(&self, line: &str) -> Option<JsonRpcResponse> {
65 let req: JsonRpcRequest = match serde_json::from_str(line) {
66 Ok(r) => r,
67 Err(e) => {
68 tracing::warn!(error = %e, line, "boost: failed to parse JSON-RPC request");
69 return None;
70 }
71 };
72
73 let id = req.id.clone().unwrap_or(Value::Null);
74 let is_notification = req.id.is_none();
75
76 let result = match req.method.as_str() {
77 "initialize" => self.handle_initialize(),
78 "notifications/initialized" | "initialized" => {
79 if is_notification {
81 return None;
82 }
83 Ok(json!({}))
84 }
85 "tools/list" => self.handle_list_tools(),
86 "tools/call" => self.handle_call_tool(&req.params).await,
87 "ping" => Ok(json!({})),
88 other => Err(format!("method not implemented: {other}")),
89 };
90
91 if is_notification {
92 return None;
93 }
94
95 Some(match result {
96 Ok(value) => JsonRpcResponse::ok(id, value),
97 Err(msg) => JsonRpcResponse::err(id, -32601, msg),
98 })
99 }
100
101 fn handle_initialize(&self) -> Result<Value, String> {
102 let result = InitializeResult {
103 protocol_version: PROTOCOL_VERSION,
104 capabilities: ServerCapabilities {
105 tools: ToolsCapability {
106 list_changed: false,
107 },
108 },
109 server_info: ServerInfo {
110 name: "anvilforge-boost",
111 version: env!("CARGO_PKG_VERSION"),
112 },
113 };
114 serde_json::to_value(result).map_err(|e| e.to_string())
115 }
116
117 fn handle_list_tools(&self) -> Result<Value, String> {
118 let mut descriptors: Vec<ToolDescriptor> = self
119 .tools
120 .values()
121 .map(|t| ToolDescriptor {
122 name: t.name().to_string(),
123 description: t.description().to_string(),
124 input_schema: t.input_schema(),
125 })
126 .collect();
127 descriptors.sort_by(|a, b| a.name.cmp(&b.name));
128 serde_json::to_value(ListToolsResult { tools: descriptors }).map_err(|e| e.to_string())
129 }
130
131 async fn handle_call_tool(&self, params: &Value) -> Result<Value, String> {
132 let parsed: CallToolParams =
133 serde_json::from_value(params.clone()).map_err(|e| format!("bad params: {e}"))?;
134 let Some(tool) = self.tools.get(parsed.name.as_str()) else {
135 return Ok(serde_json::to_value(CallToolResult {
136 content: vec![ContentBlock::Text {
137 text: format!("unknown tool: {}", parsed.name),
138 }],
139 is_error: true,
140 })
141 .unwrap());
142 };
143 let result = tool.call(&self.ctx, parsed.arguments).await;
144 serde_json::to_value(result).map_err(|e| e.to_string())
145 }
146}
147
148pub async fn serve(app: &Application) -> std::io::Result<()> {
151 let buffer = crate::log_capture::install();
152 Server::with_defaults(app, buffer).serve_stdio().await
153}