Files
Matt RossmanandGreg Richardson 9f5620c7cd feat: self-hosted MCP debugging tools (#39366)
* feat: stub `getDebuggingOperations`

* fix: logflare variables in `generateLocalEnv.js`

* chore: upgrade `supabase`, `@supabase/mcp-server-supabase`

* feat: shared `retrieveAnalyticsData`, implement `getLogs` for MCP

* feat: shared `getLints`, implement MCP security/performance advisors with filter

* chore: update lockfile

* fix: prefer `??`

Co-authored-by: Greg Richardson <greg.nmr@gmail.com>

* docs: comment origin of `LINT_SQL`

Co-authored-by: Greg Richardson <greg.nmr@gmail.com>

* chore: bump `supabase` devDependency for latest images

Changes rely on fix from loglare v1.23.0

* fix: edge function logs query

Based on https://github.com/supabase/supabase/pull/39388/files#diff-b4e9726d34a406fa1a6133a18bdb972c90be9df0c89598de0695592514e22941R199-R206

---------

Co-authored-by: Greg Richardson <greg.nmr@gmail.com>
2025-10-20 13:11:15 -04:00

136 lines
3.4 KiB
TypeScript

import {
ApplyMigrationOptions,
DatabaseOperations,
DebuggingOperations,
DevelopmentOperations,
ExecuteSqlOptions,
GetLogsOptions,
} from '@supabase/mcp-server-supabase/platform'
import { ResponseError } from 'types'
import { generateTypescriptTypes } from './generate-types'
import { getLints } from './lints'
import { getLogQuery, retrieveAnalyticsData } from './logs'
import { applyAndTrackMigrations, listMigrationVersions } from './migrations'
import { executeQuery } from './query'
import { getProjectSettings } from './settings'
export type GetDatabaseOperationsOptions = {
headers?: HeadersInit
}
export type GetDevelopmentOperationsOptions = {
headers?: HeadersInit
}
export type GetDebuggingOperationsOptions = {
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
},
}
}
export function getDebuggingOperations({
headers,
}: GetDebuggingOperationsOptions): DebuggingOperations {
return {
async getLogs(projectRef: string, options: GetLogsOptions) {
const sql = getLogQuery(options.service)
const { data, error } = await retrieveAnalyticsData({
name: 'logs.all',
projectRef,
params: {
sql,
iso_timestamp_start: options.iso_timestamp_start,
iso_timestamp_end: options.iso_timestamp_end,
},
})
if (error) {
throw error
}
return data
},
async getSecurityAdvisors(_projectRef) {
const { data, error } = await getLints({ headers })
if (error) {
throw error
}
return data.filter((lint) => lint.categories.includes('SECURITY'))
},
async getPerformanceAdvisors(_projectRef) {
const { data, error } = await getLints({ headers })
if (error) {
throw error
}
return data.filter((lint) => lint.categories.includes('PERFORMANCE'))
},
}
}