feat: migrate to docker-compose with selfhosted-supabase-mcp
- Replace legacy directory structure (api/, db/, functions/, logs/, pooler/) with single docker-compose.yml based self-hosted setup - Add selfhosted-supabase-mcp TypeScript MCP server for database management - Add .dockerignore for Docker build context - Update .gitignore to exclude .env files, volumes/, backups, logs
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* list_edge_function_logs - Lists execution logs for edge functions.
|
||||
*
|
||||
* Queries the function_edge_logs table if available in the Supabase instance.
|
||||
* This table is automatically created by Supabase for edge function logging.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { handleSqlResponse, executeSqlWithFallback } from './utils.js';
|
||||
import type { ToolContext, ToolPrivilegeLevel } from './types.js';
|
||||
|
||||
const EdgeFunctionLogSchema = z.object({
|
||||
execution_id: z.string().nullable(),
|
||||
function_id: z.string(),
|
||||
status_code: z.number().nullable(),
|
||||
request_start_time: z.string(),
|
||||
request_duration_ms: z.number().nullable(),
|
||||
error_message: z.string().nullable(),
|
||||
request_path: z.string().nullable(),
|
||||
request_method: z.string().nullable(),
|
||||
});
|
||||
|
||||
const ListEdgeFunctionLogsOutputSchema = z.array(EdgeFunctionLogSchema);
|
||||
|
||||
const ListEdgeFunctionLogsInputSchema = z.object({
|
||||
function_id: z.string().optional().describe('Filter by function ID/slug.'),
|
||||
status_code: z.number().optional().describe('Filter by HTTP status code.'),
|
||||
errors_only: z.boolean().optional().describe('Only show logs with errors (status >= 400).'),
|
||||
limit: z.number().optional().default(100).describe('Maximum number of log entries to return.'),
|
||||
});
|
||||
|
||||
type ListEdgeFunctionLogsInput = z.infer<typeof ListEdgeFunctionLogsInputSchema>;
|
||||
|
||||
const mcpInputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
function_id: {
|
||||
type: 'string',
|
||||
description: 'Filter by function ID/slug.',
|
||||
},
|
||||
status_code: {
|
||||
type: 'number',
|
||||
description: 'Filter by HTTP status code.',
|
||||
},
|
||||
errors_only: {
|
||||
type: 'boolean',
|
||||
description: 'Only show logs with errors (status >= 400).',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Maximum number of log entries to return.',
|
||||
default: 100,
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
};
|
||||
|
||||
// Pattern for function IDs (UUIDs or slugs)
|
||||
const functionIdPattern = /^[a-zA-Z0-9_\-]+$/;
|
||||
|
||||
export const listEdgeFunctionLogsTool = {
|
||||
name: 'list_edge_function_logs',
|
||||
description: 'Lists execution logs for edge functions from the function_edge_logs table.',
|
||||
privilegeLevel: 'regular' as ToolPrivilegeLevel,
|
||||
inputSchema: ListEdgeFunctionLogsInputSchema,
|
||||
mcpInputSchema: mcpInputSchema,
|
||||
outputSchema: ListEdgeFunctionLogsOutputSchema,
|
||||
|
||||
execute: async (input: ListEdgeFunctionLogsInput, context: ToolContext) => {
|
||||
const client = context.selfhostedClient;
|
||||
const { function_id, status_code, errors_only, limit = 100 } = input;
|
||||
|
||||
// Validate function_id if provided
|
||||
if (function_id && !functionIdPattern.test(function_id)) {
|
||||
throw new Error(`Invalid function ID: ${function_id}. Use only alphanumeric, underscore, and hyphen characters.`);
|
||||
}
|
||||
|
||||
// Check if function_edge_logs table exists
|
||||
const checkTableSql = `
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_name = 'function_edge_logs'
|
||||
) as exists;
|
||||
`;
|
||||
|
||||
const tableCheck = await executeSqlWithFallback(client, checkTableSql, true);
|
||||
|
||||
if (!Array.isArray(tableCheck) || tableCheck.length === 0) {
|
||||
throw new Error('Failed to check for function_edge_logs table.');
|
||||
}
|
||||
|
||||
if (!tableCheck[0].exists) {
|
||||
throw new Error(
|
||||
'Edge function logs table (function_edge_logs) not found. ' +
|
||||
'This table is automatically created by Supabase when edge functions are invoked. ' +
|
||||
'Ensure edge functions have been executed at least once.'
|
||||
);
|
||||
}
|
||||
|
||||
// Build query with filters
|
||||
const conditions: string[] = [];
|
||||
|
||||
if (function_id) {
|
||||
conditions.push(`function_id = '${function_id}'`);
|
||||
}
|
||||
|
||||
if (status_code !== undefined) {
|
||||
// status_code is a number from Zod validation, safe to use directly
|
||||
conditions.push(`status_code = ${status_code}`);
|
||||
}
|
||||
|
||||
if (errors_only) {
|
||||
conditions.push('status_code >= 400');
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
|
||||
// Ensure limit is within bounds
|
||||
const safeLimit = Math.min(Math.max(1, limit), 1000);
|
||||
|
||||
const logsSql = `
|
||||
SELECT
|
||||
execution_id::text,
|
||||
function_id,
|
||||
status_code,
|
||||
request_start_time::text,
|
||||
EXTRACT(EPOCH FROM (request_end_time - request_start_time)) * 1000 as request_duration_ms,
|
||||
error_message,
|
||||
request_path,
|
||||
request_method
|
||||
FROM function_edge_logs
|
||||
${whereClause}
|
||||
ORDER BY request_start_time DESC
|
||||
LIMIT ${safeLimit}
|
||||
`;
|
||||
|
||||
const result = await executeSqlWithFallback(client, logsSql, true);
|
||||
return handleSqlResponse(result, ListEdgeFunctionLogsOutputSchema);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user