Skip to content

Commit 95d38ab

Browse files
its-mashclaude
andcommitted
feat: handle mcpmux://install deep link from discovery UI
Add install deep link route to the Tauri backend and a ServerInstallModal component on the frontend. When the discovery web UI sends mcpmux://install?server={id}, the desktop app now shows a confirmation modal with server info and space picker, then installs via the existing install_server command. - Add "install" route to handle_deep_link() in oauth.rs - Add ServerInstallModal component with loading/ready/error/success states - Mount modal in App.tsx alongside OAuthConsentModal - Expose emit in __TAURI_TEST_API__ for E2E event simulation - Add emitEvent helper to E2E tauri-api.ts - Add deeplink-install.wdio.ts E2E tests (6 test cases) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 03e4c5b commit 95d38ab

6 files changed

Lines changed: 658 additions & 2 deletions

File tree

apps/desktop/src-tauri/src/commands/oauth.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ use super::gateway::GatewayAppState;
4444
/// Now only contains request_id - frontend must call get_pending_consent
4545
pub const OAUTH_CONSENT_EVENT: &str = "oauth-consent-request";
4646

47+
/// Event name for server install requests sent to frontend (from deep link)
48+
pub const SERVER_INSTALL_EVENT: &str = "server-install-request";
49+
4750
/// Minimal deep link payload - only request_id
4851
/// Frontend must call get_pending_consent to get full details
4952
#[derive(Debug, Clone, Serialize)]
@@ -52,6 +55,13 @@ pub struct OAuthDeepLinkPayload {
5255
pub request_id: String,
5356
}
5457

58+
/// Deep link payload for server installation requests
59+
#[derive(Debug, Clone, Serialize)]
60+
#[serde(rename_all = "camelCase")]
61+
pub struct ServerInstallDeepLinkPayload {
62+
pub server_id: String,
63+
}
64+
5565
/// Full consent request details returned by get_pending_consent
5666
#[derive(Debug, Clone, Serialize)]
5767
#[serde(rename_all = "camelCase")]
@@ -111,6 +121,9 @@ pub fn handle_deep_link<R: tauri::Runtime>(app: &tauri::AppHandle<R>, url: &str)
111121
// Inbound OAuth - client requesting approval
112122
handle_authorize_deep_link(app, &parsed);
113123
}
124+
Some("install") => {
125+
handle_install_deep_link(app, &parsed);
126+
}
114127
Some("test") => {
115128
info!("[DeepLink] Test URL received successfully!");
116129
}
@@ -157,6 +170,41 @@ fn handle_authorize_deep_link<R: tauri::Runtime>(app: &tauri::AppHandle<R>, url:
157170
}
158171
}
159172

173+
/// Handle server install deep link
174+
///
175+
/// Extracts server_id and emits to frontend.
176+
/// Frontend will look up the server definition and show install modal.
177+
fn handle_install_deep_link<R: tauri::Runtime>(app: &tauri::AppHandle<R>, url: &Url) {
178+
let params: HashMap<_, _> = url.query_pairs().collect();
179+
180+
let server_id = match params.get("server") {
181+
Some(id) if !id.is_empty() => id.to_string(),
182+
_ => {
183+
error!("[DeepLink] Install link missing required parameter: server");
184+
return;
185+
}
186+
};
187+
188+
info!(
189+
"[DeepLink] Server install request: server_id='{}'",
190+
server_id
191+
);
192+
193+
let payload = ServerInstallDeepLinkPayload { server_id };
194+
195+
if let Err(e) = app.emit(SERVER_INSTALL_EVENT, &payload) {
196+
error!("[DeepLink] Failed to emit server install event: {}", e);
197+
return;
198+
}
199+
200+
// Focus the main window
201+
if let Some(window) = app.get_webview_window("main") {
202+
let _ = window.show();
203+
let _ = window.unminimize();
204+
let _ = window.set_focus();
205+
}
206+
}
207+
160208
/// Handle OAuth callback deep link (legacy - for outbound OAuth server connections)
161209
///
162210
/// NOTE: The primary OAuth callback mechanism is now the loopback HTTP server

apps/desktop/src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from '@mcpmux/ui';
2929
import { ThemeProvider } from '@/components/ThemeProvider';
3030
import { OAuthConsentModal } from '@/components/OAuthConsentModal';
31+
import { ServerInstallModal } from '@/components/ServerInstallModal';
3132
import { SpaceSwitcher } from '@/components/SpaceSwitcher';
3233
import { useDataSync } from '@/hooks/useDataSync';
3334
import { useAppStore, useActiveSpace, useViewSpace, useTheme } from '@/stores';
@@ -203,6 +204,8 @@ function App() {
203204
<AppContent />
204205
{/* OAuth consent modal - shown when MCP clients request authorization */}
205206
<OAuthConsentModal />
207+
{/* Server install modal - shown when install deep link is received */}
208+
<ServerInstallModal />
206209
</ThemeProvider>
207210
);
208211
}

0 commit comments

Comments
 (0)