From 067766b576b7f8cf98f3e071a31fe48baaf7aea2 Mon Sep 17 00:00:00 2001 From: Dharit Shah Date: Fri, 3 Apr 2026 23:10:49 -0400 Subject: [PATCH] fix(fetch): use OpenAPI 3.0 compatible schema keywords for Gemini support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #1624 Made-with: Cursor --- src/fetch/src/mcp_server_fetch/server.py | 12 ++++++--- src/fetch/tests/test_server.py | 33 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index b42c7b1f6b..1f1c0cc530 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -18,7 +18,7 @@ INTERNAL_ERROR, ) from protego import Protego -from pydantic import BaseModel, Field, AnyUrl +from pydantic import AnyUrl, BaseModel, Field, WithJsonSchema DEFAULT_USER_AGENT_AUTONOMOUS = "ModelContextProtocol/1.0 (Autonomous; +https://github.com/modelcontextprotocol/servers)" DEFAULT_USER_AGENT_MANUAL = "ModelContextProtocol/1.0 (User-Specified; +https://github.com/modelcontextprotocol/servers)" @@ -151,14 +151,18 @@ async def fetch_url( class Fetch(BaseModel): """Parameters for fetching a URL.""" - url: Annotated[AnyUrl, Field(description="URL to fetch")] + url: Annotated[ + AnyUrl, + Field(description="URL to fetch"), + WithJsonSchema({"type": "string", "description": "URL to fetch"}), + ] max_length: Annotated[ int, Field( default=5000, description="Maximum number of characters to return.", - gt=0, - lt=1000000, + ge=1, + le=999999, ), ] start_index: Annotated[ diff --git a/src/fetch/tests/test_server.py b/src/fetch/tests/test_server.py index 96c1cb38c7..ff87b34d34 100644 --- a/src/fetch/tests/test_server.py +++ b/src/fetch/tests/test_server.py @@ -5,6 +5,7 @@ from mcp.shared.exceptions import McpError from mcp_server_fetch.server import ( + Fetch, extract_content_from_html, get_robots_txt_url, check_may_autonomously_fetch_url, @@ -13,6 +14,38 @@ ) +class TestFetchToolSchema: + """Tests for the Fetch model JSON schema compatibility.""" + + def test_schema_uses_inclusive_bounds(self): + """Ensure the schema uses minimum/maximum instead of exclusiveMinimum/exclusiveMaximum. + + Some LLM providers (e.g. Google Gemini) only support OpenAPI 3.0 + schema keywords and reject exclusiveMinimum/exclusiveMaximum. + """ + schema = Fetch.model_json_schema() + max_length_schema = schema["properties"]["max_length"] + + assert "exclusiveMinimum" not in max_length_schema + assert "exclusiveMaximum" not in max_length_schema + assert max_length_schema["minimum"] == 1 + assert max_length_schema["maximum"] == 999999 + + def test_url_schema_omits_unsupported_keywords(self): + """Ensure the url field schema avoids format/minLength keywords. + + Some LLM providers (e.g. Google Gemini) only support a limited set + of keywords for string types and reject unsupported ones like + format: "uri" or minLength. + """ + schema = Fetch.model_json_schema() + url_schema = schema["properties"]["url"] + + assert "format" not in url_schema + assert "minLength" not in url_schema + assert url_schema["type"] == "string" + + class TestGetRobotsTxtUrl: """Tests for get_robots_txt_url function."""