Skip to content

Commit 31a7bb2

Browse files
fix: implement proper MCP protocol version negotiation
- Server now responds with client's requested version if supported - Falls back to latest version only when client requests unsupported version - Follows MCP specification for version negotiation correctly - Add test cases for both supported and unsupported version scenarios Fixes issue where server always responded with latest version regardless of client request
1 parent 2b4f1d4 commit 31a7bb2

File tree

2 files changed

+35
-14
lines changed

2 files changed

+35
-14
lines changed

src/Dispatcher.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,14 @@ public function handleNotification(Notification $notification, SessionInterface
119119

120120
public function handleInitialize(InitializeRequest $request, SessionInterface $session): InitializeResult
121121
{
122-
if (! in_array($request->protocolVersion, Protocol::SUPPORTED_PROTOCOL_VERSIONS)) {
123-
$this->logger->warning("Unsupported protocol version: {$request->protocolVersion}", [
124-
'supportedVersions' => Protocol::SUPPORTED_PROTOCOL_VERSIONS,
125-
]);
122+
if (in_array($request->protocolVersion, Protocol::SUPPORTED_PROTOCOL_VERSIONS)) {
123+
$protocolVersion = $request->protocolVersion;
124+
} else {
125+
$protocolVersion = Protocol::LATEST_PROTOCOL_VERSION;
126126
}
127127

128-
$protocolVersion = Protocol::LATEST_PROTOCOL_VERSION;
129-
130128
$session->set('client_info', $request->clientInfo);
131129

132-
133130
$serverInfo = $this->configuration->serverInfo;
134131
$capabilities = $this->configuration->capabilities;
135132
$instructions = $this->configuration->instructions;

tests/Unit/DispatcherTest.php

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
'capabilities' => [],
101101
]
102102
);
103-
$this->session->shouldReceive('set')->with('client_info', Mockery::on(fn ($value) => $value->name === 'client' && $value->version === '1.0'))->once();
103+
$this->session->shouldReceive('set')->with('client_info', Mockery::on(fn($value) => $value->name === 'client' && $value->version === '1.0'))->once();
104104

105105
$result = $this->dispatcher->handleRequest($request, $this->session);
106106
expect($result)->toBeInstanceOf(InitializeResult::class);
@@ -143,6 +143,30 @@
143143
expect($result->capabilities)->toBeInstanceOf(ServerCapabilities::class);
144144
});
145145

146+
it('can handle initialize request with older supported protocol version', function () {
147+
$clientInfo = Implementation::make('TestClient', '0.9.9');
148+
$clientRequestedVersion = '2024-11-05';
149+
$request = InitializeRequest::make(1, $clientRequestedVersion, ClientCapabilities::make(), $clientInfo, []);
150+
$this->session->shouldReceive('set')->with('client_info', $clientInfo)->once();
151+
152+
$result = $this->dispatcher->handleInitialize($request, $this->session);
153+
expect($result->protocolVersion)->toBe($clientRequestedVersion);
154+
expect($result->serverInfo->name)->toBe('DispatcherTestServer');
155+
expect($result->capabilities)->toBeInstanceOf(ServerCapabilities::class);
156+
});
157+
158+
it('can handle initialize request with unsupported protocol version', function () {
159+
$clientInfo = Implementation::make('TestClient', '0.9.9');
160+
$unsupportedVersion = '1999-01-01';
161+
$request = InitializeRequest::make(1, $unsupportedVersion, ClientCapabilities::make(), $clientInfo, []);
162+
$this->session->shouldReceive('set')->with('client_info', $clientInfo)->once();
163+
164+
$result = $this->dispatcher->handleInitialize($request, $this->session);
165+
expect($result->protocolVersion)->toBe(Protocol::LATEST_PROTOCOL_VERSION);
166+
expect($result->serverInfo->name)->toBe('DispatcherTestServer');
167+
expect($result->capabilities)->toBeInstanceOf(ServerCapabilities::class);
168+
});
169+
146170
it('can handle tool list request and return paginated tools', function () {
147171
$toolSchemas = [
148172
ToolSchema::make('tool1', ['type' => 'object', 'properties' => []]),
@@ -256,14 +280,14 @@
256280
$requestP1 = ListResourcesRequest::make(1);
257281
$resultP1 = $this->dispatcher->handleResourcesList($requestP1);
258282
expect($resultP1->resources)->toHaveCount(DISPATCHER_PAGINATION_LIMIT);
259-
expect(array_map(fn ($r) => $r->name, $resultP1->resources))->toEqual(['Resource1', 'Resource2', 'Resource3']);
283+
expect(array_map(fn($r) => $r->name, $resultP1->resources))->toEqual(['Resource1', 'Resource2', 'Resource3']);
260284
expect($resultP1->nextCursor)->toBe(base64_encode('offset=3'));
261285

262286
// Page 2
263287
$requestP2 = ListResourcesRequest::make(2, $resultP1->nextCursor);
264288
$resultP2 = $this->dispatcher->handleResourcesList($requestP2);
265289
expect($resultP2->resources)->toHaveCount(2);
266-
expect(array_map(fn ($r) => $r->name, $resultP2->resources))->toEqual(['Resource4', 'Resource5']);
290+
expect(array_map(fn($r) => $r->name, $resultP2->resources))->toEqual(['Resource4', 'Resource5']);
267291
expect($resultP2->nextCursor)->toBeNull();
268292
});
269293

@@ -288,14 +312,14 @@
288312
$requestP1 = ListResourceTemplatesRequest::make(1);
289313
$resultP1 = $this->dispatcher->handleResourceTemplateList($requestP1);
290314
expect($resultP1->resourceTemplates)->toHaveCount(DISPATCHER_PAGINATION_LIMIT);
291-
expect(array_map(fn ($rt) => $rt->name, $resultP1->resourceTemplates))->toEqual(['Template1', 'Template2', 'Template3']);
315+
expect(array_map(fn($rt) => $rt->name, $resultP1->resourceTemplates))->toEqual(['Template1', 'Template2', 'Template3']);
292316
expect($resultP1->nextCursor)->toBe(base64_encode('offset=3'));
293317

294318
// Page 2
295319
$requestP2 = ListResourceTemplatesRequest::make(2, $resultP1->nextCursor);
296320
$resultP2 = $this->dispatcher->handleResourceTemplateList($requestP2);
297321
expect($resultP2->resourceTemplates)->toHaveCount(1);
298-
expect(array_map(fn ($rt) => $rt->name, $resultP2->resourceTemplates))->toEqual(['Template4']);
322+
expect(array_map(fn($rt) => $rt->name, $resultP2->resourceTemplates))->toEqual(['Template4']);
299323
expect($resultP2->nextCursor)->toBeNull();
300324
});
301325

@@ -345,14 +369,14 @@
345369
$requestP1 = ListPromptsRequest::make(1);
346370
$resultP1 = $this->dispatcher->handlePromptsList($requestP1);
347371
expect($resultP1->prompts)->toHaveCount(DISPATCHER_PAGINATION_LIMIT);
348-
expect(array_map(fn ($p) => $p->name, $resultP1->prompts))->toEqual(['promptA', 'promptB', 'promptC']);
372+
expect(array_map(fn($p) => $p->name, $resultP1->prompts))->toEqual(['promptA', 'promptB', 'promptC']);
349373
expect($resultP1->nextCursor)->toBe(base64_encode('offset=3'));
350374

351375
// Page 2
352376
$requestP2 = ListPromptsRequest::make(2, $resultP1->nextCursor);
353377
$resultP2 = $this->dispatcher->handlePromptsList($requestP2);
354378
expect($resultP2->prompts)->toHaveCount(DISPATCHER_PAGINATION_LIMIT); // 3 more
355-
expect(array_map(fn ($p) => $p->name, $resultP2->prompts))->toEqual(['promptD', 'promptE', 'promptF']);
379+
expect(array_map(fn($p) => $p->name, $resultP2->prompts))->toEqual(['promptD', 'promptE', 'promptF']);
356380
expect($resultP2->nextCursor)->toBeNull(); // End of list
357381
});
358382

0 commit comments

Comments
 (0)