Skip to content

feat(mcp): add branch cost confirmation elicitation - #394

Merged
barryroodt merged 9 commits into
feat/hosted-project-cost-elicitationfrom
feat/branch-cost-elicitation
Sep 3, 2026
Merged

feat(mcp): add branch cost confirmation elicitation#394
barryroodt merged 9 commits into
feat/hosted-project-cost-elicitationfrom
feat/branch-cost-elicitation

Conversation

@barryroodt

@barryroodt barryroodt commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Stacked on #391. Review that first. This is part of AI-1091.

What kind of change does this PR introduce?

Feature: add form-mode cost confirmation to create_branch.

What is the current behavior?

#391 provides the shared confirmation codec/verifier, ServerContext, result passthrough, and project flow. Branch creation still uses the existing hash validation.

What is the new behavior?

Modern form clients can call create_branch without confirm_cost_id. They receive an action-only, property-less prompt for $0.01344/hr (about $9.68 over 30 days).

Acceptance creates the branch from signed arguments. Missing responses and changed costs re-prompt. A wrong-tool state or changed arguments fail. Decline and cancel create no branch.

Capability-free and URL-only clients keep hash validation. A supplied legacy ID cannot bypass the capable-client flow. Project-scoped injection works, and existing output fields such as with_data remain.

How to Review

  1. Server and shared state

    • packages/mcp-server-supabase/src/server.ts
    • packages/mcp-server-supabase/src/tools/cost-confirmation.ts
  2. Branch handler and pricing

    • packages/mcp-server-supabase/src/tools/branching-tools.ts
    • packages/mcp-server-supabase/src/pricing.ts
  3. Behavioral coverage

    • packages/mcp-server-supabase/src/server.test.ts
    • Inspect the capable, legacy, argument-binding, and project-scoped paths.
  • Is create_branch enabled only through the intended server path?
  • Does the discriminated state bind project ID, branch name, and BranchCost?
  • Do capable clients always use elicitation, even with a legacy ID?
  • Do accept, decline, cancellation, wrong-tool, and argument-change outcomes look correct?

Verification

  • pnpm --filter @supabase/mcp-server-supabase test src/server.test.ts -t "create_(project|branch) cost confirmation via elicitation" (17 passed)
  • pnpm --filter @supabase/mcp-server-supabase build (typecheck + build passed)
  • pnpm test:packed-platform-consumer (3 assertions passed)
  • pnpm exec biome ci . (passed)

Additional context

Commit 24535f1 chore: refresh management API types adds two generated lines to packages/mcp-server-supabase/src/management-api/types.ts because the CI generation gate detected upstream schema drift. Feature behavior and verification remain unchanged.

This does not change create_project, mcp-utils, platform adapters, stdio, pricing values, manifests, or docs.

Known limits: confirmations are not single-use, legacy IDs remain precomputable, and the branch rate remains the existing package constant.

AI-assisted; human review required.

@barryroodt
barryroodt requested a review from a team as a code owner August 29, 2026 07:18
@barryroodt barryroodt added the publish-preview Runs `publish-preview` workflow to publish preview packages via https://pkg.pr.new/ label Aug 29, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/@supabase/mcp-server-postgrest@6b363bb
pnpm add https://pkg.pr.new/@supabase/mcp-server-supabase@6b363bb
pnpm add https://pkg.pr.new/@supabase/mcp-utils@6b363bb

commit: 6b363bb

@barryroodt
barryroodt force-pushed the feat/branch-cost-elicitation branch from 24535f1 to c06b135 Compare August 31, 2026 08:15
@barryroodt barryroodt self-assigned this Aug 31, 2026
@coveralls

coveralls commented Aug 31, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 33730957759

Warning

No base build found for commit f9831db on feat/hosted-project-cost-elicitation.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 96.466%

Details

  • Patch coverage: 2 uncovered changes across 1 file (126 of 128 lines covered, 98.44%).

Uncovered Changes

File Changed Covered %
packages/mcp-server-supabase/src/tools/branching-tools.ts 111 109 98.2%
Total (3 files) 128 126 98.44%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 3375
Covered Lines: 3273
Line Coverage: 96.98%
Relevant Branches: 473
Covered Branches: 439
Branch Coverage: 92.81%
Branches in Coverage %: Yes
Coverage Strength: 58.11 hits per line

💛 - Coveralls

