{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "headless-app-tanstack",
  "title": "Headless App for TanStack Start",
  "description": "A backend with an agent as the primary interface, combining auth, OAuth consent, and an MCP server.",
  "dependencies": [
    "@supabase/ssr@latest",
    "@supabase/supabase-js@latest"
  ],
  "registryDependencies": [
    "button",
    "https://supabase.com/library/r/password-based-auth-tanstack.json",
    "https://supabase.com/library/r/oauth-consent-tanstack.json"
  ],
  "files": [
    {
      "path": "registry/default/blocks/headless-app-tanstack/supabase/functions/mcp/tools/index.ts",
      "content": "import type { McpServer } from 'npm:@modelcontextprotocol/server@2.0.0'\n\nimport { registerTaskTools } from './tasks.ts'\nimport type { ToolContext } from './types.ts'\nimport { registerWhoamiTool } from './whoami.ts'\n\nexport type { ToolContext } from './types.ts'\n\n// Add your product's tool modules here. The shared MCP runtime supplies the\n// authenticated context for each request.\nexport function registerTools(server: McpServer, context: ToolContext): void {\n  registerWhoamiTool(server, context)\n  registerTaskTools(server, context)\n}\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/tools/index.ts"
    },
    {
      "path": "registry/default/blocks/headless-app-tanstack/supabase/functions/mcp/tools/tasks.ts",
      "content": "import type { McpServer } from 'npm:@modelcontextprotocol/server@2.0.0'\nimport { z } from 'npm:zod@4.4.3'\n\nimport { errorResult, jsonResult, runtimeErrorResult } from './result.ts'\nimport type { ToolContext } from './types.ts'\n\nconst taskFields = 'id, title, closed, created_at'\nconst taskId = z.uuid().describe('The task ID returned by list_tasks or create_task.')\nconst taskTitle = z\n  .string()\n  .trim()\n  .min(1)\n  .max(200)\n  .describe('A task title, 1–200 characters after trimming surrounding whitespace.')\nconst taskNotFound = 'Task not found or you do not have access.'\n\n// Use only the caller's client. Ownership comes from auth.uid() in the schema,\n// and RLS applies to reads and writes, including queries by a supplied task ID.\nexport function registerTaskTools(server: McpServer, { supabase }: ToolContext): void {\n  server.registerTool(\n    'list_tasks',\n    {\n      description:\n        'List your tasks, newest first. Optionally filter by closed status. Pass next_offset as offset to fetch another page; null means there are no more tasks.',\n      inputSchema: z.strictObject({\n        closed: z.boolean().optional().describe('False for open tasks, true for closed tasks.'),\n        limit: z.int().min(1).max(100).default(20).describe('Maximum tasks per page (1–100).'),\n        offset: z.int().min(0).default(0).describe('Number of tasks to skip.'),\n      }),\n      annotations: {\n        readOnlyHint: true,\n        destructiveHint: false,\n        idempotentHint: true,\n        openWorldHint: false,\n      },\n    },\n    async ({ closed, limit, offset }) => {\n      try {\n        let query = supabase\n          .from('tasks')\n          .select(taskFields)\n          .order('created_at', { ascending: false })\n          .order('id', { ascending: false })\n          .range(offset, offset + limit)\n\n        if (closed !== undefined) query = query.eq('closed', closed)\n\n        const { data } = await query.throwOnError()\n        return jsonResult({\n          tasks: data.slice(0, limit),\n          next_offset: data.length > limit ? offset + limit : null,\n        })\n      } catch (error) {\n        return runtimeErrorResult(error)\n      }\n    }\n  )\n\n  server.registerTool(\n    'create_task',\n    {\n      description:\n        'Create an open task for yourself and return it. Each call creates a new task; do not retry blindly after a connection failure.',\n      inputSchema: z.strictObject({ title: taskTitle }),\n      annotations: {\n        readOnlyHint: false,\n        destructiveHint: false,\n        idempotentHint: false,\n        openWorldHint: false,\n      },\n    },\n    async ({ title }) => {\n      try {\n        const { data } = await supabase\n          .from('tasks')\n          .insert({ title })\n          .select(taskFields)\n          .single()\n          .throwOnError()\n\n        return jsonResult({ task: data })\n      } catch (error) {\n        return runtimeErrorResult(error)\n      }\n    }\n  )\n\n  server.registerTool(\n    'update_task',\n    {\n      description:\n        'Rename, close, or reopen one of your tasks and return it. Supply title, closed, or both. Fields you omit keep their current values.',\n      inputSchema: z\n        .strictObject({\n          id: taskId,\n          title: taskTitle.optional(),\n          closed: z.boolean().optional().describe('True to close the task; false to reopen it.'),\n        })\n        .refine(({ title, closed }) => title !== undefined || closed !== undefined, {\n          message: 'Supply title or closed to update a task.',\n        }),\n      annotations: {\n        readOnlyHint: false,\n        destructiveHint: true,\n        idempotentHint: true,\n        openWorldHint: false,\n      },\n    },\n    async ({ id, title, closed }) => {\n      try {\n        const changes = {\n          ...(title !== undefined ? { title } : {}),\n          ...(closed !== undefined ? { closed } : {}),\n        }\n        const { data } = await supabase\n          .from('tasks')\n          .update(changes)\n          .eq('id', id)\n          .select(taskFields)\n          .maybeSingle()\n          .throwOnError()\n\n        return data ? jsonResult({ task: data }) : errorResult(taskNotFound)\n      } catch (error) {\n        return runtimeErrorResult(error)\n      }\n    }\n  )\n\n  server.registerTool(\n    'delete_task',\n    {\n      description:\n        'Permanently delete one of your tasks by ID. Use update_task with closed: true to keep a completed task instead.',\n      inputSchema: z.strictObject({ id: taskId }),\n      annotations: {\n        readOnlyHint: false,\n        destructiveHint: true,\n        idempotentHint: true,\n        openWorldHint: false,\n      },\n    },\n    async ({ id }) => {\n      try {\n        const { data } = await supabase\n          .from('tasks')\n          .delete()\n          .eq('id', id)\n          .select('id')\n          .maybeSingle()\n          .throwOnError()\n\n        return data ? jsonResult({ deleted: true, id: data.id }) : errorResult(taskNotFound)\n      } catch (error) {\n        return runtimeErrorResult(error)\n      }\n    }\n  )\n}\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/tools/tasks.ts"
    },
    {
      "path": "registry/default/blocks/headless-app-tanstack/routes/_protected/agents.tsx",
      "content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createFileRoute } from '@tanstack/react-router'\n\nimport { ConnectedAgents } from '@/registry/default/blocks/headless-app-tanstack/components/connected-agents'\n\nconst PRODUCT_NAME = import.meta.env.VITE_PRODUCT_NAME?.trim() || 'Your product'\nconst MCP_SERVER_URL = `${import.meta.env.VITE_SUPABASE_URL}/functions/v1/mcp`\n\n// Nested under _protected, so the layout route redirects signed-out visitors\n// before this renders.\nexport const Route = createFileRoute('/_protected/agents')({\n  component: AgentsPage,\n})\n\nfunction AgentsPage() {\n  return (\n    <main className=\"flex min-h-svh items-center justify-center p-6 md:p-10\">\n      <ConnectedAgents\n        className=\"w-full max-w-lg\"\n        mcpServerUrl={MCP_SERVER_URL}\n        productName={PRODUCT_NAME}\n      />\n    </main>\n  )\n}\n",
      "type": "registry:file",
      "target": "routes/_protected/agents.tsx"
    },
    {
      "path": "registry/default/blocks/headless-app-tanstack/components/connected-agents.tsx",
      "content": "import { Check, Copy } from 'lucide-react'\nimport { useEffect, useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport {\n  useOAuthGrants,\n  type OAuthGrant,\n} from '@/registry/default/blocks/headless-app-tanstack/hooks/use-oauth-grants'\nimport { Button } from '@/registry/default/components/ui/button'\n\nconst buildPrompt = (productName: string, mcpServerUrl: string) =>\n  `Connect to ${productName} using this MCP server:\\n\\n${mcpServerUrl}\\n\\nUse your MCP connection setup to authorize access in my browser. Then call whoami to verify the connection and list the available tools.`\n\nconst formatDate = (value: string) =>\n  new Date(value).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })\n\nfunction ConnectAgent({\n  productName,\n  mcpServerUrl,\n}: {\n  productName: string\n  mcpServerUrl: string\n}) {\n  const [copied, setCopied] = useState<'url' | 'prompt' | null>(null)\n  const [error, setError] = useState<string | null>(null)\n\n  useEffect(() => {\n    if (!copied) return\n    const timeout = window.setTimeout(() => setCopied(null), 2000)\n    return () => window.clearTimeout(timeout)\n  }, [copied])\n\n  const copy = async (type: 'url' | 'prompt') => {\n    setError(null)\n    try {\n      await navigator.clipboard.writeText(\n        type === 'url' ? mcpServerUrl : buildPrompt(productName, mcpServerUrl)\n      )\n      setCopied(type)\n    } catch {\n      setCopied(null)\n      setError('Unable to copy. Select and copy the server URL above.')\n    }\n  }\n\n  return (\n    <section className=\"flex flex-col gap-3\" aria-label=\"Connect an agent\">\n      <h2 className=\"font-medium\">Connect an agent</h2>\n      <p className=\"text-sm text-muted-foreground\">\n        Add this server URL in your agent’s MCP settings, then sign in and approve access.\n      </p>\n      <div className=\"overflow-hidden rounded-lg border bg-muted\">\n        <pre className=\"overflow-x-auto whitespace-pre-wrap break-all p-4 font-mono text-sm\">\n          {mcpServerUrl}\n        </pre>\n        <div className=\"flex flex-wrap justify-end gap-2 border-t p-2\">\n          <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => void copy('prompt')}>\n            {copied === 'prompt' ? 'Prompt copied' : 'Copy prompt'}\n          </Button>\n          <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => void copy('url')}>\n            {copied === 'url' ? <Check className=\"size-4\" /> : <Copy className=\"size-4\" />}\n            {copied === 'url' ? 'URL copied' : 'Copy URL'}\n          </Button>\n        </div>\n      </div>\n      <p className=\"text-xs text-muted-foreground\">\n        Use the prompt if your agent supports adding MCP servers through chat.\n      </p>\n      {/* Always mounted so screen readers announce the change. */}\n      <span role=\"status\" className=\"sr-only\">\n        {copied ? 'Copied to clipboard' : ''}\n      </span>\n      {error && (\n        <p role=\"alert\" className=\"text-sm text-destructive\">\n          {error}\n        </p>\n      )}\n    </section>\n  )\n}\n\nfunction GrantRow({\n  grant,\n  isRevoking,\n  disabled,\n  onRevoke,\n}: {\n  grant: OAuthGrant\n  isRevoking: boolean\n  disabled: boolean\n  onRevoke: () => void\n}) {\n  return (\n    <li className=\"flex items-center gap-4 p-4\">\n      <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n        <span className=\"truncate font-medium\">{grant.client.name}</span>\n        <span className=\"truncate text-muted-foreground\">\n          Authorized {formatDate(grant.granted_at)}\n          {grant.scopes.length > 0 && ` · ${grant.scopes.join(', ')}`}\n        </span>\n      </div>\n      <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={disabled} onClick={onRevoke}>\n        {isRevoking ? 'Revoking access...' : 'Revoke access'}\n      </Button>\n    </li>\n  )\n}\n\nexport interface ConnectedAgentsViewProps extends React.ComponentPropsWithoutRef<'div'> {\n  mcpServerUrl: string\n  productName?: string\n  grants?: OAuthGrant[] | null\n  isLoading?: boolean\n  error?: string | null\n  revokingClientId?: string | null\n  onRefresh?: () => void\n  onRevoke?: (clientId: string) => void\n}\n\nexport function ConnectedAgentsView({\n  mcpServerUrl,\n  productName = 'this app',\n  grants = null,\n  isLoading = false,\n  error = null,\n  revokingClientId = null,\n  onRefresh,\n  onRevoke,\n  className,\n  ...props\n}: ConnectedAgentsViewProps) {\n  return (\n    <div className={cn('flex flex-col gap-6', className)} {...props}>\n      <div className=\"flex flex-col space-y-1.5\">\n        <h1 className=\"text-2xl font-semibold leading-none tracking-tight\">Connected agents</h1>\n        <p className=\"text-sm text-muted-foreground\">\n          Authorize agents to use {productName} on your behalf.\n        </p>\n      </div>\n\n      <ConnectAgent productName={productName} mcpServerUrl={mcpServerUrl} />\n\n      <section className=\"flex flex-col gap-3\" aria-label=\"Authorized agents\">\n        <div className=\"flex items-center justify-between gap-4\">\n          <h2 className=\"font-medium\">Authorized agents</h2>\n          {onRefresh && (\n            <Button\n              type=\"button\"\n              size=\"sm\"\n              variant=\"outline\"\n              disabled={isLoading || revokingClientId !== null}\n              onClick={onRefresh}\n            >\n              {isLoading ? 'Refreshing...' : 'Refresh'}\n            </Button>\n          )}\n        </div>\n        {/* Always mounted so screen readers announce the change. */}\n        <p role=\"status\" className=\"text-sm text-muted-foreground\">\n          {isLoading ? 'Loading connected agents...' : ''}\n        </p>\n        {grants && grants.length > 0 && (\n          <ul className=\"divide-y rounded-lg border bg-muted text-sm\">\n            {grants.map((grant) => (\n              <GrantRow\n                key={grant.client.id}\n                grant={grant}\n                isRevoking={revokingClientId === grant.client.id}\n                disabled={isLoading || revokingClientId !== null || !onRevoke}\n                onRevoke={() => onRevoke?.(grant.client.id)}\n              />\n            ))}\n          </ul>\n        )}\n        {!isLoading && !error && grants?.length === 0 && (\n          <p className=\"text-sm text-muted-foreground\">\n            No agents authorized yet. Connect an agent using the server URL above.\n          </p>\n        )}\n        {error && (\n          <p role=\"alert\" className=\"text-sm text-destructive\">\n            {error}\n          </p>\n        )}\n        {grants && grants.length > 0 && (\n          <p className=\"text-xs text-muted-foreground\">\n            Revoking access prevents an agent from renewing its session. Its current access token\n            may work until it expires.\n          </p>\n        )}\n      </section>\n    </div>\n  )\n}\n\ninterface ConnectedAgentsProps extends React.ComponentPropsWithoutRef<'div'> {\n  mcpServerUrl: string\n  productName?: string\n}\n\nexport function ConnectedAgents(props: ConnectedAgentsProps) {\n  const { grants, error, isLoading, revokingClientId, refresh, revoke } = useOAuthGrants()\n\n  return (\n    <ConnectedAgentsView\n      grants={grants}\n      error={error}\n      isLoading={isLoading}\n      revokingClientId={revokingClientId}\n      onRefresh={() => void refresh()}\n      onRevoke={(clientId) => void revoke(clientId)}\n      {...props}\n    />\n  )\n}\n",
      "type": "registry:component"
    },
    {
      "path": "registry/default/blocks/headless-app-tanstack/hooks/use-oauth-grants.ts",
      "content": "import type { OAuthGrant } from '@supabase/supabase-js'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\n\n// Revocation invalidates refresh tokens. Already-issued access tokens can still\n// be accepted by the MCP server until they expire.\nconst useOAuthGrants = () => {\n  const [grants, setGrants] = useState<OAuthGrant[] | null>(null)\n  const [error, setError] = useState<string | null>(null)\n  const [isLoading, setIsLoading] = useState(true)\n  const [revokingClientId, setRevokingClientId] = useState<string | null>(null)\n  const mounted = useRef(false)\n  const requestId = useRef(0)\n  const isRevoking = useRef(false)\n\n  const refresh = useCallback(async () => {\n    if (!mounted.current || isRevoking.current) return\n    const id = ++requestId.current\n    setIsLoading(true)\n    setError(null)\n\n    try {\n      const { data, error } = await createClient().auth.oauth.listGrants()\n      if (error) throw error\n      if (mounted.current && id === requestId.current) setGrants(data)\n    } catch (error) {\n      if (mounted.current && id === requestId.current) {\n        setError(\n          `Unable to load connected agents. ${error instanceof Error ? error.message : 'Try refreshing the list.'}`\n        )\n      }\n    } finally {\n      if (mounted.current && id === requestId.current) setIsLoading(false)\n    }\n  }, [])\n\n  useEffect(() => {\n    mounted.current = true\n    const onFocus = () => void refresh()\n    void refresh()\n    window.addEventListener('focus', onFocus)\n    return () => {\n      mounted.current = false\n      window.removeEventListener('focus', onFocus)\n    }\n  }, [refresh])\n\n  const revoke = useCallback(async (clientId: string) => {\n    if (!mounted.current || isRevoking.current) return\n    isRevoking.current = true\n    // A list request started before revocation must not restore the removed grant.\n    const id = ++requestId.current\n    setIsLoading(false)\n    setRevokingClientId(clientId)\n    setError(null)\n\n    try {\n      const { error } = await createClient().auth.oauth.revokeGrant({ clientId })\n      if (error) throw error\n      if (mounted.current && id === requestId.current) {\n        setGrants((current) => current?.filter((grant) => grant.client.id !== clientId) ?? null)\n      }\n    } catch (error) {\n      if (mounted.current && id === requestId.current) {\n        setError(\n          `Unable to revoke access. ${error instanceof Error ? error.message : 'Try revoking access again.'}`\n        )\n      }\n    } finally {\n      isRevoking.current = false\n      if (mounted.current && id === requestId.current) setRevokingClientId(null)\n    }\n  }, [])\n\n  return { grants, error, isLoading, revokingClientId, refresh, revoke }\n}\n\ntype UseOAuthGrantsReturn = ReturnType<typeof useOAuthGrants>\n\nexport { useOAuthGrants, type OAuthGrant, type UseOAuthGrantsReturn }\n",
      "type": "registry:hook"
    },
    {
      "path": "registry/default/blocks/headless-app-tanstack/supabase/config.toml",
      "content": "# Supabase configuration for a headless app: password auth for the product\n# session, an OAuth 2.1 server for external agents, and the MCP server that both\n# call. Values below are for local development. Set production Auth URLs and\n# enable email confirmations before running `supabase config push`.\n\nproject_id = \"headless-app\"\n\n[api]\nenabled = true\nport = 54321\nschemas = [\"public\", \"graphql_public\"]\nextra_search_path = [\"public\", \"extensions\"]\nmax_rows = 1000\n\n[db]\nport = 54322\n# Used to diff ./schemas into a migration.\nshadow_port = 54320\nmajor_version = 17\n\n# One directory per schema, then one file per object, in dependency order.\n[db.migrations]\nschema_paths = [\n    \"./schemas/*/tables/*.sql\",\n    \"./schemas/*/views/*.sql\",\n    \"./schemas/*/functions/*.sql\",\n]\n\n[studio]\nenabled = true\nport = 54323\n\n[inbucket]\nenabled = true\nport = 54324\n\n[auth]\nenabled = true\n# Origin that serves this app, including /oauth/consent. Use HTTPS in production.\nsite_url = \"http://localhost:3000\"\nadditional_redirect_urls = [\"http://localhost:3000/**\"]\njwt_expiry = 3600\nenable_signup = true\n\n[auth.email]\nenable_signup = true\n# Confirmations are off so local sign-ups can reach the app immediately.\nenable_confirmations = false\n\n# External MCP clients authorize here. Auth sends the user to\n# authorization_url_path, which the OAuth Consent block serves.\n[auth.oauth_server]\nenabled = true\nauthorization_url_path = \"/oauth/consent\"\n# Lets any compatible client register itself. Set to false to register clients\n# yourself.\nallow_dynamic_registration = true\n\n# The MCP server verifies user access tokens itself, so skip the gateway check.\n[functions.mcp]\nverify_jwt = false\n",
      "type": "registry:file",
      "target": "~/supabase/config.toml"
    },
    {
      "path": "registry/default/blocks/headless-app-tanstack/supabase/schemas/public/tables/tasks.sql",
      "content": "-- Declarative schema. This file is the source of truth for the tasks table:\n-- edit it, then run `supabase db diff -f <name>` to generate the migration.\n-- Changes made in Studio or the SQL editor are not picked up by the diff.\n\ncreate table public.tasks (\n  id uuid primary key default gen_random_uuid(),\n  user_id uuid not null default auth.uid() references auth.users (id) on delete cascade,\n  title text not null constraint tasks_title_length check (\n    char_length(title) between 1 and 200 and title ~ '[^[:space:]]'\n  ),\n  closed boolean not null default false,\n  created_at timestamptz not null default now()\n);\n\n-- Support the ownership policies and the tools' newest-first ordering.\ncreate index tasks_user_id_created_at_id_idx on public.tasks (user_id, created_at desc, id desc);\n\n-- Agents call the MCP server with the user's access token, so every tool runs\n-- under these policies. A tool cannot reach another user's rows.\nalter table public.tasks enable row level security;\n\n-- Expose task operations to signed-in users even when automatic API grants are disabled.\ngrant select, insert, update, delete on table public.tasks to authenticated;\n\ncreate policy \"Users can view their own tasks\"\non public.tasks\nfor select\nto authenticated\nusing ((select auth.uid()) = user_id);\n\ncreate policy \"Users can create their own tasks\"\non public.tasks\nfor insert\nto authenticated\nwith check ((select auth.uid()) = user_id);\n\ncreate policy \"Users can update their own tasks\"\non public.tasks\nfor update\nto authenticated\nusing ((select auth.uid()) = user_id)\nwith check ((select auth.uid()) = user_id);\n\ncreate policy \"Users can delete their own tasks\"\non public.tasks\nfor delete\nto authenticated\nusing ((select auth.uid()) = user_id);\n",
      "type": "registry:file",
      "target": "~/supabase/schemas/public/tables/tasks.sql"
    },
    {
      "path": "registry/default/blocks/mcp/supabase/functions/mcp/index.ts",
      "content": "import 'jsr:@supabase/functions-js@2.108.2/edge-runtime.d.ts'\n\nimport { createMcpHandler, McpServer } from 'npm:@modelcontextprotocol/server@2.0.0'\nimport { pipeline } from 'npm:@supabase/middleware@1'\nimport {\n  withOAuthProtectedResource,\n  withSupabase,\n  type SupabaseContext,\n} from 'npm:@supabase/server@1'\n\nimport { registerTools, type ToolContext } from './tools/index.ts'\n\n// An MCP server as a single Supabase Edge Function, composed as a pipeline:\n//\n//   withOAuthProtectedResource  OAuth discovery for external MCP clients. Runs\n//                               before the auth gate so unauthenticated clients\n//                               can fetch the RFC 9728 metadata, and adds the\n//                               WWW-Authenticate challenge to the gate's 401.\n//   withSupabase                Verifies the user access token and builds an\n//                               RLS-scoped client, so both embedded product\n//                               agents and external OAuth clients act as the\n//                               signed-in user.\n//   handleMcp                   MCP transport and tools (./tools/index.ts).\n//\n// On Supabase Edge Functions the public URLs in the OAuth metadata are derived\n// automatically, locally and hosted. Off Edge Functions, pass `resourceServer`\n// and `authorizationServer` to withOAuthProtectedResource.\n\nfunction readTextEnv(name: string, fallback: string): string {\n  return Deno.env.get(name)?.trim() || fallback\n}\n\nconst SERVER_NAME = readTextEnv('MCP_SERVER_NAME', 'supabase-mcp')\nconst SERVER_DESCRIPTION = readTextEnv(\n  'MCP_SERVER_DESCRIPTION',\n  'MCP access to this Supabase project for the signed-in user.'\n)\n\nconst SERVER_INSTRUCTIONS =\n  `${SERVER_DESCRIPTION} ` +\n  'Every tool runs as the signed-in Supabase user, so role grants and Row Level Security apply. ' +\n  \"Call tools/list to discover what this project exposes, and read a tool's description and \" +\n  'annotations before calling it — some tools have side effects.'\n\nconst CORS_HEADERS: Record<string, string> = {\n  'Access-Control-Allow-Origin': '*',\n  'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',\n  'Access-Control-Allow-Headers':\n    'Authorization, Content-Type, Accept, Mcp-Protocol-Version, Mcp-Session-Id, Mcp-Method, Mcp-Name',\n  'Access-Control-Expose-Headers': 'WWW-Authenticate, Mcp-Session-Id',\n}\n\nfunction createServer(context: ToolContext): McpServer {\n  const server = new McpServer(\n    { name: SERVER_NAME, version: '1.0.0' },\n    { instructions: SERVER_INSTRUCTIONS }\n  )\n\n  registerTools(server, context)\n  return server\n}\n\nasync function handleMcp(request: Request, ctx: SupabaseContext): Promise<Response> {\n  // The server and its tools are bound to this caller for exactly one request.\n  const handler = createMcpHandler(\n    () =>\n      createServer({\n        supabase: ctx.supabase,\n        // auth: 'user' guarantees both claim shapes before this handler runs.\n        userClaims: ctx.userClaims!,\n        jwtClaims: ctx.jwtClaims!,\n      }),\n    { onerror: (error) => console.error('MCP request failed', error) }\n  )\n\n  return handler.fetch(request)\n}\n\n// The handler is passed inline so TypeScript infers its context from the entries.\n// Passing `handleMcp` directly collapses the inferred context to `object`.\nDeno.serve(\n  pipeline(\n    [withOAuthProtectedResource(), withSupabase({ auth: 'user', cors: { headers: CORS_HEADERS } })],\n    (request, ctx) => handleMcp(request, ctx)\n  )\n)\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/index.ts"
    },
    {
      "path": "registry/default/blocks/mcp/supabase/functions/mcp/deno.json",
      "content": "{\n  \"nodeModulesDir\": \"none\",\n  \"compilerOptions\": {\n    \"strict\": true\n  },\n  \"tasks\": {\n    \"check\": \"deno check index.ts\"\n  }\n}\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/deno.json"
    },
    {
      "path": "registry/default/blocks/mcp/supabase/functions/mcp/deno.lock",
      "content": "{\n  \"version\": \"5\",\n  \"specifiers\": {\n    \"jsr:@supabase/functions-js@2.108.2\": \"2.108.2\",\n    \"npm:@modelcontextprotocol/server@2.0.0\": \"2.0.0\",\n    \"npm:@supabase/middleware@1\": \"1.0.0\",\n    \"npm:@supabase/server@1\": \"1.9.0_@supabase+supabase-js@2.108.2\",\n    \"npm:@supabase/supabase-js@2.108.2\": \"2.108.2\",\n    \"npm:openai@^4.52.5\": \"4.104.0\"\n  },\n  \"jsr\": {\n    \"@supabase/functions-js@2.108.2\": {\n      \"integrity\": \"39665d68e1cb721b0714ed1f55c075fba16f8d992672b51458660b3c1ec77c8f\",\n      \"dependencies\": [\n        \"npm:openai\"\n      ]\n    }\n  },\n  \"npm\": {\n    \"@modelcontextprotocol/core@2.0.0\": {\n      \"integrity\": \"sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==\",\n      \"dependencies\": [\n        \"zod\"\n      ]\n    },\n    \"@modelcontextprotocol/server@2.0.0\": {\n      \"integrity\": \"sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==\",\n      \"dependencies\": [\n        \"@modelcontextprotocol/core\",\n        \"zod\"\n      ]\n    },\n    \"@supabase/auth-js@2.108.2\": {\n      \"integrity\": \"sha512-tNaQmBgodDZwgB40mRwVbxFy8IDYwjdpcZ0BYrWiwlULCSQoJj4QoG4zgJT7QRPXcqipefNOzvO/qAu4dF98ag==\",\n      \"dependencies\": [\n        \"tslib\"\n      ]\n    },\n    \"@supabase/functions-js@2.108.2\": {\n      \"integrity\": \"sha512-RNUX8EiBy3iLwAX19jtRzLyePnl11/fHcgwDHLnpKcDSXt/5qBnh3LUwAtIjT21Q66QsmNUR2esrHziLCpNubw==\",\n      \"dependencies\": [\n        \"tslib\"\n      ]\n    },\n    \"@supabase/middleware@1.0.0\": {\n      \"integrity\": \"sha512-ecA+z/o5E9sB+quuupGt8d7QjrnD4BBwZr+Qthm0uj6CtL/+yvAUTDlknhGuTbUidvx5CxBHJ1R3H96UjDpgyg==\",\n      \"dependencies\": [\n        \"std-env\"\n      ]\n    },\n    \"@supabase/phoenix@0.4.5\": {\n      \"integrity\": \"sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==\"\n    },\n    \"@supabase/postgrest-js@2.108.2\": {\n      \"integrity\": \"sha512-GQ28/Y8hk3CFmkb3kXH1h/AQx6JIYSQfO0CJMRVBcEKZoNy6C45cXAZ4fcJvRC5Id0cs6xnkUV0+c0rIocigsw==\",\n      \"dependencies\": [\n        \"tslib\"\n      ]\n    },\n    \"@supabase/realtime-js@2.108.2\": {\n      \"integrity\": \"sha512-aAGxCSUemZvQIibnCdvNvgaKib28I4rfrNjKbQ9cG1uBLwUsI7hVpGXgEbypCCDhLjQlDTAiJlu7rgljYUT73g==\",\n      \"dependencies\": [\n        \"@supabase/phoenix\",\n        \"tslib\"\n      ]\n    },\n    \"@supabase/server@1.9.0_@supabase+supabase-js@2.108.2\": {\n      \"integrity\": \"sha512-EhgfKFWjB6bCWy2UR2JdExnK+B8WBdwiG90+bLTt0GkdAuHZZSooNxOsn/fP3UlcXUNJ6xfAkYlvGzN8NUpz1w==\",\n      \"dependencies\": [\n        \"@supabase/middleware\",\n        \"@supabase/supabase-js\",\n        \"jose\"\n      ]\n    },\n    \"@supabase/storage-js@2.108.2\": {\n      \"integrity\": \"sha512-TVZPQxXGxY2+A6yTtm77zUHsh70lBhYUEaJL8RQC+BghcX/ygiMG/rmXrNVBce30/WAeNPa8FiG8HbqlGeV05g==\",\n      \"dependencies\": [\n        \"iceberg-js\",\n        \"tslib\"\n      ]\n    },\n    \"@supabase/supabase-js@2.108.2\": {\n      \"integrity\": \"sha512-hFhnPveb5JQg4a0QYicM0swT253YHMdfeRAl2BKHOlI5VAzuHxUGSr8RbwNLYNPauWOgQMS1H8sz8bvYlgwUfQ==\",\n      \"dependencies\": [\n        \"@supabase/auth-js\",\n        \"@supabase/functions-js\",\n        \"@supabase/postgrest-js\",\n        \"@supabase/realtime-js\",\n        \"@supabase/storage-js\"\n      ]\n    },\n    \"@types/node-fetch@2.6.13\": {\n      \"integrity\": \"sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==\",\n      \"dependencies\": [\n        \"@types/node\",\n        \"form-data\"\n      ]\n    },\n    \"@types/node@18.19.130\": {\n      \"integrity\": \"sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==\",\n      \"dependencies\": [\n        \"undici-types\"\n      ]\n    },\n    \"abort-controller@3.0.0\": {\n      \"integrity\": \"sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==\",\n      \"dependencies\": [\n        \"event-target-shim\"\n      ]\n    },\n    \"agentkeepalive@4.6.0\": {\n      \"integrity\": \"sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==\",\n      \"dependencies\": [\n        \"humanize-ms\"\n      ]\n    },\n    \"asynckit@0.4.0\": {\n      \"integrity\": \"sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==\"\n    },\n    \"call-bind-apply-helpers@1.0.2\": {\n      \"integrity\": \"sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==\",\n      \"dependencies\": [\n        \"es-errors\",\n        \"function-bind\"\n      ]\n    },\n    \"combined-stream@1.0.8\": {\n      \"integrity\": \"sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==\",\n      \"dependencies\": [\n        \"delayed-stream\"\n      ]\n    },\n    \"delayed-stream@1.0.0\": {\n      \"integrity\": \"sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==\"\n    },\n    \"dunder-proto@1.0.1\": {\n      \"integrity\": \"sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==\",\n      \"dependencies\": [\n        \"call-bind-apply-helpers\",\n        \"es-errors\",\n        \"gopd\"\n      ]\n    },\n    \"es-define-property@1.0.1\": {\n      \"integrity\": \"sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==\"\n    },\n    \"es-errors@1.3.0\": {\n      \"integrity\": \"sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==\"\n    },\n    \"es-object-atoms@1.1.2\": {\n      \"integrity\": \"sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==\",\n      \"dependencies\": [\n        \"es-errors\"\n      ]\n    },\n    \"es-set-tostringtag@2.1.0\": {\n      \"integrity\": \"sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==\",\n      \"dependencies\": [\n        \"es-errors\",\n        \"get-intrinsic\",\n        \"has-tostringtag\",\n        \"hasown\"\n      ]\n    },\n    \"event-target-shim@5.0.1\": {\n      \"integrity\": \"sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==\"\n    },\n    \"form-data-encoder@1.7.2\": {\n      \"integrity\": \"sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==\"\n    },\n    \"form-data@4.0.6\": {\n      \"integrity\": \"sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==\",\n      \"dependencies\": [\n        \"asynckit\",\n        \"combined-stream\",\n        \"es-set-tostringtag\",\n        \"hasown\",\n        \"mime-types\"\n      ]\n    },\n    \"formdata-node@4.4.1\": {\n      \"integrity\": \"sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==\",\n      \"dependencies\": [\n        \"node-domexception\",\n        \"web-streams-polyfill\"\n      ]\n    },\n    \"function-bind@1.1.2\": {\n      \"integrity\": \"sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==\"\n    },\n    \"get-intrinsic@1.3.0\": {\n      \"integrity\": \"sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==\",\n      \"dependencies\": [\n        \"call-bind-apply-helpers\",\n        \"es-define-property\",\n        \"es-errors\",\n        \"es-object-atoms\",\n        \"function-bind\",\n        \"get-proto\",\n        \"gopd\",\n        \"has-symbols\",\n        \"hasown\",\n        \"math-intrinsics\"\n      ]\n    },\n    \"get-proto@1.0.1\": {\n      \"integrity\": \"sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==\",\n      \"dependencies\": [\n        \"dunder-proto\",\n        \"es-object-atoms\"\n      ]\n    },\n    \"gopd@1.2.0\": {\n      \"integrity\": \"sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==\"\n    },\n    \"has-symbols@1.1.0\": {\n      \"integrity\": \"sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==\"\n    },\n    \"has-tostringtag@1.0.2\": {\n      \"integrity\": \"sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==\",\n      \"dependencies\": [\n        \"has-symbols\"\n      ]\n    },\n    \"hasown@2.0.4\": {\n      \"integrity\": \"sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==\",\n      \"dependencies\": [\n        \"function-bind\"\n      ]\n    },\n    \"humanize-ms@1.2.1\": {\n      \"integrity\": \"sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==\",\n      \"dependencies\": [\n        \"ms\"\n      ]\n    },\n    \"iceberg-js@0.8.1\": {\n      \"integrity\": \"sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==\"\n    },\n    \"jose@6.2.12\": {\n      \"integrity\": \"sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==\"\n    },\n    \"math-intrinsics@1.1.0\": {\n      \"integrity\": \"sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==\"\n    },\n    \"mime-db@1.52.0\": {\n      \"integrity\": \"sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==\"\n    },\n    \"mime-types@2.1.35\": {\n      \"integrity\": \"sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==\",\n      \"dependencies\": [\n        \"mime-db\"\n      ]\n    },\n    \"ms@2.1.3\": {\n      \"integrity\": \"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"\n    },\n    \"node-domexception@1.0.0\": {\n      \"integrity\": \"sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==\",\n      \"deprecated\": true\n    },\n    \"node-fetch@2.7.0\": {\n      \"integrity\": \"sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==\",\n      \"dependencies\": [\n        \"whatwg-url\"\n      ]\n    },\n    \"openai@4.104.0\": {\n      \"integrity\": \"sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==\",\n      \"dependencies\": [\n        \"@types/node\",\n        \"@types/node-fetch\",\n        \"abort-controller\",\n        \"agentkeepalive\",\n        \"form-data-encoder\",\n        \"formdata-node\",\n        \"node-fetch\"\n      ],\n      \"bin\": true\n    },\n    \"std-env@4.2.0\": {\n      \"integrity\": \"sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==\"\n    },\n    \"tr46@0.0.3\": {\n      \"integrity\": \"sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==\"\n    },\n    \"tslib@2.8.1\": {\n      \"integrity\": \"sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==\"\n    },\n    \"undici-types@5.26.5\": {\n      \"integrity\": \"sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==\"\n    },\n    \"web-streams-polyfill@4.0.0-beta.3\": {\n      \"integrity\": \"sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==\"\n    },\n    \"webidl-conversions@3.0.1\": {\n      \"integrity\": \"sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==\"\n    },\n    \"whatwg-url@5.0.0\": {\n      \"integrity\": \"sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==\",\n      \"dependencies\": [\n        \"tr46\",\n        \"webidl-conversions\"\n      ]\n    },\n    \"zod@4.6.2\": {\n      \"integrity\": \"sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ==\"\n    }\n  }\n}\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/deno.lock"
    },
    {
      "path": "registry/default/blocks/mcp/supabase/functions/mcp/.env.example",
      "content": "# Copy this file to supabase/functions/.env before serving locally:\n#   cp supabase/functions/mcp/.env.example supabase/functions/.env\n#   supabase functions serve mcp --env-file supabase/functions/.env\n\n# Deploy these values after linking your project:\n#   supabase secrets set --env-file supabase/functions/.env\n# Supabase provides the project URL and API keys automatically.\n\n# Keep the protocol-level server name short and project-specific.\nMCP_SERVER_NAME=supabase-mcp\nMCP_SERVER_DESCRIPTION=\"MCP access to this Supabase project for the signed-in user.\"\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/.env.example"
    },
    {
      "path": "registry/default/blocks/mcp/supabase/functions/mcp/tools/types.ts",
      "content": "import type { SupabaseContext } from 'npm:@supabase/server@1'\nimport type { SupabaseClient } from 'npm:@supabase/supabase-js@2.108.2'\n\n// Only expose the user-scoped client and verified identity to tools. Keeping\n// supabaseAdmin out of this type makes bypassing RLS an explicit design choice.\nexport type ToolContext = {\n  supabase: SupabaseClient\n  userClaims: NonNullable<SupabaseContext['userClaims']>\n  jwtClaims: NonNullable<SupabaseContext['jwtClaims']>\n}\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/tools/types.ts"
    },
    {
      "path": "registry/default/blocks/mcp/supabase/functions/mcp/tools/result.ts",
      "content": "import type { CallToolResult } from 'npm:@modelcontextprotocol/server@2.0.0'\n\n// Shared helpers for building MCP tool results, so every tool returns the same\n// shape and signals failure the same way.\n\n/**\n * A successful structured result with a JSON text fallback for older clients.\n */\nexport function jsonResult(value: unknown): CallToolResult {\n  return {\n    content: [{ type: 'text', text: JSON.stringify(value) ?? 'null' }],\n    structuredContent: value ?? null,\n  }\n}\n\n/**\n * A failed result. The message goes back to the model so it can correct itself,\n * so keep it actionable — and free of credentials, claims, and stack traces.\n */\nexport function errorResult(message: string): CallToolResult {\n  return {\n    isError: true,\n    content: [{ type: 'text', text: message }],\n  }\n}\n\nfunction readString(value: unknown, key: string): string | null {\n  if (!value || typeof value !== 'object' || !(key in value)) return null\n  const property = (value as Record<string, unknown>)[key]\n  return typeof property === 'string' && property ? property : null\n}\n\n/**\n * Turn an unknown thrown value into a safe MCP error. Supabase API errors often\n * carry a `code` and `hint`, both of which help a model fix its next call.\n */\nexport function runtimeErrorResult(error: unknown): CallToolResult {\n  const message = error instanceof Error ? error.message : String(error)\n  const code = readString(error, 'code')\n  const hint = readString(error, 'hint')\n\n  return errorResult(\n    [code ? `[${code}]` : null, message, hint ? `Hint: ${hint}` : null].filter(Boolean).join(' ')\n  )\n}\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/tools/result.ts"
    },
    {
      "path": "registry/default/blocks/mcp/supabase/functions/mcp/tools/whoami.ts",
      "content": "import type { McpServer } from 'npm:@modelcontextprotocol/server@2.0.0'\n\nimport { jsonResult } from './result.ts'\nimport type { ToolContext } from './types.ts'\n\n// Answers from verified claims, demonstrating that every tool runs as the\n// signed-in user. client_id is present for OAuth tokens and null for ordinary\n// product sessions.\nexport function registerWhoamiTool(\n  server: McpServer,\n  { userClaims, jwtClaims }: ToolContext\n): void {\n  const clientId =\n    typeof jwtClaims?.client_id === 'string' && jwtClaims.client_id ? jwtClaims.client_id : null\n\n  server.registerTool(\n    'whoami',\n    {\n      description: \"Return the signed-in user's identity and OAuth client id, when present.\",\n      annotations: {\n        readOnlyHint: true,\n        destructiveHint: false,\n        openWorldHint: false,\n      },\n    },\n    () =>\n      jsonResult({\n        id: userClaims.id,\n        email: userClaims.email ?? null,\n        role: userClaims.role ?? null,\n        client_id: clientId,\n      })\n  )\n}\n",
      "type": "registry:file",
      "target": "~/supabase/functions/mcp/tools/whoami.ts"
    },
    {
      "path": "registry/default/clients/tanstack/lib/supabase/client.ts",
      "content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n  return createBrowserClient(\n    import.meta.env.VITE_SUPABASE_URL!,\n    import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY!\n  )\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "registry/default/clients/tanstack/lib/supabase/server.ts",
      "content": "import { createServerClient } from '@supabase/ssr'\nimport { getCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n  return createServerClient(\n    process.env.VITE_SUPABASE_URL!,\n    process.env.VITE_SUPABASE_PUBLISHABLE_KEY!,\n    {\n      cookies: {\n        getAll() {\n          return Object.entries(getCookies()).map(\n            ([name, value]) =>\n              ({\n                name,\n                value,\n              }) as { name: string; value: string }\n          )\n        },\n        setAll(cookies) {\n          cookies.forEach((cookie) => {\n            setCookie(cookie.name, cookie.value)\n          })\n        },\n      },\n    }\n  )\n}\n",
      "type": "registry:lib"
    }
  ],
  "envVars": {
    "VITE_PRODUCT_NAME": "Your product",
    "VITE_SUPABASE_URL": "",
    "VITE_SUPABASE_PUBLISHABLE_KEY": ""
  },
  "docs": "Follow https://supabase.com/library/docs/tanstack/headless-app for setup. Merge `supabase/config.toml` with existing project settings. Open `/agents` to connect your agent and approve access, then try the example task tools.\n\nYou'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_KEY`.",
  "type": "registry:block"
}