Summary
When a seller's DecisioningPlatform subclass overrides a tool method with a more-specific request annotation, the SDK's _resolve_params_pydantic_model resolves the base class's annotation, not the subclass's. The seller's extended schema (e.g. with extra="forbid" for strict-extra rejection) is silently bypassed at the wire boundary.
Repro
Salesagent has its own GetProductsRequest extending the library's, with extra="forbid" in dev mode to catch unknown fields:
# src/core/schemas/product.py
from adcp.types import GetProductsWholesaleRequest as LibraryGetProductsRequest
class GetProductsRequest(LibraryGetProductsRequest):
model_config = ConfigDict(extra=get_pydantic_extra_mode()) # "forbid" in dev
buying_mode: str | None = Field(None, ...) # widened
...
The subclass platform method:
# core/platforms/mock.py
class MockSellerPlatform(DecisioningPlatform):
async def get_products(
self,
req: GetProductsRequest, # ← salesagent's strict-extra subclass
ctx: RequestContext[Any],
) -> dict[str, Any]:
return await _delegate_get_products(req, ctx)
Annotations resolve correctly via typing.get_type_hints:
hints: {'req': <class 'src.core.schemas.product.GetProductsRequest'>, ...}
But when the SDK builds the tool caller via create_tool_caller(handler, "get_products", ...), _resolve_params_pydantic_model(method) returns the library's type:
[DEBUG SDK] get_products: params_model=<class 'adcp.types.generated_poc.media_buy.get_products_request.GetProductsRequest'>
Library's GetProductsRequest has extra="allow", so calls with unknown fields slip through validation untouched. The seller's intended strict-extra contract is silently bypassed.
Impact
Any seller who extends a library request type to add stricter validation (forbid extra fields, narrow union types, custom validators) finds those rules silently bypassed at the MCP wire boundary. Tests that rely on the wire layer rejecting unknown fields (e.g. salesagent's test_unknown_field_rejected) fail with no obvious cause — the validation simply doesn't run.
Suspected cause
_resolve_params_pydantic_model likely walks the platform's base class methods (because that's how it gets the catalog of advertised tools and their canonical signatures) instead of the subclass's actual override. The base-class signature uses the library type; the subclass override uses the seller's extended type.
Suggested fix
When iterating advertised tools to build callers, look up getattr(handler, method_name) (which is the subclass-bound method) and use that method's annotations for the params model resolution — not the base class's signature. The base class's role is to advertise; the subclass's role is to type-narrow.
Reproduction repo: https://github.com/bokelley/salesagent (hits this on 4.5.0; salesagent test tests/integration/test_mcp_unknown_field_handling.py::TestMcpDevMode::test_unknown_field_rejected).
Summary
When a seller's
DecisioningPlatformsubclass overrides a tool method with a more-specific request annotation, the SDK's_resolve_params_pydantic_modelresolves the base class's annotation, not the subclass's. The seller's extended schema (e.g. withextra="forbid"for strict-extra rejection) is silently bypassed at the wire boundary.Repro
Salesagent has its own
GetProductsRequestextending the library's, withextra="forbid"in dev mode to catch unknown fields:The subclass platform method:
Annotations resolve correctly via
typing.get_type_hints:But when the SDK builds the tool caller via
create_tool_caller(handler, "get_products", ...),_resolve_params_pydantic_model(method)returns the library's type:Library's
GetProductsRequesthasextra="allow", so calls with unknown fields slip through validation untouched. The seller's intended strict-extra contract is silently bypassed.Impact
Any seller who extends a library request type to add stricter validation (forbid extra fields, narrow union types, custom validators) finds those rules silently bypassed at the MCP wire boundary. Tests that rely on the wire layer rejecting unknown fields (e.g. salesagent's
test_unknown_field_rejected) fail with no obvious cause — the validation simply doesn't run.Suspected cause
_resolve_params_pydantic_modellikely walks the platform's base class methods (because that's how it gets the catalog of advertised tools and their canonical signatures) instead of the subclass's actual override. The base-class signature uses the library type; the subclass override uses the seller's extended type.Suggested fix
When iterating advertised tools to build callers, look up
getattr(handler, method_name)(which is the subclass-bound method) and use that method's annotations for the params model resolution — not the base class's signature. The base class's role is to advertise; the subclass's role is to type-narrow.Reproduction repo: https://github.com/bokelley/salesagent (hits this on 4.5.0; salesagent test
tests/integration/test_mcp_unknown_field_handling.py::TestMcpDevMode::test_unknown_field_rejected).