Skip to content

10.0.0b1: an SDK seller's default tools/list exceeds the SDK client's 100,000-member bound, so call_tool fails #1692

Description

@bokelley

On 10.0.0b1, a plain sales seller built with create_mcp_server and the default schema_mode="compact" publishes a tools/list larger than the SDK client's own default JSON bound (MAX_JSON_MEMBERS = 100_000, adcp/_json_bounds.py). The MCP client calls list_tools before call_tool, so every tool call from an SDK 10 client to that seller fails with ADCPResponseTooComplexError, even though the payload (about 2.5 MB) is under the 4 MiB byte budget.

The bound is unchanged from 10.0.0b0, but the listing grew: our seller's MCP client test (ADCPClient calling get_products) passed on 10.0.0b0 and fails on 10.0.0b1.

Repro

import asyncio
import json
from typing import Any

from adcp.server import ADCPHandler, create_mcp_server


def members(value: Any) -> int:
    if isinstance(value, dict):
        return len(value) + sum(members(v) for v in value.values())
    if isinstance(value, list):
        return len(value) + sum(members(v) for v in value)
    return 0


class Seller(ADCPHandler[Any]):
    _adcp_version = "3.2"
    async def get_products(self, params, context=None): ...
    async def create_media_buy(self, params, context=None): ...
    async def update_media_buy(self, params, context=None): ...
    async def get_media_buys(self, params, context=None): ...
    async def get_media_buy_delivery(self, params, context=None): ...
    async def sync_creatives(self, params, context=None): ...
    async def list_creatives(self, params, context=None): ...
    async def provide_performance_feedback(self, params, context=None): ...
    async def list_creative_formats(self, params, context=None): ...
    async def list_accounts(self, params, context=None): ...
    async def sync_accounts(self, params, context=None): ...


async def main() -> None:
    mcp = create_mcp_server(Seller(), supported_versions=["3.1", "3.2"])
    tools = [t.model_dump(mode="json", by_alias=True, exclude_none=True) for t in await mcp.list_tools()]
    listing = {"jsonrpc": "2.0", "id": 1, "result": {"tools": tools}}
    print(f"{len(tools)} tools, {len(json.dumps(listing))} bytes, {members(listing)} JSON members")


asyncio.run(main())

Output on 10.0.0b1: 11 tools, 2519482 bytes, 106250 JSON members. Without supported_versions the same handler lists about 24,600 members.

A real seller (the Scope3 embedded sales agent: 12 tools on the decisioning serve() path, supported_versions 3.1/3.2) lists 101,604 members, 73,697 of them in outputSchemas (get_products 14,865, get_media_buys 12,731, create_media_buy 12,075). Calling it with ADCPClient:

mcp/client/session.py in call_tool -> await self.list_tools()
adcp._response_limit.ADCPResponseTooComplexError: Agent response exceeds the bound of 100000 JSON object members and array elements

Expected

A seller using the SDK's defaults can be called by a client using the SDK's defaults. Some options:

  • keep the compact outputSchemas (or the per-version alternates) small enough that a full sales seller fits;
  • exempt or separately bound tools/list, which comes from the server's own schemas;
  • have serve() refuse or warn when its listing exceeds the client bound, the way it already warns at half the byte budget.

Related: #1636, #1656.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions