Files
supabase/apps/studio/lib/api/self-hosted/mcp.ts
Matt Rossman 31b6368049 feat: read-only mode for self-hosted MCP (#39041)
* feat: add `crypto-js`, `encryptString` with sample key

* feat: include POSTGRES_PASSWORD in generated .env.test

* feat: include POSTGRES_PASSWORD in turbo.json for studio

* feat: read only query support

* feat: configurable `POSTGRES_HOST`, `POSTGRES_DB`, `POSTGRES_PORT`

* chore: rename POSTGRES_USER to clarify write permission

* feat: configurable `PG_META_CRYPTO_KEY`

* chore: add `PG_META_CRYPTO_KEY` to generateLocalEnv

* feat: add 'postgres-meta' to linter dictionary

* feat: restore read-only toggle in local MCP URL builder
2025-10-06 16:14:27 -04:00

84 lines
2.2 KiB
TypeScript

import {
ApplyMigrationOptions,
DatabaseOperations,
DevelopmentOperations,
ExecuteSqlOptions,
} from '@supabase/mcp-server-supabase/platform'
import { applyAndTrackMigrations, listMigrationVersions } from './migrations'
import { executeQuery } from './query'
import { getProjectSettings } from './settings'
import { generateTypescriptTypes } from './generate-types'
import { ResponseError } from 'types'
export type GetDatabaseOperationsOptions = {
headers?: HeadersInit
}
export type GetDevelopmentOperationsOptions = {
headers?: HeadersInit
}
export function getDatabaseOperations({
headers,
}: GetDatabaseOperationsOptions): DatabaseOperations {
return {
async executeSql<T>(_projectRef: string, options: ExecuteSqlOptions) {
const { query, read_only: readOnly } = options
const { data, error } = await executeQuery<T>({ query, headers, readOnly })
if (error) {
throw error
}
return data
},
async listMigrations() {
const { data, error } = await listMigrationVersions({ headers })
if (error) {
throw error
}
return data
},
async applyMigration(_projectRef: string, options: ApplyMigrationOptions) {
const { query, name } = options
const { error } = await applyAndTrackMigrations({ query, name, headers })
if (error) {
throw error
}
},
}
}
export function getDevelopmentOperations({
headers,
}: GetDevelopmentOperationsOptions): DevelopmentOperations {
return {
async getProjectUrl(_projectRef) {
const settings = getProjectSettings()
return `${settings.app_config.protocol}://${settings.app_config.endpoint}`
},
async getAnonKey(_projectRef) {
const settings = getProjectSettings()
const anonKey = settings.service_api_keys.find((key) => key.name === 'anon key')
if (!anonKey) {
throw new Error('Anon key not found in project settings')
}
return anonKey.api_key
},
async generateTypescriptTypes(_projectRef) {
const response = await generateTypescriptTypes({ headers })
if (response instanceof ResponseError) {
throw response
}
return response
},
}
}