The exact displayed example is typechecked and exercised against the published package. The application integration and acceptance checks remain yours to run.
Version and verification details
Example checked with Chumbo 0.11.3 on 2026-09-18 (UTC). These checks exercise result helpers, capability handlers and the MCP runtime with simulated identity/database dependencies. They do not prove your application’s real auth, RLS or deployment. Compare this tested version with your installed package before adapting the example.
Keep a test beside the capability it protects.
A recipe becomes dependable when its promised behavior has a test at the right boundary. “A reader cannot submit a review” should assert both the missing tool in discovery and the rejected direct invocation. Matching a function name in a list alone would miss the more consequential failure.
The published chumbo/testing entry point lets your tests inject runtime dependencies. It is useful for deterministic protocol and composition checks. A fake identity verifier makes the test predictable; it also means the test says nothing about whether a real Supabase token is valid.
Create a test-only runtime fixture.
Before you start
- A Node/TypeScript application test project with the published chumbo package and zod available.
- A test-only file that cannot become your deployed server entrypoint.
- Separate real Supabase fixtures for claims about token verification, RLS or persisted writes.
The example uses synthetic reader/writer identities and an in-memory write counter. It makes no database query and must not be deployed.
Add the fixture below to your app’s test files. Adapt the registration to your actual capability composition, while keeping the assertions focused on observable behavior. The ordinary deployed entrypoint should continue using createSupabaseMcp from chumbo, without these test dependency overrides.
Drive the actual fetch boundary.
import { textResult } from 'chumbo';
import { createSupabaseMcpForTesting, type RuntimeDependencies } from 'chumbo/testing';
import { z } from 'zod';
const endpoint = 'https://fixture.example/mcp';
// TEST ONLY. These stand-ins do not verify JWTs or query Postgres.
export function createFixture() {
const dependencies: RuntimeDependencies = {
async verifyToken(token) {
if (!['reader', 'writer'].includes(token)) throw new Error('Unknown fixture identity');
return { token, userClaims: { id: token, role: 'authenticated' }, jwtClaims: { sub: token, exp: Math.floor(Date.now() / 1000) + 3600 } };
},
createClient() { return {} as ReturnType<RuntimeDependencies['createClient']>; },
createAdminClient() { throw new Error('This fixture must not use admin access'); },
fetch: globalThis.fetch, now: Date.now, randomUUID: () => crypto.randomUUID(),
};
let writes = 0;
const app = createSupabaseMcpForTesting({
server: { name: 'review-fixture', version: '1.0.0' }, resourceUrl: endpoint,
auth: { mode: 'bearer' },
access: { resolveScopes: ctx => ctx.user?.id === 'writer' ? ['review:read', 'review:write'] : ['review:read'] },
register(server) {
server.withScopes(['review:read']).registerTool('read_review', { inputSchema: z.object({}) }, async () => textResult('Review is ready.'));
server.withScopes(['review:write']).registerTool('submit_review', { inputSchema: z.object({}) }, async () => { writes++; return textResult('Review submitted.'); });
},
}, dependencies);
return { app, writes: () => writes };
}
export function request(method: string, token?: string, params: Record<string, unknown> = {}) {
const headers = new Headers({ 'content-type': 'application/json', 'mcp-method': method, 'mcp-protocol-version': '2026-07-28' });
if (token) headers.set('authorization', `Bearer ${token}`);
if (typeof params.name === 'string') headers.set('mcp-name', params.name);
return new Request(endpoint, { method: 'POST', headers, body: JSON.stringify({
jsonrpc: '2.0', id: crypto.randomUUID(), method,
params: { ...params, _meta: {
'io.modelcontextprotocol/protocolVersion': '2026-07-28',
'io.modelcontextprotocol/clientInfo': { name: 'app-contract-test', version: '1.0.0' },
'io.modelcontextprotocol/clientCapabilities': {},
} },
}) });
}Call fixture.app.fetch(request(...)) from your test runner. Parse the JSON response and compare exact discovered names. Then call submit_review as reader and assert an error plus fixture.writes() equal to zero. Repeat as writer and assert the expected result and one write. Finally list again as reader to catch leaked request permissions.
The request helper encodes the protocol used by the shipped package fixture. Keep this wire helper aligned with the package’s generated tests when upgrading; application tests should not silently send a different protocol than the client they intend to simulate.
Add the evidence the fixture deliberately leaves out.
Run the generated function’s check and test tasks after adapting its capabilities. Add real Supabase integration tests with two users, known disjoint data and invalid credentials when access behavior is part of the promise. For a mutation, inspect persisted state and competing requests in the real application.
deno task --config supabase/functions/mcp/deno.json check
deno task --config supabase/functions/mcp/deno.json testIf you need an audit of the tools a caller actually discovered, the runtime’s onSurface callback emits a surface proof after a complete successful tools/list. It includes a canonical digest of the advertised tools. This can help identify surface changes; it is not evidence that every tool succeeded or that the callback was durably stored. The application owns persistence.
The test fails when the promised boundary breaks.
- 01Missing and invalid fixture credentials receive HTTP 401.
- 02Reader and writer discovery contain the exact expected tool names.
- 03A direct reader call to submit_review is rejected without incrementing the write counter.
- 04The writer succeeds, and a subsequent reader request remains restricted.
- 05Separate real integration tests cover token verification, database access and persisted outcomes wherever the application claims them.
Run these in your application before you call it done.
If the result differs
All fixture tests pass but production access is wrong
Check which dependencies your test replaced. A stubbed verifier, query or state store cannot establish the behavior of the real one. Reproduce with the generated app and real Supabase fixtures before widening permissions or changing auth. Read the reference ↗
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: Exercise the Chumbo MCP boundary with controlled dependencies, then keep real Supabase authentication and RLS checks as a separate integration layer. Read the complete guide at https://chumbo.dev/recipes/test-mcp-capabilities/recipe.md and the canonical reference at https://github.com/elsheppo/chumbo/blob/main/skills/chumbo/references/run-deploy-verify.md. Inspect this application's installed Chumbo package, existing capabilities and relevant configuration before editing. Place the displayed runtime dependency fixture only in test code. Assert exact discovery, denied direct calls with zero handler effects, permitted calls and subsequent caller isolation. Keep genuine JWT/RLS/persistence integration separate. Use onSurface only as proof of complete successful tool discovery, not execution or durable delivery. Prerequisites: - A Node/TypeScript application test project with the published chumbo package and zod available. - A test-only file that cannot become your deployed server entrypoint. - Separate real Supabase fixtures for claims about token verification, RLS or persisted writes. Acceptance: - Missing and invalid fixture credentials receive HTTP 401. - Reader and writer discovery contain the exact expected tool names. - A direct reader call to submit_review is rejected without incrementing the write counter. - The writer succeeds, and a subsequent reader request remains restricted. - Separate real integration tests cover token verification, database access and persisted outcomes wherever the application claims them. 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.
