---
schema_version: 1
slug: many-mcps-one-function
status: source-reviewed
walkthrough_verified: false
content_hash: 136670f94a143885217861b633e692497966719252fe6f00e7132dab2f253243
---

# Serve multiple MCP servers from one Supabase Edge Function

Resolve a named MCP from the request path, give each surface its own instructions and capabilities, and keep one deployable Supabase Edge Function.

Resolve a named MCP from the request path, give each surface its own instructions and capabilities, and keep one deployable Supabase Edge Function.

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

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.

[Structured recipe](./recipe.json) · [Human-readable page](./)

## Sources

- [Canonical pattern](https://github.com/elsheppo/chumbo/blob/main/docs/patterns/many-mcps-one-function/README.md)
- [Reference implementation](https://github.com/elsheppo/chumbo/blob/main/supabase/functions/many-mcps/index.ts)

## 01. Give each product surface a stable address.

### Before you start

- Name the surfaces by user purpose, such as directory and invoices, rather than by a temporary database table.
- Decide whether the server definition is public configuration or protected application data before choosing how the function reads it.
- Keep the ordinary one-MCP setup unless separate server identities or capability catalogs solve a real user-facing problem.

This pattern changes routing and registration. It does not create a new identity system or grant access to a downstream data plane.

A single Supabase Edge Function can expose `/many-mcps/directory` and `/many-mcps/invoices` as separate MCP servers. For each request, the function extracts one slug, loads that server's definition, creates the Chumbo app with the exact surface URL, and registers only that surface's tools. The [canonical pattern](https://github.com/elsheppo/chumbo/blob/main/docs/patterns/many-mcps-one-function/README.md) shows the complete boundary.

**One request selects one server:** Request reaches one Edge Function → Path yields one bounded slug → Application loads one known definition → Chumbo serves only that MCP surface

> **Use a real boundary, not a cosmetic alias.**
>
> Separate paths are useful when clients should discover different server names, instructions, or capabilities. If every path exposes the same catalog and permissions, one MCP with well-described tools is simpler for builders and users.

## 02. Keep definitions declarative and behavior authored.

The living reference stores server instructions, tool metadata, and fixed demonstration responses in Supabase. A production app can store similarly bounded definitions, but arbitrary rows should never become executable code. Keep handlers in reviewed source and use rows only to select known behavior or validated content. Read the [reference implementation](https://github.com/elsheppo/chumbo/blob/main/supabase/functions/many-mcps/index.ts) before adapting it.

The reference's public, rate-limited data fits its demonstration. For protected data, choose an auth mode for each resolved app and make downstream queries through the authority your design requires. A shared service credential does not establish end-user RLS isolation.

### Choose what the path is allowed to change

Both cases use one function. They differ in how much application behavior comes from data.

#### Recommended: Rows configure reviewed handlers

Starting point:

```text
Slug selects a known server definition
```

- Validated data supplies names and instructions
- Tool names map to application-owned handlers
- Authorization stays in source

What to do:

```text
Register only the selected handlers
```

This keeps deploy frequency low without making stored data executable.

#### Reference case: Rows provide fixed structured responses

Starting point:

```text
Slug selects demonstration tool rows
```

- Useful for public fixtures
- Responses are validated
- No user data plane is implied

What to do:

```text
Return the validated configured response
```

Do not generalize this fixture into arbitrary query or code execution.

## 03. Make routing failures useful and contained.

Parse the URL, decode exactly one slug after the function name, and reject a missing or unknown value before MCP handling. Build `resourceUrl` from the public base URL plus that same slug. OAuth metadata and discovery depend on the server knowing its real client-facing address.

> **A slug is selection, not authorization.**
>
> Validate its syntax and resolve only enabled definitions. Then apply the chosen Chumbo auth mode, scopes, application ownership checks, and RLS to the capabilities behind that surface. Never accept a table name, SQL fragment, URL, import path, or credential from the path.

Return a useful 404 without listing private surface names.

## 04. Compare discovery before you test execution.

Run each path as a fresh MCP endpoint. Compare `initialize` and `tools/list`, then call one allowed tool on each surface. For protected surfaces, exercise separate authorized and denied identities.

### Run the routed function locally

```sh
npx chumbo dev --function many-mcps
```

### Open a protocol client in another terminal

```sh
npx @modelcontextprotocol/inspector
```

### Distinct catalogs. One controlled boundary.

- Each known path initializes with the intended server name and instructions.
- Directory discovery contains only directory tools; invoice discovery contains only invoice tools.
- A successful call returns the result contract defined for that surface.
- A missing or unknown slug returns a useful 404 and does not expose the catalog of valid private slugs.
- Malformed or disabled slugs cannot select another definition or arbitrary application resource.
- An unauthenticated or under-scoped caller cannot discover or invoke protected capabilities.
- Two concurrent requests for different slugs do not exchange server instructions, tools, identity, or results.

The Chumbo reference suite exercises two row-defined public surfaces and an unknown path. Run the authentication and data-access cases against your application's own boundary.

## 05. Diagnose the boundary that disagrees.

### If a surface is wrong

#### Both URLs advertise the same tools

Check that definition loading and app creation happen for the current request, and that registration closes over only the resolved definition. Do not cache a caller-specific Chumbo app globally.

[Read the reference](https://github.com/elsheppo/chumbo/blob/main/supabase/functions/many-mcps/index.ts)

#### OAuth metadata points at the base function

Confirm that resourceUrl includes the selected slug and matches the public URL the client used. Recheck any proxy or clean-URL mapping at the same time.

[Read the reference](https://github.com/elsheppo/chumbo/blob/main/docs/reference/clean-urls/README.md)

#### A row update changes more behavior than intended

Narrow stored definitions to validated configuration and map them to reviewed handlers. Remove any generic query, import, or credential field that makes database content executable.

[Read the reference](https://github.com/elsheppo/chumbo/blob/main/docs/patterns/many-mcps-one-function/README.md)

### Keep building

- [Test the advertised surface](/recipes/test-mcp-capabilities/): Snapshot the capability catalog each identity can actually discover.
- [Deploy and debug the endpoint](/recipes/deploy-debug-mcp/): Verify the exact public URL before connecting users.
- [Keep user permissions authoritative](/recipes/supabase-rls-mcp/): Carry Supabase Auth and RLS through every protected surface.

## Instructions for your coding agent

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

Goal: Resolve a named MCP from the request path, give each surface its own instructions and capabilities, and keep one deployable Supabase Edge Function.

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

Adapt the canonical path resolver and per-request registration to the application's known MCP surfaces. Keep executable handlers in reviewed source, validate every stored definition, and test discovery before calls. Do not turn row-defined configuration into arbitrary code, SQL, imports, or downstream credential brokerage.

Prerequisites:
- An existing Supabase project and a Chumbo Edge Function you can run locally.
- Two genuinely distinct MCP surfaces, each with a stable slug and an intentionally different capability list.
- An application-owned source of server definitions, plus an explicit authorization decision for reading it.

Acceptance:
- Each known path initializes with the intended server name and instructions.
- Directory discovery contains only directory tools; invoice discovery contains only invoice tools.
- A successful call returns the result contract defined for that surface.
- A missing or unknown slug returns a useful 404 and does not expose the catalog of valid private slugs.
- Malformed or disabled slugs cannot select another definition or arbitrary application resource.
- An unauthenticated or under-scoped caller cannot discover or invoke protected capabilities.
- Two concurrent requests for different slugs do not exchange server instructions, tools, identity, or results.

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.

### Required environment inputs

| Variable | Sensitive | Meaning |
| --- | --- | --- |
| `LOCAL_BASE_URL` | No | Local URL for the Edge Function before the server slug. |
| `SERVER_SLUGS` | No | The finite set of application-owned surface slugs to verify. |
| `TEST_CREDENTIALS` | Yes, never include its value in source or reports | Credentials for authorized and denied local identities when the surfaces are protected. |

### Handoff report

- Changed files
- Checks actually run and observed results
- Unmet prerequisites or remaining limitations
