list_instance_tools documents 2 of the 4 tier values it returns, and its default response is 117 KB — enough to exceed a host's limit on one instance
Two small, independently-shippable contract defects on the same tool. Both measured against instance bd43f4de-ef35-4051-bdec-43f8571414a1, GET /v1/instances/:id/tools, production 2026-08-15, 104 rows.
1. A third of the rows carry an undocumented tier
The tool's description (workers/mcp/src/instance-tools/base.ts:29) says:
plus tier (base = a universal facility every agent has; connector = reaches an external system)
Measured tier distribution over the 104 rows:
| tier |
rows |
documented? |
connector |
49 |
yes |
standard |
30 |
no |
base |
21 |
yes |
runtime |
4 |
no |
34 of 104 rows (32.7%) carry a value the description does not define. A caller filtering on the two documented values silently drops a third of the surface — including every runtime tool, which is the set that reaches the owner's own machine.
The vocabulary is already written down, correctly, one layer below — workers/api/src/lib/builtin-tool-policy.ts:145:
/** base = always granted · standard = creator-selectable · runtime = needs a local runner · connector = external system. */
export type ToolTier = "base" | "standard" | "runtime" | "connector";
and again on ToolDef (lib/connectors/types.ts:129). So this is a description that fell behind its own type, not a missing concept. Paste the type's gloss.
2. The default response is 117 KB / 3,563 lines for one instance
Measured, same call:
|
chars (pretty-printed) |
lines |
| default (all 104 rows) |
117,273 |
3,563 |
allowed_only: true (35 rows) |
37,363 |
— |
all rows, jsonSchema stripped |
60,427 |
— |
Raw body over HTTP is 89,281 bytes. jsonSchema is 48% of the payload, and it is present on every row including the 69 the instance may not run — a schema for a tool that is allowed: false describes inputs the caller can never send.
This exceeded the calling host's response limit in the session that produced this issue, so the tool's own most useful mode — "return EVERY tool, not just the allowed ones", which the resolver's doc comment (lib/instance-tool-policy.ts:154-156) rightly defends — is the one that does not fit.
What to do
Cheapest, and it fixes both: paste the four-value tier gloss into the description, and drop jsonSchema from rows where allowed === false (60,427 chars, comfortably under any limit, with no loss of auditable information — the verdict, reason, scope, tier, connector and writeConsent all stay).
If more is wanted: add an optional schemas: boolean (default false) so the audit read is cheap and the schema read is opt-in. Do NOT make allowed_only the default — the resolver's stated design is that "what can this agent do" is only answerable if the answer includes what it can't, and defaulting it away would quietly retire that.
Alternatives considered and rejected
- Truncate the description strings. They are the substance of an audit —
github_update_issue's description is where the "labels REPLACE" warning lives.
- Paginate. A tool listing is a set, and a paged audit is one a caller can stop halfway through and believe.
Acceptance criteria
- The description defines all four
tier values, matching ToolTier's own gloss.
- A test asserts every value in the
ToolTier union appears in the description string — so a fifth tier cannot ship undocumented.
- The default response for a 104-tool instance is under 64 KB.
Regression risk
Dropping jsonSchema from disallowed rows changes a response shape the console may read. store/console/src/lib/toolPolicy.ts mirrors the type — check it does not render schemas for the not-declared rows before removing the field, and make it optional in the type rather than removing it outright.
Related: #563 (the same description's scope gloss is wrong in a way that matters more), #561/#562 (the tools/list payload budget).
list_instance_toolsdocuments 2 of the 4tiervalues it returns, and its default response is 117 KB — enough to exceed a host's limit on one instanceTwo small, independently-shippable contract defects on the same tool. Both measured against instance
bd43f4de-ef35-4051-bdec-43f8571414a1,GET /v1/instances/:id/tools, production 2026-08-15, 104 rows.1. A third of the rows carry an undocumented
tierThe tool's description (
workers/mcp/src/instance-tools/base.ts:29) says:Measured tier distribution over the 104 rows:
connectorstandardbaseruntime34 of 104 rows (32.7%) carry a value the description does not define. A caller filtering on the two documented values silently drops a third of the surface — including every
runtimetool, which is the set that reaches the owner's own machine.The vocabulary is already written down, correctly, one layer below —
workers/api/src/lib/builtin-tool-policy.ts:145:and again on
ToolDef(lib/connectors/types.ts:129). So this is a description that fell behind its own type, not a missing concept. Paste the type's gloss.2. The default response is 117 KB / 3,563 lines for one instance
Measured, same call:
allowed_only: true(35 rows)jsonSchemastrippedRaw body over HTTP is 89,281 bytes.
jsonSchemais 48% of the payload, and it is present on every row including the 69 the instance may not run — a schema for a tool that isallowed: falsedescribes inputs the caller can never send.This exceeded the calling host's response limit in the session that produced this issue, so the tool's own most useful mode — "return EVERY tool, not just the allowed ones", which the resolver's doc comment (
lib/instance-tool-policy.ts:154-156) rightly defends — is the one that does not fit.What to do
Cheapest, and it fixes both: paste the four-value tier gloss into the description, and drop
jsonSchemafrom rows whereallowed === false(60,427 chars, comfortably under any limit, with no loss of auditable information — the verdict, reason, scope, tier, connector andwriteConsentall stay).If more is wanted: add an optional
schemas: boolean(defaultfalse) so the audit read is cheap and the schema read is opt-in. Do NOT makeallowed_onlythe default — the resolver's stated design is that "what can this agent do" is only answerable if the answer includes what it can't, and defaulting it away would quietly retire that.Alternatives considered and rejected
github_update_issue's description is where the "labels REPLACE" warning lives.Acceptance criteria
tiervalues, matchingToolTier's own gloss.ToolTierunion appears in the description string — so a fifth tier cannot ship undocumented.Regression risk
Dropping
jsonSchemafrom disallowed rows changes a response shape the console may read.store/console/src/lib/toolPolicy.tsmirrors the type — check it does not render schemas for the not-declared rows before removing the field, and make it optional in the type rather than removing it outright.Related: #563 (the same description's
scopegloss is wrong in a way that matters more), #561/#562 (thetools/listpayload budget).