Skip to content

fix(agent): emit a valid schema when capping oversized MCP tool schemas - #3192

Open
noah-shipley-centric wants to merge 1 commit into
block:mainfrom
noah-shipley-centric:fix/mcp-oversized-schema-replacement
Open

fix(agent): emit a valid schema when capping oversized MCP tool schemas#3192
noah-shipley-centric wants to merge 1 commit into
block:mainfrom
noah-shipley-centric:fix/mcp-oversized-schema-replacement

Conversation

@noah-shipley-centric

Copy link
Copy Markdown

Problem

cap_schema in crates/buzz-agent/src/mcp.rs replaces any tool schema over MAX_SCHEMA_BYTES with a bare {}. That isn't a valid JSON Schema — Anthropic's /v1/messages requires input_schema.type:

400 invalid_request_error
tools.N.custom.input_schema.type: Field required

Because the API validates the entire tools array, one oversized tool breaks every turn in that session rather than just becoming an unusable tool. The failure is also hard to trace: the agent connects, lists tools, reports healthy, and then every prompt 400s with an error that never names the offending tool.

Reproduction

Connect buzz-agent to an MCP server exposing tools with schemas above the 4096-byte cap. Observed with a server whose two largest tools were 4376 and 7680 bytes:

WARN buzz_agent::mcp: tool <srv>__<tool_a> schema is 4376 bytes (>4096); replacing with empty object
WARN buzz_agent::mcp: tool <srv>__<tool_b> schema is 7680 bytes (>4096); replacing with empty object
ERROR pool::prompt: session_prompt error: Agent reported error (code -32000):
  llm: 400 Bad Request: tools.11.custom.input_schema.type: Field required

Every subsequent prompt failed identically until the oversized tools were removed.

Fix

Emit the smallest valid equivalent instead of a bare object:

{ "type": "object", "properties": {} }

This preserves the original intent — drop the oversized schema, keep the tool callable with no arguments — while remaining acceptable to providers. The replacement is well under the cap, so the guard can't recurse.

Tests

Adds a cap_schema test module (the function had no direct coverage):

  • under-cap schemas pass through unchanged
  • oversized schemas are replaced
  • the replacement declares type: object and carries propertiesthis one fails against the previous implementation
  • the replacement is itself within MAX_SCHEMA_BYTES
  • a schema landing exactly on the cap is not replaced (the guard is <=)
cargo test -p buzz-agent --lib cap_schema
test result: ok. 5 passed; 0 failed

Verified red-then-green: reverting only the one-line behaviour change fails replacement_declares_object_type and passes the other four.

cargo fmt and cargo clippy -p buzz-agent --all-targets -- -D warnings are clean.

Notes

Only the provider-facing shape changes; the capping policy and threshold are untouched. A follow-up worth considering separately (happy to open an issue rather than expand this PR): rather than discarding the schema wholesale, a large schema could be pruned — keeping type, required, and top-level property names — so oversized tools stay usable instead of becoming argument-less.

`cap_schema` replaced any tool schema over MAX_SCHEMA_BYTES with a bare
`{}`. That is not a valid JSON Schema: Anthropic's /v1/messages requires
`input_schema.type` and rejects the request with

    400 invalid_request_error
    tools.N.custom.input_schema.type: Field required

Because the API validates the whole `tools` array, a single oversized
tool takes down *every* turn for that session rather than just becoming
an unusable tool — the agent connects, lists tools, then fails on each
prompt with no obviously related error.

Observed against an MCP server with two large tool schemas (4376 and
7680 bytes): both were capped, and every subsequent prompt 400'd.

Replace the bare `{}` with the smallest valid equivalent,
`{"type":"object","properties":{}}`, preserving the existing intent
(drop the oversized schema, keep the tool callable with no arguments)
while remaining acceptable to providers.

Adds a test module for `cap_schema` covering pass-through, replacement,
the `<=` boundary at exactly MAX_SCHEMA_BYTES, that the replacement is
itself within the cap, and that it declares `type: object` — the last
of which fails against the previous implementation.

Signed-off-by: Noah Shipley <nshipley@centricsoftware.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant