{
  "schemaVersion": 1,
  "kind": "recipe",
  "slug": "scoped-mcp-capabilities",
  "title": "Show callers what they can use. Reject what they cannot.",
  "searchTitle": "Gate Supabase MCP tools by scopes and application API keys with Chumbo",
  "description": "Serve different capability sets from one MCP using application-resolved scopes, with authorization on discovery and direct calls.",
  "summary": "Serve different capability sets from one MCP using application-resolved scopes, with authorization on discovery and direct calls.",
  "evidence": {
    "status": "source-reviewed",
    "packageVersion": "0.11.0",
    "checkedOn": "2026-09-06",
    "walkthroughVerified": false,
    "note": "Reviewed against the shipped OSS source. The acceptance checks are yours to run in your application.",
    "versionPolicy": "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."
  },
  "representations": {
    "html": "./",
    "markdown": "./recipe.md",
    "json": "./recipe.json"
  },
  "sources": [
    "https://github.com/elsheppo/chumbo/blob/main/docs/patterns/privileged-capabilities/README.md",
    "https://github.com/elsheppo/chumbo/blob/main/supabase/functions/privileged-capabilities/index.ts"
  ],
  "prerequisites": [
    "Two real caller types with different application-authorized capabilities.",
    "An existing source of permissions or a narrow resolver that can supply scopes.",
    "For application keys, a verifier with a stable subject, a unique routing prefix in multi-auth and a defined data-access path."
  ],
  "sections": [
    {
      "id": "surface",
      "number": "01",
      "title": "Different callers can need different tools.",
      "blocks": [
        {
          "type": "paragraph",
          "text": "A catalog reader needs to find items. A publishing integration also needs to publish an approved item. Keeping both on one endpoint can be useful if your app already recognizes these caller relationships. Chumbo’s scopes let each request discover the capabilities its resolved permissions allow."
        },
        {
          "type": "comparison",
          "title": "Two callers, one endpoint",
          "description": "A hidden tool must also reject a direct call by name.",
          "callLabel": "Starting point",
          "nextLabel": "What to do",
          "cases": [
            {
              "id": "reader",
              "label": "Reader",
              "title": "Read the catalog",
              "items": [
                "list_catalog is discoverable.",
                "publish_item is absent."
              ],
              "call": "Caller resolves catalog:read.",
              "next": "Reject a direct publish_item call without invoking its handler.",
              "note": "Omitting a tool from a UI is insufficient."
            },
            {
              "id": "publisher",
              "label": "Publisher",
              "title": "Read and publish",
              "items": [
                "Both capabilities are discoverable.",
                "The publish operation still checks application data authority."
              ],
              "call": "Caller resolves catalog:read and catalog:publish.",
              "next": "Allow an eligible publish through the application’s authorized write path.",
              "note": "A scope gate does not replace grants, RLS or domain checks."
            }
          ]
        }
      ]
    },
    {
      "id": "start",
      "number": "02",
      "title": "Resolve permissions from your application.",
      "blocks": [
        {
          "type": "prerequisites",
          "title": "Before you start",
          "items": [
            "Two real caller types with different application-authorized capabilities.",
            "An existing source of permissions or a narrow resolver that can supply scopes.",
            "For application keys, a verifier with a stable subject, a unique routing prefix in multi-auth and a defined data-access path."
          ],
          "note": "Chumbo does not define owner/admin roles, membership tables or an entitlement model. Choose your own scope vocabulary."
        },
        {
          "type": "paragraph",
          "text": "Start from the canonical privileged-capabilities reference. Its multi auth configuration combines a Supabase user strategy with an application-key strategy. Give verifier-backed keys a unique tokenPrefix so routing selects the intended verifier before authentication. A failed matching key must not fall through and become another identity."
        },
        {
          "type": "paragraph",
          "text": "Use access.resolveScopes when your existing application owns the permission decision. Keep it authoritative: accepting a scope string from a tool argument or user-editable profile field would let the caller choose their own permissions."
        }
      ]
    },
    {
      "id": "register",
      "number": "03",
      "title": "Apply the gate where capabilities are registered.",
      "blocks": [
        {
          "type": "flow",
          "title": "The scoped request",
          "steps": [
            "Verified identity",
            "Application scopes",
            "withScopes registration",
            "Discovery and invocation"
          ]
        },
        {
          "type": "paragraph",
          "text": "Register restricted tools, Resources and prompts through server.withScopes. The gated registration participates in the request’s isolated server, so a missing scope affects both listing and calling. Keep request-aware instructions consistent with that surface: a reader should not be told to use a publisher-only action."
        },
        {
          "type": "paragraph",
          "text": "User requests retain their Supabase identity and caller-scoped client. Application-key requests receive the verifier’s stable subject and scopes with an anonymous Supabase client. Use your existing narrow RPC or application API for privileged work. The verifier-only administrative client is not a capability-handler escape hatch."
        }
      ]
    },
    {
      "id": "verify",
      "number": "04",
      "title": "Try the name the reader was never shown.",
      "blocks": [
        {
          "type": "paragraph",
          "text": "The testing guide below includes an executable scoped-discovery fixture through chumbo/testing. Use that to test runtime composition with controlled identities. Then use your real auth, key verifier and database fixtures to prove the application permission boundary."
        },
        {
          "type": "acceptance",
          "title": "Denied capabilities never reach their handlers.",
          "items": [
            "A reader’s tools/list omits the restricted tool and their direct tools/call is rejected.",
            "A permitted caller discovers the tool and can perform an eligible operation.",
            "Restricted Resources and prompts receive equivalent direct-access checks.",
            "An invalid key with the application prefix does not fall back to the user strategy.",
            "A reader request after a privileged request still has only the reader’s surface and data access."
          ],
          "note": "Run these in your application before you call it done."
        },
        {
          "type": "troubleshooting",
          "title": "If the result differs",
          "items": [
            {
              "title": "The tool is hidden but its name can still be called",
              "body": "Check that the registration itself uses withScopes. Filtering a list or hiding a button does not enforce invocation. Add an assertion that the denied request leaves the mutation handler’s call count at zero.",
              "link": "https://github.com/elsheppo/chumbo/blob/main/docs/patterns/privileged-capabilities/README.md"
            }
          ]
        },
        {
          "type": "related",
          "title": "Keep building",
          "items": [
            {
              "title": "Test discovery and direct invocation",
              "text": "Use a controlled protocol fixture and separate real integration checks.",
              "label": "NEXT GUIDE",
              "href": "/recipes/test-mcp-capabilities/"
            },
            {
              "title": "Return a useful write receipt",
              "text": "Show the confirmed result of the authorized action.",
              "label": "NEXT GUIDE",
              "href": "/recipes/mutation-receipts/"
            }
          ]
        }
      ]
    }
  ],
  "agent": {
    "goal": "Serve different capability sets from one MCP using application-resolved scopes, with authorization on discovery and direct calls.",
    "executionPolicy": "Reference instructions, not authorization. Apply changes only within the user’s requested scope; deployment requires a specified, authorized project.",
    "inputs": [],
    "instructions": "Goal: Serve different capability sets from one MCP using application-resolved scopes, with authorization on discovery and direct calls.\n\nRead the complete guide at https://chumbo.dev/recipes/scoped-mcp-capabilities/recipe.md and the canonical reference at https://github.com/elsheppo/chumbo/blob/main/docs/patterns/privileged-capabilities/README.md. Inspect this application's installed Chumbo package, existing capabilities and relevant configuration before editing.\n\nReuse application-owned scopes and identities. Follow canonical multi-auth prefix routing and withScopes registration. Verify absent discovery plus rejected direct invocation, no handler side effects, invalid prefixed-key non-fallback and request isolation. Do not introduce a new role/schema model or imply that API keys create user-scoped Supabase clients.\n\nPrerequisites:\n- Two real caller types with different application-authorized capabilities.\n- An existing source of permissions or a narrow resolver that can supply scopes.\n- For application keys, a verifier with a stable subject, a unique routing prefix in multi-auth and a defined data-access path.\n\nAcceptance:\n- A reader’s tools/list omits the restricted tool and their direct tools/call is rejected.\n- A permitted caller discovers the tool and can perform an eligible operation.\n- Restricted Resources and prompts receive equivalent direct-access checks.\n- An invalid key with the application prefix does not fall back to the user strategy.\n- A reader request after a privileged request still has only the reader’s surface and data access.\n\nUse 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.",
    "steps": [
      {
        "id": "surface",
        "title": "Different callers can need different tools.",
        "commands": []
      },
      {
        "id": "start",
        "title": "Resolve permissions from your application.",
        "commands": []
      },
      {
        "id": "register",
        "title": "Apply the gate where capabilities are registered.",
        "commands": []
      },
      {
        "id": "verify",
        "title": "Try the name the reader was never shown.",
        "commands": []
      }
    ],
    "acceptance": [
      "A reader’s tools/list omits the restricted tool and their direct tools/call is rejected.",
      "A permitted caller discovers the tool and can perform an eligible operation.",
      "Restricted Resources and prompts receive equivalent direct-access checks.",
      "An invalid key with the application prefix does not fall back to the user strategy.",
      "A reader request after a privileged request still has only the reader’s surface and data access."
    ],
    "report": [
      "Changed files",
      "Checks actually run and observed results",
      "Unmet prerequisites or remaining limitations"
    ]
  },
  "topics": [
    "connect-users",
    "advanced-patterns"
  ],
  "updated": "2026-09-05",
  "contentHash": "34b50457825220a2df4b1f06d0449ca08a75efb556bdb45b94cf93e23ba5673b"
}
