Add MCP server (v1 read-only): toggle, per-pane gate, panel UI
This commit is contained in:
parent
6068522ee3
commit
83d8932c98
15 changed files with 1235 additions and 7 deletions
|
|
@ -1,10 +1,14 @@
|
|||
//! Tauri command surface. Every JS-callable function lives here.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::creds;
|
||||
use crate::hosts::{self, SshHost, SshHostView};
|
||||
use crate::mcp::{self, McpMirror, McpServerHandle, McpState, RunningServer};
|
||||
use crate::pty::{list_wsl_distros, PaneId, PtyManager, SpawnSpec};
|
||||
|
||||
const WORKSPACE_FILE: &str = "workspace.json";
|
||||
|
|
@ -17,7 +21,7 @@ pub async fn list_distros() -> Result<Vec<String>, String> {
|
|||
#[tauri::command]
|
||||
pub async fn spawn_pane(
|
||||
app: AppHandle,
|
||||
manager: tauri::State<'_, PtyManager>,
|
||||
manager: tauri::State<'_, Arc<PtyManager>>,
|
||||
spec: SpawnSpec,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
|
|
@ -29,7 +33,7 @@ pub async fn spawn_pane(
|
|||
/// strings; the frontend encodes before sending).
|
||||
#[tauri::command]
|
||||
pub async fn write_to_pane(
|
||||
manager: tauri::State<'_, PtyManager>,
|
||||
manager: tauri::State<'_, Arc<PtyManager>>,
|
||||
id: PaneId,
|
||||
data_b64: String,
|
||||
) -> Result<(), String> {
|
||||
|
|
@ -41,7 +45,7 @@ pub async fn write_to_pane(
|
|||
|
||||
#[tauri::command]
|
||||
pub async fn resize_pane(
|
||||
manager: tauri::State<'_, PtyManager>,
|
||||
manager: tauri::State<'_, Arc<PtyManager>>,
|
||||
id: PaneId,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
|
|
@ -51,7 +55,7 @@ pub async fn resize_pane(
|
|||
|
||||
#[tauri::command]
|
||||
pub async fn kill_pane(
|
||||
manager: tauri::State<'_, PtyManager>,
|
||||
manager: tauri::State<'_, Arc<PtyManager>>,
|
||||
id: PaneId,
|
||||
) -> Result<(), String> {
|
||||
manager.kill(id).map_err(|e| e.to_string())
|
||||
|
|
@ -137,3 +141,80 @@ pub async fn delete_host_password(host_id: String) -> Result<(), String> {
|
|||
pub async fn has_host_password(host_id: String) -> Result<bool, String> {
|
||||
Ok(creds::has(&host_id))
|
||||
}
|
||||
|
||||
// ---- MCP server lifecycle --------------------------------------------------
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct McpStatus {
|
||||
pub running: bool,
|
||||
pub url: Option<String>,
|
||||
pub token: Option<String>,
|
||||
}
|
||||
|
||||
fn server_status(handle: &McpServerHandle) -> McpStatus {
|
||||
let g = handle.0.lock();
|
||||
match g.as_ref() {
|
||||
Some(srv) => McpStatus {
|
||||
running: true,
|
||||
url: Some(format!("http://{}/mcp", srv.addr)),
|
||||
token: Some(srv.token.clone()),
|
||||
},
|
||||
None => McpStatus {
|
||||
running: false,
|
||||
url: None,
|
||||
token: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mcp_start(
|
||||
ptys: tauri::State<'_, Arc<PtyManager>>,
|
||||
state: tauri::State<'_, Arc<RwLock<McpState>>>,
|
||||
handle: tauri::State<'_, McpServerHandle>,
|
||||
) -> Result<McpStatus, String> {
|
||||
{
|
||||
let g = handle.0.lock();
|
||||
if g.is_some() {
|
||||
return Ok(server_status(&handle));
|
||||
}
|
||||
}
|
||||
let ptys_arc: Arc<PtyManager> = (*ptys).clone();
|
||||
let state_arc: Arc<RwLock<McpState>> = (*state).clone();
|
||||
let running: RunningServer = mcp::start_server(ptys_arc, state_arc)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
{
|
||||
let mut g = handle.0.lock();
|
||||
*g = Some(running);
|
||||
}
|
||||
Ok(server_status(&handle))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mcp_stop(
|
||||
handle: tauri::State<'_, McpServerHandle>,
|
||||
) -> Result<McpStatus, String> {
|
||||
mcp::stop_server(&handle);
|
||||
Ok(server_status(&handle))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mcp_status(
|
||||
handle: tauri::State<'_, McpServerHandle>,
|
||||
) -> Result<McpStatus, String> {
|
||||
Ok(server_status(&handle))
|
||||
}
|
||||
|
||||
/// Frontend pushes the gated mirror after every tree/host change. Backend
|
||||
/// caches it for MCP responses — the MCP server only ever sees what the
|
||||
/// frontend chose to mirror (default-deny per-leaf gate).
|
||||
#[tauri::command]
|
||||
pub async fn mcp_update_state(
|
||||
state: tauri::State<'_, Arc<RwLock<McpState>>>,
|
||||
mirror: McpMirror,
|
||||
) -> Result<(), String> {
|
||||
let mut g = state.write().await;
|
||||
g.mirror = mirror;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue