diff --git a/apps/docs/components/MetricsStackCards.tsx b/apps/docs/components/MetricsStackCards.tsx index 19ba4b02edec9..3ee2cbfb0ec12 100644 --- a/apps/docs/components/MetricsStackCards.tsx +++ b/apps/docs/components/MetricsStackCards.tsx @@ -53,14 +53,11 @@ const metricsStackOptions: MetricsStackOption[] = [ Supabase alongside your app telemetry. ), - href: '/guides/telemetry/metrics/datadog', + href: 'https://docs.datadoghq.com/integrations/supabase/', icon: , iconColor: '#632CA6', iconBg: 'rgba(99,44,166,0.1)', - badges: [ - { label: 'Supabase guide', variant: 'default' }, - { label: 'Community', variant: 'community' }, - ], + badges: [{ label: 'Community', variant: 'community' }], }, { title: 'Vendor-agnostic / BYO Prometheus', diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 50330ec471144..ae10e996a5301 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -491,9 +491,13 @@ export const gettingstarted: NavMenuConstant = { url: '/guides/getting-started/ai-prompts' as `/${string}`, }, { - name: 'Model context protocol (MCP)', + name: 'Supabase MCP server', url: '/guides/getting-started/mcp' as `/${string}`, }, + { + name: 'Deploy MCP servers', + url: '/guides/getting-started/byom' as `/${string}`, + }, ], }, ], @@ -2769,7 +2773,7 @@ export const telemetry: NavMenuConstant = { }, { name: 'Datadog', - url: '/guides/telemetry/metrics/datadog' as `/${string}`, + url: 'https://docs.datadoghq.com/integrations/supabase/' as `/${string}`, }, { name: 'Vendor-agnostic setup', diff --git a/apps/docs/content/guides/getting-started/byom.mdx b/apps/docs/content/guides/getting-started/byom.mdx new file mode 100644 index 0000000000000..69e6de4d4c24e --- /dev/null +++ b/apps/docs/content/guides/getting-started/byom.mdx @@ -0,0 +1,265 @@ +--- +id: 'ai-tools-byom' +title: 'Deploy MCP servers' +description: 'Build and deploy remote MCP servers on Supabase Edge Functions' +subtitle: 'Deploy custom MCP servers on Supabase Edge Functions' +--- + +Deploy your [Model Context Protocol](https://modelcontextprotocol.io/specification/2025-11-25) (MCP) servers on Supabase take advantage of features like [Edge Functions](/docs/guides/functions), [OAuth](/docs/guides/auth/oauth-server), and scaling for AI applications. + +- Get started with [deploying](#deploy-your-mcp-server) MCP servers on Supabase + +## Deploy your MCP server + +### Prerequisites + +Before you begin, make sure you have: + +- [Docker](https://docs.docker.com/get-docker/) installed (required for local Supabase development) +- [Deno](https://deno.land/) installed (Supabase Edge Functions runtime) +- [Supabase CLI](/docs/guides/cli/getting-started) installed + +### Create a new project + +Start by creating a new Supabase project: + +```bash +mkdir my-mcp-server +cd my-mcp-server +supabase init +``` + +### Create the MCP server function + +Create a new Edge Function for your MCP server: + +```bash +supabase functions new simple-mcp-server +``` + +Create a `deno.json` file in `supabase/functions/simple-mcp-server/` with the required dependencies: + +```json +{ + "imports": { + "@hono/mcp": "npm:@hono/mcp@^0.1.1", + "@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.24.3", + "@modelcontextprotocol/sdk/": "npm:/@modelcontextprotocol/sdk@^1.24.3/", + "hono": "npm:hono@^4.9.2", + "zod": "npm:zod@^4.1.13" + } +} +``` + + + +This tutorial uses the [official MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk), but you can use any MCP framework that's compatible with the [Edge Runtime](/docs/guides/functions), such as [mcp-lite](https://github.com/fiberplane/mcp-lite), [mcp-use](https://github.com/mcp-use/mcp-use), or [mcp-handler](https://github.com/vercel/mcp-handler). + + + +Replace the contents of `supabase/functions/simple-mcp-server/index.ts` with: + +```ts +// Setup type definitions for built-in Supabase Runtime APIs +import 'jsr:@supabase/functions-js/edge-runtime.d.ts' + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { StreamableHTTPTransport } from '@hono/mcp' +import { Hono } from 'hono' +import { z } from 'zod' + +// Change this to your function name +const functionName = 'simple-mcp-server' +const app = new Hono().basePath(`/${functionName}`) + +// Create your MCP server +const server = new McpServer({ + name: 'simple-mcp-server', + version: '1.0.0', +}) + +// Register a simple addition tool +server.registerTool( + 'add', + { + title: 'Addition Tool', + description: 'Add two numbers together', + inputSchema: { a: z.number(), b: z.number() }, + }, + ({ a, b }) => ({ + content: [{ type: 'text', text: String(a + b) }], + }) +) + +// Handle MCP requests +app.all('/mcp', async (c) => { + const transport = new StreamableHTTPTransport() + await server.connect(transport) + return transport.handleRequest(c) +}) + +Deno.serve(app.fetch) +``` + +### Understanding the code + +The MCP server implementation uses several key components: + +**Hono routing**: Supabase Edge Functions route all requests to `//*`. The Hono app uses `basePath` to handle this: + +```ts +const functionName = 'simple-mcp-server' +const app = new Hono().basePath(`/${functionName}`) +``` + +**MCP server setup**: The `McpServer` class from the official SDK handles the MCP protocol: + +```ts +const server = new McpServer({ + name: 'simple-mcp-server', + version: '1.0.0', +}) +``` + +**Tool registration**: Tools are registered with a name, metadata, input schema (using Zod), and a handler function: + +```ts +server.registerTool( + 'add', + { + title: 'Addition Tool', + description: 'Add two numbers together', + inputSchema: { a: z.number(), b: z.number() }, + }, + ({ a, b }) => ({ + content: [{ type: 'text', text: String(a + b) }], + }) +) +``` + +**HTTP transport**: The `StreamableHTTPTransport` from `@hono/mcp` connects your MCP server to HTTP requests: + +```ts +app.all('/mcp', async (c) => { + const transport = new StreamableHTTPTransport() + await server.connect(transport) + return transport.handleRequest(c) +}) +``` + +### Local development + +Start the Supabase local development stack: + +```bash +supabase start +``` + +In a separate terminal, serve your function: + +```bash +supabase functions serve --no-verify-jwt simple-mcp-server +``` + +Your MCP server is now running at: + +``` +http://localhost:54321/functions/v1/simple-mcp-server/mcp +``` + + + +The `--no-verify-jwt` flag disables JWT verification at the Edge Function layer. This is required because MCP authentication is handled by the MCP server itself, not by Supabase's standard JWT validation. + + + +### Test your MCP server + +Test your server with the official [MCP Inspector](https://github.com/modelcontextprotocol/inspector): + +```bash +npx -y @modelcontextprotocol/inspector +``` + +Enter your MCP endpoint URL in the inspector UI to explore available tools and test them interactively. + +### Deploy to production + +When you're ready to deploy, link your project and deploy the function: + +```bash +supabase link --project-ref +supabase functions deploy --no-verify-jwt simple-mcp-server +``` + +Your MCP server will be available at: + +``` +https://.supabase.co/functions/v1/simple-mcp-server/mcp +``` + +Update your MCP client configuration to use the production URL. + +## Adding more tools + +Extend your MCP server by registering additional tools. Here's an example that queries your Supabase database: + +```ts +import { createClient } from 'jsr:@supabase/supabase-js@2' + +// Create Supabase client +const supabase = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! +) + +server.registerTool( + 'list_users', + { + title: 'List Users', + description: 'Get a list of users from the database', + inputSchema: { limit: z.number().optional().default(10) }, + }, + async ({ limit }) => { + const { data, error } = await supabase + .from('users') + .select('id, email, created_at') + .limit(limit) + + if (error) { + return { + content: [{ type: 'text', text: `Error: ${error.message}` }], + isError: true, + } + } + + return { + content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], + } + } +) +``` + +## Add authentication + + + +MCP authentication is not yet supported on Edge Functions. For now, MCP servers deployed on Supabase Edge Functions are publicly accessible. + + + +## Examples + +You can find ready-to-use MCP server implementations here: + +- [MCP server examples on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/mcp/) + +## Resources + +- [Model Context Protocol Specification](https://modelcontextprotocol.io/specification/2025-11-25) +- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) +- [Supabase Edge Functions](/docs/guides/functions) +- [OAuth 2.1 Server](/docs/guides/auth/oauth-server) +- [MCP Authentication](/docs/guides/auth/oauth-server/mcp-authentication) +- [MCP server examples on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/mcp) +- [Building MCP servers with mcp-lite](/docs/guides/functions/examples/mcp-server-mcp-lite) - Alternative lightweight framework diff --git a/apps/docs/content/guides/platform/upgrading.mdx b/apps/docs/content/guides/platform/upgrading.mdx index 06dc63387e629..22567b873ddad 100644 --- a/apps/docs/content/guides/platform/upgrading.mdx +++ b/apps/docs/content/guides/platform/upgrading.mdx @@ -85,7 +85,10 @@ During the 90-day restore window a paused project can be restored to the platfor src="/docs/img/guides/platform/paused-90-day.png" /> -After the 90-day restore window, you can download your project's backup file, and Storage objects from the project dashboard. See [restoring a backup locally](/docs/guides/local-development/restoring-downloaded-backup) for instructions on how to load that backup locally to recover your data. +After the 90-day restore window, you can download your project's backup file, and Storage objects from the project dashboard. You can restore the data in the following ways: + +- [Restore a backup to a new Supabase project](/docs/guides/platform/migrating-within-supabase/dashboard-restore) +- [Restore a backup locally](/docs/guides/local-development/restoring-downloaded-backup) diff --git a/apps/docs/content/guides/telemetry/metrics/datadog.mdx b/apps/docs/content/guides/telemetry/metrics/datadog.mdx deleted file mode 100644 index 8bcf3628ea17b..0000000000000 --- a/apps/docs/content/guides/telemetry/metrics/datadog.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -id: 'metrics-datadog' -title: 'Metrics API with Datadog' -description: 'Scrape Supabase metrics with the Datadog Agent or Prometheus integrations' ---- - -Datadog can ingest the Supabase Metrics API through its [OpenMetrics integration](https://docs.datadoghq.com/integrations/openmetrics/) or by proxying Prometheus data into Datadog’s time-series store. This guide covers both options so you can monitor database health alongside application metrics, logs, and traces in one place. - - - -Datadog publishes a [Supabase integration reference](https://docs.datadoghq.com/integrations/supabase/) that uses the same Metrics API outlined here. That integration is managed by Datadog and is not officially supported by Supabase. - - - -## Prerequisites - -- Supabase service role key (or another secret key) with Metrics API access. -- Datadog API/APP keys (for SaaS) and the Datadog Agent installed wherever you plan to run the scraper. - -<$Partial path="metrics_access.mdx" /> - -## Option A: Datadog agent + OpenMetrics check - -1. Install the Datadog Agent on a host that can reach `https://.supabase.co`. -2. Enable the OpenMetrics integration by creating `openmetrics.d/conf.yaml` (Linux path: `/etc/datadog-agent/conf.d/openmetrics.d/conf.yaml`): - -```yaml -init_config: - -instances: - - openmetrics_endpoint: https://.supabase.co/customer/v1/privileged/metrics - namespace: supabase - metrics: - - 'supabase_*' - auth_type: basic - username: service_role - password: { { env "SUPABASE_SERVICE_ROLE_KEY" } } - ssl_verify: true - prometheus_timeout: 30 - labels_mapper: - project: project_ref -``` - -3. Restart the Datadog Agent (`sudo systemctl restart datadog-agent`). -4. In Datadog, search for metrics with the `supabase.` prefix. Create dashboards or notebooks to visualize CPU, IO, replication, and connection metrics. - -### Tuning tips - -- Set `min_collection_interval: 60` to match Supabase’s one-minute refresh cadence. -- Use `labels_mapper` to rename high-cardinality labels into shorter Datadog tag keys. -- If you monitor multiple projects, duplicate the instance block with a unique `namespace` and tag each project (`tags: ["supabase_project:"]`). - -## Option B: Prometheus remote write into Datadog - -If you already run [Prometheus](https://prometheus.io/docs/prometheus/latest/installation/) (self-hosted or managed), push scraped metrics into Datadog using the [Prometheus Remote Write integration](https://docs.datadoghq.com/integrations/guide/prometheus_remote_write/): - -1. Configure Prometheus with the Supabase scrape job (see the [self-hosted guide](/guides/telemetry/metrics/grafana-self-hosted#1-deploy-prometheus)). -2. Add a `remote_write` section to `prometheus.yml` that targets Datadog’s endpoint: - -```yaml -remote_write: - - url: https://api.datadoghq.com/api/v1/prometheus/write?api_key= - write_relabel_configs: - - source_labels: [job] - target_label: datadog_namespace - replacement: supabase -``` - -3. (Optional) Use [Datadog’s Prometheus mapping profiles](https://docs.datadoghq.com/integrations/guide/prometheus-metrics-map/) to convert metric/label names into Datadog-friendly conventions. - -## Dashboards and alerts - -- Use the Datadog Dashboard Gallery to build widgets for CPU usage, tuple IO, replication lag, and connection saturation. -- Create monitors (for example, `avg(last_5m):supabase_pg_stat_database_xact_commit{project:} > 5e5`) to detect runaway workloads. -- Tag metrics with `env`, `service`, or `team` so you can slice Supabase telemetry the same way you slice application telemetry. - -## Troubleshooting - -- **401 errors:** rotate the service role key in Supabase and update the Datadog credentials. -- **SSL errors:** ensure the host trusts the standard certificate authorities used by `*.supabase.co`. -- **High cardinality warnings:** filter out labels such as `datname` or `application_name` if you only need aggregate views. - -[← Back to the Metrics API landing](/guides/telemetry/metrics) diff --git a/apps/docs/features/ui/CodeBlock/CodeBlock.client.tsx b/apps/docs/features/ui/CodeBlock/CodeBlock.client.tsx index 9b3ca95161f1f..a075ae9f2e080 100644 --- a/apps/docs/features/ui/CodeBlock/CodeBlock.client.tsx +++ b/apps/docs/features/ui/CodeBlock/CodeBlock.client.tsx @@ -1,10 +1,11 @@ 'use client' -import { Check, Copy } from 'lucide-react' -import { type MouseEvent, useCallback, useEffect, useState } from 'react' +import { Check, Copy, WrapText, ArrowRightFromLine } from 'lucide-react' +import { type MouseEvent, useCallback, useEffect, useState, useRef } from 'react' import { type ThemedToken } from 'shiki' import { type NodeHover } from 'twoslash' import { cn, copyToClipboard, Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { getFontStyle } from './CodeBlock.utils' export function AnnotatedSpan({ token, @@ -113,3 +114,46 @@ export function CodeCopyButton({ className, content }: { className?: string; con ) } + +export function CodeBlockControls({ content }: { content: string }) { + const [isWrapped, setIsWrapped] = useState(false) + const wrapperRef = useRef(null) + + const toggleWrap = useCallback(() => { + setIsWrapped((prev) => { + const newValue = !prev + // Find the parent code block and toggle the wrap data attribute + const codeBlock = wrapperRef.current?.closest('.shiki') + if (codeBlock) { + if (newValue) { + codeBlock.setAttribute('data-wrapped', 'true') + } else { + codeBlock.removeAttribute('data-wrapped') + } + } + return newValue + }) + }, []) + + return ( +
+ + + + + {isWrapped ? 'Disable word wrap' : 'Enable word wrap'} + + +
+ ) +} diff --git a/apps/docs/features/ui/CodeBlock/CodeBlock.tsx b/apps/docs/features/ui/CodeBlock/CodeBlock.tsx index 518171367c01e..c79c416b9a620 100644 --- a/apps/docs/features/ui/CodeBlock/CodeBlock.tsx +++ b/apps/docs/features/ui/CodeBlock/CodeBlock.tsx @@ -1,9 +1,9 @@ -import { type PropsWithChildren } from 'react' +import { Fragment, type PropsWithChildren } from 'react' import { bundledLanguages, createHighlighter, type BundledLanguage, type ThemedToken } from 'shiki' import { createTwoslasher, type ExtraFiles, type NodeHover } from 'twoslash' import { cn } from 'ui' -import { AnnotatedSpan, CodeCopyButton } from './CodeBlock.client' +import { AnnotatedSpan, CodeBlockControls } from './CodeBlock.client' import { getFontStyle } from './CodeBlock.utils' import theme from './supabase-2.json' with { type: 'json' } import denoTypes from './types/lib.deno.d.ts.include' @@ -72,27 +72,42 @@ export async function CodeBlock({ )} >
-        
-          {lineNumbers && (
-            
- {tokens.map((_, idx) => ( -
- {idx + 1} -
+ + {lineNumbers ? ( + <> + {tokens.map((line, idx) => ( + +
+ {idx + 1} +
+
+ +
+
+ ))} + + ) : ( +
+ {tokens.map((line, idx) => ( + ))}
)} -
- {tokens.map((line, idx) => ( - - ))} -
- + ) } @@ -112,7 +127,7 @@ function CodeLine({ }) return ( - + {tokens.map((token) => twoslash?.has(token.offset) ? (