mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
> [!IMPORTANT] > > Only merge this when (https://github.com/supabase/platform/pull/36804) is merged, as the AI assistant will not have access to the `query_logs` tool for the remote MCP server ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature (self-hosted / CLI Studio MCP server). ## What is the current behavior? Self-hosted `getDebuggingOperations` (`apps/studio/lib/api/self-hosted/mcp.ts`) implements only `getLogs`, so the MCP `debugging` group exposes `get_logs` — a fixed per-service log dump built by `getLogQuery`. Logs are served by Logflare, which speaks BigQuery SQL. ## What is the new behavior? Bumps `@supabase/mcp-server-supabase` to `^0.10.0` (adds `query_logs` + `logsDialect`, and hides `get_logs` wherever a platform declares `queryLogs`) and moves logs over to it. - **Self-hosted `query_logs`:** declares `logsDialect: 'bigquery'` and implements `queryLogs`, passing the model's SQL straight through to the same Logflare `logs.all` endpoint (arbitrary `sql` param) — no new endpoint, no dialect translation. - **Drops `get_logs` from self-hosted:** `getLogs` throws (the server hides it once `queryLogs` exists) and the per-service `getLogQuery` builder is deleted; the model now writes its own BigQuery SQL, guided by the dialect schema hint. - **Honors no-logs mode:** `query_logs` throws when `logs:all` is disabled — the self-hosted default, enabled via the `docker-compose.logs.yml` override. - **Assistant:** switches the dashboard assistant from `get_logs` to `query_logs` (allowlist, drift guard, prompt, mocks, evals). Refs AI-1046 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * AI debugging can query recent project logs using read-only SQL. * Log queries support optional time-range filters, filtering, aggregation, and joins. * Self-hosted debugging checks whether logging is enabled before running queries. * **Bug Fixes** * Updated debugging workflows and validation to consistently use the new log-query capability. * Removed reliance on legacy service-specific log filtering and query behavior. * **Documentation** * Updated MCP debugging tool guidance to describe SQL-based log queries. * **Tests** * Expanded coverage for enabled, disabled, and unsupported logging scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
191 lines
5.4 KiB
TypeScript
191 lines
5.4 KiB
TypeScript
import {
|
|
ApiKey,
|
|
ApiKeyType,
|
|
ApplyMigrationOptions,
|
|
DatabaseOperations,
|
|
DebuggingOperations,
|
|
DevelopmentOperations,
|
|
ExecuteSqlOptions,
|
|
QueryLogsOptions,
|
|
} from '@supabase/mcp-server-supabase/platform'
|
|
import { isFeatureEnabled, type Feature } from 'common/enabled-features'
|
|
import { getEnabledFeaturesOverrideDisabledList } from 'common/enabled-features/overrides'
|
|
|
|
import { DEFAULT_EXPOSED_SCHEMAS } from './constants'
|
|
import { generateTypescriptTypes } from './generate-types'
|
|
import { getLints } from './lints'
|
|
import { retrieveAnalyticsData } from './logs'
|
|
import { applyAndTrackMigrations, listMigrationVersions } from './migrations'
|
|
import { executeQuery } from './query'
|
|
import { getProjectSettings } from './settings'
|
|
import { ResponseError } from '@/types'
|
|
|
|
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, parameters, read_only: readOnly } = options
|
|
|
|
const { data, error } = await executeQuery<T>({ query, parameters, 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 getPublishableKeys(_projectRef) {
|
|
if (process.env.SUPABASE_PUBLISHABLE_KEY) {
|
|
const publishableKeysArray: ApiKey[] = [
|
|
{
|
|
api_key: process.env.SUPABASE_PUBLISHABLE_KEY,
|
|
name: 'publishable',
|
|
type: 'publishable' as ApiKeyType,
|
|
},
|
|
]
|
|
return publishableKeysArray
|
|
}
|
|
|
|
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')
|
|
}
|
|
|
|
const publishableKeysArray: ApiKey[] = [
|
|
{
|
|
api_key: anonKey.api_key,
|
|
name: anonKey.name,
|
|
type: 'legacy' as ApiKeyType,
|
|
},
|
|
]
|
|
return publishableKeysArray
|
|
},
|
|
async generateTypescriptTypes(_projectRef) {
|
|
const response = await generateTypescriptTypes({ headers })
|
|
|
|
if (response instanceof ResponseError) {
|
|
throw response
|
|
}
|
|
|
|
return response
|
|
},
|
|
}
|
|
}
|
|
|
|
// Logs are disabled by default for self-hosted; enabled via the
|
|
// `docker-compose.logs.yml` override, which sets ENABLED_FEATURES_LOGS_ALL=true.
|
|
function assertLogsEnabled() {
|
|
const disabledFeatures = getEnabledFeaturesOverrideDisabledList(process.env) as Feature[]
|
|
|
|
if (!isFeatureEnabled('logs:all', disabledFeatures)) {
|
|
throw new Error(
|
|
'Logs are disabled on this instance. Enable the `docker-compose.logs.yml` override to query logs.'
|
|
)
|
|
}
|
|
}
|
|
|
|
export function getDebuggingOperations({
|
|
headers,
|
|
}: GetDebuggingOperationsOptions): DebuggingOperations {
|
|
return {
|
|
// Self-hosted logs are served by Logflare, which speaks BigQuery SQL.
|
|
logsDialect: 'bigquery',
|
|
// `query_logs` replaces `get_logs` on self-hosted. Declaring `queryLogs`
|
|
// makes the MCP server hide `get_logs` from clients (see `getDebuggingTools`
|
|
// in @supabase/mcp-server-supabase), so this method is never reached over
|
|
// MCP. The `DebuggingOperations` interface still requires it, so it throws
|
|
// defensively rather than serving logs.
|
|
async getLogs() {
|
|
throw new Error('get_logs is not supported on self-hosted; use query_logs instead.')
|
|
},
|
|
async queryLogs(projectRef: string, options: QueryLogsOptions) {
|
|
assertLogsEnabled()
|
|
|
|
// Pass the model's SQL straight through to the Logflare `logs.all`
|
|
// endpoint, which accepts an arbitrary `sql` param.
|
|
const { data, error } = await retrieveAnalyticsData({
|
|
name: 'logs.all',
|
|
projectRef,
|
|
params: {
|
|
sql: options.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,
|
|
exposedSchemas: DEFAULT_EXPOSED_SCHEMAS,
|
|
})
|
|
|
|
if (error) {
|
|
throw error
|
|
}
|
|
|
|
return data.filter((lint) => lint.categories.includes('SECURITY'))
|
|
},
|
|
async getPerformanceAdvisors(_projectRef) {
|
|
const { data, error } = await getLints({
|
|
headers,
|
|
exposedSchemas: DEFAULT_EXPOSED_SCHEMAS,
|
|
})
|
|
|
|
if (error) {
|
|
throw error
|
|
}
|
|
|
|
return data.filter((lint) => lint.categories.includes('PERFORMANCE'))
|
|
},
|
|
}
|
|
}
|