From de77f12db51ec4494aa61c838cffb11bd8fbfcb8 Mon Sep 17 00:00:00 2001 From: anhtahaylove Date: Thu, 9 Jul 2026 14:49:19 +0700 Subject: [PATCH 1/7] Improve MCP setup guidance in Settings --- src/App.css | 20 ++++++++++++++++++++ src/App.tsx | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/App.css b/src/App.css index 854018e..213b089 100644 --- a/src/App.css +++ b/src/App.css @@ -1118,6 +1118,26 @@ textarea { resize: vertical; min-height: 60px; line-height: 1.5; } } .copy-icon:hover { color: var(--tx-1); background: var(--bg-1); } +.settings-steps { + margin: 10px 0 12px; + padding-left: 20px; + color: var(--tx-2); + font-size: 12px; + line-height: 1.5; +} +.settings-steps code { color: var(--tx-1); } +.mcp-setup-box { + margin-top: 10px; + padding: 10px; + border: 1px solid var(--bd-1); + border-radius: var(--radius); + background: var(--bg-2); +} +.mcp-setup-actions { + margin-top: 10px; + flex-wrap: wrap; +} + /* Confirm modal */ .dialog-confirm { max-width: 440px; } .confirm-msg { color: var(--tx-2); font-size: 13px; line-height: 1.5; margin: 0; } diff --git a/src/App.tsx b/src/App.tsx index 585118e..afddeba 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4747,6 +4747,7 @@ function SettingsView() { }; const [mcpBusy, setMcpBusy] = useState(false); + const [mcpPath, setMcpPath] = useState(""); // Download MCP server source; user manages install + client setup. const downloadMcp = async () => { const dir = await open({ directory: true, title: "Where to download the MCP server" }); @@ -4754,10 +4755,38 @@ function SettingsView() { setMcpBusy(true); try { const path = await invoke("mcp_download", { dir }); + setMcpPath(path); toast.ok(`MCP downloaded to ${path}`); } catch (e) { toast.err("MCP download failed: " + String(e)); } finally { setMcpBusy(false); } }; + const mcpIndexPath = (dir: string) => { + const clean = dir.replace(/[\\/]+$/, ""); + return `${clean}${clean.includes("\\") ? "\\" : "/"}index.js`; + }; + const copyMcpInstall = async () => { + if (!mcpPath) return; + try { + await clip.write(`cd '${mcpPath.replace(/'/g, "''")}'; npm install`); + toast.ok("Copied MCP install command"); + } catch (e) { toast.err(String(e)); } + }; + const copyMcpConfig = async () => { + if (!mcpPath) return; + const snippet = { + mcpServers: { + shardx: { + command: "node", + args: [mcpIndexPath(mcpPath)], + env: { SHARDX_API: api?.base_url ?? "http://127.0.0.1:40325" }, + }, + }, + }; + try { + await clip.write(JSON.stringify(snippet, null, 2)); + toast.ok("Copied MCP config snippet"); + } catch (e) { toast.err(String(e)); } + }; const save = async () => { try { await invoke("settings_save", { value: s }); toast.ok("Settings saved"); } catch (e) { toast.err(String(e)); } @@ -4860,9 +4889,29 @@ function SettingsView() { it — install its deps and register it with your MCP client per the included README. Requires Node.js.

+
    +
  1. Download the server.
  2. +
  3. Run npm install inside the downloaded folder.
  4. +
  5. Register index.js with your MCP client and keep the token in SHARDX_TOKEN.
  6. +
