execute_sql runs MERGE ... WHEN MATCHED THEN DELETE without the destructive confirmation
Maintainers usually reply within 1 day
@barryroodt is already working on this.
Since Nov 9, 2026.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 69/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
Research direction
Start with packages/mcp-server-supabase/src/tools/destructive-sql.ts, especially isDestructiveSql and the EXPLAIN recheck, then review the confirmation path in database-operation-tools.ts. Reproduce the listed MERGE cases and compare them with DELETE and UPDATE. Done means destructive MERGE actions trigger confirmation while safe statements retain their current behavior.
Written by the indexing model from the issue text.
Description
Hey folks, I think execute_sql lets a MERGE ... WHEN MATCHED THEN DELETE (or THEN UPDATE) run without the destructive SQL confirmation, even when it touches every row.
What the confirmation is for
When execute_sql is in confirmation.enabledTools (opt in, and the client needs elicitation support), the server asks before running SQL it considers destructive. The prompt text in database-operation-tools.ts:502 says it covers "DROP, DELETE, TRUNCATE or UPDATE without WHERE". A MERGE that deletes or updates every matched row seems to fit that intent pretty squarely.
Current behavior
- Create a table with two rows:
create table films (id int, title text); insert into films values (1, 'a'), (2, 'b'); - Run the server not read only, with
execute_sqlinconfirmation.enabledTools, using a client that supports elicitation. - Call
execute_sqlwithmerge into films f using films s on true when matched then delete - No confirmation is shown (the decision is recorded as
bypasswith reasonnot_destructive). - Both rows are gone.
Expected behavior
The same confirmation you get for delete from films should show up before the MERGE runs.
Minimal repro
Against main at 7e5037b:
import { isDestructiveSql } from './packages/mcp-server-supabase/src/tools/destructive-sql.js';
isDestructiveSql('merge into films f using (select 1) s on true when matched then delete'); // false
isDestructiveSql('merge into films f using films s on true when matched then delete'); // false
isDestructiveSql('explain analyze merge into films f using films s on true when matched then delete'); // false
isDestructiveSql('merge into films f using films s on true when matched then update set title = null'); // false
isDestructiveSql('delete from films'); // true
isDestructiveSql('update films set title = null'); // true
From reading the code, I think this is why:
destructive-sql.ts:70only flags statements that start withdrop,deleteortruncate.isUpdateWithoutWhereFromScan(destructive-sql.ts:191to197) needs the statement to start withupdate.- The
explain analyzerecheck (destructive-sql.ts:272to279) runs those same checks on the body, so it misses MERGE too. database-operation-tools.ts:78then skips the confirmation withnot_destructive.
flowchart LR
A[execute_sql with MERGE] --> B{starts with drop, delete, truncate or update?}
B -- no --> C[not_destructive]
C --> D[runs with no confirmation]
Related work
Huge thanks to @barryroodt for #408, which added this whole confirmation flow, and for #426, which moves the check onto a real parser. The data modifying CTE forms (with d as (delete ... returning) select ..., and explain analyze of them) are already listed as a known trade off in #408 and handled in #426, so I'm only raising MERGE here. From what I can see, classifyStatement in #426 doesn't have a MergeStmt branch yet, so MERGE would still slip through after it lands.
Question
Would you rather have MERGE added as a small check on the current scanner, or handled as a MergeStmt case in #426? Totally happy either way, and I'm glad to send a PR for whichever you prefer (or leave it to Barry if it fits better in #426). Let me know if I've misread anything!
- Dominant language
- TypeScript
- Stars
- 3k
- Forks
- 432
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 52
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from supabase/mcp
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
postgrestRequest returns PostgREST error responses as successful resultsPossibly taken @irisfield claimed this 4 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Let execute_sql run as a project role you choosePossibly taken @barryroodt claimed this 5 days ago. Open
supabase/mcp#537 · 1 assignee ·
Maintainers usually reply within 1 day
-
list_tables: schemas says it defaults to all schemas but defaults to ["public"], so the RLS advisory silently reports on a partial databasePossibly taken @PranshulSoni claimed this 75 days ago. Openbug MCP
Difficulty 4/5 3-5 days Newbie friendliness 55/100
supabase/mcp#395 · 4 comments · 1 reaction ·
Maintainers usually reply within 1 day
-
Feature MCP
Difficulty 3/5 1-2 days Newbie friendliness 65/100
supabase/mcp#361 · 3 comments ·
Maintainers usually reply within 1 day
Similar issues
-
Wallet functions throws TypeError when the wallet has no account IDPossibly taken @Medhansh-741 claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
hiero-ledger/hiero-sdk-js#4781 · 1 comment ·
Maintainers usually reply within 1 day
-
good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
dataforgoodfr/mediterranean-rescue#91 ·
Maintainers usually reply within 1 day
-
transformer-directives: `@apply` drops a utility whose CSS is a substring of an earlier one'sPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Add: KRT TVOpenapproved check:passed streams:add
Difficulty 1/5 Under an hour Newbie friendliness 65/100
iptv-org/iptv#58647 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
milind-soni/OpenMausBot#5829 ·
Maintainers usually reply within 1 day