preview_fix
Test security fix changes safely by wrapping SQL in a transaction and rolling back to preview impact without applying modifications.
Instructions
Preview what a fix would change WITHOUT applying it. Wraps the fix SQL in BEGIN; ... ROLLBACK; and returns what would have happened. Safe to call for any finding.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_ref | Yes | ||
| finding_index | Yes | 0-based index from list_findings output |
Implementation Reference
- src/server.js:96-121 (handler)The handler function for the 'preview_fix' tool. It retrieves a finding from cache by index, strips SQL comments, wraps the fix SQL in BEGIN/ROLLBACK, and executes it via the sql() helper. Returns preview OK on success or error message on failure.
async ({ project_ref, finding_index }) => { const c = cache.get(project_ref); if (!c) return { content: [{ type: "text", text: `No cached audit. Run audit_project first.` }], isError: true }; const f = c.result.findings[finding_index]; if (!f) return { content: [{ type: "text", text: `Finding index ${finding_index} out of range (have ${c.result.findings.length})` }], isError: true }; // Only attempt preview for SQL-runnable fixes (not Dashboard-toggle ones) const sqlOnly = f.fix_sql.split("\n").filter((l) => l.trim() && !l.trim().startsWith("--")).join("\n"); if (!sqlOnly) { return { content: [{ type: "text", text: `Finding "${f.title}" requires a Dashboard change, not SQL. Cannot preview. Fix instructions:\n\n${f.fix_sql}` }] }; } try { const wrapped = `BEGIN;\n${sqlOnly}\nROLLBACK;`; await sql(c.token, project_ref, wrapped); return { content: [ { type: "text", text: `Preview OK — fix runs cleanly inside a transaction. Safe to apply with apply_fix(project_ref, ${finding_index}, confirm=true).` }, { type: "text", text: `SQL that would run:\n\`\`\`sql\n${sqlOnly}\n\`\`\`` }, ], }; } catch (e) { return { content: [{ type: "text", text: `Preview FAILED — fix SQL would error: ${e.message}\n\nDo NOT apply. Investigate first.` }], isError: true }; } } ); - src/server.js:89-95 (schema)Input schema for preview_fix: project_ref (string) and finding_index (integer, 0-based from list_findings output).
{ description: "Preview what a fix would change WITHOUT applying it. Wraps the fix SQL in BEGIN; ... ROLLBACK; and returns what would have happened. Safe to call for any finding.", inputSchema: { project_ref: z.string(), finding_index: z.number().int().describe("0-based index from list_findings output"), }, }, - src/server.js:87-121 (registration)Registration of the 'preview_fix' tool with the MCP server using server.registerTool().
server.registerTool( "preview_fix", { description: "Preview what a fix would change WITHOUT applying it. Wraps the fix SQL in BEGIN; ... ROLLBACK; and returns what would have happened. Safe to call for any finding.", inputSchema: { project_ref: z.string(), finding_index: z.number().int().describe("0-based index from list_findings output"), }, }, async ({ project_ref, finding_index }) => { const c = cache.get(project_ref); if (!c) return { content: [{ type: "text", text: `No cached audit. Run audit_project first.` }], isError: true }; const f = c.result.findings[finding_index]; if (!f) return { content: [{ type: "text", text: `Finding index ${finding_index} out of range (have ${c.result.findings.length})` }], isError: true }; // Only attempt preview for SQL-runnable fixes (not Dashboard-toggle ones) const sqlOnly = f.fix_sql.split("\n").filter((l) => l.trim() && !l.trim().startsWith("--")).join("\n"); if (!sqlOnly) { return { content: [{ type: "text", text: `Finding "${f.title}" requires a Dashboard change, not SQL. Cannot preview. Fix instructions:\n\n${f.fix_sql}` }] }; } try { const wrapped = `BEGIN;\n${sqlOnly}\nROLLBACK;`; await sql(c.token, project_ref, wrapped); return { content: [ { type: "text", text: `Preview OK — fix runs cleanly inside a transaction. Safe to apply with apply_fix(project_ref, ${finding_index}, confirm=true).` }, { type: "text", text: `SQL that would run:\n\`\`\`sql\n${sqlOnly}\n\`\`\`` }, ], }; } catch (e) { return { content: [{ type: "text", text: `Preview FAILED — fix SQL would error: ${e.message}\n\nDo NOT apply. Investigate first.` }], isError: true }; } } ); - src/audit.js:77-89 (helper)The sql() helper function that executes a query against the Supabase project's database/query API. Used by preview_fix to run the wrapped BEGIN/ROLLBACK fix SQL.
async function sql(token, ref, query) { const r = await fetch(`${API}/projects/${ref}/database/query`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "User-Agent": UA, }, body: JSON.stringify({ query }), }); if (!r.ok) throw new Error(`SQL ${r.status}: ${await r.text()}`); return r.json(); }