mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-12 23:01:07 +00:00
fix mcp widget lifecycle and setup
This commit is contained in:
parent
4c2486fa38
commit
14c4f1076c
11 changed files with 262 additions and 233 deletions
|
|
@ -1,6 +1,11 @@
|
|||
import { Component, type ErrorInfo, type ReactNode } from "react"
|
||||
import {
|
||||
Component,
|
||||
type ContextType,
|
||||
type ErrorInfo,
|
||||
type ReactNode,
|
||||
} from "react"
|
||||
import { Button, Stack } from "./design/ui"
|
||||
import { app } from "./lib/app"
|
||||
import { McpAppContext } from "./McpAppProvider"
|
||||
|
||||
interface Props {
|
||||
children: ReactNode
|
||||
|
|
@ -12,6 +17,8 @@ interface State {
|
|||
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
state: State = { error: null }
|
||||
static contextType = McpAppContext
|
||||
declare context: ContextType<typeof McpAppContext>
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { error }
|
||||
|
|
@ -19,11 +26,18 @@ export class ErrorBoundary extends Component<Props, State> {
|
|||
|
||||
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||
try {
|
||||
app.sendLog({
|
||||
const report = this.context?.app?.sendLog({
|
||||
level: "error",
|
||||
logger: "ErrorBoundary",
|
||||
data: `${error.name}: ${error.message}\n${info.componentStack ?? ""}`,
|
||||
})
|
||||
if (report) {
|
||||
void report.catch(() => {
|
||||
console.error("[ErrorBoundary]", error, info)
|
||||
})
|
||||
} else {
|
||||
console.error("[ErrorBoundary]", error, info)
|
||||
}
|
||||
} catch {
|
||||
console.error("[ErrorBoundary]", error, info)
|
||||
}
|
||||
|
|
|
|||
157
apps/mcp/src/widget/McpAppProvider.tsx
Normal file
157
apps/mcp/src/widget/McpAppProvider.tsx
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import type {
|
||||
App as McpApp,
|
||||
McpUiHostContext,
|
||||
} from "@modelcontextprotocol/ext-apps"
|
||||
import { useApp as useMcpApp } from "@modelcontextprotocol/ext-apps/react"
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"
|
||||
import {
|
||||
createContext,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react"
|
||||
import type { ViewMessage } from "../shared/types"
|
||||
import { loadViewCheckpoint, saveViewCheckpoint } from "./lib/viewCheckpoint"
|
||||
|
||||
export type ViewState =
|
||||
| { kind: "loading" }
|
||||
| { kind: "view"; message: ViewMessage }
|
||||
| { kind: "error"; message: string }
|
||||
| { kind: "raw"; structuredContent: unknown }
|
||||
|
||||
export interface McpAppContextValue {
|
||||
app: McpApp | null
|
||||
hostContext: McpUiHostContext | null
|
||||
isConnected: boolean
|
||||
state: ViewState
|
||||
setView: (message: ViewMessage) => void
|
||||
setError: (message: string) => void
|
||||
}
|
||||
|
||||
export const McpAppContext = createContext<McpAppContextValue | null>(null)
|
||||
|
||||
function safeLog(
|
||||
app: McpApp,
|
||||
level: "debug" | "info" | "warning" | "error",
|
||||
message: string,
|
||||
) {
|
||||
try {
|
||||
void app.sendLog({ level, data: message }).catch(() => {
|
||||
// Host logging is optional.
|
||||
})
|
||||
} catch {
|
||||
// The transport may not be ready yet.
|
||||
}
|
||||
}
|
||||
|
||||
function initialViewState(): ViewState {
|
||||
const checkpoint = loadViewCheckpoint()
|
||||
return checkpoint
|
||||
? { kind: "view", message: checkpoint }
|
||||
: { kind: "loading" }
|
||||
}
|
||||
|
||||
export function McpAppProvider({ children }: { children: ReactNode }) {
|
||||
const [state, setState] = useState<ViewState>(initialViewState)
|
||||
const [hostContext, setHostContext] = useState<McpUiHostContext | null>(null)
|
||||
|
||||
const { app, isConnected, error } = useMcpApp({
|
||||
appInfo: { name: "Supermemory MCP", version: "1.0.0" },
|
||||
capabilities: {},
|
||||
strict: true,
|
||||
onAppCreated: (createdApp) => {
|
||||
createdApp.ontoolinput = (input: unknown) => {
|
||||
const name =
|
||||
typeof input === "object" && input !== null && "name" in input
|
||||
? String((input as { name: unknown }).name)
|
||||
: "?"
|
||||
safeLog(createdApp, "info", `[host] ontoolinput: ${name}`)
|
||||
setState({ kind: "loading" })
|
||||
}
|
||||
createdApp.ontoolinputpartial = () => setState({ kind: "loading" })
|
||||
createdApp.ontoolcancelled = () => {
|
||||
safeLog(createdApp, "info", "[host] ontoolcancelled")
|
||||
setState({ kind: "loading" })
|
||||
}
|
||||
createdApp.ontoolresult = (result: CallToolResult) => {
|
||||
const structuredContent = (result as { structuredContent?: unknown })
|
||||
.structuredContent
|
||||
if (!structuredContent || typeof structuredContent !== "object") {
|
||||
safeLog(
|
||||
createdApp,
|
||||
"warning",
|
||||
"[host] ontoolresult: no structuredContent",
|
||||
)
|
||||
setState({ kind: "raw", structuredContent })
|
||||
return
|
||||
}
|
||||
if ("view" in structuredContent) {
|
||||
const message = structuredContent as ViewMessage
|
||||
safeLog(
|
||||
createdApp,
|
||||
"info",
|
||||
`[host] ontoolresult: view=${message.view}`,
|
||||
)
|
||||
const checkpoint = loadViewCheckpoint(message.viewId)
|
||||
setState({ kind: "view", message: checkpoint ?? message })
|
||||
return
|
||||
}
|
||||
safeLog(
|
||||
createdApp,
|
||||
"warning",
|
||||
"[host] ontoolresult: structuredContent without view",
|
||||
)
|
||||
setState({ kind: "raw", structuredContent })
|
||||
}
|
||||
createdApp.onhostcontextchanged = (next) => {
|
||||
setHostContext(createdApp.getHostContext() ?? next)
|
||||
}
|
||||
createdApp.onerror = (nextError: unknown) => {
|
||||
safeLog(createdApp, "error", `[host] onerror: ${String(nextError)}`)
|
||||
setState({ kind: "error", message: String(nextError) })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const setView = useCallback((message: ViewMessage) => {
|
||||
saveViewCheckpoint(message)
|
||||
setState({ kind: "view", message })
|
||||
}, [])
|
||||
const setError = useCallback((message: string) => {
|
||||
setState({ kind: "error", message })
|
||||
}, [])
|
||||
|
||||
const value = useMemo<McpAppContextValue>(
|
||||
() => ({
|
||||
app,
|
||||
hostContext: hostContext ?? app?.getHostContext() ?? null,
|
||||
isConnected,
|
||||
state: error ? { kind: "error", message: error.message } : state,
|
||||
setView,
|
||||
setError,
|
||||
}),
|
||||
[app, error, hostContext, isConnected, setError, setView, state],
|
||||
)
|
||||
|
||||
return (
|
||||
<McpAppContext.Provider value={value}>{children}</McpAppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const previewValue: McpAppContextValue = {
|
||||
app: null,
|
||||
hostContext: null,
|
||||
isConnected: false,
|
||||
state: { kind: "loading" },
|
||||
setView: () => {},
|
||||
setError: () => {},
|
||||
}
|
||||
|
||||
export function McpAppPreviewProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<McpAppContext.Provider value={previewValue}>
|
||||
{children}
|
||||
</McpAppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"
|
||||
import { useMemo } from "react"
|
||||
import { useContext, useMemo } from "react"
|
||||
import type { ViewMessage } from "../../shared/types"
|
||||
import { app } from "../lib/app"
|
||||
import {
|
||||
handoffToModel as performModelHandoff,
|
||||
type ModelHandoffRequest,
|
||||
} from "../lib/modelHandoff"
|
||||
import { McpAppContext } from "../McpAppProvider"
|
||||
|
||||
export interface ToolCallResult<T = unknown> {
|
||||
ok: boolean
|
||||
|
|
@ -23,6 +23,12 @@ export interface ToolCallResult<T = unknown> {
|
|||
* that class of mistake impossible: there is no `callTool` exposed.
|
||||
*/
|
||||
export function useApp() {
|
||||
const context = useContext(McpAppContext)
|
||||
if (!context) {
|
||||
throw new Error("useApp must be used within McpAppProvider")
|
||||
}
|
||||
const { app } = context
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
/** Call an MCP server tool and await the result. */
|
||||
|
|
@ -30,6 +36,9 @@ export function useApp() {
|
|||
name: string,
|
||||
args: Record<string, unknown>,
|
||||
): Promise<ToolCallResult<T>> {
|
||||
if (!app) {
|
||||
return { ok: false, error: "MCP host is not connected" }
|
||||
}
|
||||
try {
|
||||
const result = (await app.callServerTool({
|
||||
name,
|
||||
|
|
@ -53,6 +62,9 @@ export function useApp() {
|
|||
|
||||
/** Make widget state available to the model on a future turn. */
|
||||
async updateModelContext(content: string): Promise<ToolCallResult> {
|
||||
if (!app) {
|
||||
return { ok: false, error: "MCP host is not connected" }
|
||||
}
|
||||
try {
|
||||
await app.updateModelContext({
|
||||
content: [{ type: "text", text: content }],
|
||||
|
|
@ -69,24 +81,36 @@ export function useApp() {
|
|||
* rejects or drops model-context updates.
|
||||
*/
|
||||
handoffToModel(request: ModelHandoffRequest) {
|
||||
if (!app) {
|
||||
const error = "MCP host is not connected"
|
||||
return Promise.resolve({
|
||||
ok: false,
|
||||
contextUpdate: { ok: false, error },
|
||||
conversationMessage: { ok: false, error },
|
||||
})
|
||||
}
|
||||
return performModelHandoff(app, request)
|
||||
},
|
||||
|
||||
/** Send a structured log line to the host. */
|
||||
log(level: "debug" | "info" | "warning" | "error", message: string) {
|
||||
return app.sendLog({ level, data: message })
|
||||
return app
|
||||
? app.sendLog({ level, data: message })
|
||||
: Promise.resolve(undefined)
|
||||
},
|
||||
|
||||
/** Request the host to switch display mode. */
|
||||
requestDisplayMode(mode: "inline" | "fullscreen" | "pip") {
|
||||
return app.requestDisplayMode({ mode })
|
||||
return app
|
||||
? app.requestDisplayMode({ mode })
|
||||
: Promise.resolve({ mode })
|
||||
},
|
||||
|
||||
getHostContext() {
|
||||
return app.getHostContext()
|
||||
return app?.getHostContext()
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
}, [app])
|
||||
}
|
||||
|
||||
export type AppApi = ReturnType<typeof useApp>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,15 @@
|
|||
import type { McpUiHostContext } from "@modelcontextprotocol/ext-apps"
|
||||
import { useEffect, useState } from "react"
|
||||
import { app } from "../lib/app"
|
||||
import { useContext } from "react"
|
||||
import { McpAppContext } from "../McpAppProvider"
|
||||
|
||||
/**
|
||||
* Subscribes to host context changes (theme, dimensions, displayMode, fonts).
|
||||
* Returns the latest context, or null until the connection is established.
|
||||
* Returns the provider-owned host context, including updates received after
|
||||
* the connection handshake.
|
||||
*/
|
||||
export function useHostContext(): McpUiHostContext | null {
|
||||
const [ctx, setCtx] = useState<McpUiHostContext | null>(
|
||||
() => app.getHostContext() ?? null,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (next: McpUiHostContext) => setCtx(next)
|
||||
app.onhostcontextchanged = handler
|
||||
return () => {
|
||||
if (app.onhostcontextchanged === handler) {
|
||||
app.onhostcontextchanged = () => {}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return ctx
|
||||
const context = useContext(McpAppContext)
|
||||
if (!context) {
|
||||
throw new Error("useHostContext must be used within McpAppProvider")
|
||||
}
|
||||
return context.hostContext
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,8 @@
|
|||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"
|
||||
import { useEffect, useState } from "react"
|
||||
import type { ViewMessage } from "../../shared/types"
|
||||
import { app } from "../lib/app"
|
||||
import { loadViewCheckpoint, saveViewCheckpoint } from "../lib/viewCheckpoint"
|
||||
|
||||
function safeLog(
|
||||
level: "debug" | "info" | "warning" | "error",
|
||||
message: string,
|
||||
) {
|
||||
try {
|
||||
void app.sendLog({ level, data: message })
|
||||
} catch {
|
||||
// host may not support logging — ignore
|
||||
}
|
||||
}
|
||||
|
||||
type ViewState =
|
||||
| { kind: "loading" }
|
||||
| { kind: "view"; message: ViewMessage }
|
||||
| { kind: "error"; message: string }
|
||||
| { kind: "raw"; structuredContent: unknown }
|
||||
import { useContext } from "react"
|
||||
import { McpAppContext, type McpAppContextValue } from "../McpAppProvider"
|
||||
|
||||
/**
|
||||
* Drives the widget's top-level view state from MCP host events.
|
||||
* Exposes the top-level view state owned by McpAppProvider.
|
||||
*
|
||||
* - `ontoolinput` / `ontoolinputpartial`: shows loading
|
||||
* - `ontoolresult`: parses `structuredContent` as `ViewMessage` and renders
|
||||
|
|
@ -34,68 +14,17 @@ type ViewState =
|
|||
* not flow through this hook.
|
||||
*/
|
||||
export function useViewState(): {
|
||||
state: ViewState
|
||||
setView: (msg: ViewMessage) => void
|
||||
state: McpAppContextValue["state"]
|
||||
setView: McpAppContextValue["setView"]
|
||||
setError: (message: string) => void
|
||||
} {
|
||||
const [state, setState] = useState<ViewState>(() => {
|
||||
const checkpoint = loadViewCheckpoint()
|
||||
return checkpoint
|
||||
? { kind: "view", message: checkpoint }
|
||||
: { kind: "loading" }
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
app.ontoolinput = (input: unknown) => {
|
||||
const name =
|
||||
typeof input === "object" && input !== null && "name" in input
|
||||
? String((input as { name: unknown }).name)
|
||||
: "?"
|
||||
safeLog("info", `[host] ontoolinput: ${name}`)
|
||||
setState({ kind: "loading" })
|
||||
}
|
||||
app.ontoolinputpartial = () => setState({ kind: "loading" })
|
||||
app.ontoolcancelled = () => {
|
||||
safeLog("info", "[host] ontoolcancelled")
|
||||
setState({ kind: "loading" })
|
||||
}
|
||||
app.ontoolresult = (result: CallToolResult) => {
|
||||
const sc = (result as { structuredContent?: unknown }).structuredContent
|
||||
if (!sc || typeof sc !== "object") {
|
||||
safeLog("warning", "[host] ontoolresult: no structuredContent")
|
||||
setState({ kind: "raw", structuredContent: sc })
|
||||
return
|
||||
}
|
||||
if ("view" in sc) {
|
||||
const msg = sc as ViewMessage
|
||||
safeLog("info", `[host] ontoolresult: view=${msg.view}`)
|
||||
const checkpoint = loadViewCheckpoint(msg.viewId)
|
||||
setState({ kind: "view", message: checkpoint ?? msg })
|
||||
return
|
||||
}
|
||||
safeLog("warning", "[host] ontoolresult: structuredContent without view")
|
||||
setState({ kind: "raw", structuredContent: sc })
|
||||
}
|
||||
app.onerror = (error: unknown) => {
|
||||
safeLog("error", `[host] onerror: ${String(error)}`)
|
||||
setState({ kind: "error", message: String(error) })
|
||||
}
|
||||
|
||||
return () => {
|
||||
app.ontoolinput = () => {}
|
||||
app.ontoolinputpartial = () => {}
|
||||
app.ontoolcancelled = () => {}
|
||||
app.ontoolresult = () => {}
|
||||
app.onerror = undefined
|
||||
}
|
||||
}, [])
|
||||
|
||||
const context = useContext(McpAppContext)
|
||||
if (!context) {
|
||||
throw new Error("useViewState must be used within McpAppProvider")
|
||||
}
|
||||
return {
|
||||
state,
|
||||
setView: (msg) => {
|
||||
saveViewCheckpoint(msg)
|
||||
setState({ kind: "view", message: msg })
|
||||
},
|
||||
setError: (message) => setState({ kind: "error", message }),
|
||||
state: context.state,
|
||||
setView: context.setView,
|
||||
setError: context.setError,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
/**
|
||||
* Singleton App instance — one MCP App connection per widget.
|
||||
* Imported only via the `useApp()` hook for typed access.
|
||||
*/
|
||||
import { App } from "@modelcontextprotocol/ext-apps"
|
||||
|
||||
export const app = new App({ name: "Supermemory MCP", version: "1.0.0" })
|
||||
|
|
@ -2,17 +2,16 @@ import { StrictMode } from "react"
|
|||
import { createRoot } from "react-dom/client"
|
||||
import { App as WidgetApp } from "./App"
|
||||
import { ErrorBoundary } from "./ErrorBoundary"
|
||||
import { app } from "./lib/app"
|
||||
import { McpAppProvider } from "./McpAppProvider"
|
||||
import "./design/globals.css"
|
||||
|
||||
const root = createRoot(document.getElementById("app") as HTMLElement)
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<ErrorBoundary>
|
||||
<WidgetApp />
|
||||
</ErrorBoundary>
|
||||
<McpAppProvider>
|
||||
<ErrorBoundary>
|
||||
<WidgetApp />
|
||||
</ErrorBoundary>
|
||||
</McpAppProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
// Establish the postMessage channel with the MCP host.
|
||||
app.connect()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { StrictMode } from "react"
|
||||
import { createRoot } from "react-dom/client"
|
||||
import { McpAppPreviewProvider } from "../McpAppProvider"
|
||||
import { Studio } from "./Studio"
|
||||
import "../design/globals.css"
|
||||
|
||||
|
|
@ -10,6 +11,8 @@ document.documentElement.setAttribute("data-theme", "light")
|
|||
const root = createRoot(document.getElementById("studio") as HTMLElement)
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<Studio />
|
||||
<McpAppPreviewProvider>
|
||||
<Studio />
|
||||
</McpAppPreviewProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { $fetch } from "@lib/api"
|
||||
import { authClient } from "@lib/auth"
|
||||
import { useAuth } from "@lib/auth-context"
|
||||
import { generateId } from "@lib/generate-id"
|
||||
import { useForm } from "@tanstack/react-form"
|
||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import { Button } from "@ui/components/button"
|
||||
|
|
@ -130,7 +127,6 @@ export function ConnectAIModal({
|
|||
openInitialClient,
|
||||
openInitialTab,
|
||||
}: ConnectAIModalProps) {
|
||||
const { org } = useAuth()
|
||||
const [selectedClient, setSelectedClient] = useState<
|
||||
keyof typeof clients | null
|
||||
>(openInitialClient || null)
|
||||
|
|
@ -142,7 +138,6 @@ export function ConnectAIModal({
|
|||
const [setupTab, setSetupTab] = useState<"oneClick" | "manual">(
|
||||
openInitialTab ?? "manual",
|
||||
)
|
||||
const [manualApiKey, setManualApiKey] = useState<string | null>(null)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
|
||||
const [projectId, setProjectId] = useState("default")
|
||||
|
|
@ -236,33 +231,6 @@ export function ConnectAIModal({
|
|||
},
|
||||
})
|
||||
|
||||
const createMcpApiKeyMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!org?.id) {
|
||||
throw new Error("Organization ID is required")
|
||||
}
|
||||
|
||||
const res = await authClient.apiKey.create({
|
||||
metadata: {
|
||||
organizationId: org?.id,
|
||||
type: "mcp-manual",
|
||||
},
|
||||
name: `mcp-manual-${generateId().slice(0, 8)}`,
|
||||
prefix: `sm_${org?.id}_`,
|
||||
})
|
||||
return res.key
|
||||
},
|
||||
onSuccess: (apiKey) => {
|
||||
setManualApiKey(apiKey)
|
||||
toast.success("API key created successfully!")
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Failed to create API key", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (openInitialClient) {
|
||||
setSelectedClient(openInitialClient as keyof typeof clients)
|
||||
|
|
@ -279,20 +247,6 @@ export function ConnectAIModal({
|
|||
if (!s.manual && setupTab === "manual") setSetupTab("oneClick")
|
||||
}, [selectedClient, setupTab])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedClient !== "mcp-url" || setupTab !== "manual" || !org?.id)
|
||||
return
|
||||
if (manualApiKey || createMcpApiKeyMutation.isPending) return
|
||||
createMcpApiKeyMutation.mutate()
|
||||
}, [
|
||||
selectedClient,
|
||||
setupTab,
|
||||
org?.id,
|
||||
manualApiKey,
|
||||
createMcpApiKeyMutation.isPending,
|
||||
createMcpApiKeyMutation.mutate,
|
||||
])
|
||||
|
||||
function getMcpServerUrl() {
|
||||
return "https://mcp.supermemory.ai/mcp"
|
||||
}
|
||||
|
|
@ -496,12 +450,6 @@ export function ConnectAIModal({
|
|||
onClick={() => {
|
||||
setSelectedClient("mcp-url")
|
||||
setSetupTab("manual")
|
||||
if (
|
||||
!manualApiKey &&
|
||||
!createMcpApiKeyMutation.isPending
|
||||
) {
|
||||
createMcpApiKeyMutation.mutate()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -714,48 +662,32 @@ export function ConnectAIModal({
|
|||
)
|
||||
}
|
||||
if (manual.kind === "generic-remote") {
|
||||
const remoteSnippet = buildMcpUrlRemoteJson(
|
||||
manualApiKey || "your-api-key-here",
|
||||
)
|
||||
const remoteSnippet = buildMcpUrlRemoteJson()
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Paste into your MCP config. We create an API key for
|
||||
you when you open this tab; copy the block after it
|
||||
appears.
|
||||
Paste this into your MCP config. Your client will
|
||||
open Supermemory OAuth when it first connects.
|
||||
</p>
|
||||
{createMcpApiKeyMutation.isPending ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<Loader2 className="size-6 animate-spin text-primary" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="relative min-w-0 max-w-full">
|
||||
<pre className="max-h-80 max-w-full overflow-x-auto overflow-y-auto rounded-lg border border-border bg-muted p-3 pr-12 text-xs sm:p-4">
|
||||
<code className="block font-mono whitespace-pre-wrap break-all">
|
||||
{remoteSnippet}
|
||||
</code>
|
||||
</pre>
|
||||
<Button
|
||||
className="absolute top-2 right-2 size-8 cursor-pointer p-0 bg-muted/80 hover:bg-muted"
|
||||
onClick={() =>
|
||||
copyManualSnippet(remoteSnippet)
|
||||
}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckIcon className="size-3.5 text-green-600" />
|
||||
) : (
|
||||
<CopyIcon className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Bearer token uses your supermemory API key.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<div className="relative min-w-0 max-w-full">
|
||||
<pre className="max-h-80 max-w-full overflow-x-auto overflow-y-auto rounded-lg border border-border bg-muted p-3 pr-12 text-xs sm:p-4">
|
||||
<code className="block font-mono whitespace-pre-wrap break-all">
|
||||
{remoteSnippet}
|
||||
</code>
|
||||
</pre>
|
||||
<Button
|
||||
className="absolute top-2 right-2 size-8 cursor-pointer p-0 bg-muted/80 hover:bg-muted"
|
||||
onClick={() => copyManualSnippet(remoteSnippet)}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckIcon className="size-3.5 text-green-600" />
|
||||
) : (
|
||||
<CopyIcon className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -557,13 +557,12 @@ export function MCPSteps({ variant = "full" }: MCPStepsProps) {
|
|||
)
|
||||
}
|
||||
if (manual.kind === "generic-remote") {
|
||||
const snippet = buildMcpUrlRemoteJson("your-api-key-here")
|
||||
const snippet = buildMcpUrlRemoteJson()
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<p className="text-[13px] leading-relaxed text-[#A1A1AA]">
|
||||
Add this to your client's MCP config. Replace the
|
||||
placeholder with an API key from supermemory settings
|
||||
(Integrations).
|
||||
Add this to your client's MCP config. Your client
|
||||
will open Supermemory OAuth when it first connects.
|
||||
</p>
|
||||
<McpCodeBlock
|
||||
code={snippet}
|
||||
|
|
@ -573,13 +572,6 @@ export function MCPSteps({ variant = "full" }: MCPStepsProps) {
|
|||
setActiveStep(3)
|
||||
}}
|
||||
/>
|
||||
{detailSetup?.oneClick ? (
|
||||
<p className="text-[12px] text-[#737373]">
|
||||
Use Bearer auth in headers, or switch to One click
|
||||
setup and paste the HTTPS URL if your client
|
||||
supports OAuth only.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,11 @@ export const ANTIGRAVITY_MCP_SNIPPET = `{
|
|||
}
|
||||
}`
|
||||
|
||||
export function buildMcpUrlRemoteJson(apiKeyPlaceholder: string) {
|
||||
export function buildMcpUrlRemoteJson() {
|
||||
return `{
|
||||
"supermemory-mcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "https://mcp.supermemory.ai/mcp"],
|
||||
"env": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${apiKeyPlaceholder}"
|
||||
}
|
||||
"args": ["-y", "mcp-remote@latest", "https://mcp.supermemory.ai/mcp"]
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue