Repository navigation
[Server] Keep JSON types of tool call arguments - #87
Closed
chr-hertel wants to merge 12 commits into
Closed
chr-hertel wants to merge 12 commits into
chr-hertel wants to merge 12 commits into
Conversation
There was a problem hiding this comment.
🟡 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
chr-hertel
force-pushed
the
json-decode-issue
branch
from
November 5, 2026 01:50
c3d674d to
5d606f8
Compare
| throw $e; | ||
| } | ||
|
|
||
| return [self::invalidMemberName($input)]; |
chr-hertel
force-pushed
the
json-decode-issue
branch
from
November 8, 2026 11:00
5d606f8 to
9fd3621
Compare
…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>
chr-hertel
force-pushed
the
json-decode-issue
branch
from
November 8, 2026 12:06
ced4dd1 to
ea6054c
Compare
…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.
chr-hertel
force-pushed
the
json-decode-issue
branch
from
November 8, 2026 13:00
fe43bf0 to
1eaef58
Compare
…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
force-pushed
the
json-decode-issue
branch
from
November 8, 2026 13:08
1eaef58 to
e0ef029
Compare
Owner
Author
|
Merged upstream as modelcontextprotocol#678. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


json_decode(assoc: true)makes{}and[]the same value, so tool arguments got validated against the wrong JSON type -tools/callarguments now keep their types, the rest of the message stays arrays.arrayparameters still get arrays at every level,\stdClass,object,mixedand untyped ones get\stdClassargumentsmust be a JSON object now, so"arguments": []is refused with-32600- PHP clients encoding an empty array will noticearray<string, T>generatesobjectinstead ofarrayBC breaks are in the CHANGELOG. For upstream: closes
#586and#587, tho#587asked for\stdClasseverywhere.