@barryroodt
barryroodt force-pushed the feat/branch-cost-elicitation branch from c06b135 to bb4f0a7 Compare September 1, 2026 08:42
Comment thread packages/mcp-server-supabase/src/server.test.ts
Comment thread packages/mcp-server-supabase/src/tools/branching-tools.ts
Comment thread packages/mcp-server-supabase/src/server.ts
Comment thread packages/mcp-server-supabase/src/pricing.ts
Comment thread packages/mcp-server-supabase/src/tools/branching-tools.ts Outdated

@Rodriguespn Rodriguespn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does read as #391 with the branch flow swapped in, and I can confirm the fixes from my earlier review all carried over (safe defaults on the elicitation action, drift check below decline/cancel, and the suffix driven off cost.recurrence rather than hardcoded).

Ran the elicitation suite locally (21 passing) plus a few probes of my own for the paths the suite doesn't cover, both cross-tool state and the single-tool enabledTools configs. Both behave correctly; I've left inline comments suggesting tests for them, plus a couple of small type/pricing notes. Nothing blocking.

One thing worth adding to the description: replay is better contained here than for projects. preview_branches has a real UNIQUE (project_id, branch_name) constraint (migration), so a replayed branch confirmation fails at the DB rather than relying on the read-then-check that covers create_project. Same known limit, weaker consequence.

@barryroodt
barryroodt force-pushed the feat/branch-cost-elicitation branch from 4382f06 to a00f977 Compare September 1, 2026 17:26
@mattrossman

Copy link
Copy Markdown
Collaborator

Could you explain if there is a path to test this with our stdio server?

I see here we don't populate costConfirmation on the stdio server. I'd think we want elicitations on stdio too, particularly because that's the workflow in local development to test the MCP package standalone w/ a real client. Is that something we can tie in to make verification easier?

+   costConfirmation: {
+      requestStateKey: randomBytes(32),
+      principal: 'local-stdio-user',
+      enabledTools: ['create_project', 'create_branch'],
+    },

I initially expected that old clients would continue to work the same as they have been with the old cost confirmation tools, and that new clients would use elicitations, regardless of transport. But from what I can tell, populating costConfirmation changes the tool schemas without consideration for elicitation support. I suppose for that to work, we'd need the tool signatures to vary based on capabilities, maybe by passing the SDK-provided context through into getTools() so it can see them on ctx.mcpReq.envelope.

@barryroodt

Copy link
Copy Markdown
Contributor Author

Could you explain if there is a path to test this with our stdio server?

I see here we don't populate costConfirmation on the stdio server. I'd think we want elicitations on stdio too, particularly because that's the workflow in local development to test the MCP package standalone w/ a real client. Is that something we can tie in to make verification easier?

+   costConfirmation: {
+      requestStateKey: randomBytes(32),
+      principal: 'local-stdio-user',
+      enabledTools: ['create_project', 'create_branch'],
+    },

I initially expected that old clients would continue to work the same as they have been with the old cost confirmation tools, and that new clients would use elicitations, regardless of transport. But from what I can tell, populating costConfirmation changes the tool schemas without consideration for elicitation support. I suppose for that to work, we'd need the tool signatures to vary based on capabilities, maybe by passing the SDK-provided context through into getTools() so it can see them on ctx.mcpReq.envelope.

Apologies for the confusion here - after numerous chats with @gregnr the decision was taken not to add elicitation support to stdio (due to it being deprecated). To do a full e2e test for elicitations you'll need to run the mcp server via the platform setup (PR #37722) - i'll update the linked Linear ticket with a detailed runbook on how to get that setup and running.

Thanks for taking the time to look at this.

Comment thread packages/mcp-server-supabase/src/tools/branching-tools.ts Outdated
Comment thread packages/mcp-server-supabase/src/tools/branching-tools.ts Outdated
Comment thread packages/mcp-server-supabase/src/tools/branching-tools.ts
Comment thread packages/mcp-server-supabase/src/tools/branching-tools.ts
Comment thread packages/mcp-server-supabase/src/server.test.ts
Comment thread packages/mcp-server-supabase/src/server.test.ts
Comment thread packages/mcp-server-supabase/src/server.ts

@mattrossman mattrossman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this by fusing changes from #402 onto the branch locally to connect to Claude Code launched w/ the modern protocol flags:

MCP_SDK_GENERATION=v2 MCP_PROTOCOL_NEGOTIATION=auto claude

This was my first time seeing elicitations working in Claude which was cool :) It was working as expected. Would love to get the HTTP dev server in so future stuff stays low lift to test.

