diff --git a/desktop/package.json b/desktop/package.json index 6726bfdcad..12d87ae410 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -31,6 +31,8 @@ "@emoji-mart/react": "^1.1.1", "@fontsource-variable/inter": "^5.2.8", "@mediapipe/tasks-vision": "^0.10.35", + "@modelcontextprotocol/ext-apps": "1.7.5", + "@modelcontextprotocol/sdk": "1.29.0", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-checkbox": "^1.3.3", diff --git a/desktop/src-tauri/src/commands/mcp_apps.rs b/desktop/src-tauri/src/commands/mcp_apps.rs new file mode 100644 index 0000000000..37203d7349 --- /dev/null +++ b/desktop/src-tauri/src/commands/mcp_apps.rs @@ -0,0 +1,965 @@ +//! MCP Apps host transport and sandbox registration. +//! +//! The webview never receives MCP credentials or a general-purpose network +//! primitive. Rust owns the reviewed server connection and exposes only the +//! MCP methods required by the Apps protocol. + +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, Mutex, + }, + time::Duration, +}; + +use base64::Engine; +use futures_util::StreamExt; +use reqwest::{Client, Response}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use tauri::{http, AppHandle, Manager, State}; +use tokio::sync::Mutex as AsyncMutex; +use url::Url; +use uuid::Uuid; + +const MCP_APP_MIME_TYPE: &str = "text/html;profile=mcp-app"; +const MCP_PROTOCOL_VERSION: &str = "2025-11-25"; +const MAX_MCP_RESPONSE_BYTES: usize = 4 * 1024 * 1024; +const MAX_MCP_APP_HTML_BYTES: usize = 4 * 1024 * 1024; +const MAX_SERVERS: usize = 16; +const MAX_VIEWS: usize = 32; +const MAX_TOOLS: usize = 256; +const MAX_RESOURCES: usize = 256; + +const SANDBOX_PROXY_HTML: &str = r#" + +
+ + + + + + + +"#; + +#[derive(Debug, Clone)] +struct McpServerConnection { + endpoint: Url, + client: Client, + protocol_version: String, + session_id: Option+ {error ? "App unavailable" : `Opening ${app.title}…`} +
+ {error ? ( +{error}
+ ) : null} +