Skip to content

fix(fetch): make fetch tool schema Gemini/OpenAPI 3.0 compatible - #3812

Open
Dharit13 wants to merge 3 commits into
modelcontextprotocol:mainfrom
Dharit13:fix/fetch-schema-inclusive-bounds
Open

fix(fetch): make fetch tool schema Gemini/OpenAPI 3.0 compatible#3812
Dharit13 wants to merge 3 commits into
modelcontextprotocol:mainfrom
Dharit13:fix/fetch-schema-inclusive-bounds

Conversation

@Dharit13

@Dharit13 Dharit13 commented Apr 4, 2026

Copy link
Copy Markdown

Summary

Fixes the fetch server's tool schema to be compatible with LLM providers that only support OpenAPI 3.0 schema keywords (e.g. Google Gemini 2.5 Pro), which currently reject the schema with a 400 INVALID_ARGUMENT error.

Two incompatibilities are addressed:

  1. max_length field used gt=0 / lt=1000000 Pydantic constraints, which generate exclusiveMinimum / exclusiveMaximum in the JSON Schema. Gemini does not recognize these keywords. Changed to ge=1 / le=999999 — identical semantics for integers — which emits the universally supported minimum / maximum keywords instead.

  2. url field used AnyUrl, which generates format: \"uri\" and minLength: 1. Gemini only supports \"enum\" and \"date-time\" for string format, and does not support minLength. Added a WithJsonSchema override to emit a plain {"type": "string"} schema while preserving AnyUrl runtime validation — so invalid URLs are still rejected with a proper error, but the schema no longer contains unsupported keywords.

Fixes #1624

Changes

src/fetch/src/mcp_server_fetch/server.py

  • max_length field: gt=0ge=1, lt=1000000le=999999
  • url field: added WithJsonSchema({"type": "string", "description": "URL to fetch"}) to override schema output while keeping AnyUrl runtime validation

src/fetch/tests/test_server.py

  • Added TestFetchToolSchema.test_schema_uses_inclusive_bounds — asserts minimum/maximum present, exclusiveMinimum/exclusiveMaximum absent
  • Added TestFetchToolSchema.test_url_schema_omits_unsupported_keywords — asserts format and minLength absent from url schema

Evidence

Live Gemini validation was run against gemini-2.5-pro using the exact generated tool schema sent to generateContent.

Pre-fix schema (reproduced locally):

  • HTTP 400
  • INVALID_ARGUMENT
  • Error excerpt:
    • Unknown name "exclusiveMaximum"
    • Unknown name "exclusiveMinimum"

Current PR schema:

  • HTTP 200
  • Gemini accepted the tool declaration payload
  • Response text: Acknowledged.

Test plan

  • All 22 tests pass (uv run python -m pytest tests/test_server.py -v)
  • Schema verified: no exclusiveMinimum, exclusiveMaximum, format, or minLength in output
  • AnyUrl runtime validation confirmed: valid URLs accepted, invalid URLs rejected with ValidationError
  • Live Gemini validation: sent the exact generated schema to gemini-2.5-pro via generateContent; request was accepted with HTTP 200 and returned Acknowledged.
  • No behavioral change — same validation semantics, only schema representation changed

@Dharit13
Dharit13 force-pushed the fix/fetch-schema-inclusive-bounds branch from 26d0503 to 067766b Compare April 4, 2026 03:20
@Dharit13 Dharit13 changed the title fix(fetch): use inclusive minimum/maximum instead of exclusive bounds in schema fix(fetch): make fetch tool schema Gemini/OpenAPI 3.0 compatible Apr 4, 2026
@cliffhall cliffhall added bug Something isn't working server-fetch Reference implementation for the Fetch MCP server - src/fetch labels Apr 20, 2026
@cliffhall

Copy link
Copy Markdown
Member

@claude review

@claude

claude Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 1m 7s —— View job


PR Review: fix(fetch): make fetch tool schema Gemini/OpenAPI 3.0 compatible

Todo:

  • Read the changed files
  • Review server.py changes
  • Review test changes
  • Check for correctness, edge cases, and best practices
  • Post final review

