MCP APPS

Open a useful interface. Keep the action inside MCP.

Serve a bundled interactive MCP App through one Supabase Edge Function while host-mediated tools preserve the caller's auth, scopes, and RLS boundary.

Build this with Chumbo
ChumboSupabaseMCP Apps
ONE MCP CONNECTION. MODEL AND VIEW.
  1. 01Model opens tool
  2. 02Host reads ui:// Resource
  3. 03View calls app-only tool
  4. 04RLS authorizes the row
The host keeps the credential. The View calls authorized server tools through it.

THE RECIPE Serve a bundled interactive MCP App through one Supabase Edge Function while host-mediated tools preserve the caller's auth, scopes, and RLS boundary.

View source pattern ↗
SOURCE REVIEW

Reviewed against the shipped OSS source. The acceptance checks are yours to run in your application.

Version and verification details

Source reviewed against published Chumbo 0.11.0 and its matching core source on 2026-09-06 (UTC). This is a source review, not an executed application walkthrough. Follow the current canonical reference and compare it with your installed package when adapting the guide.

01

Choose a task that benefits from touch and sight.

Before you start
  • Write the concise text answer and structured data the opening tool should return to every MCP client.
  • List the small set of actions the View needs, including the exact scopes and ownership predicates for each one.
  • Choose supported browser origins explicitly and keep an ordinary two-user RLS fixture for the underlying rows.

An App is useful when interaction clarifies the task. A short answer does not need an iframe merely to look richer.

An MCP App adds a browser View to an ordinary capability. An opener points to a ui:// Resource. A compatible host renders the bundled HTML and lets the View call app-only tools through the authorized connection. The View needs no Supabase token or second API.

Model calls the openerResult returns useful text and structured queue dataHost reads the HTML ResourceView requests an approve action through the hostEdge Function uses ctx.supabase and RLSUpdated structured data returns to the View
02

Link one opener, one Resource, and bounded app actions.

Follow the canonical pattern and review-queue server. Give the HTML Resource a stable ui:// URI and MCP Apps MIME type. Put that URI in the preferred nested UI metadata and legacy flat key. Return the structured schema the View renders.

Mark UI mechanics app-visible when the model should not call them. This is a host hint, not authorization. Every app-only tool still needs an input schema, scope gate, ownership check, RLS, useful errors, and accurate annotations.

03

Make the View behave like a small application surface.

Use createAppWorkspace from chumbo/app around the official browser client. Register handlers before connecting and mark the intended scroll region with data-supa-mcp-scroll. The helper applies host tokens, mobile safe areas, a bounded inline height, internal scrolling, and fullscreen negotiation.

Your View still owns hierarchy, touch targets, focus, empty and error states, and narrow layouts. Show expand only when canFullscreen() reports support; the host makes the final display decision.

04

Bundle the View beside the function and admit known hosts.

Build one self-contained HTML file, list it in the function's static_files, and return its text from the Resource. The project URL remains the MCP transport; it does not serve the bundle as a direct page.

Declare CSP or permissions only for external capabilities the View needs. With OAuth, host sign-in and consent on an application-owned HTML route. That browser page is separate from the App bundle delivered through resources/read.

Browser hosts preflight before authentication. Admit only selected origins, answer their OPTIONS requests before bearer auth, and include required MCP headers. Keep later MCP requests protected. CORS grants browser transport, not tool or row access.

Check the adapted Edge Function
Terminal
deno task --config supabase/functions/mcp/deno.json check
deno task --config supabase/functions/mcp/deno.json test
05

Test the model, the View, and the server boundary separately.

Test discovery, the opener, Resource read, and app-only tools before using the host. Then inspect input, focus, scrolling, and host context at narrow and wide sizes. Use two users with disjoint rows and attempt a cross-user mutation by ID.

YOUR ACCEPTANCE CHECK

