Skip to content

fix(server): normalize tool schemas for gemini/vertex clients - #119

Merged
plusky merged 1 commit into
plusky:mainfrom
mimi1vx:gemini_fix
Aug 18, 2026
Merged

fix(server): normalize tool schemas for gemini/vertex clients#119
plusky merged 1 commit into
plusky:mainfrom
mimi1vx:gemini_fix

Conversation

@mimi1vx

@mimi1vx mimi1vx commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

What

BugWarden::new now runs a portable_schema pass over every route's
input_schema once, at router construction: it collapses schemars'
Option<T> type union ("type": [T, "null"]) to a plain type, drops the
resulting "default": null, and drops any format outside a portable
keep-list (date-time, int32, int64, float, double, enum).

Why

A Gemini (Google Vertex) client refused the whole tool list:

Unable to submit request because `bugzilla_bug_comments` functionDeclaration
`parameters.new_since` schema specified other fields alongside any_of.
When using any_of, it must be the only field set.

schemars 1.2.2 renders every Option<T> tool parameter as a JSON-Schema
type union plus a stray "default": null, and every Rust integer
parameter carries a uint64/uint32 format Gemini doesn't recognize.
rmcp's SchemaSettings::draft2020_12() runs zero transforms, so this is
exactly what list_tools/get_tool served — confirmed by dumping the
real tools/list payload over stdio. The Gemini/Vertex client rewrites
the type union into anyOf while keeping description/format/default
as siblings of it, a shape Vertex itself then refuses.

serde still accepts an explicit null for every Option<T> field —
only the advertised schema got stricter, not what the server accepts —
so no invariant (I1–I16) is touched.

How verified

  • cargo fmt --check clean
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo clippy -p bugwarden --features gen --all-targets -- -D warnings clean
  • cargo test --workspace --all-targets --locked green, including three
    new tests: a recursive schema-portability regression guard over all
    served tools, an exact-shape assertion on bug_comments.new_since, and
    a check that an explicit null still deserializes for an optional param
  • A real stdio tools/list probe shows 0 type unions, 0 "default": null,
    and formats reduced to exactly {date-time} across all 20 served tools

schemars 1.2.2 renders every Option<T> tool parameter as a JSON-Schema
type union ("type": [T, "null"]) plus a stray "default": null, and
every Rust integer parameter carries a uint64/uint32 format Gemini
doesn't recognize. rmcp's SchemaSettings::draft2020_12() runs zero
transforms, so this is exactly what list_tools/get_tool served. A
Gemini/Vertex client rewrites the type union into anyOf while keeping
description/format/default as siblings of it, a shape Vertex itself
then refuses ("when using any_of, it must be the only field set") -
so the whole tool list was rejected, not just the one property Vertex
happened to name.

BugWarden::new now runs a portable_schema pass once, at router
construction, over every route's input_schema: collapse the type
union to a plain type, drop the resulting default:null, and drop any
format outside a portable keep-list (date-time, int32, int64, float,
double, enum). serde still accepts an explicit null for every
Option<T> field - only the advertised schema got stricter, not what
the server accepts, so no invariant (I1-I16) is touched.

Verified against a real stdio tools/list probe: 0 type unions, 0
default:null, formats reduced to exactly {date-time} across all 20
served tools.
@mimi1vx
mimi1vx requested a review from plusky August 18, 2026 15:23
@plusky
plusky merged commit e0459d2 into plusky:main Aug 18, 2026
13 checks passed
@mimi1vx
mimi1vx deleted the gemini_fix branch August 18, 2026 15:48
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.

2 participants