diff --git a/src-tauri/src/bin/wraith_mcp_bridge.rs b/src-tauri/src/bin/wraith_mcp_bridge.rs index 7e978b6..908b77b 100644 --- a/src-tauri/src/bin/wraith_mcp_bridge.rs +++ b/src-tauri/src/bin/wraith_mcp_bridge.rs @@ -264,6 +264,21 @@ fn handle_tools_list(id: Value) -> JsonRpcResponse { "description": "Show recent commits on a remote repository", "inputSchema": { "type": "object", "properties": { "session_id": { "type": "string" }, "path": { "type": "string" } }, "required": ["session_id", "path"] } }, + { + "name": "rdp_click", + "description": "Click at a position in an RDP session (use terminal_screenshot first to see coordinates)", + "inputSchema": { "type": "object", "properties": { "session_id": { "type": "string" }, "x": { "type": "number" }, "y": { "type": "number" }, "button": { "type": "string", "description": "left (default), right, or middle" } }, "required": ["session_id", "x", "y"] } + }, + { + "name": "rdp_type", + "description": "Type text into an RDP session via clipboard paste", + "inputSchema": { "type": "object", "properties": { "session_id": { "type": "string" }, "text": { "type": "string" } }, "required": ["session_id", "text"] } + }, + { + "name": "rdp_clipboard", + "description": "Set the clipboard content on a remote RDP session", + "inputSchema": { "type": "object", "properties": { "session_id": { "type": "string" }, "text": { "type": "string" } }, "required": ["session_id", "text"] } + }, { "name": "list_sessions", "description": "List all active Wraith sessions (SSH, RDP, PTY) with connection details", @@ -329,6 +344,9 @@ fn handle_tool_call(id: Value, port: u16, tool_name: &str, args: &Value) -> Json "git_status" => call_wraith(port, "/mcp/git/status", args.clone()), "git_pull" => call_wraith(port, "/mcp/git/pull", args.clone()), "git_log" => call_wraith(port, "/mcp/git/log", args.clone()), + "rdp_click" => call_wraith(port, "/mcp/rdp/click", args.clone()), + "rdp_type" => call_wraith(port, "/mcp/rdp/type", args.clone()), + "rdp_clipboard" => call_wraith(port, "/mcp/rdp/clipboard", args.clone()), "terminal_screenshot" => { let result = call_wraith(port, "/mcp/screenshot", args.clone()); // Screenshot returns base64 PNG — wrap as image content for multimodal AI diff --git a/src-tauri/src/mcp/server.rs b/src-tauri/src/mcp/server.rs index a2ab797..5b37cc2 100644 --- a/src-tauri/src/mcp/server.rs +++ b/src-tauri/src/mcp/server.rs @@ -432,6 +432,44 @@ async fn handle_git_log(AxumState(state): AxumState>, Json(r match tool_exec(&session.handle, &format!("cd {} && git log --oneline -20 2>&1", req.path)).await { Ok(o) => ok_response(o), Err(e) => err_response(e) } } +// ── RDP interaction handlers ───────────────────────────────────────────────── + +#[derive(Deserialize)] +struct RdpClickRequest { session_id: String, x: u16, y: u16, button: Option } + +#[derive(Deserialize)] +struct RdpTypeRequest { session_id: String, text: String } + +#[derive(Deserialize)] +struct RdpClipboardRequest { session_id: String, text: String } + +async fn handle_rdp_click(AxumState(state): AxumState>, Json(req): Json) -> Json> { + use crate::rdp::input::mouse_flags; + let button_flag = match req.button.as_deref().unwrap_or("left") { + "right" => mouse_flags::BUTTON2, + "middle" => mouse_flags::BUTTON3, + _ => mouse_flags::BUTTON1, + }; + // Move to position + if let Err(e) = state.rdp.send_mouse(&req.session_id, req.x, req.y, mouse_flags::MOVE) { return err_response(e); } + // Click down + if let Err(e) = state.rdp.send_mouse(&req.session_id, req.x, req.y, button_flag | mouse_flags::DOWN) { return err_response(e); } + // Click up + if let Err(e) = state.rdp.send_mouse(&req.session_id, req.x, req.y, button_flag) { return err_response(e); } + ok_response(format!("clicked ({}, {})", req.x, req.y)) +} + +async fn handle_rdp_type(AxumState(state): AxumState>, Json(req): Json) -> Json> { + // Type text by sending it via clipboard paste (most reliable for arbitrary text) + if let Err(e) = state.rdp.send_clipboard(&req.session_id, &req.text) { return err_response(e); } + ok_response(format!("typed {} chars", req.text.len())) +} + +async fn handle_rdp_clipboard(AxumState(state): AxumState>, Json(req): Json) -> Json> { + if let Err(e) = state.rdp.send_clipboard(&req.session_id, &req.text) { return err_response(e); } + ok_response("clipboard set".to_string()) +} + /// Start the MCP HTTP server and write the port to disk. pub async fn start_mcp_server( ssh: SshService, @@ -469,6 +507,9 @@ pub async fn start_mcp_server( .route("/mcp/git/status", post(handle_git_status)) .route("/mcp/git/pull", post(handle_git_pull)) .route("/mcp/git/log", post(handle_git_log)) + .route("/mcp/rdp/click", post(handle_rdp_click)) + .route("/mcp/rdp/type", post(handle_rdp_type)) + .route("/mcp/rdp/clipboard", post(handle_rdp_clipboard)) .with_state(state); let listener = TcpListener::bind("127.0.0.1:0").await