Support deep linking MCP (#1550)

<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Adds support for `dyad://add-mcp-server` deep links that prefill MCP
server settings, and updates deep link context/consumers to use
timestamp-based effects and clearing to avoid repeat handling.
> 
> - **Deep Link Infrastructure**:
> - Introduce `src/ipc/deep_link_data.ts` with zod schema
(`AddMcpServerConfigSchema`) and typed `DeepLinkData`.
> - Extend `DeepLinkContext` with `clearLastDeepLink`, timestamped
events, and auto-navigate to `/settings#tools-mcp` on `add-mcp-server`.
> - **Main Process**:
>   - Handle `dyad://add-mcp-server?name=...&config=...`:
> - Base64-decode and validate `config`; send `deep-link-received` with
typed payload or show error.
> - **Settings UI (MCP)**:
> - In `ToolsMcpSettings`, prefill form from `add-mcp-server` payload
(supports `stdio` command/args and `http` url) and show info toast;
clear deep link after handling.
> - **Connectors/UI**:
>   - Update `TitleBar`, `NeonConnector`, `SupabaseConnector` to:
> - Depend on `lastDeepLink?.timestamp` and call `clearLastDeepLink()`
after handling (`dyad-pro-return`, `neon-oauth-return`,
`supabase-oauth-return`).
> - **IPC Renderer**:
>   - Use centralized `DeepLinkData` types in `ipc_client.ts`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
294a9c6f38442241b54e9bcbe19a7a772d338ee0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This commit is contained in:
Will Chen
2025-10-16 10:20:16 -07:00
committed by GitHub
parent d76f428447
commit 744e413e71
8 changed files with 117 additions and 15 deletions

View File

@@ -11,7 +11,7 @@ import { NeonDisconnectButton } from "@/components/NeonDisconnectButton";
export function NeonConnector() {
const { settings, refreshSettings } = useSettings();
const { lastDeepLink } = useDeepLink();
const { lastDeepLink, clearLastDeepLink } = useDeepLink();
const { isDarkMode } = useTheme();
useEffect(() => {
@@ -19,10 +19,11 @@ export function NeonConnector() {
if (lastDeepLink?.type === "neon-oauth-return") {
await refreshSettings();
toast.success("Successfully connected to Neon!");
clearLastDeepLink();
}
};
handleDeepLink();
}, [lastDeepLink]);
}, [lastDeepLink?.timestamp]);
if (settings?.neon?.accessToken) {
return (

View File

@@ -40,17 +40,18 @@ import { useTheme } from "@/contexts/ThemeContext";
export function SupabaseConnector({ appId }: { appId: number }) {
const { settings, refreshSettings } = useSettings();
const { app, refreshApp } = useLoadApp(appId);
const { lastDeepLink } = useDeepLink();
const { lastDeepLink, clearLastDeepLink } = useDeepLink();
const { isDarkMode } = useTheme();
useEffect(() => {
const handleDeepLink = async () => {
if (lastDeepLink?.type === "supabase-oauth-return") {
await refreshSettings();
await refreshApp();
clearLastDeepLink();
}
};
handleDeepLink();
}, [lastDeepLink]);
}, [lastDeepLink?.timestamp]);
const {
projects,
loading,

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -11,8 +11,10 @@ import {
SelectValue,
} from "@/components/ui/select";
import { useMcp, type Transport } from "@/hooks/useMcp";
import { showError, showSuccess } from "@/lib/toast";
import { showError, showInfo, showSuccess } from "@/lib/toast";
import { Edit2, Plus, Save, Trash2, X } from "lucide-react";
import { useDeepLink } from "@/contexts/DeepLinkContext";
import { AddMcpServerDeepLinkData } from "@/ipc/deep_link_data";
type KeyValue = { key: string; value: string };
@@ -299,6 +301,29 @@ export function ToolsMcpSettings() {
const [args, setArgs] = useState<string>("");
const [url, setUrl] = useState("");
const [enabled, setEnabled] = useState(true);
const { lastDeepLink, clearLastDeepLink } = useDeepLink();
console.log("lastDeepLink!!!", lastDeepLink);
useEffect(() => {
console.log("rerun effect");
const handleDeepLink = async () => {
if (lastDeepLink?.type === "add-mcp-server") {
const deepLink = lastDeepLink as AddMcpServerDeepLinkData;
const payload = deepLink.payload;
showInfo(`Prefilled ${payload.name} MCP server`);
setName(payload.name);
setTransport(payload.config.type);
if (payload.config.type === "stdio") {
const [command, ...args] = payload.config.command.split(" ");
setCommand(command);
setArgs(args.join(" "));
} else {
setUrl(payload.config.url);
}
clearLastDeepLink();
}
};
handleDeepLink();
}, [lastDeepLink?.timestamp]);
React.useEffect(() => {
setConsents(consentsMap);