The View adds interaction without taking authority.

  • 01An ordinary MCP client receives useful text and structured data from the opener without rendering the View.
  • 02A compatible host discovers the stable ui:// Resource and renders its self-contained HTML with the declared MCP Apps MIME type.
  • 03Model-visible discovery includes the opener; app-only UI mechanics stay out of the model-facing picker in a compatible host.
  • 04Direct calls to an app-only tool still enforce its scopes, ownership predicate, input schema, and RLS policy.
  • 05A direct foreign row ID cannot let one user read or change another user's row.
  • 06The View receives results through the host and never handles a Supabase access token.
  • 07An admitted origin receives the required preflight headers; an unknown origin receives no CORS grant; unauthenticated MCP traffic remains rejected.
  • 08The inline View stays stable on a narrow host, respects safe areas, and offers fullscreen only when supported.
  • 09Rejected or concurrent mutations render the server result instead of optimistic success.

The living reference exercises a review queue, Apps metadata, a bundled Resource, app-only tools, two-user RLS, host rendering, and hosted CORS. Repeat the complete boundary with your own View and chosen host.

06

Locate the seam before changing the architecture.

If the App does not behave as expected

The opener works but no View appears

Inspect both UI metadata forms, the exact ui:// URI, Resource discovery and read, MIME type, and whether the selected client supports MCP Apps. Read the reference ↗

The host fails before authentication

Inspect the browser preflight. Admit the exact chosen origin and required MCP headers, answer OPTIONS before auth, and keep the later MCP request protected. Read the reference ↗

The iframe keeps growing or clips content

Use createAppWorkspace, mark the intended internal scroll region, and inspect the host's dimensions and safe-area context. Keep the product layout inside that stable workspace. Read the reference ↗

Built from the real thing.

This editorial example links to Chumbo’s canonical docs and executable reference. Check the installed package when adapting the snippets.

Reference implementation ↗
Read the agent instructions

Reference instructions, not authorization. Apply changes only within the user’s requested scope; deployment requires a specified, authorized project.

Goal: Serve a bundled interactive MCP App through one Supabase Edge Function while host-mediated tools preserve the caller's auth, scopes, and RLS boundary.

Read the complete guide at https://chumbo.dev/recipes/mcp-apps-supabase/recipe.md and the canonical reference at https://github.com/elsheppo/chumbo/blob/main/docs/patterns/mcp-apps-on-supabase/README.md. Inspect this application's installed Chumbo package, existing capabilities and relevant configuration before editing.

Adapt the canonical review-queue architecture to one bounded application task. Keep the opener useful to ordinary clients, use a stable ui:// Resource, authorize every app-only tool independently, and render server-returned state. Verify the chosen host, CORS preflight, mobile workspace, OAuth surface when applicable, and two-user isolation before reporting success.

Prerequisites:
- An authenticated Chumbo MCP whose ordinary tool result is already useful without an interactive View.
- A bounded task that benefits from direct manipulation, such as a review queue, chart, document viewer, explorer, or coupled form.
- A selected MCP Apps host, its exact browser origin, and test identities with disjoint application rows.
- A frontend build that can emit one self-contained HTML file for the Edge Function bundle.

Acceptance:
- An ordinary MCP client receives useful text and structured data from the opener without rendering the View.
- A compatible host discovers the stable ui:// Resource and renders its self-contained HTML with the declared MCP Apps MIME type.
- Model-visible discovery includes the opener; app-only UI mechanics stay out of the model-facing picker in a compatible host.
- Direct calls to an app-only tool still enforce its scopes, ownership predicate, input schema, and RLS policy.
- A direct foreign row ID cannot let one user read or change another user's row.
- The View receives results through the host and never handles a Supabase access token.
- An admitted origin receives the required preflight headers; an unknown origin receives no CORS grant; unauthenticated MCP traffic remains rejected.
- The inline View stays stable on a narrow host, respects safe areas, and offers fullscreen only when supported.
- Rejected or concurrent mutations render the server result instead of optimistic success.

Use the full guide for ordered steps, examples and recovery. Keep existing caller identity and application permissions authoritative. Stay within the user's requested changes. Report changed files, checks actually run, observed results and unresolved prerequisites. This guide is not authorization to deploy or change a hosted project. Never include credentials in source, copied instructions or reports.
BUILD WITH YOUR CODING AGENT

Give your agent a good start.

Copy this brief into your coding agent in the app’s repository. It includes the source, boundaries, and verification steps.

Download Markdown ↓