---
schema_version: 1
slug: design-useful-mcp-tools
status: contract-tested
walkthrough_verified: false
content_hash: 64edf83efb7313a5d32d6bcfe7a61e3c9f7337ef58b1c33b2617c5346edc0dbd
---

# Design useful MCP tools for a Supabase application with Chumbo

Turn an existing product operation into a narrow MCP tool with clear inputs, caller-owned access and a result the agent can use.

Turn an existing product operation into a narrow MCP tool with clear inputs, caller-owned access and a result the agent can use.

The exact displayed example is typechecked and exercised against the published package. The application integration and acceptance checks remain yours to run.

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.

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

## Sources

- [Canonical pattern](https://github.com/elsheppo/chumbo/blob/main/skills/chumbo/references/build-capabilities.md)
- [Reference implementation](https://github.com/elsheppo/chumbo/blob/main/templates/function/capabilities.ts.tpl)

## 01. Start from a question a user would ask.

In a project tracker, “Show me my projects” is a recognizable task. “Query the projects table” describes an implementation. The first framing helps you choose a small input contract, a useful output and an access rule that the application already owns.

### One task, two possible interfaces

The tool name and inputs shape what an agent can safely attempt.

#### Generic data access: The client must understand your schema

Starting point:

```text
update_table(table, columns, filters)
```

- The model chooses storage fields and arbitrary changes.
- The product meaning and authority are difficult to see from the contract.

What to do:

```text
Replace this with an existing application operation.
```

This is a design comparison; no generic database operation is exposed here.

#### Application operation: The contract expresses the task

Starting point:

```text
list_projects()
```

- The caller’s session determines which rows are visible.
- The result contains recognizable names and stable IDs.

What to do:

```text
Choose a project and invoke the next narrow capability when needed.
```

A domain ID identifies a target; it never establishes caller authority.

## 02. Write the smallest contract that answers it.

### Before you start

- An existing authenticated MCP and a clear read operation from your application.
- For this exact example, a projects table with UUID id and text name, appropriate grants and tested RLS.
- Known local project fixtures for two separate users.

If list_projects already exists, adapt that registration deliberately rather than registering the same name twice.

Describe what the capability does and what the result means. Inputs should be values the task needs, not a bag of arbitrary SQL filters. This first listing needs no user ID because the request already has an authenticated caller. It reads through ctx.supabase and limits the result to ten projects.

## 03. Keep the handler close to the application meaning.

### Adapt this checked example in your app

```ts
import {
  errorResult, textResult,
  type SupabaseMcpContext,
  type SupabaseMcpServer,
} from "chumbo";
import { z } from "zod";

export function registerCapabilities(
  server: SupabaseMcpServer,
  ctx: SupabaseMcpContext,
) {
  server.registerTool("list_projects", {
    description: "Show up to 10 of your projects in ID order.",
    inputSchema: z.object({}),
    annotations: { readOnlyHint: true },
  }, async () => {
    const { data, error } = await ctx.supabase
      .from("projects")
      .select("id, name")
      .order("id")
      .limit(10)
      .overrideTypes<{ id: string; name: string }[], { merge: false }>();

    if (error) return errorResult(
      "Could not load your projects.",
      "Retry; if this continues, contact your app's support."
    );

    return textResult(data?.length
      ? data.map(p => `- ${p.name} (${p.id})`).join("\n")
      : "No projects are visible to you. Create one in the app, then try again.");
  });
}
```

The handler selects two fields and writes a readable result. Database error details are not useful instructions for a user’s agent, so the failure response gives a next step instead. An empty result is treated as a valid outcome; it does not automatically mean authentication failed.

This limited list is a starting point. If users need the complete collection, add the bounded pagination contract linked below. Silently treating the first ten results as every project would turn a sensible limit into a misleading answer.

## 04. Review the tool as a caller would encounter it.

### A caller can understand and use the tool.

- Discovery exposes a meaningful name, description and input contract.
- The exact expected project fixture appears with recognizable names and durable IDs.
- Empty and error cases tell the caller what happened and a useful next step.
- No input can choose a different caller’s identity; separate user fixtures remain separate at the real MCP boundary.
- The agent is not led to believe that a capped list is a complete collection.

Run these in your application before you call it done.

### If the result differs

#### The agent keeps passing irrelevant fields

Inspect the input schema and description. Remove arguments that do not affect the task, make required domain values explicit and provide a concise example in your server guidance.

[Read the reference](https://github.com/elsheppo/chumbo/blob/main/skills/chumbo/references/build-capabilities.md)

### Keep building

- [Add bounded pagination](/recipes/compact-project-results/): Return a small page with a precise continuation.
- [Design the next action](/recipes/mutation-receipts/): Expose a narrow write and report what actually changed.

## 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: Turn an existing product operation into a narrow MCP tool with clear inputs, caller-owned access and a result the agent can use.

Read the complete guide at https://chumbo.dev/recipes/design-useful-mcp-tools/recipe.md and the canonical reference at https://github.com/elsheppo/chumbo/blob/main/skills/chumbo/references/build-capabilities.md. Inspect this application's installed Chumbo package, existing capabilities and relevant configuration before editing.

Implement the product task rather than generic table access. Use verified caller context, narrow inputs and a useful projected result. Keep the ten-row limit explicit; use the pagination guide when completeness matters. Verify the real application with disjoint user fixtures.

Prerequisites:
- An existing authenticated MCP and a clear read operation from your application.
- For this exact example, a projects table with UUID id and text name, appropriate grants and tested RLS.
- Known local project fixtures for two separate users.

Acceptance:
- Discovery exposes a meaningful name, description and input contract.
- The exact expected project fixture appears with recognizable names and durable IDs.
- Empty and error cases tell the caller what happened and a useful next step.
- No input can choose a different caller’s identity; separate user fixtures remain separate at the real MCP boundary.
- The agent is not led to believe that a capped list is a complete collection.

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 |
| --- | --- | --- |


### Handoff report

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