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.
+
+ Download the server.
+ Run npm install inside the downloaded folder.
+ Register index.js with your MCP client and keep the token in SHARDX_TOKEN.
+
{mcpBusy ? "Downloading…" : "Download MCP server"}
+ {mcpPath && (
+
+
+ Downloaded folder
+
+
+
+ openPath(mcpPath).catch((e) => toast.err(String(e)))}>
+ Open folder
+
+ Copy install command
+ Copy MCP config
+
+
+ )}
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() {
{mcpReady ? "MCP files are already downloaded." : "Download the server once."}
Run npm install inside the downloaded folder.
- Register index.js with your MCP client and keep the token in SHARDX_TOKEN.
+ Set SHARDX_TOKEN in your user environment, restart your MCP client, then register index.js.
{!mcpReady && (
From fca29f019a90b6951b2a3bba9a7711eb8a84a3d0 Mon Sep 17 00:00:00 2001
From: anhtahaylove
Date: Thu, 9 Jul 2026 16:25:53 +0700
Subject: [PATCH 4/7] Add custom build label and MCP auto-detect
---
package-lock.json | 4 ++--
package.json | 2 +-
src-tauri/Cargo.lock | 2 +-
src-tauri/Cargo.toml | 2 +-
src-tauri/src/lib.rs | 40 ++++++++++++++++++++++++--------
src-tauri/src/mcp_setup.rs | 47 ++++++++++++++++++++++++++++++++++++++
src-tauri/tauri.conf.json | 2 +-
src/App.css | 1 +
src/App.tsx | 3 ++-
9 files changed, 86 insertions(+), 17 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 827f342..a591547 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "shardx-launcher",
- "version": "0.1.5",
+ "version": "0.1.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "shardx-launcher",
- "version": "0.1.5",
+ "version": "0.1.11",
"dependencies": {
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.7.1",
diff --git a/package.json b/package.json
index 1f05384..1dcf5eb 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "shardx-launcher",
"private": true,
- "version": "0.1.10",
+ "version": "0.1.11",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 67562e6..a1975cb 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -3863,7 +3863,7 @@ dependencies = [
[[package]]
name = "shardx-launcher"
-version = "0.1.10"
+version = "0.1.11"
dependencies = [
"aes",
"aes-gcm",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 2a6a707..131bbd5 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "shardx-launcher"
-version = "0.1.10"
+version = "0.1.11"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 1e27fb7..42e173c 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -61,18 +61,38 @@ async fn mcp_download(dir: String) -> Result {
#[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!({
+ let mut s = settings::load().map_err(|e| e.to_string())?;
+ if let Some(path) = &s.mcp_path {
+ let installed = mcp_setup::is_mcp_dir(std::path::Path::new(path));
+ if installed {
+ return Ok(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." },
- })
+ "installed": true,
+ "state": "ready",
+ "message": "MCP server files are downloaded.",
+ }));
}
+ }
+
+ if let Some(path) = mcp_setup::find_existing_mcp() {
+ let path = path.display().to_string();
+ s.mcp_path = Some(path.clone());
+ settings::save(&s).map_err(|e| e.to_string())?;
+ return Ok(serde_json::json!({
+ "path": path,
+ "installed": true,
+ "state": "ready",
+ "message": "Found existing MCP server files from a previous download.",
+ }));
+ }
+
+ Ok(match s.mcp_path {
+ Some(path) => serde_json::json!({
+ "path": path,
+ "installed": false,
+ "state": "missing",
+ "message": "Saved MCP folder is missing index.js or package.json.",
+ }),
None => serde_json::json!({
"path": null,
"installed": false,
diff --git a/src-tauri/src/mcp_setup.rs b/src-tauri/src/mcp_setup.rs
index c28ee79..3cc8755 100644
--- a/src-tauri/src/mcp_setup.rs
+++ b/src-tauri/src/mcp_setup.rs
@@ -64,3 +64,50 @@ pub async fn download_mcp(dir: &Path) -> Result {
}
Ok(dest)
}
+
+pub fn is_mcp_dir(dir: &Path) -> bool {
+ if !dir.join("index.js").is_file() || !dir.join("package.json").is_file() {
+ return false;
+ }
+ std::fs::read_to_string(dir.join("package.json"))
+ .map(|s| s.contains("\"shardx-mcp\""))
+ .unwrap_or(false)
+}
+
+pub fn find_existing_mcp() -> Option {
+ let mut candidates = Vec::new();
+ if let Some(dir) = dirs::document_dir() {
+ candidates.extend([
+ dir.join("MCP").join("ShardBrowser").join("mcp"),
+ dir.join("MCP").join("mcp"),
+ dir.join("ShardBrowser").join("mcp"),
+ dir.join("GitHub").join("ShardBrowser").join("mcp"),
+ dir.join("mcp"),
+ ]);
+ }
+ if let Some(dir) = dirs::download_dir() {
+ candidates.push(dir.join("mcp"));
+ }
+ if let Some(dir) = dirs::desktop_dir() {
+ candidates.push(dir.join("mcp"));
+ }
+ candidates.into_iter().find(|p| is_mcp_dir(p))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::is_mcp_dir;
+
+ #[test]
+ fn detects_downloaded_mcp_folder() {
+ let dir = std::env::temp_dir().join(format!("shardx-mcp-test-{}", std::process::id()));
+ let _ = std::fs::remove_dir_all(&dir);
+ std::fs::create_dir_all(&dir).unwrap();
+ std::fs::write(dir.join("index.js"), "").unwrap();
+ std::fs::write(dir.join("package.json"), r#"{ "name": "shardx-mcp" }"#).unwrap();
+
+ assert!(is_mcp_dir(&dir));
+
+ let _ = std::fs::remove_dir_all(&dir);
+ }
+}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 9d3cbd0..423d96d 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ShardX Launcher",
- "version": "0.1.10",
+ "version": "0.1.11",
"identifier": "com.shardx.launcher",
"build": {
"beforeDevCommand": "npm run dev",
diff --git a/src/App.css b/src/App.css
index e29f542..2d0e7c5 100644
--- a/src/App.css
+++ b/src/App.css
@@ -337,6 +337,7 @@ textarea { resize: vertical; min-height: 60px; line-height: 1.5; }
.version-pill .shard-mini { color: var(--accent); flex: 0 0 auto; }
.version-pill-text { display: flex; flex-direction: column; min-width: 0; }
.version-pill-current { font-size: 12px; font-weight: 500; }
+.version-pill-label { color: var(--accent); font-size: 10.5px; font-weight: 600; }
.version-pill-sub { font-size: 10.5px; color: var(--tx-4); }
.version-pill.update-available {
diff --git a/src/App.tsx b/src/App.tsx
index c513360..652d81f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -16,6 +16,7 @@ function detectHostOs(): "macOS" | "Windows" | "Linux" {
return "macOS";
}
const HOST_OS = detectHostOs();
+const CUSTOM_BUILD_LABEL = "custom build";
// OS clipboard via Tauri plugin (webview navigator.clipboard throws).
const clip = {
@@ -3693,7 +3694,7 @@ function VersionPill() {
- ShardX Launcher v{info?.current ?? "…"}
+ ShardX Launcher v{info?.current ?? "…"} {CUSTOM_BUILD_LABEL}
{info === null
From fc5b35c844f5a5fb94a0d1c7ff9b8b4b53966718 Mon Sep 17 00:00:00 2001
From: anhtahaylove
Date: Thu, 9 Jul 2026 16:34:00 +0700
Subject: [PATCH 5/7] Allow selecting existing MCP folder
---
src-tauri/src/lib.rs | 18 ++++++++++++++++++
src-tauri/src/mcp_setup.rs | 11 ++++++++++-
src/App.tsx | 21 ++++++++++++++++++---
3 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 42e173c..7a1b6df 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -59,6 +59,23 @@ async fn mcp_download(dir: String) -> Result {
Ok(path_string)
}
+#[tauri::command]
+fn mcp_set_path(dir: String) -> Result {
+ let path = mcp_setup::resolve_mcp_dir(std::path::Path::new(&dir))
+ .ok_or_else(|| "Selected folder is not a ShardX MCP server folder.".to_string())?;
+ let path = path.display().to_string();
+ let mut s = settings::load().map_err(|e| e.to_string())?;
+ s.mcp_path = Some(path.clone());
+ settings::save(&s).map_err(|e| e.to_string())?;
+ notify_store_changed("settings");
+ Ok(serde_json::json!({
+ "path": path,
+ "installed": true,
+ "state": "ready",
+ "message": "Using existing MCP server files.",
+ }))
+}
+
#[tauri::command]
fn mcp_status() -> Result {
let mut s = settings::load().map_err(|e| e.to_string())?;
@@ -1251,6 +1268,7 @@ pub fn run() {
cookies_export_to_file,
cookies_import,
mcp_download,
+ mcp_set_path,
mcp_status,
runtime::runtime_status,
runtime::runtime_install,
diff --git a/src-tauri/src/mcp_setup.rs b/src-tauri/src/mcp_setup.rs
index 3cc8755..481ae25 100644
--- a/src-tauri/src/mcp_setup.rs
+++ b/src-tauri/src/mcp_setup.rs
@@ -74,6 +74,14 @@ pub fn is_mcp_dir(dir: &Path) -> bool {
.unwrap_or(false)
}
+pub fn resolve_mcp_dir(dir: &Path) -> Option {
+ if is_mcp_dir(dir) {
+ return Some(dir.to_path_buf());
+ }
+ let nested = dir.join("mcp");
+ is_mcp_dir(&nested).then_some(nested)
+}
+
pub fn find_existing_mcp() -> Option {
let mut candidates = Vec::new();
if let Some(dir) = dirs::document_dir() {
@@ -91,7 +99,7 @@ pub fn find_existing_mcp() -> Option {
if let Some(dir) = dirs::desktop_dir() {
candidates.push(dir.join("mcp"));
}
- candidates.into_iter().find(|p| is_mcp_dir(p))
+ candidates.into_iter().find_map(|p| resolve_mcp_dir(&p))
}
#[cfg(test)]
@@ -107,6 +115,7 @@ mod tests {
std::fs::write(dir.join("package.json"), r#"{ "name": "shardx-mcp" }"#).unwrap();
assert!(is_mcp_dir(&dir));
+ assert_eq!(super::resolve_mcp_dir(&dir).as_deref(), Some(dir.as_path()));
let _ = std::fs::remove_dir_all(&dir);
}
diff --git a/src/App.tsx b/src/App.tsx
index 652d81f..8264784 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -4774,6 +4774,16 @@ function SettingsView() {
} catch (e) { toast.err("MCP download failed: " + String(e)); }
finally { setMcpBusy(false); }
};
+ const useExistingMcp = async () => {
+ const dir = await open({ directory: true, title: "Select an existing ShardX MCP folder" });
+ if (typeof dir !== "string") return;
+ 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)); }
+ };
const mcpIndexPath = (dir: string) => {
const clean = dir.replace(/[\\/]+$/, "");
return `${clean}${clean.includes("\\") ? "\\" : "/"}index.js`;
@@ -4916,9 +4926,14 @@ function SettingsView() {
Set SHARDX_TOKEN in your user environment, restart your MCP client, then register index.js.
{!mcpReady && (
-
- {mcpBusy ? "Downloading…" : mcpMissing ? "Repair / choose folder…" : "Download MCP server"}
-
+
+
+ {mcpBusy ? "Downloading…" : mcpMissing ? "Repair / choose folder…" : "Download MCP server"}
+
+
+ Use existing folder…
+
+
)}
{(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…"}
- {mcpReady ? "MCP files are already downloaded." : "Download the server once."}
+ {mcpReady ? "MCP files are already downloaded; use existing folder to switch without re-downloading." : "Download the server once."}
Run npm install inside the downloaded folder.
Set SHARDX_TOKEN in your user environment, restart your MCP client, then register index.js.
@@ -4948,9 +4955,24 @@ function SettingsView() {
Copy install command
Copy MCP config
{mcpReady && (
-
- {mcpBusy ? "Downloading…" : "Change / repair…"}
-
+ <>
+
+ Use existing folder…
+
+
+ {mcpBusy ? "Downloading…" : "Download / repair…"}
+
+ >
)}