"""What the model sees in the tool schema — the part that makes it batch.
The parameter used to be ``str | list[str]``, i.e. ``anyOf: [string, array]``, and
the model took the first branch every time: 20 of 20 recorded ``web_search`` calls
and 8 of 8 ``web_view`` calls in production carried a scalar, including sessions
where three searches on one topic went out one after another. Prose did not move
it — the same recommendation already stood in the parameter description, in the
tool docstring and in the server's ``summary`` line.
So these tests pin the shape, not the wording: the property must be an array, and
a caller that still sends a bare string must keep working.
"""
import asyncio
def _property(navi_web_server, tool: str, name: str) -> dict:
tools = asyncio.run(navi_web_server.mcp.list_tools())
schema = next(t for t in tools if t.name == tool).inputSchema
return schema["properties"][name]
class TestBatchParameters:
def test_web_search_takes_a_list_of_queries(self, navi_web_server):
param = _property(navi_web_server, "web_search", "query")
assert param["type"] == "array", "a union with a string branch invites a scalar"
assert param["items"] == {"type": "string"}
assert param["minItems"] == 1
assert param["maxItems"] == 3
assert param["examples"], "an example batch is the shape the model copies"
def test_web_view_takes_a_list_of_urls(self, navi_web_server):
param = _property(navi_web_server, "web_view", "url")
assert param["type"] == "array"
assert param["items"] == {"type": "string"}
assert param["maxItems"] == 3
assert param["examples"]
def test_a_bare_string_still_validates(self, navi_web_server, monkeypatch):
"""The array is the advertised shape, not a breaking change."""
seen: dict = {}
async def fake(settings, query, max_results=5):
seen["query"] = query
return {"success": True, "output": "ok", "error": None, "metadata": {}}
monkeypatch.setattr(navi_web_server, "web_search", fake)
asyncio.run(navi_web_server.mcp.call_tool("web_search", {"query": "one"}))
assert seen["query"] == ["one"]
def test_the_cap_is_advisory_so_the_server_can_explain_itself(
self, navi_web_server, monkeypatch
):
"""Four queries must reach the code that runs three and says so.
``maxItems`` rides in the schema, not in pydantic's ``max_length``: a
validation error tells the model nothing, while "only the first 3 of 4
ran" tells it what happened.
"""
seen: dict = {}
async def fake(settings, query, max_results=5):
seen["query"] = query
return {"success": True, "output": "ok", "error": None, "metadata": {}}
monkeypatch.setattr(navi_web_server, "web_search", fake)
asyncio.run(
navi_web_server.mcp.call_tool("web_search", {"query": ["a", "b", "c", "d"]})
)
assert seen["query"] == ["a", "b", "c", "d"]