diff --git a/src-tauri/src/bin/proxy_server.rs b/src-tauri/src/bin/proxy_server.rs index cb2f363..613746b 100644 --- a/src-tauri/src/bin/proxy_server.rs +++ b/src-tauri/src/bin/proxy_server.rs @@ -195,6 +195,15 @@ async fn main() { ) .arg(Arg::new("action").required(true).help("Action (start)")), ) + .subcommand( + Command::new("mcp-bridge") + .about("Bridge stdio MCP to a local HTTP MCP server") + .arg( + Arg::new("url") + .required(true) + .help("HTTP MCP server URL (e.g. http://127.0.0.1:51080/mcp/TOKEN)"), + ), + ) .get_matches(); if let Some(proxy_matches) = matches.subcommand_matches("proxy") { @@ -461,6 +470,78 @@ async fn main() { log::error!("Invalid action for vpn-worker. Use 'start'"); process::exit(1); } + } else if let Some(bridge_matches) = matches.subcommand_matches("mcp-bridge") { + let url = bridge_matches + .get_one::("url") + .expect("url is required") + .clone(); + + // stdio↔HTTP MCP bridge: translates stdio JSON-RPC to Streamable HTTP transport + let client = reqwest::Client::new(); + let stdin = tokio::io::stdin(); + let reader = tokio::io::BufReader::new(stdin); + let mut session_id: Option = None; + + use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; + let mut lines = reader.lines(); + let mut stdout = tokio::io::stdout(); + + while let Ok(Some(line)) = lines.next_line().await { + if line.trim().is_empty() { + continue; + } + + // Check if this is a notification (no "id" field) to handle 202 responses + let is_notification = serde_json::from_str::(&line) + .ok() + .map(|v| v.get("id").is_none() || v["id"].is_null()) + .unwrap_or(false); + + let mut req = client + .post(&url) + .header("Content-Type", "application/json") + .header("Accept", "application/json"); + + if let Some(sid) = &session_id { + req = req.header("mcp-session-id", sid); + } + + match req.body(line).send().await { + Ok(resp) => { + // Capture session ID from initialize response + if let Some(sid) = resp.headers().get("mcp-session-id") { + if let Ok(s) = sid.to_str() { + session_id = Some(s.to_string()); + } + } + + // Notifications return 202 with no body — don't write anything + if is_notification { + continue; + } + + if let Ok(body) = resp.text().await { + if !body.is_empty() { + let _ = stdout.write_all(body.as_bytes()).await; + let _ = stdout.write_all(b"\n").await; + let _ = stdout.flush().await; + } + } + } + Err(e) => { + if !is_notification { + let err = serde_json::json!({ + "jsonrpc": "2.0", + "id": null, + "error": {"code": -32000, "message": format!("HTTP error: {e}")}, + }); + let _ = stdout.write_all(err.to_string().as_bytes()).await; + let _ = stdout.write_all(b"\n").await; + let _ = stdout.flush().await; + } + } + } + } } else { log::error!("No command specified"); process::exit(1); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c5118f4..7e56c06 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -47,6 +47,7 @@ mod commercial_license; mod cookie_manager; pub mod daemon; pub mod daemon_client; +#[allow(dead_code)] mod daemon_spawn; pub mod daemon_ws; pub mod events; @@ -474,7 +475,6 @@ fn get_mcp_server_status() -> bool { struct McpConfig { port: u16, token: String, - config_json: String, } #[tauri::command] @@ -495,23 +495,283 @@ async fn get_mcp_config(app_handle: tauri::AppHandle) -> Result Option { + #[cfg(target_os = "macos")] + { + dirs::home_dir().map(|h| { + h.join("Library") + .join("Application Support") + .join("Claude") + .join("Claude Extensions") + .join("local.mcpb.donut-browser.donut-browser") + }) + } + #[cfg(target_os = "windows")] + { + std::env::var("APPDATA").ok().map(|appdata| { + std::path::PathBuf::from(appdata) + .join("Claude") + .join("Claude Extensions") + .join("local.mcpb.donut-browser.donut-browser") + }) + } + #[cfg(target_os = "linux")] + { + dirs::config_dir().map(|c| { + c.join("Claude") + .join("Claude Extensions") + .join("local.mcpb.donut-browser.donut-browser") + }) + } +} + +#[tauri::command] +fn is_mcp_in_claude_desktop() -> Result { + let dir = claude_desktop_extension_dir().ok_or("Unsupported platform")?; + Ok(dir.join("manifest.json").exists()) +} + +#[tauri::command] +async fn add_mcp_to_claude_desktop(app_handle: tauri::AppHandle) -> Result<(), String> { + let mcp_server = mcp_server::McpServer::instance(); + let port = mcp_server.get_port().ok_or("MCP server is not running")?; + + let settings_manager = settings_manager::SettingsManager::instance(); + let token = settings_manager + .get_mcp_token(&app_handle) + .await + .map_err(|e| format!("Failed to get MCP token: {e}"))? + .ok_or("MCP token not found")?; + + let ext_dir = claude_desktop_extension_dir().ok_or("Unsupported platform")?; + let server_dir = ext_dir.join("server"); + std::fs::create_dir_all(&server_dir) + .map_err(|e| format!("Failed to create extension directory: {e}"))?; + + let mcp_url = format!("http://127.0.0.1:{port}/mcp/{token}"); + + let manifest = serde_json::json!({ + "manifest_version": "0.3", + "name": "donut-browser", + "display_name": "Donut Browser", + "version": env!("CARGO_PKG_VERSION"), + "description": "Control Donut Browser profiles, proxies, and automation via MCP", + "author": { "name": "Donut Browser" }, + "tools_generated": true, + "server": { + "type": "node", + "entry_point": "server/index.js", + "mcp_config": { + "command": "node", + "args": ["${__dirname}/server/index.js"], + "env": {} + } + }, + "license": "AGPL-3.0" + }); + std::fs::write( + ext_dir.join("manifest.json"), + serde_json::to_string_pretty(&manifest) + .map_err(|e| format!("Failed to serialize manifest: {e}"))?, + ) + .map_err(|e| format!("Failed to write manifest: {e}"))?; + + let bridge_js = format!( + r#"#!/usr/bin/env node +const http = require("http"); +const readline = require("readline"); +const MCP_URL = "{mcp_url}"; +let sid = null; +function post(line) {{ + return new Promise((resolve, reject) => {{ + const u = new URL(MCP_URL); + const o = {{ + hostname: u.hostname, port: u.port, path: u.pathname, method: "POST", + headers: {{ "Content-Type": "application/json", Accept: "application/json" }}, + }}; + if (sid) o.headers["mcp-session-id"] = sid; + const r = http.request(o, (res) => {{ + const s = res.headers["mcp-session-id"]; + if (s) sid = s; + let b = ""; + res.on("data", (c) => (b += c)); + res.on("end", () => resolve(b)); + }}); + r.on("error", reject); + r.write(line); + r.end(); + }}); +}} +const rl = readline.createInterface({{ input: process.stdin, crlfDelay: Infinity }}); +rl.on("line", (line) => {{ + if (!line.trim()) return; + let notif = false; + try {{ notif = JSON.parse(line).id == null; }} catch {{}} + post(line).then((b) => {{ + if (!notif && b.trim()) process.stdout.write(b.trim() + "\n"); + }}).catch((e) => {{ + if (!notif) process.stdout.write(JSON.stringify({{ + jsonrpc: "2.0", id: null, error: {{ code: -32000, message: "HTTP error: " + e.message }} + }}) + "\n"); + }}); +}}); +rl.on("close", () => setTimeout(() => process.exit(0), 500)); +"# + ); + std::fs::write(server_dir.join("index.js"), bridge_js) + .map_err(|e| format!("Failed to write bridge script: {e}"))?; + + // Update the extensions-installations.json registry so Claude Desktop picks it up + update_claude_extensions_registry("local.mcpb.donut-browser.donut-browser", Some(manifest))?; + + Ok(()) +} + +#[tauri::command] +fn remove_mcp_from_claude_desktop() -> Result<(), String> { + let ext_dir = claude_desktop_extension_dir().ok_or("Unsupported platform")?; + if ext_dir.exists() { + std::fs::remove_dir_all(&ext_dir).map_err(|e| format!("Failed to remove extension: {e}"))?; + } + update_claude_extensions_registry("local.mcpb.donut-browser.donut-browser", None)?; + Ok(()) +} + +fn update_claude_extensions_registry( + ext_id: &str, + manifest: Option, +) -> Result<(), String> { + let registry_path = claude_desktop_extension_dir() + .ok_or("Unsupported platform")? + .parent() + .and_then(|p| p.parent()) + .map(|p| p.join("extensions-installations.json")) + .ok_or("Failed to resolve registry path")?; + + let mut registry: serde_json::Value = if registry_path.exists() { + let content = std::fs::read_to_string(®istry_path) + .map_err(|e| format!("Failed to read registry: {e}"))?; + serde_json::from_str(&content).unwrap_or(serde_json::json!({"extensions": {}})) + } else { + serde_json::json!({"extensions": {}}) + }; + + if registry.get("extensions").is_none() { + registry["extensions"] = serde_json::json!({}); + } + + match manifest { + Some(m) => { + registry["extensions"][ext_id] = serde_json::json!({ + "id": ext_id, + "version": m.get("version").and_then(|v| v.as_str()).unwrap_or("0.0.0"), + "hash": "", + "installedAt": chrono::Utc::now().to_rfc3339(), + "manifest": m, + "signatureInfo": { "status": "unsigned" }, + "source": "local" + }); + } + None => { + if let Some(exts) = registry + .get_mut("extensions") + .and_then(|e| e.as_object_mut()) + { + exts.remove(ext_id); } } - }) - .to_string(); + } - Ok(Some(McpConfig { - port, - token, - config_json, - })) + let output = + serde_json::to_string(®istry).map_err(|e| format!("Failed to serialize registry: {e}"))?; + let tmp = registry_path.with_extension("json.tmp"); + std::fs::write(&tmp, &output).map_err(|e| format!("Failed to write registry: {e}"))?; + std::fs::rename(&tmp, ®istry_path).map_err(|e| format!("Failed to save registry: {e}"))?; + Ok(()) +} + +fn find_claude_cli() -> Option { + let mut candidates: Vec = vec![ + std::path::PathBuf::from("/usr/local/bin/claude"), + std::path::PathBuf::from("/opt/homebrew/bin/claude"), + ]; + if let Some(home) = dirs::home_dir() { + candidates.insert(0, home.join(".local/bin/claude")); + candidates.push(home.join(".claude/local/claude")); + } + #[cfg(windows)] + if let Ok(appdata) = std::env::var("APPDATA") { + candidates.insert( + 0, + std::path::PathBuf::from(appdata).join("Claude/claude.exe"), + ); + } + for p in &candidates { + if p.exists() { + return Some(p.clone()); + } + } + None +} + +#[tauri::command] +fn is_mcp_in_claude_code() -> Result { + let cli = find_claude_cli().ok_or("Claude Code CLI not found")?; + let output = std::process::Command::new(&cli) + .args(["mcp", "list"]) + .output() + .map_err(|e| format!("Failed to run claude: {e}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(stdout.contains("donut-browser")) +} + +#[tauri::command] +async fn add_mcp_to_claude_code(app_handle: tauri::AppHandle) -> Result<(), String> { + let cli = find_claude_cli().ok_or("Claude Code CLI not found")?; + + let mcp_server = mcp_server::McpServer::instance(); + let port = mcp_server.get_port().ok_or("MCP server is not running")?; + + let settings_manager = settings_manager::SettingsManager::instance(); + let token = settings_manager + .get_mcp_token(&app_handle) + .await + .map_err(|e| format!("Failed to get MCP token: {e}"))? + .ok_or("MCP token not found")?; + + let url = format!("http://127.0.0.1:{port}/mcp/{token}"); + + let _ = std::process::Command::new(&cli) + .args(["mcp", "remove", "donut-browser"]) + .output(); + + let output = std::process::Command::new(&cli) + .args(["mcp", "add", "--transport", "http", "donut-browser", &url]) + .output() + .map_err(|e| format!("Failed to run claude: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("Failed to add MCP to Claude Code: {stderr}")); + } + Ok(()) +} + +#[tauri::command] +fn remove_mcp_from_claude_code() -> Result<(), String> { + let cli = find_claude_cli().ok_or("Claude Code CLI not found")?; + let output = std::process::Command::new(&cli) + .args(["mcp", "remove", "donut-browser"]) + .output() + .map_err(|e| format!("Failed to run claude: {e}"))?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("Failed to remove MCP from Claude Code: {stderr}")); + } + Ok(()) } #[tauri::command] @@ -965,38 +1225,13 @@ pub fn run() { mgr.ensure_icons_extracted(); } - // Start the daemon for tray icon - if let Err(e) = daemon_spawn::ensure_daemon_running() { - log::warn!("Failed to start daemon: {e}"); - } - - // Register this GUI's PID in daemon state so the daemon can kill us directly - daemon_spawn::register_gui_pid(); - - // Monitor daemon health - quit GUI if daemon dies - tauri::async_runtime::spawn(async move { - // Give the daemon time to fully start - tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; - - let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(1)); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - interval.tick().await; - - let is_running = tokio::task::spawn_blocking(daemon_spawn::is_daemon_running) - .await - .unwrap_or(false); - - if !is_running { - log::warn!("Daemon is no longer running, quitting GUI immediately"); - // Use process::exit for immediate termination. Tauri's exit() - // triggers a slow graceful shutdown that can take over a minute - // waiting for async tasks (sync, version updater, etc.) to finish. - std::process::exit(0); - } + // Daemon (tray icon) is currently disabled — clean up any existing autostart + if daemon::autostart::is_autostart_enabled() { + log::info!("Removing daemon autostart (daemon is disabled)"); + if let Err(e) = daemon::autostart::disable_autostart() { + log::warn!("Failed to remove daemon autostart: {e}"); } - }); + } // Create the main window programmatically #[allow(unused_variables)] @@ -1403,12 +1638,8 @@ pub fn run() { if is_running { scheduler.mark_profile_running(&profile_id).await; } else { + // Sync was queued at launch; mark_profile_stopped triggers it scheduler.mark_profile_stopped(&profile_id).await; - // Queue sync after profile stops (if sync is enabled) - if profile.is_sync_enabled() { - log::info!("Profile '{}' stopped, queuing sync", profile.name); - scheduler.queue_profile_sync(profile_id.clone()).await; - } } } @@ -1679,6 +1910,12 @@ pub fn run() { stop_mcp_server, get_mcp_server_status, get_mcp_config, + is_mcp_in_claude_desktop, + add_mcp_to_claude_desktop, + remove_mcp_from_claude_desktop, + is_mcp_in_claude_code, + add_mcp_to_claude_code, + remove_mcp_from_claude_code, // VPN commands import_vpn_config, list_vpn_configs, diff --git a/src-tauri/src/mcp_server.rs b/src-tauri/src/mcp_server.rs index d3c4564..6f8d0c2 100644 --- a/src-tauri/src/mcp_server.rs +++ b/src-tauri/src/mcp_server.rs @@ -41,7 +41,7 @@ pub struct McpRequest { params: Option, } -const PROTOCOL_VERSION: &str = "2025-03-26"; +const PROTOCOL_VERSION: &str = "2025-11-25"; const SERVER_NAME: &str = "donut-browser"; const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -202,7 +202,6 @@ impl McpServer { return Ok(preferred); } - // Try random ports in 51000-51999 range for _ in 0..10 { let port = 51000 + (rand::random::() % 1000); let addr = SocketAddr::from(([127, 0, 0, 1], port)); @@ -220,6 +219,12 @@ impl McpServer { shutdown_rx: tokio::sync::oneshot::Receiver<()>, ) { let app = Router::new() + .route( + "/mcp/{token}", + post(Self::handle_mcp_post) + .get(Self::handle_mcp_get) + .delete(Self::handle_mcp_delete), + ) .route( "/mcp", post(Self::handle_mcp_post) @@ -234,26 +239,26 @@ impl McpServer { .with_state(state); let addr = SocketAddr::from(([127, 0, 0, 1], port)); - let listener = match TcpListener::bind(addr).await { - Ok(l) => l, - Err(e) => { - log::error!("[mcp] Failed to bind to port {}: {}", port, e); - return; + + let server = async { + match TcpListener::bind(addr).await { + Ok(listener) => { + log::info!("[mcp] Server listening on http://127.0.0.1:{}/mcp", port); + if let Err(e) = axum::serve(listener, app).await { + log::error!("[mcp] Server error: {}", e); + } + } + Err(e) => { + log::error!("[mcp] Failed to bind on port {}: {}", port, e); + } } }; - log::info!( - "[mcp] HTTP server listening on http://127.0.0.1:{}/mcp", - port - ); - - let server = axum::serve(listener, app).with_graceful_shutdown(async { - let _ = shutdown_rx.await; - log::info!("[mcp] HTTP server shutting down"); - }); - - if let Err(e) = server.await { - log::error!("[mcp] HTTP server error: {}", e); + tokio::select! { + _ = server => {}, + _ = shutdown_rx => { + log::info!("[mcp] Server shutting down"); + }, } } @@ -262,19 +267,28 @@ impl McpServer { req: Request, next: Next, ) -> Result { - // Health endpoint is public - if req.uri().path() == "/health" { + let path = req.uri().path(); + + if path == "/health" { return Ok(next.run(req).await); } - let auth_header = req + // Check token from URL path: /mcp/{token} + let path_token = path + .strip_prefix("/mcp/") + .filter(|t| !t.is_empty() && !t.contains('/')); + + // Check token from Authorization header + let header_token = req .headers() .get(header::AUTHORIZATION) - .and_then(|h| h.to_str().ok()); + .and_then(|h| h.to_str().ok()) + .and_then(|h| h.strip_prefix("Bearer ")); - let token = auth_header.and_then(|h| h.strip_prefix("Bearer ")); + let valid = + path_token == Some(state.token.as_str()) || header_token == Some(state.token.as_str()); - if token != Some(&state.token) { + if !valid { return Err(StatusCode::UNAUTHORIZED); } diff --git a/src/app/page.tsx b/src/app/page.tsx index b9f6153..1b7b0fe 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1056,7 +1056,6 @@ export default function Home() { onExtensionManagementDialogOpen={setExtensionManagementDialogOpen} searchQuery={searchQuery} onSearchQueryChange={setSearchQuery} - crossOsUnlocked={crossOsUnlocked} />
diff --git a/src/components/home-header.tsx b/src/components/home-header.tsx index b2d596c..f832db5 100644 --- a/src/components/home-header.tsx +++ b/src/components/home-header.tsx @@ -177,7 +177,6 @@ type Props = { onExtensionManagementDialogOpen: (open: boolean) => void; searchQuery: string; onSearchQueryChange: (query: string) => void; - crossOsUnlocked?: boolean; }; const HomeHeader = ({ @@ -191,7 +190,6 @@ const HomeHeader = ({ onExtensionManagementDialogOpen, searchQuery, onSearchQueryChange, - crossOsUnlocked = false, }: Props) => { const { t } = useTranslation(); const { diff --git a/src/components/integrations-dialog.tsx b/src/components/integrations-dialog.tsx index b5be096..230fb74 100644 --- a/src/components/integrations-dialog.tsx +++ b/src/components/integrations-dialog.tsx @@ -31,7 +31,6 @@ interface AppSettings { interface McpConfig { port: number; token: string; - config_json: string; } interface IntegrationsDialogProps { @@ -59,6 +58,8 @@ export function IntegrationsDialog({ const [showMcpToken, setShowMcpToken] = useState(false); const [isApiStarting, setIsApiStarting] = useState(false); const [isMcpStarting, setIsMcpStarting] = useState(false); + const [mcpInClaudeDesktop, setMcpInClaudeDesktop] = useState(false); + const [mcpInClaudeCode, setMcpInClaudeCode] = useState(false); const { termsAccepted } = useWayfernTerms(); @@ -98,12 +99,32 @@ export function IntegrationsDialog({ } }, []); + const loadClaudeDesktopStatus = useCallback(async () => { + try { + const exists = await invoke("is_mcp_in_claude_desktop"); + setMcpInClaudeDesktop(exists); + } catch { + // Not critical + } + }, []); + + const loadClaudeCodeStatus = useCallback(async () => { + try { + const exists = await invoke("is_mcp_in_claude_code"); + setMcpInClaudeCode(exists); + } catch { + // Claude CLI may not be installed + } + }, []); + useEffect(() => { if (isOpen) { loadSettings(); loadApiServerStatus(); loadMcpConfig(); loadMcpServerStatus(); + loadClaudeDesktopStatus(); + loadClaudeCodeStatus(); } }, [ isOpen, @@ -111,6 +132,8 @@ export function IntegrationsDialog({ loadApiServerStatus, loadMcpConfig, loadMcpServerStatus, + loadClaudeDesktopStatus, + loadClaudeCodeStatus, ]); const handleApiToggle = async (enabled: boolean) => { @@ -175,45 +198,9 @@ export function IntegrationsDialog({ } }; - const obfuscateToken = (token: string) => + const _obfuscateToken = (token: string) => "•".repeat(Math.min(token.length, 32)); - const getFormattedMcpConfig = () => { - if (!mcpConfig) return ""; - return JSON.stringify( - { - mcpServers: { - "donut-browser": { - url: `http://127.0.0.1:${mcpConfig.port}/mcp`, - headers: { - Authorization: `Bearer ${mcpConfig.token}`, - }, - }, - }, - }, - null, - 2, - ); - }; - - const getObfuscatedMcpConfig = () => { - if (!mcpConfig) return ""; - return JSON.stringify( - { - mcpServers: { - "donut-browser": { - url: `http://127.0.0.1:${mcpConfig.port}/mcp`, - headers: { - Authorization: `Bearer ${obfuscateToken(mcpConfig.token)}`, - }, - }, - }, - }, - null, - 2, - ); - }; - return ( !open && onClose()}> @@ -331,39 +318,129 @@ export function IntegrationsDialog({
{mcpConfig && ( -
-
-
-                      {showMcpToken
-                        ? getFormattedMcpConfig()
-                        : getObfuscatedMcpConfig()}
-                    
-
- +
+
+ +
+
+ + +
-

- {t("integrations.mcpCopyHint")} -

-

- {t("integrations.mcpConfigPath")} -

+ +
+

+ {t("integrations.mcp.claudeDesktopTitle")} +

+ {mcpInClaudeDesktop ? ( + + ) : ( + + )} +
+ +
+

+ {t("integrations.mcp.claudeCodeTitle")} +

+ {mcpInClaudeCode ? ( + + ) : ( + + )} +
)} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 3adb626..ab8e3d1 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -417,12 +417,24 @@ "enabled": "MCP Enabled", "disabled": "MCP Disabled", "port": "Port", - "token": "MCP Token", + "token": "Authentication Token", + "tokenCopied": "Token copied", + "url": "MCP Server URL", + "urlCopied": "URL copied", + "claudeDesktopTitle": "Claude Desktop", + "claudeDesktopHint": "Configures claude_desktop_config.json automatically", + "addToClaudeDesktop": "Add to Claude Desktop", + "removeFromClaudeDesktop": "Remove from Claude Desktop", + "addedToClaudeDesktop": "Added to Claude Desktop. Restart Claude Desktop and enable the extension in Settings.", + "removedFromClaudeDesktop": "Removed from Claude Desktop config. Please restart Claude Desktop.", + "claudeCodeTitle": "Claude Code", + "addToClaudeCode": "Add to Claude Code", + "removeFromClaudeCode": "Remove from Claude Code", + "addedToClaudeCode": "Added to Claude Code", + "removedFromClaudeCode": "Removed from Claude Code", "config": "MCP Configuration", "copyConfig": "Copy Configuration" - }, - "mcpCopyHint": "Copy and paste this into your MCP client configuration.", - "mcpConfigPath": "Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\\Claude\\claude_desktop_config.json (Windows)" + } }, "import": { "title": "Import Profile", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 0be2da0..f158932 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -417,12 +417,24 @@ "enabled": "MCP Habilitado", "disabled": "MCP Deshabilitado", "port": "Puerto", - "token": "Token MCP", + "token": "Token de autenticación", + "tokenCopied": "Token copiado", + "url": "URL del servidor MCP", + "urlCopied": "URL copiada", + "claudeDesktopTitle": "Claude Desktop", + "claudeDesktopHint": "Configura claude_desktop_config.json automáticamente", + "addToClaudeDesktop": "Agregar a Claude Desktop", + "removeFromClaudeDesktop": "Eliminar de Claude Desktop", + "addedToClaudeDesktop": "Agregado a Claude Desktop. Reinicia Claude Desktop y activa la extensión en Configuración.", + "removedFromClaudeDesktop": "Eliminado de la configuración de Claude Desktop. Reinicia Claude Desktop.", + "claudeCodeTitle": "Claude Code", + "addToClaudeCode": "Agregar a Claude Code", + "removeFromClaudeCode": "Eliminar de Claude Code", + "addedToClaudeCode": "Agregado a Claude Code", + "removedFromClaudeCode": "Eliminado de Claude Code", "config": "Configuración MCP", "copyConfig": "Copiar Configuración" - }, - "mcpCopyHint": "Copia y pega esto en la configuración de tu cliente MCP.", - "mcpConfigPath": "Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) o %APPDATA%\\Claude\\claude_desktop_config.json (Windows)" + } }, "import": { "title": "Importar Perfil", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index eb5ee2b..5ffd92b 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -417,12 +417,24 @@ "enabled": "MCP activé", "disabled": "MCP désactivé", "port": "Port", - "token": "Jeton MCP", + "token": "Jeton d'authentification", + "tokenCopied": "Jeton copié", + "url": "URL du serveur MCP", + "urlCopied": "URL copiée", + "claudeDesktopTitle": "Claude Desktop", + "claudeDesktopHint": "Configure claude_desktop_config.json automatiquement", + "addToClaudeDesktop": "Ajouter à Claude Desktop", + "removeFromClaudeDesktop": "Supprimer de Claude Desktop", + "addedToClaudeDesktop": "Ajouté à Claude Desktop. Redémarrez Claude Desktop et activez l'extension dans les Paramètres.", + "removedFromClaudeDesktop": "Supprimé de la configuration de Claude Desktop. Veuillez redémarrer Claude Desktop.", + "claudeCodeTitle": "Claude Code", + "addToClaudeCode": "Ajouter à Claude Code", + "removeFromClaudeCode": "Supprimer de Claude Code", + "addedToClaudeCode": "Ajouté à Claude Code", + "removedFromClaudeCode": "Supprimé de Claude Code", "config": "Configuration MCP", "copyConfig": "Copier la configuration" - }, - "mcpCopyHint": "Copiez et collez ceci dans la configuration de votre client MCP.", - "mcpConfigPath": "Claude Desktop : ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) ou %APPDATA%\\Claude\\claude_desktop_config.json (Windows)" + } }, "import": { "title": "Importer un profil", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index da41ed2..430d95b 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -417,12 +417,24 @@ "enabled": "MCP有効", "disabled": "MCP無効", "port": "ポート", - "token": "MCPトークン", + "token": "認証トークン", + "tokenCopied": "トークンをコピーしました", + "url": "MCPサーバーURL", + "urlCopied": "URLをコピーしました", + "claudeDesktopTitle": "Claude Desktop", + "claudeDesktopHint": "claude_desktop_config.json を自動的に設定します", + "addToClaudeDesktop": "Claude Desktop に追加", + "removeFromClaudeDesktop": "Claude Desktop から削除", + "addedToClaudeDesktop": "Claude Desktop に追加しました。Claude Desktop を再起動し、設定で拡張機能を有効にしてください。", + "removedFromClaudeDesktop": "Claude Desktop の設定から削除しました。Claude Desktop を再起動してください。", + "claudeCodeTitle": "Claude Code", + "addToClaudeCode": "Claude Code に追加", + "removeFromClaudeCode": "Claude Code から削除", + "addedToClaudeCode": "Claude Code に追加しました", + "removedFromClaudeCode": "Claude Code から削除しました", "config": "MCP設定", "copyConfig": "設定をコピー" - }, - "mcpCopyHint": "MCPクライアントの設定にコピーして貼り付けてください。", - "mcpConfigPath": "Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) または %APPDATA%\\Claude\\claude_desktop_config.json (Windows)" + } }, "import": { "title": "プロファイルをインポート", diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json index 85425b5..6ae0ff6 100644 --- a/src/i18n/locales/pt.json +++ b/src/i18n/locales/pt.json @@ -417,12 +417,24 @@ "enabled": "MCP Ativado", "disabled": "MCP Desativado", "port": "Porta", - "token": "Token MCP", + "token": "Token de autenticação", + "tokenCopied": "Token copiado", + "url": "URL do servidor MCP", + "urlCopied": "URL copiada", + "claudeDesktopTitle": "Claude Desktop", + "claudeDesktopHint": "Configura claude_desktop_config.json automaticamente", + "addToClaudeDesktop": "Adicionar ao Claude Desktop", + "removeFromClaudeDesktop": "Remover do Claude Desktop", + "addedToClaudeDesktop": "Adicionado ao Claude Desktop. Reinicie o Claude Desktop e ative a extensão em Configurações.", + "removedFromClaudeDesktop": "Removido da configuração do Claude Desktop. Reinicie o Claude Desktop.", + "claudeCodeTitle": "Claude Code", + "addToClaudeCode": "Adicionar ao Claude Code", + "removeFromClaudeCode": "Remover do Claude Code", + "addedToClaudeCode": "Adicionado ao Claude Code", + "removedFromClaudeCode": "Removido do Claude Code", "config": "Configuração MCP", "copyConfig": "Copiar Configuração" - }, - "mcpCopyHint": "Copie e cole isso na configuração do seu cliente MCP.", - "mcpConfigPath": "Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) ou %APPDATA%\\Claude\\claude_desktop_config.json (Windows)" + } }, "import": { "title": "Importar Perfil", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 65ecb0f..2f829a1 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -417,12 +417,24 @@ "enabled": "MCP включён", "disabled": "MCP выключен", "port": "Порт", - "token": "MCP токен", + "token": "Токен аутентификации", + "tokenCopied": "Токен скопирован", + "url": "URL MCP сервера", + "urlCopied": "URL скопирован", + "claudeDesktopTitle": "Claude Desktop", + "claudeDesktopHint": "Автоматически настраивает claude_desktop_config.json", + "addToClaudeDesktop": "Добавить в Claude Desktop", + "removeFromClaudeDesktop": "Удалить из Claude Desktop", + "addedToClaudeDesktop": "Добавлено в Claude Desktop. Перезапустите Claude Desktop и включите расширение в Настройках.", + "removedFromClaudeDesktop": "Удалено из конфигурации Claude Desktop. Перезапустите Claude Desktop.", + "claudeCodeTitle": "Claude Code", + "addToClaudeCode": "Добавить в Claude Code", + "removeFromClaudeCode": "Удалить из Claude Code", + "addedToClaudeCode": "Добавлено в Claude Code", + "removedFromClaudeCode": "Удалено из Claude Code", "config": "Конфигурация MCP", "copyConfig": "Копировать конфигурацию" - }, - "mcpCopyHint": "Скопируйте и вставьте это в конфигурацию вашего MCP-клиента.", - "mcpConfigPath": "Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) или %APPDATA%\\Claude\\claude_desktop_config.json (Windows)" + } }, "import": { "title": "Импорт профиля", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 13c9e00..5bb4778 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -417,12 +417,24 @@ "enabled": "MCP 已启用", "disabled": "MCP 已禁用", "port": "端口", - "token": "MCP 令牌", + "token": "认证令牌", + "tokenCopied": "令牌已复制", + "url": "MCP 服务器 URL", + "urlCopied": "URL 已复制", + "claudeDesktopTitle": "Claude Desktop", + "claudeDesktopHint": "自动配置 claude_desktop_config.json", + "addToClaudeDesktop": "添加到 Claude Desktop", + "removeFromClaudeDesktop": "从 Claude Desktop 移除", + "addedToClaudeDesktop": "已添加到 Claude Desktop。请重启 Claude Desktop 并在设置中启用扩展。", + "removedFromClaudeDesktop": "已从 Claude Desktop 配置移除。请重启 Claude Desktop。", + "claudeCodeTitle": "Claude Code", + "addToClaudeCode": "添加到 Claude Code", + "removeFromClaudeCode": "从 Claude Code 移除", + "addedToClaudeCode": "已添加到 Claude Code", + "removedFromClaudeCode": "已从 Claude Code 移除", "config": "MCP 配置", "copyConfig": "复制配置" - }, - "mcpCopyHint": "将此内容复制并粘贴到您的 MCP 客户端配置中。", - "mcpConfigPath": "Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) 或 %APPDATA%\\Claude\\claude_desktop_config.json (Windows)" + } }, "import": { "title": "导入配置文件",