Left a couple minor comments about test coverage opportunities. The Claude Code elicitation UI design is questionable w/ how it truncates info, but that's not something we have a lot control over and who knows how it'll change over time. Leaving it to you how you want to address the remaining feedback.

Noting this PR will partially address #285, in that the unavailable cost confirmation tools aren't needed w/ elicitations 🙌

@barryroodt
barryroodt force-pushed the feat/branch-cost-elicitation branch from 07425eb to 07c889d Compare September 3, 2026 07:35
…owances

Identify the $0.01344/hr rate and 30-day estimate in the create_branch
cost confirmation prompt as standard/list-price figures before plan
allowances or exemptions, and state that actual charges may be lower.
Pins the qualifier in the existing branch-prompt assertion.
Replace the hand-built RegExp matcher with two plain toMatchObject
checks: the existing hourly-rate substring check, plus a second check
for the standard/list-price qualifier phrase.
@barryroodt
barryroodt force-pushed the feat/branch-cost-elicitation branch from 07c889d to 6b363bb Compare September 3, 2026 07:59
@barryroodt
barryroodt merged commit 867a160 into main Sep 3, 2026
7 checks passed
@supabase-releaser supabase-releaser Bot mentioned this pull request Sep 2, 2026
barryroodt pushed a commit that referenced this pull request Sep 4, 2026
Makes `tools/list` adapt to `costConfirmation.enabledTools` to prevent
supported clients from getting prompted twice, from both the legacy
tools and elicitation strategy, surfaced when testing
supabase/platform#37717

The `tools` callback now receives the per-request `ServerContext` so
`tools/list` can vary by client:
- If both `create_project` and `create_branch` are in
`costConfirmation.enabledTools`, `get_cost` and `confirm_cost` are no
longer advertised. They stay callable, so a client that already knows
the names keeps working.
- If only one `create_` tool is enabled, the two cost tools are still
advertised because the other `create_` tool still needs a
`confirm_cost_id`, but their `type` argument is narrowed.
- `create_project` and `create_branch` drop `confirm_cost_id` when
they're in `enabledTools`, since a form-capable client confirms inline
and never needs it.

Legacy clients are unaffected.

## How to review

Run the server over HTTP w/ `costConfirmation` enabled for both tools
and connect a form-capable client (e.g. Claude Code w/ the SDK v2 flag
as described
[here](#394 (review))).
The tool list should have no `get_cost` or `confirm_cost`, and
`create_project` no longer has a `confirm_cost_id` argument. Connect
again without declaring `elicitation` and both tools are back.

## Notes

I renamed the test helper `setupFormCapable` to `setupModern` and made
its capabilities explicit via a `FORM_CAPABLE` constant, because
technically legacy clients can have form capabilities.

The #391 test "a supplied `confirm_cost_id` cannot bypass the form" is
now "is rejected". The field isn't in a form-capable client's schema
anymore, so strict arg parsing errors instead of routing to the form.
barryroodt pushed a commit that referenced this pull request Sep 4, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>mcp-utils: 0.8.0</summary>

##
[0.8.0](mcp-utils-v0.7.0...mcp-utils-v0.8.0)
(2026-09-04)


### Features

* **mcp:** add project cost confirmation elicitation
([#391](#391))
([fb50882](fb50882))
* **mcp:** hide legacy cost tools from form-capable clients
([#411](#411))
([2f04461](2f04461))
</details>

<details><summary>mcp-server-supabase: 0.12.0</summary>

##
[0.12.0](mcp-server-supabase-v0.11.0...mcp-server-supabase-v0.12.0)
(2026-09-04)


### Features

* group lints in `get_advisors` response
([#390](#390))
([48d593a](48d593a))
* **mcp:** add branch cost confirmation elicitation
([#394](#394))
([867a160](867a160))
* **mcp:** add project cost confirmation elicitation
([#391](#391))
([fb50882](fb50882))
* **mcp:** hide legacy cost tools from form-capable clients
([#411](#411))
([2f04461](2f04461))


### Bug Fixes

* ensure correct escaping in advisor
([#407](#407))
([a6cf4a0](a6cf4a0))
* make server instructions intent-based instead of naming tools
([#372](#372))
([fc54ea2](fc54ea2))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: supabase-releaser[bot] <223506987+supabase-releaser[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

publish-preview Runs `publish-preview` workflow to publish preview packages via https://pkg.pr.new/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants