Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,21 @@ Example client config:
}
```

The dev server supports the same [query params as the hosted endpoint](https://supabase.com/docs/guides/ai-tools/mcp#configuration-options). The access token comes from the client's `Authorization` header on each request. Restart the server in your MCP client after each change.
The dev server supports the same [query params as the hosted endpoint](https://supabase.com/docs/guides/ai-tools/mcp#configuration-options). Set `project_ref`, `read_only`, and `features` in the HTTP URL, not through stdio CLI flags. The access token comes from the client's `Authorization` header on each request.

Flags: `--http`, `--port` (default 3111), `--api-url`, `--content-api-url`, `--version`.
Secret creation is configured by default over HTTP, subject to the `functions` feature, read-only mode, and client and platform support. The production API remains the default and uses `https://supabase.com/dashboard/mcp/secrets?ref={ref}&name={name}`. Selecting `--api-url https://api.supabase.green` uses `https://supabase.green/dashboard/mcp/secrets?ref={ref}&name={name}` without an override.

The optional HTTP-only `--secret-url-template` overrides this URL. Custom API origins require an explicit template; no dashboard host is derived from them. The template must be an absolute URL containing both `{ref}` and `{name}`. An explicitly empty value is invalid, not a fallback to the default.

For a custom local API, replace the placeholders:

```bash
pnpm dev:http --api-url 'http://127.0.0.1:<port>' --secret-url-template '<absolute URL containing {ref} and {name}>'
```

Signed `requestState` used by HTTP secret collection and cost confirmation expires after 120 seconds. The secret tool retains its 600-second timestamp-based recovery window. Watch restarts invalidate pending state. Restart the server in your MCP client after each change.

Flags: `--http`, `--port` (default 3111), `--api-url`, `--content-api-url`, `--secret-url-template`, `--version`.

To try the HTTP entry from a PR without cloning, run the preview build published by pkg.pr.new:

Expand Down
28 changes: 22 additions & 6 deletions packages/mcp-server-supabase/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ async function main() {
['read-only']: readOnly,
['api-url']: apiUrl,
['content-api-url']: cliContentApiUrl,
['secret-url-template']: secretUrlTemplate,
['version']: showVersion,
['features']: cliFeatures,
['http']: http,
Expand All @@ -42,6 +43,9 @@ async function main() {
['content-api-url']: {
type: 'string',
},
['secret-url-template']: {
type: 'string',
},
['version']: {
type: 'boolean',
},
Expand All @@ -64,18 +68,30 @@ async function main() {
process.exit(0);
}

if (secretUrlTemplate !== undefined && !http) {
console.error('--secret-url-template requires --http.');
process.exitCode = 1;
return;
}

const features = cliFeatures ? parseList(cliFeatures) : undefined;

const contentApiUrl =
cliContentApiUrl ?? process.env.SUPABASE_CONTENT_API_URL;

if (http) {
const entry = await startLocalHttpEntry({
port: Number(cliPort),
apiUrl,
contentApiUrl,
});
console.error(`Supabase MCP server listening on ${entry.url}`);
try {
const entry = await startLocalHttpEntry({
port: Number(cliPort),
apiUrl,
contentApiUrl,
secretUrlTemplate,
});
console.error(`Supabase MCP server listening on ${entry.url}`);
} catch (error) {
console.error(error);
process.exitCode = 1;
}
return;
}

Expand Down
26 changes: 26 additions & 0 deletions packages/mcp-server-supabase/src/platform/api-platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
type GetLogsOptions,
type QueryLogsOptions,
type ResetBranchOptions,
type SecretOperations,
type StorageConfig,
type StorageOperations,
type SupabasePlatform,
Expand Down Expand Up @@ -815,6 +816,30 @@ export function createSupabaseApiPlatform(
},
};

const secrets: SecretOperations = {
async getUpdatedAt(projectId: string, name: string) {
const response = await managementApiClient.GET(
'/v1/projects/{ref}/secrets',
{
params: {
path: {
ref: projectId,
},
},
}
);

assertSuccess(response, 'Failed to fetch secrets');

const secret = response.data.find((s) => s.name === name);
if (!secret || !secret.updated_at) {
return undefined;
}

return new Date(secret.updated_at);
},
};

const platform: SupabasePlatform = {
async init(info: InitData) {
const { clientInfo } = info;
Expand All @@ -838,6 +863,7 @@ export function createSupabaseApiPlatform(
functions,
branching,
storage,
secrets,
};

return platform;
Expand Down
9 changes: 9 additions & 0 deletions packages/mcp-server-supabase/src/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,14 @@ export type BranchingOperations = {
rebaseBranch(branchId: string): Promise<void>;
};

/**
* Returns only the `updated_at` timestamp of the named secret, never its
* value or digest.
*/
export type SecretOperations = {
getUpdatedAt(projectId: string, name: string): Promise<Date | undefined>;
};

export type SupabasePlatform = {
init?(info: InitData): Promise<void>;
account?: AccountOperations;
Expand All @@ -284,4 +292,5 @@ export type SupabasePlatform = {
development?: DevelopmentOperations;
storage?: StorageOperations;
branching?: BranchingOperations;
secrets?: SecretOperations;
};
Loading
Loading