From 2909f4531b62a5d18c06267f04ab2f24f2258129 Mon Sep 17 00:00:00 2001
From: shamAnimates <145093437+shamAnimates@users.noreply.github.com>
Date: Sun, 16 Aug 2026 18:48:28 +0530
Subject: [PATCH] fix(web): use canonical MCP URL in Cursor deeplinks
---
.github/workflows/ci.yml | 4 ++++
apps/web/components/connect-ai-modal.tsx | 7 ++----
.../components/mcp-modal/mcp-detail-view.tsx | 7 ++----
apps/web/lib/mcp-manual-instructions.test.ts | 22 +++++++++++++++++++
apps/web/lib/mcp-manual-instructions.ts | 5 +++++
5 files changed, 35 insertions(+), 10 deletions(-)
create mode 100644 apps/web/lib/mcp-manual-instructions.test.ts
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": {