+ {isPlatform + ? 'Scope the MCP server to a project. If no project is selected, all projects will be accessible.' + : 'Project selection is only available for the hosted platform.'} +
+{dayjs(timestamp).format(DateTimeFormats.FULL_SECONDS)}
+ Configure your MCP client to connect with your Supabase project +
+{selectedClient.configFile}:
+ + Windsurf does not currently support remote MCP servers over HTTP transport. You need to use + the mcp-remote package as a proxy. +
+ ), + }, + { + key: 'claude-code', + label: 'Claude Code', + icon: 'claude', + configFile: '~/.claude.json', + externalDocsUrl: 'https://docs.anthropic.com/en/docs/claude-code/mcp', + transformConfig: (config): ClaudeCodeMcpConfig => { + return { + mcpServers: { + supabase: { + type: 'http', + url: config.mcpServers.supabase.url, + }, + }, + } + }, + alternateInstructions: (_config) => { + const config = _config as ClaudeCodeMcpConfig + const command = `claude mcp add --transport http supabase "${config.mcpServers.supabase.url}"` + return ( ++ Alternatively, add the MCP server using the command line: +
++ These generic MCP settings may work with other MCP clients, but there are no guarantees, + due to differences between clients. Refer to your specific client docs for where to input + the configuration. +
+ ) + }, + }, +] + +export const DEFAULT_MCP_URL_PLATFORM = 'http://localhost:8080/mcp' +export const DEFAULT_MCP_URL_NON_PLATFORM = 'http://localhost:54321/mcp' diff --git a/packages/ui-patterns/src/McpUrlBuilder/index.ts b/packages/ui-patterns/src/McpUrlBuilder/index.ts new file mode 100644 index 0000000000000..a15b032d88468 --- /dev/null +++ b/packages/ui-patterns/src/McpUrlBuilder/index.ts @@ -0,0 +1,19 @@ +export { ClientSelectDropdown } from './components/ClientSelectDropdown' +export { ConnectionIcon } from './components/ConnectionIcon' +export { McpConfigurationDisplay } from './components/McpConfigurationDisplay' +export { McpConfigurationOptions } from './components/McpConfigurationOptions' +export { + DEFAULT_MCP_URL_PLATFORM, + DEFAULT_MCP_URL_NON_PLATFORM, + FEATURE_GROUPS_PLATFORM, + FEATURE_GROUPS_NON_PLATFORM, + MCP_CLIENTS, +} from './constants' +export { getMcpUrl } from './utils/getMcpUrl' +export { McpConfigPanel, type McpConfigPanelProps } from './McpConfigPanel' +export type { + McpClient, + McpClientBaseConfig as McpClientConfig, + McpFeatureGroup, + McpUrlBuilderConfig, +} from './types' diff --git a/packages/ui-patterns/src/McpUrlBuilder/types.ts b/packages/ui-patterns/src/McpUrlBuilder/types.ts new file mode 100644 index 0000000000000..7b6ae93001244 --- /dev/null +++ b/packages/ui-patterns/src/McpUrlBuilder/types.ts @@ -0,0 +1,88 @@ +export interface McpFeatureGroup { + id: string + name: string + description: string +} + +export interface McpClient { + key: string + label: string + icon?: string + docsUrl?: string + externalDocsUrl?: string + configFile?: string + generateDeepLink?: (config: McpClientConfig) => string | null + transformConfig?: (config: McpClientBaseConfig) => McpClientConfig + alternateInstructions?: (config: McpClientConfig) => React.ReactNode +} + +export interface McpUrlBuilderConfig { + projectRef: string + readonly?: boolean + features?: string[] +} + +export interface McpClientBaseConfig { + mcpServers: { + supabase: { + url: string + } + } +} + +export interface CursorMcpConfig extends McpClientBaseConfig {} + +export interface VSCodeMcpConfig extends McpClientBaseConfig { + mcpServers: { + supabase: { + type: 'http' + url: string + } + } +} + +export interface WindsurfMcpConfig { + mcpServers: { + supabase: { + command: 'npx' + args: ['-y', 'mcp-remote', string] + } + } +} + +export interface ClaudeCodeMcpConfig extends McpClientBaseConfig { + mcpServers: { + supabase: { + type: 'http' + url: string + } + } +} + +export interface ClaudeDesktopMcpConfig extends McpClientBaseConfig { + mcpServers: { + supabase: { + type: 'http' + url: string + } + } +} + +export interface OtherMcpConfig extends McpClientBaseConfig { + mcpServers: { + supabase: { + type: 'http' + url: string + } + } +} + +// Union of all possible config types +export type McpClientConfig = + | ClaudeCodeMcpConfig + | ClaudeDesktopMcpConfig + | CursorMcpConfig + | McpClientBaseConfig + | OtherMcpConfig + | VSCodeMcpConfig + | WindsurfMcpConfig diff --git a/packages/ui-patterns/src/McpUrlBuilder/utils/getMcpButtonData.ts b/packages/ui-patterns/src/McpUrlBuilder/utils/getMcpButtonData.ts new file mode 100644 index 0000000000000..b53d07c582890 --- /dev/null +++ b/packages/ui-patterns/src/McpUrlBuilder/utils/getMcpButtonData.ts @@ -0,0 +1,31 @@ +import type { McpClient, McpClientConfig } from '../types' + +interface GetMcpButtonDataOptions { + basePath: string + theme?: 'light' | 'dark' + client: McpClient + clientConfig: McpClientConfig +} + +export function getMcpButtonData({ + basePath, + theme, + client, + clientConfig, +}: GetMcpButtonDataOptions) { + if (client.generateDeepLink) { + const deepLink = client.generateDeepLink(clientConfig) + if (!deepLink) return null + + const imageSrc = `${basePath}/img/mcp-clients/${client.icon}${ + theme === 'dark' ? '-dark' : '' + }-icon.svg` + + return { + deepLink, + imageSrc, + label: client.label, + } + } + return null +} diff --git a/packages/ui-patterns/src/McpUrlBuilder/utils/getMcpUrl.ts b/packages/ui-patterns/src/McpUrlBuilder/utils/getMcpUrl.ts new file mode 100644 index 0000000000000..94dc41f8cb7f7 --- /dev/null +++ b/packages/ui-patterns/src/McpUrlBuilder/utils/getMcpUrl.ts @@ -0,0 +1,68 @@ +import { DEFAULT_MCP_URL_NON_PLATFORM, DEFAULT_MCP_URL_PLATFORM } from '../constants' +import type { McpClient, McpClientConfig } from '../types' + +interface GetMcpUrlOptions { + projectRef?: string + readonly?: boolean + features?: string[] + selectedClient?: McpClient + isPlatform: boolean + apiUrl?: string +} + +interface GetMcpUrlReturn { + mcpUrl: string + clientConfig: McpClientConfig +} + +export function getMcpUrl({ + projectRef, + isPlatform, + apiUrl, + readonly = false, + features = [], + selectedClient, +}: GetMcpUrlOptions): GetMcpUrlReturn { + // Generate the MCP URL based on current configuration + const url = new URL(getMcpUrlBase({ isPlatform, apiUrl })) + if (projectRef && isPlatform) { + url.searchParams.set('project_ref', projectRef) + } + if (readonly && isPlatform) { + url.searchParams.set('read_only', 'true') + } + if (features.length > 0) { + url.searchParams.set('features', features.join(',')) + } + const mcpUrl = url.toString() + + let clientConfig: McpClientConfig = { + mcpServers: { + supabase: { + url: mcpUrl, + }, + }, + } + // Apply client-specific transformation if available + if (selectedClient?.transformConfig) { + clientConfig = selectedClient.transformConfig(clientConfig) + } + + return { + mcpUrl, + clientConfig, + } +} + +/** + * Assembles base `/mcp` endpoint URL for the given environment + */ +function getMcpUrlBase({ isPlatform, apiUrl }: { isPlatform: boolean; apiUrl?: string }) { + // Hosted platform uses environment variable with fallback + if (isPlatform) { + return process.env.NEXT_PUBLIC_MCP_URL ?? DEFAULT_MCP_URL_PLATFORM + } + + // Self-hosted uses API URL with fallback + return apiUrl ? `${apiUrl}/mcp` : DEFAULT_MCP_URL_NON_PLATFORM +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 78798e906acf3..397ad1df060e7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -26,3 +26,6 @@ minimumReleaseAgeExclude: - '@ai-sdk/*' - '@supabase/mcp-server-supabase' - '@supabase/mcp-utils' + - '@supabase/auth-js' + - '@supabase/supabase-js' + - '@supabase/realtime-js' diff --git a/turbo.json b/turbo.json index 028863f609c95..a7c33bf553390 100644 --- a/turbo.json +++ b/turbo.json @@ -78,6 +78,7 @@ "NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID", "NEXT_PUBLIC_VERCEL_ENV", "NEXT_PUBLIC_USERCENTRICS_RULESET_ID", + "NEXT_PUBLIC_MCP_URL", // These envs are technically passthrough env vars because they're only used on the server side of Nextjs "PLATFORM_PG_META_URL", "STUDIO_PG_META_URL",