+ {mcpPath && ( +
+ +
+ + + +
+
+ )}
From 6c72d77a028be3e32948e48f424455735623c255 Mon Sep 17 00:00:00 2001 From: anhtahaylove Date: Thu, 9 Jul 2026 15:30:10 +0700 Subject: [PATCH 2/7] Add MCP download status in settings --- src-tauri/src/lib.rs | 35 +++++++++++++++++++-- src-tauri/src/settings.rs | 4 +++ src/App.css | 23 ++++++++++++++ src/App.tsx | 64 ++++++++++++++++++++++++++++----------- 4 files changed, 105 insertions(+), 21 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 66bcabd..1e27fb7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -48,10 +48,38 @@ pub fn notify_store_changed(kind: &str) { /// Download MCP server source into `/mcp`; user manages registration. #[tauri::command] async fn mcp_download(dir: String) -> Result { - mcp_setup::download_mcp(std::path::Path::new(&dir)) + let path = mcp_setup::download_mcp(std::path::Path::new(&dir)) .await - .map(|p| p.display().to_string()) - .map_err(|e| e.to_string()) + .map_err(|e| e.to_string())?; + let path_string = path.display().to_string(); + let mut s = settings::load().map_err(|e| e.to_string())?; + s.mcp_path = Some(path_string.clone()); + settings::save(&s).map_err(|e| e.to_string())?; + notify_store_changed("settings"); + Ok(path_string) +} + +#[tauri::command] +fn mcp_status() -> Result { + let s = settings::load().map_err(|e| e.to_string())?; + Ok(match s.mcp_path { + Some(path) => { + let dir = std::path::Path::new(&path); + let installed = dir.join("index.js").is_file() && dir.join("package.json").is_file(); + serde_json::json!({ + "path": path, + "installed": installed, + "state": if installed { "ready" } else { "missing" }, + "message": if installed { "MCP server files are downloaded." } else { "Saved MCP folder is missing index.js or package.json." }, + }) + } + None => serde_json::json!({ + "path": null, + "installed": false, + "state": "not_downloaded", + "message": "MCP server has not been downloaded yet.", + }), + }) } // ---- Profiles ---- @@ -1203,6 +1231,7 @@ pub fn run() { cookies_export_to_file, cookies_import, mcp_download, + mcp_status, runtime::runtime_status, runtime::runtime_install, runtime::launcher_update_check, diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index ad04929..a8e9421 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -33,6 +33,9 @@ pub struct Settings { /// (see `ensure_secret`); rotating it invalidates issued tokens. #[serde(default)] pub api_secret: String, + /// Last downloaded MCP server folder, if any. + #[serde(default)] + pub mcp_path: Option, } fn default_theme() -> String { @@ -63,6 +66,7 @@ pub fn load() -> Result { api_enabled: default_api_enabled(), api_port: default_api_port(), api_secret: String::new(), + mcp_path: None, }); } let body = fs::read_to_string(&path)?; diff --git a/src/App.css b/src/App.css index 213b089..e29f542 100644 --- a/src/App.css +++ b/src/App.css @@ -282,6 +282,11 @@ textarea { resize: vertical; min-height: 60px; line-height: 1.5; } } .side-auto-btn:hover { border-color: var(--accent); } .side-auto-btn:disabled { opacity: .5; cursor: default; } +.side-auto-btn-ok { + background: #4ade8014; + color: var(--ok); +} +.side-auto-btn-ok:hover { border-color: #4ade8055; } /* Theme switch — a 2-segment pill (Light | Dark) sitting just above the user pill. The active half gets the accent fill; clicking @@ -1126,6 +1131,24 @@ textarea { resize: vertical; min-height: 60px; line-height: 1.5; } line-height: 1.5; } .settings-steps code { color: var(--tx-1); } +.mcp-status { + margin-top: 10px; + padding: 9px 10px; + border: 1px solid var(--bd-1); + border-radius: var(--radius); + background: var(--bg-2); + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + font-size: 12px; +} +.mcp-status strong { color: var(--tx-1); white-space: nowrap; } +.mcp-status span { color: var(--tx-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.mcp-status-ready { border-color: #4ade8033; } +.mcp-status-ready strong { color: var(--ok); } +.mcp-status-missing { border-color: #f59e0b55; } +.mcp-status-missing strong { color: #fbbf24; } .mcp-setup-box { margin-top: 10px; padding: 10px; diff --git a/src/App.tsx b/src/App.tsx index afddeba..d526344 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -280,6 +280,7 @@ type Settings = { api_enabled?: boolean; api_port?: number; api_secret?: string; + mcp_path?: string | null; }; type ApiInfo = { enabled: boolean; @@ -287,6 +288,12 @@ type ApiInfo = { base_url: string; token: string; }; +type McpStatus = { + path: string | null; + installed: boolean; + state: "not_downloaded" | "ready" | "missing"; + message: string; +}; type Section = "browsers" | "proxies" | "proxyshard" | "fingerprints" | "settings"; /// Library fingerprint backing the editor GPU select; payload supplies the coherent base. @@ -701,22 +708,15 @@ function Sidebar({ // Automation/MCP quick widget (fills the sidebar's lower space). const [autoUrl, setAutoUrl] = useState(""); - const [mcpBusy, setMcpBusy] = useState(false); + const [mcp, setMcp] = useState(null); + const refreshMcp = () => invoke("mcp_status").then(setMcp).catch(() => {}); useEffect(() => { invoke<{ base_url: string; enabled: boolean }>("api_info") .then((i) => setAutoUrl(i.enabled ? i.base_url : "")) .catch(() => {}); + refreshMcp(); }, []); - const downloadMcp = async () => { - const dir = await open({ directory: true, title: "Where to download the MCP server" }); - if (typeof dir !== "string") return; - setMcpBusy(true); - try { - const p = await invoke("mcp_download", { dir }); - toast.ok(`MCP downloaded to ${p}`); - } catch (e) { toast.err("MCP download failed: " + String(e)); } - finally { setMcpBusy(false); } - }; + useStoreChanged(refreshMcp); return (
)} From 9c0f04e741a306d4db1ea34a4285baaee62a5dde Mon Sep 17 00:00:00 2001 From: anhtahaylove Date: Thu, 9 Jul 2026 15:48:11 +0700 Subject: [PATCH 3/7] Clarify MCP token setup step --- src/App.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d526344..c513360 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4901,8 +4901,7 @@ function SettingsView() {

Download the MCP server source (lets an AI client drive profiles and a CDP browser) into a folder you choose. The app does not run - it — install its deps and register it with your MCP client per the included - README. Requires Node.js. + it — install deps, set SHARDX_TOKEN in your user environment, restart your MCP client, then register it. Requires Node.js.

@@ -4913,7 +4912,7 @@ function SettingsView() {
  1. {mcpReady ? "MCP files are already downloaded." : "Download the server once."}
  2. Run npm install inside the downloaded folder.
  3. -
  4. Register index.js with your MCP client and keep the token in SHARDX_TOKEN.
  5. +
  6. Set SHARDX_TOKEN in your user environment, restart your MCP client, then register index.js.
{!mcpReady && ( +
+ + +
)} {(mcpPath || mcpReady) && (
From cc43c3e3100ec9e92dbf30fd8837b13809335c82 Mon Sep 17 00:00:00 2001 From: anhtahaylove Date: Thu, 9 Jul 2026 22:25:52 +0700 Subject: [PATCH 6/7] Remove unused launch import --- src-tauri/src/launch.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src-tauri/src/launch.rs b/src-tauri/src/launch.rs index 0b7ac92..ec022c7 100644 --- a/src-tauri/src/launch.rs +++ b/src-tauri/src/launch.rs @@ -200,7 +200,6 @@ pub async fn launch_profile( cmd.stdout(Stdio::null()).stderr(Stdio::null()); #[cfg(target_os = "windows")] { - use std::os::windows::process::CommandExt; // 0x08000000 = CREATE_NO_WINDOW — suppress the brief console flash // when a Tauri GUI app spawns the engine binary. cmd.creation_flags(0x08000000); From 18b0f04cc442fcff7ee8efd65d4225bcaa192c0e Mon Sep 17 00:00:00 2001 From: anhtahaylove Date: Fri, 10 Jul 2026 00:42:30 +0700 Subject: [PATCH 7/7] Clarify MCP repair and existing folder actions --- src-tauri/src/mcp_setup.rs | 40 ++++++++++++++++++++++++++++++++++++-- src/App.tsx | 32 +++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/mcp_setup.rs b/src-tauri/src/mcp_setup.rs index 481ae25..c6106be 100644 --- a/src-tauri/src/mcp_setup.rs +++ b/src-tauri/src/mcp_setup.rs @@ -19,9 +19,22 @@ const MCP_ARCHIVE_URL: &str = /// Top-level directory inside the tarball that wraps the actual files. const MCP_TOP_DIR: &str = "ShardX-MCP"; +fn download_destination(dir: &Path) -> PathBuf { + // The picker text says "choose a folder" while the status box shows the + // actual `mcp` folder. If the user repairs by selecting that shown folder, + // update it in place instead of creating `mcp/mcp`. + if is_mcp_dir(dir) || dir.file_name().is_some_and(|name| name == "mcp") { + dir.to_path_buf() + } else { + dir.join("mcp") + } +} + /// Download the MCP server into `/mcp` and return that path. +/// +/// If `` already is the MCP folder, repair/update it in place. pub async fn download_mcp(dir: &Path) -> Result { - let dest = dir.join("mcp"); + let dest = download_destination(dir); let bytes = reqwest::get(MCP_ARCHIVE_URL) .await .context("download MCP archive")? @@ -104,7 +117,7 @@ pub fn find_existing_mcp() -> Option { #[cfg(test)] mod tests { - use super::is_mcp_dir; + use super::{download_destination, is_mcp_dir}; #[test] fn detects_downloaded_mcp_folder() { @@ -119,4 +132,27 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + + #[test] + fn download_destination_avoids_nested_mcp_when_repairing() { + let base = std::env::temp_dir().join(format!( + "shardx-mcp-destination-test-{}", + std::process::id() + )); + let normal_parent = base.join("ShardBrowser"); + let existing_mcp = normal_parent.join("mcp"); + let _ = std::fs::remove_dir_all(&base); + std::fs::create_dir_all(&existing_mcp).unwrap(); + std::fs::write(existing_mcp.join("index.js"), "").unwrap(); + std::fs::write( + existing_mcp.join("package.json"), + r#"{ "name": "shardx-mcp" }"#, + ) + .unwrap(); + + assert_eq!(download_destination(&normal_parent), existing_mcp); + assert_eq!(download_destination(&existing_mcp), existing_mcp); + + let _ = std::fs::remove_dir_all(&base); + } } diff --git a/src/App.tsx b/src/App.tsx index 8264784..3953eae 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4762,7 +4762,12 @@ function SettingsView() { useEffect(() => { refreshMcp(); }, []); // Download MCP server source; user manages install + client setup. const downloadMcp = async () => { - const dir = await open({ directory: true, title: "Where to download the MCP server" }); + const dir = await open({ + directory: true, + title: mcpStatus?.installed + ? "Choose an MCP folder to repair, or a parent folder to download into" + : "Where to download the MCP server", + }); if (typeof dir !== "string") return; setMcpBusy(true); try { @@ -4777,12 +4782,14 @@ function SettingsView() { const useExistingMcp = async () => { const dir = await open({ directory: true, title: "Select an existing ShardX MCP folder" }); if (typeof dir !== "string") return; + setMcpBusy(true); try { const status = await invoke("mcp_set_path", { dir }); applyMcpStatus(status); setS((cur) => ({ ...cur, mcp_path: status.path })); toast.ok(`Using MCP at ${status.path}`); } catch (e) { toast.err(String(e)); } + finally { setMcpBusy(false); } }; const mcpIndexPath = (dir: string) => { const clean = dir.replace(/[\\/]+$/, ""); @@ -4921,7 +4928,7 @@ function SettingsView() { {mcpStatus?.message ?? "Checking MCP status…"}
    -
  1. {mcpReady ? "MCP files are already downloaded." : "Download the server once."}
  2. +
  3. {mcpReady ? "MCP files are already downloaded; use existing folder to switch without re-downloading." : "Download the server once."}
  4. Run npm install inside the downloaded folder.
  5. Set SHARDX_TOKEN in your user environment, restart your MCP client, then register index.js.
@@ -4948,9 +4955,24 @@ function SettingsView() { {mcpReady && ( - + <> + + + )}