Skip to content

[Server] Keep JSON types of tool call arguments - #87

Closed
chr-hertel wants to merge 12 commits into
mainfrom
json-decode-issue
Closed

chr-hertel wants to merge 12 commits into
mainfrom
json-decode-issue

Conversation

@chr-hertel

Copy link
Copy Markdown
Owner

json_decode(assoc: true) makes {} and [] the same value, so tool arguments got validated against the wrong JSON type - tools/call arguments now keep their types, the rest of the message stays arrays.

  • array parameters still get arrays at every level, \stdClass, object, mixed and untyped ones get \stdClass
  • arguments must be a JSON object now, so "arguments": [] is refused with -32600 - PHP clients encoding an empty array will notice
  • array<string, T> generates object instead of array

BC breaks are in the CHANGELOG. For upstream: closes #586 and #587, tho #587 asked for \stdClass everywhere.

Copilot AI 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.

🟡 Changes recommended

Explicit null arguments bypass object-only validation, allowing a no-argument tool to execute instead of returning the expected -32600 error.

4 open issues
What changed in this PR

Preserves JSON object and array types in tool-call arguments so the PHP SDK validates the types clients actually send.

Changes:

  • Retains nested JSON types during decoding and converts objects to arrays for array-typed handlers.
  • Requires object-shaped tool arguments and updates generic-array schema inference.
  • Adds regression coverage and documents compatibility changes.
File Description
tests/​Unit/​Server/​Transport/​StdioDualEraTest.php Tests argument types and validation in both protocol eras.
tests/​Unit/​Server/​Stateless/​StatelessProtocolTest.php Uses object-shaped empty arguments.
tests/​Unit/​Server/​Stateless/​StandardHeaderValidatorTest.php Updates empty argument fixtures.
tests/​Unit/​Server/​RequestContextTest.php Updates request fixture arguments.
tests/​Unit/​Server/​Handler/​Request/​CallToolHandlerTest.php Builds object-shaped argument fixtures.
tests/​Unit/​Server/​BuilderTest.php Updates tool-call fixture arguments.
tests/​Unit/​Schema/​Request/​CallToolRequestTest.php Covers argument parsing and serialization.
tests/​Unit/​JsonRpc/​MessageFactoryTest.php Covers preserved JSON types and batch detection.
tests/​Unit/​Client/​Stateless/​HeaderFactoryTest.php Updates the header-generation fixture.
tests/​Unit/​Capability/​Registry/​ReferenceHandlerTest.php Tests delivery by PHP parameter type.
tests/​Unit/​Capability/​Discovery/​SchemaGeneratorTest.php Updates string-keyed map expectations.
tests/​Integration/​StatelessLifecycleTest.php Uses object-shaped lifecycle arguments.
src/​Server/​Stateless/​StatelessProtocol.php Passes the original body to message parsing.
src/​Server/​Handler/​Request/​CallToolHandler.php Validates root arguments as an object.
src/​Schema/​Request/​CallToolRequest.php Preserves nested types and enforces object arguments.
src/​Schema/​JsonRpc/​Request.php Adds overridable parameter normalization.
src/​Schema/​JsonRpc/​Notification.php Normalizes notification objects to arrays.
src/​JsonRpc/​MessageFactory.php Decodes objects distinctly from arrays.
src/​JsonRpc/​Json.php Adds recursive object-to-array conversion.
src/​Capability/​Registry/​ReferenceHandler.php Adapts argument conversion to parameter types.
src/​Capability/​Discovery/​SchemaGenerator.php Infers generic-array types from their keys.
docs/​servers/​schemas.md Documents argument delivery and schema types.
CHANGELOG.md Records backward-incompatible changes.

Review effort: Balanced

Comment thread src/Schema/Request/CallToolRequest.php Resolved

Copilot AI 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.

🟡 Changes recommended

A NUL-prefixed member in one batch element rejects the entire batch, dropping valid sibling requests and losing error ID correlation.

1 open issue
4 resolved since last review

Review effort: Balanced

Comment thread src/JsonRpc/MessageFactory.php Outdated
throw $e;
}

return [self::invalidMemberName($input)];
…odelcontextprotocol#616)

* [Server] Let an event loop drive the fibers request handlers run in

* [Server] Fail with guidance when a fiber scheduler wakes the server too early

* [Server] Answer a request whose handler fiber fails on a transport resume

* [Docs] Document deferred scheduler wake-ups and event loop limits

* [Tests] Show a handler keeps running after a failed call under an event loop

* [Server] Fix PHP 8.1 segfault when freeing a handler fiber's state

---------

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
aton-of-data and others added 4 commits November 8, 2026 13:08
…ol#548)

* [Server] Log the tool name at info for tools/call

Since modelcontextprotocol#525 the info-level "Handling request." record in Protocol carries
only the method and id, and CallToolHandler logs the called tool at debug
level only, so a server logging at INFO no longer shows which tool ran.

CallToolHandler now logs the tool name at info level. The arguments stay
in the existing debug record, and Protocol stays method-agnostic.

* [Server] Log "Executing tool" only once the tool is about to run

* [Server] Remove comment on the tool name log record

---------

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
… an event loop (modelcontextprotocol#636)

* [Server] Add a Revolt event loop example

* [Examples] Answer streamed responses with 501 in the Revolt example

* [Examples] Add a ReactPHP event loop example

* [Tests] Cover the fiber schedulers on real event loops

* [Server] Wait for a session lock on the event loop

* [Server] Log a handler outliving its request instead of firing events

* [Docs] Correct the event loop limits

* [Server] Name the protocol constructors in the missing fiber scheduler error

* [Server] Fire no event for a listener failing after its request was answered
)

* [Server][Client] Remove JSON-RPC batch support

MCP removed batching in 2025-06-18. A JSON array is refused with -32600
for every protocol version, and MessageFactory::create() returns one
message and throws on invalid input.

* [Server] Refuse an empty JSON array as a batch

It decoded like {} and got past the classifier, which answered it with HTTP 200 in a session.
nicolas-grekas and others added 7 commits November 8, 2026 14:04
…hem (modelcontextprotocol#627)

* [Server] Bind Streamable HTTP sessions to the principal that opened them

* [Server] Fix the session binding on unreadable and busy sessions

A session opened without a principal now records it, so a session the
store fails to read serves no one. Who opened a session is checked before
waiting for its lock, so another principal can't tell the session is busy,
and an initialize never takes the lock.

* [Server] Derive the session principal from the access token

Bind tokens without a subject to their issuer and client, and refuse an invalid principal attribute.

* [Server] Answer 503 when the session owner cannot be read

A failed read no longer sends the owner to re-initialize, and a store outage no longer escapes from a DELETE or a transport's shutdown.

* [Server] Stop a claim that is not UTF-8 from failing the session principal

* [Server] Stop refused requests from draining another principal's queue

---------

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
@chr-hertel

Copy link
Copy Markdown
Owner Author

Merged upstream as modelcontextprotocol#678.

@chr-hertel chr-hertel closed this Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants