diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80600ae5..c1398b7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,5 +29,9 @@ jobs: - name: Run TypeScript type checking run: bunx turbo run check-types --filter='@supermemory/ai-sdk' --filter='@supermemory/memory-graph' + - name: Run web unit tests + working-directory: apps/web + run: bun test + - name: Run Biome CI (format & lint on changed files) run: bunx biome ci --changed --since=origin/main --no-errors-on-unmatched diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx index 4e467567..a9889342 100644 --- a/apps/web/components/connect-ai-modal.tsx +++ b/apps/web/components/connect-ai-modal.tsx @@ -36,6 +36,7 @@ import { } from "@/lib/mcp-client-setup" import { ClaudeDesktopManualTimeline } from "@/components/mcp-modal/claude-desktop-manual-timeline" import { + buildCursorMcpDeeplink, buildMcpUrlRemoteJson, CHATGPT_REMOTE_MCP_URL, CLAUDE_DESKTOP_MCP_SNIPPET, @@ -277,10 +278,6 @@ export function ConnectAIModal({ return JSON.stringify(config, null, 2) } - function getCursorDeeplink() { - return "cursor://anysphere.cursor-deeplink/mcp/install?name=supermemory&config=eyJ1cmwiOiJodHRwczovL2FwaS5zdXBlcm1lbW9yeS5haS9tY3AifQ%3D%3D" - } - const copyToClipboard = () => { navigator.clipboard.writeText(getMcpServerUrl()) analytics.mcpInstallCmdCopied() @@ -469,7 +466,7 @@ export function ConnectAIModal({ mcp.json yourself.

{ analytics.mcpInstallCmdCopied() toast.success("Opening Cursor installer…") diff --git a/apps/web/components/mcp-modal/mcp-detail-view.tsx b/apps/web/components/mcp-modal/mcp-detail-view.tsx index d7408671..ac8491be 100644 --- a/apps/web/components/mcp-modal/mcp-detail-view.tsx +++ b/apps/web/components/mcp-modal/mcp-detail-view.tsx @@ -24,6 +24,7 @@ import { setupInstructionsSubtitle, } from "@/lib/mcp-client-setup" import { + buildCursorMcpDeeplink, buildMcpUrlRemoteJson, CHATGPT_REMOTE_MCP_URL, CLAUDE_DESKTOP_MCP_SNIPPET, @@ -219,10 +220,6 @@ export function MCPSteps({ variant = "full" }: MCPStepsProps) { if (!s.manual && setupTab === "manual") setSetupTab("oneClick") }, [selectedClient, setupTab, setSetupTab]) - function getCursorDeeplink() { - return "cursor://anysphere.cursor-deeplink/mcp/install?name=supermemory&config=eyJ1cmwiOiJodHRwczovL2FwaS5zdXBlcm1lbW9yeS5haS9tY3AifQ%3D%3D" - } - function getMcpServerUrl() { return "https://mcp.supermemory.ai/mcp" } @@ -446,7 +443,7 @@ export function MCPSteps({ variant = "full" }: MCPStepsProps) {

{ analytics.mcpInstallCmdCopied() toast.success("Opening Cursor installer...") diff --git a/apps/web/lib/mcp-manual-instructions.test.ts b/apps/web/lib/mcp-manual-instructions.test.ts new file mode 100644 index 00000000..98ddb95f --- /dev/null +++ b/apps/web/lib/mcp-manual-instructions.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from "bun:test" +import { + buildCursorMcpDeeplink, + CHATGPT_REMOTE_MCP_URL, +} from "./mcp-manual-instructions" + +describe("buildCursorMcpDeeplink", () => { + test("encodes the canonical remote MCP URL", () => { + const deeplink = new URL(buildCursorMcpDeeplink()) + + expect(deeplink.protocol).toBe("cursor:") + expect(deeplink.hostname).toBe("anysphere.cursor-deeplink") + expect(deeplink.pathname).toBe("/mcp/install") + expect(deeplink.searchParams.get("name")).toBe("supermemory") + + const encodedConfig = deeplink.searchParams.get("config") + expect(encodedConfig).not.toBeNull() + expect(JSON.parse(atob(encodedConfig ?? ""))).toEqual({ + url: CHATGPT_REMOTE_MCP_URL, + }) + }) +}) diff --git a/apps/web/lib/mcp-manual-instructions.ts b/apps/web/lib/mcp-manual-instructions.ts index 3c9fd9d1..ce045a83 100644 --- a/apps/web/lib/mcp-manual-instructions.ts +++ b/apps/web/lib/mcp-manual-instructions.ts @@ -1,5 +1,10 @@ export const CHATGPT_REMOTE_MCP_URL = "https://mcp.supermemory.ai/mcp" +export function buildCursorMcpDeeplink() { + const config = btoa(JSON.stringify({ url: CHATGPT_REMOTE_MCP_URL })) + return `cursor://anysphere.cursor-deeplink/mcp/install?name=supermemory&config=${encodeURIComponent(config)}` +} + export const SUPERMEMORY_MCP_OAUTH_JSON = `{ "mcpServers": { "supermemory": {