This is a clean, well-scoped fix for a real compatibility issue with OpenAPI 3.0-only providers like Google Gemini. Here's my analysis:

server.py — Correctness ✅

max_length bounds change (gt=0, lt=1000000ge=1, le=999999):
Semantically identical for integers. gt=0 == ge=1 and lt=1000000 == le=999999 for int types. The emitted JSON schema changes from exclusiveMinimum/exclusiveMaximum (JSON Schema draft-07) to minimum/maximum (OpenAPI 3.0). Correct fix.

url field with WithJsonSchema override:
Using WithJsonSchema({"type": "string", "description": "URL to fetch"}) is the proper Pydantic v2 idiom for decoupling schema representation from runtime validation. AnyUrl still validates at parse time — only the emitted schema is simplified. The description must be repeated inside WithJsonSchema because it replaces the schema fragment entirely; this is correct and expected behavior.

One observation: Field(description="URL to fetch") will be shadowed by the WithJsonSchema override, making the Field(...) description redundant (though harmless). You could simplify to Field() but it's not a problem.

Other fields: start_index already uses ge=0 (emits minimum), raw is a boolean — no issues there.

test_server.py — Tests ✅

The two new tests in TestFetchToolSchema are focused and correct:

  • test_schema_uses_inclusive_bounds — directly asserts the right keyword names and values in the emitted schema. Good coverage.
  • test_url_schema_omits_unsupported_keywords — asserts format and minLength are absent and type == "string" is present. Correct.

A minor gap: neither test verifies that AnyUrl runtime validation still rejects invalid URLs (e.g., passing "not-a-url" to Fetch(url="not-a-url") should raise a ValidationError). The PR description claims this behavior, but it's not tested. This is low priority — the behavior follows from AnyUrl's design — but a small test would strengthen the PR.

Summary

The fix is minimal, correct, and well-documented. The semantics are preserved, the approach is idiomatic Pydantic v2, and the tests verify the schema output directly.

Suggestion (non-blocking): Consider adding a runtime validation test to confirm AnyUrl still rejects invalid URLs after the WithJsonSchema override, e.g.:

def test_url_runtime_validation_still_rejects_invalid_urls(self):
    with pytest.raises(Exception):  # pydantic.ValidationError
        Fetch(url="not-a-url")

Overall: ready to merge. The fix correctly addresses the Gemini/OpenAPI 3.0 incompatibility without changing any runtime behavior.

…port

The Fetch model's JSON schema used keywords incompatible with LLM
providers that only support OpenAPI 3.0 (e.g. Google Gemini 2.5 Pro),
causing 400 INVALID_ARGUMENT errors:

1. max_length field used gt=0/lt=1000000 (Pydantic Field constraints),
   which generated exclusiveMinimum/exclusiveMaximum — not recognized
   by Gemini. Changed to ge=1/le=999999 (identical semantics for
   integers), which emits the supported minimum/maximum keywords.

2. url field used AnyUrl, which generated format: "uri" and minLength: 1
   — Gemini only supports "enum" and "date-time" for string format.
   Added WithJsonSchema override to emit a plain string schema while
   preserving AnyUrl runtime validation.

Fixes modelcontextprotocol#1624

Made-with: Cursor
Verifies that AnyUrl runtime validation is preserved after the
WithJsonSchema override — invalid URLs still raise ValidationError,
valid URLs parse successfully. Addresses review feedback on modelcontextprotocol#3812.
@Dharit13
Dharit13 force-pushed the fix/fetch-schema-inclusive-bounds branch from 067766b to 14e4107 Compare April 21, 2026 14:57
Pyright doesn't recognize Pydantic's Annotated + Field(default=...)
pattern as making __init__ args optional, so Fetch(url="...") flags
max_length/start_index/raw as missing. Switch to Fetch.model_validate
which accepts a dict and avoids the false positive. Same behavior,
same coverage.

@LuuOW LuuOW left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technical audit: Verified MCP server implementation for consistency with current SDK patterns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working server-fetch Reference implementation for the Fetch MCP server - src/fetch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] mcp-server-fetch tool json schema can‘t use in Google Gemini 2.5 pro

4 participants