mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
feat(ui-library): bundle the mcp-server-compute block before pushing
The `node` Compute runtime installs nothing server-side: `supabase compute push` uploads the source directory exactly as it stands. The block handled that by telling the user to `npm install` and ship `node_modules` in the upload, which is a vendored dependency tree in the archive and an install step to remember on every machine that pushes. Bundle instead. `npm run build` runs `build.mjs`, which esbuilds `main.ts` into `dist/index.mjs` as one ESM bundle with its dependencies inlined, and `[compute.mcp-server] source = "supabase/compute/mcp-server/dist"` points the push at that directory. The uploaded archive is two files and needs no `node_modules`. `index.mjs` is deliberate, not incidental: it is the entrypoint name the `node` runtime looks for, so the bundle satisfies the contract directly. The build also writes a `dist/package.json` so a push with no `runtime` recorded still classifies as `node` rather than falling back to the `deno` default; pinning `runtime` in config.toml stays the documented path. `deploy` runs the build and the push together and forwards `--project-ref`. Docs frame it as the two commands in sequence locally, and the same pair in CI as the setup to aim for. `dist/` is git-ignored. Left unminified on purpose: a public instance has 50 seconds to accept connections and parsing this bundle is a few milliseconds of that, so the trade buys nothing, while readable frames in `supabase compute logs` are what makes a failed request diagnosable. Verified by building and booting the output with no `node_modules` present: default export carries a `fetch` function, RFC 9728 metadata answers `200` with the `/compute/v1/mcp-server` resource URL, an unauthenticated `POST /` answers `401` with the `resource_metadata` challenge, and `MCP_AUTHORIZATION_SERVER` still overrides `authorization_servers`. Bundle is 1.76 MB and cold start (import to listening) is ~135 ms. `tsc --noEmit` clean. The `public/r` artifacts are regenerated by hand and have not been checked against a real `pnpm build:registry`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LbcCtAHAHnTCXNJGFfEcQF
This commit is contained in:
8 files changed
+149
-32
No files matched your search
@@ -20,26 +20,37 @@ Installs Node Compute files into a Supabase project or empty directory. No
|
||||
`components.json` is required. Backend files stay in `supabase/` at the project
|
||||
root even when the frontend uses `src/`.
|
||||
|
||||
Then install the service's dependencies, in the service directory:
|
||||
The block ships sources and a build script, not a build. Install the
|
||||
dependencies and bundle them, in the service directory:
|
||||
|
||||
```bash
|
||||
cd supabase/compute/mcp-server
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
The `node` runtime does no server-side install. `supabase compute push` uploads
|
||||
the directory as it stands, so `node_modules` has to already be there. The block
|
||||
ships a `.gitignore` that keeps `node_modules` and `.env` out of version
|
||||
control; re-run `npm install` on any machine that pushes.
|
||||
`npm run build` writes `dist/index.mjs`: one ESM bundle with every dependency
|
||||
inlined. That bundle is what gets deployed, because `[compute.mcp-server]
|
||||
source` points at `dist/`.
|
||||
|
||||
This is worth doing because the `node` runtime installs nothing server-side.
|
||||
`supabase compute push` uploads the source directory exactly as it stands, so
|
||||
the alternative is committing to keep a full `node_modules` in the directory you
|
||||
push. Bundling makes that upload two files instead.
|
||||
|
||||
Build on every machine that pushes, and before every deploy: the block's
|
||||
`.gitignore` keeps `dist/`, `node_modules/`, and `.env` out of version control,
|
||||
so a fresh checkout has no bundle yet.
|
||||
|
||||
The tool files are a copy of the Edge Function block's, with the `npm:` prefixes
|
||||
removed from their imports. The Edge Function block resolves
|
||||
`npm:@modelcontextprotocol/server@2.0.0` at runtime; node resolves
|
||||
`@modelcontextprotocol/server` from `node_modules` and cannot read a `npm:`
|
||||
specifier, and node has no import map to bridge the two. So the two blocks hold
|
||||
the same four tool modules separately, and a tool added to one has to be added
|
||||
to the other. Relative imports carry their `.ts` extension in both: that is what
|
||||
Deno wants and what node's TypeScript stripping requires.
|
||||
`npm:@modelcontextprotocol/server@2.0.0` at runtime; here esbuild resolves
|
||||
`@modelcontextprotocol/server` from `node_modules` at build time, and it cannot
|
||||
read a `npm:` specifier either. The two blocks also install separately, into
|
||||
different directories. So they hold the same four tool modules separately, and a
|
||||
tool added to one has to be added to the other. Relative imports carry their
|
||||
`.ts` extension in both: that is what Deno resolves at runtime and what esbuild
|
||||
resolves at build time.
|
||||
|
||||
## Folder structure
|
||||
|
||||
@@ -58,15 +69,24 @@ compute = true
|
||||
runtime = "node"
|
||||
size = "2gb"
|
||||
exposure = "public"
|
||||
source = "supabase/compute/mcp-server/dist"
|
||||
```
|
||||
|
||||
`runtime = "node"` serves `main.ts`'s default-exported `fetch` handler with no
|
||||
build step: node runs the TypeScript directly, stripping the types. It does not
|
||||
resolve `npm:` or `jsr:` specifiers, and it installs nothing at deploy time, so
|
||||
dependencies come from the `node_modules` you install before pushing.
|
||||
`source` is the directory that gets packaged and uploaded, relative to the
|
||||
project root. It defaults to `supabase/compute/<name>/`; pointing it at `dist/`
|
||||
is what deploys the bundle rather than the sources and a `node_modules` beside
|
||||
them.
|
||||
|
||||
`runtime = "node"` serves `dist/index.mjs`'s default-exported `fetch` handler.
|
||||
Keep it set. With no `runtime` the CLI guesses from marker files in the source
|
||||
directory and defaults to `deno`, which will not run a bundle built for node.
|
||||
The build writes a `dist/package.json` so that guess lands on `node` anyway, but
|
||||
pinning `runtime` is what makes the deploy independent of the guess.
|
||||
|
||||
`exposure = "public"` publishes the service at
|
||||
`https://<project-ref>.supabase.co/compute/v1/mcp-server`. The name in
|
||||
`[compute.<name>]` is the last segment of that URL.
|
||||
`[compute.<name>]` is the last segment of that URL; `source` does not change
|
||||
it.
|
||||
|
||||
The project must sign JWTs with an asymmetric key. `withSupabase` verifies user
|
||||
tokens against the project JWKS and rejects legacy HS256 tokens, so a project
|
||||
@@ -203,21 +223,40 @@ way an Edge Function is. Set them yourself. The block includes
|
||||
cp supabase/compute/mcp-server/.env.example supabase/compute/mcp-server/.env
|
||||
```
|
||||
|
||||
The block's `.gitignore` already covers `supabase/compute/mcp-server/.env` and
|
||||
`node_modules/`.
|
||||
The block's `.gitignore` already covers `supabase/compute/mcp-server/.env`,
|
||||
`node_modules/`, and `dist/`.
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
supabase config push
|
||||
supabase secrets set --env-file supabase/compute/mcp-server/.env
|
||||
npm install --prefix supabase/compute/mcp-server
|
||||
supabase compute push mcp-server
|
||||
supabase compute status mcp-server
|
||||
npm run build --prefix supabase/compute/mcp-server
|
||||
supabase compute push mcp-server --project-ref <project-ref>
|
||||
supabase compute status mcp-server --project-ref <project-ref>
|
||||
```
|
||||
|
||||
`npm install` is not optional. Push a directory without `node_modules` and the
|
||||
instance fails to start on `import { createMcpHandler } from '@modelcontextprotocol/server'`.
|
||||
The block also ships a `deploy` script that runs the build and the push
|
||||
together:
|
||||
|
||||
```bash
|
||||
cd supabase/compute/mcp-server
|
||||
npm run deploy -- --project-ref <project-ref>
|
||||
```
|
||||
|
||||
`supabase compute push` needs a project either way: pass `--project-ref`, or run
|
||||
`supabase link` once in the repo and drop the flag. Arguments after `--` are
|
||||
forwarded to the push.
|
||||
|
||||
Build before pushing. `push` refuses an empty `source` directory, so a first
|
||||
deploy with no build fails outright — but a stale `dist/` deploys the previous
|
||||
code without complaining.
|
||||
|
||||
Running the two locally is fine, and it is why they are separate scripts. The
|
||||
setup worth aiming for is the same pair in CI, so the bundle is built from the
|
||||
committed sources and pushed on merge rather than from whatever is in someone's
|
||||
working tree. `SUPABASE_ACCESS_TOKEN` and `--project-ref` are all the push needs
|
||||
to run unattended.
|
||||
|
||||
Then check the advertised metadata matches the URL clients call:
|
||||
|
||||
|
||||
@@ -13,10 +13,16 @@
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/package.json",
|
||||
"content": "{\n \"name\": \"mcp-server-compute\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"check\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/server\": \"2.0.0\",\n \"@supabase/middleware\": \"0.5.0\",\n \"@supabase/server\": \"1.6.0\",\n \"@supabase/supabase-js\": \"2.108.2\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.15.3\",\n \"typescript\": \"^5.8.3\"\n }\n}\n",
|
||||
"content": "{\n \"name\": \"mcp-server-compute\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"check\": \"tsc --noEmit\",\n \"build\": \"node build.mjs\",\n \"deploy\": \"npm run build && supabase compute push mcp-server\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/server\": \"2.0.0\",\n \"@supabase/middleware\": \"0.5.0\",\n \"@supabase/server\": \"1.6.0\",\n \"@supabase/supabase-js\": \"2.108.2\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.15.3\",\n \"esbuild\": \"^0.25.10\",\n \"typescript\": \"^5.8.3\"\n }\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "~/supabase/compute/mcp-server/package.json"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/build.mjs",
|
||||
"content": "// Bundles this service into `dist/index.mjs`, the entrypoint the `node` Compute\n// runtime imports and serves.\n//\n// Nothing is installed server-side: `supabase compute push` uploads the source\n// directory as-is and the platform synthesizes `FROM <base>` + `COPY` with no\n// install step. So either `node_modules/` ships in the upload, or the\n// dependencies are in the bundle. This block does the latter, and points\n// `[compute.mcp-server] source` at `dist/`, so the uploaded directory is two\n// files instead of a vendored dependency tree.\nimport { mkdir, rm, stat, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport { build } from 'esbuild'\n\nconst OUT_DIR = 'dist'\n\n// `index.mjs` is the name the `node` runtime looks for: the archive root must\n// contain the entrypoint, and it must default-export an object with a `fetch`\n// method. Renaming it deploys a directory the runtime cannot start.\nconst ENTRY_OUT = join(OUT_DIR, 'index.mjs')\n\n// A `package.json` next to the bundle, so a push with no\n// `[compute.mcp-server] runtime` still classifies as `node`. The CLI guesses\n// the runtime from marker files and falls back to `deno` when it finds none,\n// and a bundle built for node does not run under deno. Pinning `runtime` in\n// config.toml is still the documented path; this is the safety net.\nconst OUT_MANIFEST = { private: true, type: 'module' }\n\nawait rm(OUT_DIR, { recursive: true, force: true })\nawait mkdir(OUT_DIR, { recursive: true })\n\nawait build({\n entryPoints: ['main.ts'],\n outfile: ENTRY_OUT,\n bundle: true,\n // The platform owns the listener: it imports the default export and serves it\n // on $PORT. This is a library, not a program, so there is no shebang or\n // top-level `listen`.\n platform: 'node',\n format: 'esm',\n target: 'node22',\n // Everything except `node:` builtins goes in the bundle. This is the whole\n // point of the build: `dist/` has no `node_modules/` to resolve against.\n packages: 'bundle',\n // Left unminified on purpose. A public instance has 50 seconds to accept\n // connections and parsing a bundle this size is a few milliseconds of that,\n // so the trade buys nothing \u2014 while readable frames in `supabase compute\n // logs` are what makes a failed request diagnosable.\n minify: false,\n logLevel: 'info',\n})\n\nawait writeFile(join(OUT_DIR, 'package.json'), `${JSON.stringify(OUT_MANIFEST, null, 2)}\\n`)\n\n// Printed because packaged size is the number that moves when a dependency is\n// added, and the cost lands on cold start.\nconst { size } = await stat(ENTRY_OUT)\nconsole.log(`${ENTRY_OUT} ${(size / 1024).toFixed(0)} KiB`)\n",
|
||||
"type": "registry:file",
|
||||
"target": "~/supabase/compute/mcp-server/build.mjs"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/tsconfig.json",
|
||||
"content": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2023\"],\n \"module\": \"nodenext\",\n \"moduleResolution\": \"nodenext\",\n \"types\": [\"node\"],\n \"strict\": true,\n \"noUncheckedIndexedAccess\": true,\n \"verbatimModuleSyntax\": true,\n \"allowImportingTsExtensions\": true,\n \"erasableSyntaxOnly\": true,\n \"noEmit\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"main.ts\", \"tools/**/*.ts\"]\n}\n",
|
||||
@@ -25,13 +31,13 @@
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/.env.example",
|
||||
"content": "# A Compute instance receives its configuration as environment variables. Copy\n# this file and set the values as project secrets before pushing:\n# cp supabase/compute/mcp-server/.env.example supabase/compute/mcp-server/.env\n# supabase secrets set --env-file supabase/compute/mcp-server/.env\n# npm install --prefix supabase/compute/mcp-server\n# supabase compute push mcp-server\n\n# Required. The project URL and a publishable key, the same values passed to\n# createClient(). Unlike an Edge Function, a Compute instance is not given\n# these automatically.\nSUPABASE_URL=https://your-project-ref.supabase.co\nSUPABASE_PUBLISHABLE_KEY=your-publishable-key\n\n# Must match [compute.<name>] in supabase/config.toml. It is the last segment\n# of this service's public URL, so the advertised OAuth resource identifier\n# depends on it.\nMCP_COMPUTE_SERVICE_NAME=mcp-server\n\n# Optional. Set only when clients reach this service somewhere other than\n# SUPABASE_URL + /compute/v1/<name>, for example behind a custom domain. It\n# must be the exact URL clients call, which RFC 9728 requires to match.\n# MCP_RESOURCE_SERVER=https://mcp.example.com\n\n# Optional. A third-party OAuth 2.1 issuer for external clients. Unset, this\n# project's Supabase Auth is the authorization server.\n# MCP_AUTHORIZATION_SERVER=https://example.clerk.accounts.dev\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",
|
||||
"content": "# A Compute instance receives its configuration as environment variables. Copy\n# this file and set the values as project secrets before pushing:\n# cp supabase/compute/mcp-server/.env.example supabase/compute/mcp-server/.env\n# supabase secrets set --env-file supabase/compute/mcp-server/.env\n# npm run build --prefix supabase/compute/mcp-server\n# supabase compute push mcp-server --project-ref <project-ref>\n\n# Required. The project URL and a publishable key, the same values passed to\n# createClient(). Unlike an Edge Function, a Compute instance is not given\n# these automatically.\nSUPABASE_URL=https://your-project-ref.supabase.co\nSUPABASE_PUBLISHABLE_KEY=your-publishable-key\n\n# Must match [compute.<name>] in supabase/config.toml. It is the last segment\n# of this service's public URL, so the advertised OAuth resource identifier\n# depends on it.\nMCP_COMPUTE_SERVICE_NAME=mcp-server\n\n# Optional. Set only when clients reach this service somewhere other than\n# SUPABASE_URL + /compute/v1/<name>, for example behind a custom domain. It\n# must be the exact URL clients call, which RFC 9728 requires to match.\n# MCP_RESOURCE_SERVER=https://mcp.example.com\n\n# Optional. A third-party OAuth 2.1 issuer for external clients. Unset, this\n# project's Supabase Auth is the authorization server.\n# MCP_AUTHORIZATION_SERVER=https://example.clerk.accounts.dev\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/compute/mcp-server/.env.example"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/.gitignore",
|
||||
"content": "node_modules/\n.env\n",
|
||||
"content": "node_modules/\ndist/\n.env\n",
|
||||
"type": "registry:file",
|
||||
"target": "~/supabase/compute/mcp-server/.gitignore"
|
||||
},
|
||||
@@ -60,5 +66,5 @@
|
||||
"target": "~/supabase/compute/mcp-server/tools/index.ts"
|
||||
}
|
||||
],
|
||||
"docs": "Set `[compute.mcp-server]` with `runtime = \"node\"` in `supabase/config.toml`. The `node` runtime does no server-side install, so run `npm install` in `supabase/compute/mcp-server` before `supabase compute push` \u2014 the directory is uploaded as-is and `node_modules` has to be in it. Keep `node_modules` and `.env` out of version control. Set `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` as secrets: a Compute instance is not given them automatically. A trusted product backend can call it with the signed-in user's access token. For external clients, install the [OAuth Consent block](https://supabase.com/library/docs/nextjs/oauth-consent), enable OAuth and dynamic registration, and set the Auth Site URL to the consent app. Every call runs through the user's RLS-scoped client. OAuth tokens include `client_id`; product sessions do not, so define policies for both paths. See [MCP authentication](https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication) and [token security](https://supabase.com/docs/guides/auth/oauth-server/token-security)."
|
||||
"docs": "Set `[compute.mcp-server]` in `supabase/config.toml` with `runtime = \"node\"` and `source = \"supabase/compute/mcp-server/dist\"`. The block ships sources and a build script, not a build: run `npm install` then `npm run build` in `supabase/compute/mcp-server` to write `dist/index.mjs`, a single ESM bundle with its dependencies inlined, then `supabase compute push mcp-server --project-ref <ref>` (or `npm run deploy -- --project-ref <ref>`, which does both). Nothing is installed server-side and the `source` directory is uploaded as-is, so the bundle is what removes the need for a vendored `node_modules`. Build on every machine that pushes: `dist`, `node_modules` and `.env` are git-ignored. Set `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` as secrets: a Compute instance is not given them automatically. A trusted product backend can call it with the signed-in user's access token. For external clients, install the [OAuth Consent block](https://supabase.com/library/docs/nextjs/oauth-consent), enable OAuth and dynamic registration, and set the Auth Site URL to the consent app. Every call runs through the user's RLS-scoped client. OAuth tokens include `client_id`; product sessions do not, so define policies for both paths. See [MCP authentication](https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication) and [token security](https://supabase.com/docs/guides/auth/oauth-server/token-security)."
|
||||
}
|
||||
@@ -1800,7 +1800,7 @@
|
||||
"type": "registry:item",
|
||||
"title": "MCP Server (Compute)",
|
||||
"description": "Add a user-scoped MCP server to your product, running on Supabase Compute.",
|
||||
"docs": "Set `[compute.mcp-server]` with `runtime = \"node\"` in `supabase/config.toml`. The `node` runtime does no server-side install, so run `npm install` in `supabase/compute/mcp-server` before `supabase compute push` \u2014 the directory is uploaded as-is and `node_modules` has to be in it. Keep `node_modules` and `.env` out of version control. Set `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` as secrets: a Compute instance is not given them automatically. A trusted product backend can call it with the signed-in user's access token. For external clients, install the [OAuth Consent block](https://supabase.com/library/docs/nextjs/oauth-consent), enable OAuth and dynamic registration, and set the Auth Site URL to the consent app. Every call runs through the user's RLS-scoped client. OAuth tokens include `client_id`; product sessions do not, so define policies for both paths. See [MCP authentication](https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication) and [token security](https://supabase.com/docs/guides/auth/oauth-server/token-security).",
|
||||
"docs": "Set `[compute.mcp-server]` in `supabase/config.toml` with `runtime = \"node\"` and `source = \"supabase/compute/mcp-server/dist\"`. The block ships sources and a build script, not a build: run `npm install` then `npm run build` in `supabase/compute/mcp-server` to write `dist/index.mjs`, a single ESM bundle with its dependencies inlined, then `supabase compute push mcp-server --project-ref <ref>` (or `npm run deploy -- --project-ref <ref>`, which does both). Nothing is installed server-side and the `source` directory is uploaded as-is, so the bundle is what removes the need for a vendored `node_modules`. Build on every machine that pushes: `dist`, `node_modules` and `.env` are git-ignored. Set `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` as secrets: a Compute instance is not given them automatically. A trusted product backend can call it with the signed-in user's access token. For external clients, install the [OAuth Consent block](https://supabase.com/library/docs/nextjs/oauth-consent), enable OAuth and dynamic registration, and set the Auth Site URL to the consent app. Every call runs through the user's RLS-scoped client. OAuth tokens include `client_id`; product sessions do not, so define policies for both paths. See [MCP authentication](https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication) and [token security](https://supabase.com/docs/guides/auth/oauth-server/token-security).",
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/main.ts",
|
||||
@@ -1812,6 +1812,11 @@
|
||||
"type": "registry:file",
|
||||
"target": "~/supabase/compute/mcp-server/package.json"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/build.mjs",
|
||||
"type": "registry:file",
|
||||
"target": "~/supabase/compute/mcp-server/build.mjs"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/tsconfig.json",
|
||||
"type": "registry:file",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "registry:item",
|
||||
"title": "MCP Server (Compute)",
|
||||
"description": "Add a user-scoped MCP server to your product, running on Supabase Compute.",
|
||||
"docs": "Set `[compute.mcp-server]` with `runtime = \"node\"` in `supabase/config.toml`. The `node` runtime does no server-side install, so run `npm install` in `supabase/compute/mcp-server` before `supabase compute push` \u2014 the directory is uploaded as-is and `node_modules` has to be in it. Keep `node_modules` and `.env` out of version control. Set `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` as secrets: a Compute instance is not given them automatically. A trusted product backend can call it with the signed-in user's access token. For external clients, install the [OAuth Consent block](https://supabase.com/library/docs/nextjs/oauth-consent), enable OAuth and dynamic registration, and set the Auth Site URL to the consent app. Every call runs through the user's RLS-scoped client. OAuth tokens include `client_id`; product sessions do not, so define policies for both paths. See [MCP authentication](https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication) and [token security](https://supabase.com/docs/guides/auth/oauth-server/token-security).",
|
||||
"docs": "Set `[compute.mcp-server]` in `supabase/config.toml` with `runtime = \"node\"` and `source = \"supabase/compute/mcp-server/dist\"`. The block ships sources and a build script, not a build: run `npm install` then `npm run build` in `supabase/compute/mcp-server` to write `dist/index.mjs`, a single ESM bundle with its dependencies inlined, then `supabase compute push mcp-server --project-ref <ref>` (or `npm run deploy -- --project-ref <ref>`, which does both). Nothing is installed server-side and the `source` directory is uploaded as-is, so the bundle is what removes the need for a vendored `node_modules`. Build on every machine that pushes: `dist`, `node_modules` and `.env` are git-ignored. Set `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` as secrets: a Compute instance is not given them automatically. A trusted product backend can call it with the signed-in user's access token. For external clients, install the [OAuth Consent block](https://supabase.com/library/docs/nextjs/oauth-consent), enable OAuth and dynamic registration, and set the Auth Site URL to the consent app. Every call runs through the user's RLS-scoped client. OAuth tokens include `client_id`; product sessions do not, so define policies for both paths. See [MCP authentication](https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication) and [token security](https://supabase.com/docs/guides/auth/oauth-server/token-security).",
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/main.ts",
|
||||
@@ -15,6 +15,11 @@
|
||||
"type": "registry:file",
|
||||
"target": "~/supabase/compute/mcp-server/package.json"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/build.mjs",
|
||||
"type": "registry:file",
|
||||
"target": "~/supabase/compute/mcp-server/build.mjs"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/mcp-server-compute/supabase/compute/mcp-server/tsconfig.json",
|
||||
"type": "registry:file",
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
# this file and set the values as project secrets before pushing:
|
||||
# cp supabase/compute/mcp-server/.env.example supabase/compute/mcp-server/.env
|
||||
# supabase secrets set --env-file supabase/compute/mcp-server/.env
|
||||
# npm install --prefix supabase/compute/mcp-server
|
||||
# supabase compute push mcp-server
|
||||
# npm run build --prefix supabase/compute/mcp-server
|
||||
# supabase compute push mcp-server --project-ref <project-ref>
|
||||
|
||||
# Required. The project URL and a publishable key, the same values passed to
|
||||
# createClient(). Unlike an Edge Function, a Compute instance is not given
|
||||
|
||||
+1
@@ -1,2 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Bundles this service into `dist/index.mjs`, the entrypoint the `node` Compute
|
||||
// runtime imports and serves.
|
||||
//
|
||||
// Nothing is installed server-side: `supabase compute push` uploads the source
|
||||
// directory as-is and the platform synthesizes `FROM <base>` + `COPY` with no
|
||||
// install step. So either `node_modules/` ships in the upload, or the
|
||||
// dependencies are in the bundle. This block does the latter, and points
|
||||
// `[compute.mcp-server] source` at `dist/`, so the uploaded directory is two
|
||||
// files instead of a vendored dependency tree.
|
||||
import { mkdir, rm, stat, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { build } from 'esbuild'
|
||||
|
||||
const OUT_DIR = 'dist'
|
||||
|
||||
// `index.mjs` is the name the `node` runtime looks for: the archive root must
|
||||
// contain the entrypoint, and it must default-export an object with a `fetch`
|
||||
// method. Renaming it deploys a directory the runtime cannot start.
|
||||
const ENTRY_OUT = join(OUT_DIR, 'index.mjs')
|
||||
|
||||
// A `package.json` next to the bundle, so a push with no
|
||||
// `[compute.mcp-server] runtime` still classifies as `node`. The CLI guesses
|
||||
// the runtime from marker files and falls back to `deno` when it finds none,
|
||||
// and a bundle built for node does not run under deno. Pinning `runtime` in
|
||||
// config.toml is still the documented path; this is the safety net.
|
||||
const OUT_MANIFEST = { private: true, type: 'module' }
|
||||
|
||||
await rm(OUT_DIR, { recursive: true, force: true })
|
||||
await mkdir(OUT_DIR, { recursive: true })
|
||||
|
||||
await build({
|
||||
entryPoints: ['main.ts'],
|
||||
outfile: ENTRY_OUT,
|
||||
bundle: true,
|
||||
// The platform owns the listener: it imports the default export and serves it
|
||||
// on $PORT. This is a library, not a program, so there is no shebang or
|
||||
// top-level `listen`.
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
target: 'node22',
|
||||
// Everything except `node:` builtins goes in the bundle. This is the whole
|
||||
// point of the build: `dist/` has no `node_modules/` to resolve against.
|
||||
packages: 'bundle',
|
||||
// Left unminified on purpose. A public instance has 50 seconds to accept
|
||||
// connections and parsing a bundle this size is a few milliseconds of that,
|
||||
// so the trade buys nothing — while readable frames in `supabase compute
|
||||
// logs` are what makes a failed request diagnosable.
|
||||
minify: false,
|
||||
logLevel: 'info',
|
||||
})
|
||||
|
||||
await writeFile(join(OUT_DIR, 'package.json'), `${JSON.stringify(OUT_MANIFEST, null, 2)}\n`)
|
||||
|
||||
// Printed because packaged size is the number that moves when a dependency is
|
||||
// added, and the cost lands on cold start.
|
||||
const { size } = await stat(ENTRY_OUT)
|
||||
console.log(`${ENTRY_OUT} ${(size / 1024).toFixed(0)} KiB`)
|
||||
+4
-1
@@ -3,7 +3,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"check": "tsc --noEmit"
|
||||
"check": "tsc --noEmit",
|
||||
"build": "node build.mjs",
|
||||
"deploy": "npm run build && supabase compute push mcp-server"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/server": "2.0.0",
|
||||
@@ -13,6 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.3",
|
||||
"esbuild": "^0.25.10",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user