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:
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user