1use std::io::{BufRead, BufReader, Write};
4use std::sync::Arc;
5
6use tokio::sync::RwLock;
7
8use webpuppet::PermissionGuard;
9
10use crate::error::{codes, Result};
11use crate::protocol::{
12 ClientCapabilities, InitializeParams, InitializeResult, JsonRpcId, JsonRpcRequest,
13 JsonRpcResponse, ListToolsResult, McpMessage, ServerCapabilities, ServerInfo, ToolCallParams,
14 ToolsCapability,
15};
16use crate::tools::ToolRegistry;
17
18pub const PROTOCOL_VERSION: &str = "2024-11-05";
20
21pub const SERVER_NAME: &str = "webpuppet-mcp";
23
24pub const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ServerState {
30 Uninitialized,
32 Ready,
34 ShuttingDown,
36}
37
38pub struct McpServer {
40 state: Arc<RwLock<ServerState>>,
41 tools: Arc<ToolRegistry>,
42 #[allow(dead_code)]
43 client_capabilities: Arc<RwLock<Option<ClientCapabilities>>>,
44}
45
46impl McpServer {
47 pub fn new() -> Self {
49 Self::with_permissions(PermissionGuard::secure())
50 }
51
52 pub fn with_permissions(permissions: PermissionGuard) -> Self {
54 Self {
55 state: Arc::new(RwLock::new(ServerState::Uninitialized)),
56 tools: Arc::new(ToolRegistry::new(permissions)),
57 client_capabilities: Arc::new(RwLock::new(None)),
58 }
59 }
60
61 pub fn with_visible_browser(permissions: PermissionGuard) -> Self {
63 Self {
64 state: Arc::new(RwLock::new(ServerState::Uninitialized)),
65 tools: Arc::new(ToolRegistry::with_visible_browser(permissions)),
66 client_capabilities: Arc::new(RwLock::new(None)),
67 }
68 }
69
70 pub async fn run_stdio(&self) -> Result<()> {
72 let stdin = std::io::stdin();
73 let mut stdout = std::io::stdout();
74 let reader = BufReader::new(stdin.lock());
75
76 tracing::info!("MCP server starting on stdio");
77
78 for line in reader.lines() {
79 let line = line?;
80
81 if line.is_empty() {
82 continue;
83 }
84
85 tracing::debug!("Received: {}", line);
86
87 let response = self.handle_message(&line).await;
88
89 if let Some(response) = response {
90 let json = serde_json::to_string(&response)?;
91 tracing::debug!("Sending: {}", json);
92 writeln!(stdout, "{}", json)?;
93 stdout.flush()?;
94 }
95
96 if *self.state.read().await == ServerState::ShuttingDown {
98 break;
99 }
100 }
101
102 tracing::info!("MCP server shutting down");
103 Ok(())
104 }
105
106 pub async fn handle_message(&self, json: &str) -> Option<JsonRpcResponse> {
108 match McpMessage::parse(json) {
109 Ok(McpMessage::Request(request)) => Some(self.handle_request(request).await),
110 Ok(McpMessage::Notification(notification)) => {
111 self.handle_notification(notification).await;
112 None
113 }
114 Ok(McpMessage::Response(_)) => {
115 None
117 }
118 Err(e) => Some(JsonRpcResponse::error(
119 None,
120 codes::PARSE_ERROR,
121 e.to_string(),
122 )),
123 }
124 }
125
126 async fn handle_request(&self, request: JsonRpcRequest) -> JsonRpcResponse {
128 let id = request.id.clone();
129
130 match request.method.as_str() {
131 "initialize" => self.handle_initialize(id, request.params).await,
132 "tools/list" => self.handle_tools_list(id).await,
133 "tools/call" => self.handle_tools_call(id, request.params).await,
134 "ping" => JsonRpcResponse::success(id, serde_json::json!({})),
135 "shutdown" => {
136 *self.state.write().await = ServerState::ShuttingDown;
137 JsonRpcResponse::success(id, serde_json::json!({}))
138 }
139 _ => JsonRpcResponse::error(
140 id,
141 codes::METHOD_NOT_FOUND,
142 format!("method not found: {}", request.method),
143 ),
144 }
145 }
146
147 async fn handle_notification(&self, notification: JsonRpcRequest) {
149 match notification.method.as_str() {
150 "notifications/initialized" => {
151 tracing::info!("Client initialized");
152 }
153 "notifications/cancelled" => {
154 tracing::debug!("Request cancelled by client");
155 }
156 "exit" => {
157 *self.state.write().await = ServerState::ShuttingDown;
158 }
159 _ => {
160 tracing::debug!("Unknown notification: {}", notification.method);
161 }
162 }
163 }
164
165 async fn handle_initialize(
167 &self,
168 id: Option<JsonRpcId>,
169 params: Option<serde_json::Value>,
170 ) -> JsonRpcResponse {
171 let _params: InitializeParams = match params {
173 Some(p) => match serde_json::from_value(p) {
174 Ok(params) => params,
175 Err(e) => {
176 return JsonRpcResponse::error(
177 id,
178 codes::INVALID_PARAMS,
179 format!("invalid initialize params: {}", e),
180 );
181 }
182 },
183 None => {
184 return JsonRpcResponse::error(
185 id,
186 codes::INVALID_PARAMS,
187 "initialize params required",
188 );
189 }
190 };
191
192 *self.state.write().await = ServerState::Ready;
194
195 let result = InitializeResult {
197 protocol_version: PROTOCOL_VERSION.into(),
198 capabilities: ServerCapabilities {
199 tools: Some(ToolsCapability {
200 list_changed: false,
201 }),
202 resources: None,
203 prompts: None,
204 logging: None,
205 },
206 server_info: ServerInfo {
207 name: SERVER_NAME.into(),
208 version: SERVER_VERSION.into(),
209 },
210 };
211
212 JsonRpcResponse::success(id, result)
213 }
214
215 async fn handle_tools_list(&self, id: Option<JsonRpcId>) -> JsonRpcResponse {
217 let state = *self.state.read().await;
218 if state != ServerState::Ready {
219 return JsonRpcResponse::error(id, codes::INTERNAL_ERROR, "server not initialized");
220 }
221
222 let tools = self.tools.list_tools();
223 let result = ListToolsResult { tools };
224
225 JsonRpcResponse::success(id, result)
226 }
227
228 async fn handle_tools_call(
230 &self,
231 id: Option<JsonRpcId>,
232 params: Option<serde_json::Value>,
233 ) -> JsonRpcResponse {
234 let state = *self.state.read().await;
235 if state != ServerState::Ready {
236 return JsonRpcResponse::error(id, codes::INTERNAL_ERROR, "server not initialized");
237 }
238
239 let params: ToolCallParams = match params {
241 Some(p) => match serde_json::from_value(p) {
242 Ok(params) => params,
243 Err(e) => {
244 return JsonRpcResponse::error(
245 id,
246 codes::INVALID_PARAMS,
247 format!("invalid tool call params: {}", e),
248 );
249 }
250 },
251 None => {
252 return JsonRpcResponse::error(
253 id,
254 codes::INVALID_PARAMS,
255 "tool call params required",
256 );
257 }
258 };
259
260 match self.tools.execute(¶ms.name, params.arguments).await {
262 Ok(result) => JsonRpcResponse::success(id, result),
263 Err(e) => {
264 tracing::error!("Tool {} failed: {}", params.name, e);
265 JsonRpcResponse::error(id, e.code(), e.to_string())
266 }
267 }
268 }
269}
270
271impl Default for McpServer {
272 fn default() -> Self {
273 Self::new()
274 }
275}