From ac059db32649b3e6229af6c30d306b801d0db780 Mon Sep 17 00:00:00 2001 From: Kyle Bernhardy Date: Wed, 8 Jul 2026 17:42:16 -0600 Subject: [PATCH 1/2] Gate the quota-hook example's table off the MCP surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The example exported the counter table, which surfaces update_/delete_ verb tools and REST — letting a permitted client reset its own quota. Found by a blind agent evaluation of the harper-mcp skill (skills#69). Co-Authored-By: Claude Fable 5 --- reference/mcp/configuration.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/reference/mcp/configuration.md b/reference/mcp/configuration.md index aefb03ed..f4d6396e 100644 --- a/reference/mcp/configuration.md +++ b/reference/mcp/configuration.md @@ -153,6 +153,12 @@ mcp: // resources.js const DAILY_LIMIT = 100; export class McpQuota extends tables.QuotaCounter { + // Exporting makes the class config-addressable — but it would also surface + // update_/delete_McpQuota MCP tools and a REST endpoint, letting a + // permitted client reset its own counter. Keep the quota table off the MCP + // surface and restrict its REST permissions. + static exportTypes = { mcp: false }; + static async allowMcpCall({ identity, tool, user, profile, sessionId }) { const id = identity ?? 'unknown'; const existing = await McpQuota.get(id); From bfcbd930f7a72dc3bb078b69b9f583a331fe4063 Mon Sep 17 00:00:00 2001 From: Kyle Bernhardy Date: Thu, 9 Jul 2026 18:53:54 -0600 Subject: [PATCH 2/2] Use the real registration mechanism to gate the quota class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit static exportTypes is never read off exported classes (kriszyp) — the wired mechanism is server.resources.set(name, Class, exportTypes), and rest: false is needed for the REST side independently. Runtime-verified on main: McpQuota absent from tools/list, REST 404, quota hook still resolves and denies past the limit. Co-Authored-By: Claude Fable 5 --- reference/mcp/configuration.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/reference/mcp/configuration.md b/reference/mcp/configuration.md index f4d6396e..220101af 100644 --- a/reference/mcp/configuration.md +++ b/reference/mcp/configuration.md @@ -152,13 +152,7 @@ mcp: ```javascript // resources.js const DAILY_LIMIT = 100; -export class McpQuota extends tables.QuotaCounter { - // Exporting makes the class config-addressable — but it would also surface - // update_/delete_McpQuota MCP tools and a REST endpoint, letting a - // permitted client reset its own counter. Keep the quota table off the MCP - // surface and restrict its REST permissions. - static exportTypes = { mcp: false }; - +class McpQuota extends tables.QuotaCounter { static async allowMcpCall({ identity, tool, user, profile, sessionId }) { const id = identity ?? 'unknown'; const existing = await McpQuota.get(id); @@ -170,6 +164,12 @@ export class McpQuota extends tables.QuotaCounter { return true; } } + +// Register the class so the quota hook can resolve it by name — WITHOUT +// module-exporting it, which would surface update_/delete_McpQuota MCP tools +// and a REST endpoint that let a permitted client reset its own counter. +// exportTypes gates each transport independently (see the HTTP API reference). +server.resources.set('McpQuota', McpQuota, { mcp: false, rest: false }); ``` ### `mcp..quota.resource`