From d7197ba745952955928de70b06cdd6d7f8ed90d4 Mon Sep 17 00:00:00 2001 From: Tom H Anderson Date: Mon, 5 Oct 2026 16:26:17 -0600 Subject: [PATCH] fix: handle notifications and batches for invalid sessions in Protocol processMessage() read $message->id when the session was not found, which crashed with "Undefined property" for notifications (e.g. a client sending notifications/initialized with a stale Mcp-Session-Id). Notifications are now logged and ignored, and requests/batches use getId() for the error. Co-Authored-By: Claude Opus 5.5 --- src/Protocol.php | 7 ++++++- tests/Unit/ProtocolTest.php | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Protocol.php b/src/Protocol.php index 918885e..7870a49 100644 --- a/src/Protocol.php +++ b/src/Protocol.php @@ -120,7 +120,12 @@ public function processMessage(Request|Notification|BatchRequest $message, strin $session = $this->sessionManager->getSession($sessionId); if ($session === null) { - $error = Error::forInvalidRequest('Invalid or expired session. Please re-initialize the session.', $message->id); + if ($message instanceof Notification) { + $this->logger->warning('Notification received for invalid or expired session. Ignoring.', ['sessionId' => $sessionId, 'method' => $message->method]); + return; + } + + $error = Error::forInvalidRequest('Invalid or expired session. Please re-initialize the session.', $message->getId()); $messageContext['status_code'] = 404; $this->transport->sendMessage($error, $sessionId, $messageContext) diff --git a/tests/Unit/ProtocolTest.php b/tests/Unit/ProtocolTest.php index 4bc0b15..0975930 100644 --- a/tests/Unit/ProtocolTest.php +++ b/tests/Unit/ProtocolTest.php @@ -271,6 +271,31 @@ function expectSuccessResponse(mixed $response, mixed $expectedResult, string|in $this->session->shouldNotHaveReceived('save'); }); +it('ignores notification if session is not found', function () { + $notification = createNotification('notifications/initialized'); + $this->sessionManager->shouldReceive('getSession')->with('unknown-client')->andReturn(null); + + $this->transport->shouldNotReceive('sendMessage'); + $this->dispatcher->shouldNotReceive('handleNotification'); + + $this->protocol->processMessage($notification, 'unknown-client', ['is_initialize_request' => false]); + $this->session->shouldNotHaveReceived('save'); +}); + +it('sends error response with first request id if session is not found for batch', function () { + $batchRequest = new BatchRequest([createNotification('notifications/initialized'), createRequest('tools/list', [], 'batch-req-1')]); + $this->sessionManager->shouldReceive('getSession')->with('unknown-client')->andReturn(null); + + $this->transport->shouldReceive('sendMessage')->once() + ->with(Mockery::on(function (Error $error) { + expectErrorResponse($error, \PhpMcp\Schema\Constants::INVALID_REQUEST, 'batch-req-1'); + return true; + }), 'unknown-client', Mockery::any()) + ->andReturn(resolve(null)); + + $this->protocol->processMessage($batchRequest, 'unknown-client'); +}); + it('sends error response if session is not initialized for non-initialize request', function () { $request = createRequest('tools/list'); $this->session->shouldReceive('get')->with('initialized', false)->andReturn(false);