Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ All notable changes to `mcp/sdk` will be documented in this file.
* [BC Break] Keep the JSON types of `tools/call` arguments, so validation sees what the client sent: `{}` and `{"0":"x"}` are objects, `[]` and `["x"]` are arrays. Nested objects in `CallToolRequest::$arguments` and in the arguments of `ToolHandlerInterface::execute()` are `\stdClass`, as they are for parameters without a type or typed `mixed`, `object` or `\stdClass`; parameters typed `array` still receive arrays at every level. `array<string, T>` parameters generate `{"type": "object"}`, and `array<int, T>` parameters accept a JSON array or object.
* [BC Break] Reject `tools/call` whose `arguments` is not a JSON object, including `[]`, with `-32602`, and one whose message has a member name starting with a NUL byte with `-32600`.
* [BC Break] Answer params that do not fit a known method (e.g. `tools/call` without `name`) with `-32602` instead of `-32600`, as JSON-RPC requires. `Request::fromArray()` throws the new `InvalidParamsException`, a subclass of `InvalidArgumentException`, and `InvalidInputMessageException::isInvalidParams()` tells the protocols which code to send.
* Fix a handshake-era request for a method the SDK does not know being answered with `-32600`: it gets `-32601`, as JSON-RPC requires and as the `2026-07-28` era already answers.
* [BC Break] Answer an argument that cannot be cast to its parameter type with `-32602` instead of `-32603`, for tools, prompts and resources. The message names the parameter. A `string` parameter refuses a list or an object instead of receiving `"Array"`, and an int-backed enum refuses a value with a fraction instead of truncating it. A missing required argument of a prompt, or of a tool that gets past schema validation, gets `-32602` too.
* [BC Break] Modern tools with an `outputSchema` send and validate a null result as structured content, with JSON text `null` alongside it. A tool whose schema does not allow null, including a `void` tool, returns a tool error instead of the text `(null)`. `ToolReference::extractStructuredContent()` returns `StructuredContent::Absent` when there is nothing to send.
* [BC Break] `ReferenceHandler` resolves a string handler that is not an invokable class from the container by service id. The container goes first, so a function handler no longer runs if the container has an entry of that name.
Expand Down
9 changes: 6 additions & 3 deletions src/Server/Protocol.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Mcp\Server;

use Mcp\Schema\Exception\InvalidInputMessageException;
use Mcp\Schema\Exception\UnknownMethodException;
use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\MessageInterface;
use Mcp\Schema\JsonRpc\Notification;
Expand Down Expand Up @@ -376,9 +377,11 @@ private function handleInvalidMessage(TransportInterface $transport, InvalidInpu
{
$this->logger->warning('Failed to create message.', ['exception' => $exception]);

$error = $exception->isInvalidParams()
? Error::forInvalidParams($exception->getMessage(), $exception->getRequestId())
: Error::forInvalidRequest($exception->getMessage(), $exception->getRequestId());
$error = match (true) {
$exception instanceof UnknownMethodException => Error::forMethodNotFound($exception->getMessage(), $exception->getRequestId()),
$exception->isInvalidParams() => Error::forInvalidParams($exception->getMessage(), $exception->getRequestId()),
default => Error::forInvalidRequest($exception->getMessage(), $exception->getRequestId()),
};
$this->sendResponse($transport, $error, $session, $context);
}

Expand Down
29 changes: 29 additions & 0 deletions tests/Unit/Server/ProtocolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,35 @@ public function testRequestWithoutHandlerReturnsMethodNotFoundError(): void
$this->assertStringContainsString('No handler found', $message['error']['message']);
}

#[TestDox('Request for a method the SDK does not know returns method not found error')]
public function testUnknownMethodReturnsMethodNotFoundError(): void
{
$session = $this->createMock(SessionInterface::class);

$this->sessionManager->method('createWithId')->willReturn($session);
$this->sessionManager->method('exists')->willReturn(true);

$transport = new RecordingTransport();

$protocol = new Protocol(
requestHandlers: [],
notificationHandlers: [],
messageFactory: MessageFactory::make(),
sessionManager: $this->sessionManager,
);

$protocol->processInput(
$transport,
'{"jsonrpc": "2.0", "id": 7, "method": "nonexistent/method", "params": {}}',
Uuid::v4()
);

$this->assertCount(1, $transport->sent);
$message = json_decode($transport->sent[0]['message'], true);
$this->assertSame(Error::METHOD_NOT_FOUND, $message['error']['code']);
$this->assertSame(7, $message['id']);
}

#[TestDox('Handler throwing InvalidArgumentException returns invalid params error')]
public function testHandlerInvalidArgumentReturnsInvalidParamsError(): void
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Server/Transport/StdioDualEraTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ public function testCustomDiscoverHandlerIsNotReachedOverTheHandshake(): void
['jsonrpc' => '2.0', 'id' => 2, 'method' => 'server/discover'],
]);

$this->assertSame(Error::INVALID_REQUEST, $answers[2]['error']['code']);
$this->assertSame(Error::METHOD_NOT_FOUND, $answers[2]['error']['code']);
$this->assertArrayNotHasKey('result', $answers[2]);
}

Expand Down
Loading