|
2 | 2 |
|
3 | 3 | namespace PhpMcp\Server\Tests\Unit; |
4 | 4 |
|
5 | | -use Grpc\Call; |
6 | 5 | use Mockery; |
7 | 6 | use Mockery\MockInterface; |
8 | 7 | use PhpMcp\Schema\ClientCapabilities; |
|
73 | 72 | $this->session = Mockery::mock(SessionInterface::class); |
74 | 73 | /** @var MockInterface&ContainerInterface $container */ |
75 | 74 | $this->container = Mockery::mock(ContainerInterface::class); |
| 75 | + $this->context = new Context(Mockery::mock(SessionInterface::class)); |
76 | 76 |
|
77 | 77 | $configuration = new Configuration( |
78 | 78 | serverInfo: Implementation::make('DispatcherTestServer', '1.0'), |
|
106 | 106 | $this->session->shouldReceive('set')->with('client_info', Mockery::on(fn($value) => $value['name'] === 'client' && $value['version'] === '1.0'))->once(); |
107 | 107 | $this->session->shouldReceive('set')->with('protocol_version', Protocol::LATEST_PROTOCOL_VERSION)->once(); |
108 | 108 |
|
109 | | - $result = $this->dispatcher->handleRequest($request, $this->session, new Context(null, Mockery::mock(SessionInterface::class))); |
| 109 | + $result = $this->dispatcher->handleRequest($request, $this->session, $this->context); |
110 | 110 | expect($result)->toBeInstanceOf(InitializeResult::class); |
111 | 111 | expect($result->protocolVersion)->toBe(Protocol::LATEST_PROTOCOL_VERSION); |
112 | 112 | expect($result->serverInfo->name)->toBe('DispatcherTestServer'); |
113 | 113 | }); |
114 | 114 |
|
115 | 115 | it('routes to handlePing for ping request', function () { |
116 | 116 | $request = new JsonRpcRequest('2.0', 'id1', 'ping', []); |
117 | | - $result = $this->dispatcher->handleRequest($request, $this->session, new Context(null, Mockery::mock(SessionInterface::class))); |
| 117 | + $result = $this->dispatcher->handleRequest($request, $this->session, $this->context); |
118 | 118 | expect($result)->toBeInstanceOf(EmptyResult::class); |
119 | 119 | }); |
120 | 120 |
|
121 | 121 | it('throws MethodNotFound for unknown request method', function () { |
122 | 122 | $rawRequest = new JsonRpcRequest('2.0', 'id1', 'unknown/method', []); |
123 | | - $this->dispatcher->handleRequest($rawRequest, $this->session, new Context(null, Mockery::mock(SessionInterface::class))); |
| 123 | + $this->dispatcher->handleRequest($rawRequest, $this->session, $this->context); |
124 | 124 | })->throws(McpServerException::class, "Method 'unknown/method' not found."); |
125 | 125 |
|
126 | 126 | it('routes to handleNotificationInitialized for initialized notification', function () { |
|
202 | 202 | $args = ['a' => 10, 'b' => 5]; |
203 | 203 | $toolSchema = ToolSchema::make($toolName, ['type' => 'object', 'properties' => ['a' => ['type' => 'integer'], 'b' => ['type' => 'integer']]]); |
204 | 204 | $registeredToolMock = Mockery::mock(RegisteredTool::class, [$toolSchema, 'MyToolHandler', 'handleTool', false]); |
205 | | - $context = new Context(null, Mockery::mock(SessionInterface::class)); |
206 | 205 |
|
207 | 206 | $this->registry->shouldReceive('getTool')->with($toolName)->andReturn($registeredToolMock); |
208 | 207 | $this->schemaValidator->shouldReceive('validateAgainstJsonSchema')->with($args, $toolSchema->inputSchema)->andReturn([]); // No validation errors |
209 | | - $registeredToolMock->shouldReceive('call')->with($this->container, $args, $context)->andReturn([TextContent::make("Result: 15")]); |
| 208 | + $registeredToolMock->shouldReceive('call')->with($this->container, $args, $this->context)->andReturn([TextContent::make("Result: 15")]); |
210 | 209 |
|
211 | 210 | $request = CallToolRequest::make(1, $toolName, $args); |
212 | | - $result = $this->dispatcher->handleToolCall($request, $context); |
| 211 | + $result = $this->dispatcher->handleToolCall($request, $this->context); |
213 | 212 |
|
214 | 213 | expect($result)->toBeInstanceOf(CallToolResult::class); |
215 | 214 | expect($result->content[0]->text)->toBe("Result: 15"); |
|
219 | 218 | it('can handle tool call request and throw exception if tool not found', function () { |
220 | 219 | $this->registry->shouldReceive('getTool')->with('unknown-tool')->andReturn(null); |
221 | 220 | $request = CallToolRequest::make(1, 'unknown-tool', []); |
222 | | - $this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class))); |
| 221 | + $this->dispatcher->handleToolCall($request, $this->context); |
223 | 222 | })->throws(McpServerException::class, "Tool 'unknown-tool' not found."); |
224 | 223 |
|
225 | 224 | it('can handle tool call request and throw exception if argument validation fails', function () { |
|
234 | 233 |
|
235 | 234 | $request = CallToolRequest::make(1, $toolName, $args); |
236 | 235 | try { |
237 | | - $this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class))); |
| 236 | + $this->dispatcher->handleToolCall($request, $this->context); |
238 | 237 | } catch (McpServerException $e) { |
239 | 238 | expect($e->getMessage())->toContain("Invalid parameters for tool 'strict-tool'"); |
240 | 239 | expect($e->getData()['validation_errors'])->toBeArray(); |
|
251 | 250 | $registeredToolMock->shouldReceive('call')->andThrow(new \RuntimeException("Tool crashed!")); |
252 | 251 |
|
253 | 252 | $request = CallToolRequest::make(1, $toolName, []); |
254 | | - $result = $this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class))); |
| 253 | + $result = $this->dispatcher->handleToolCall($request, $this->context); |
255 | 254 |
|
256 | 255 | expect($result->isError)->toBeTrue(); |
257 | 256 | expect($result->content[0]->text)->toBe("Tool execution failed: Tool crashed!"); |
|
268 | 267 |
|
269 | 268 |
|
270 | 269 | $request = CallToolRequest::make(1, $toolName, []); |
271 | | - $result = $this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class))); |
| 270 | + $result = $this->dispatcher->handleToolCall($request, $this->context); |
272 | 271 |
|
273 | 272 | expect($result->isError)->toBeTrue(); |
274 | 273 | expect($result->content[0]->text)->toBe("Failed to serialize tool result: Unencodable."); |
275 | 274 | }); |
276 | 275 |
|
277 | | - |
278 | 276 | it('can handle resources list request and return paginated resources', function () { |
279 | 277 | $resourceSchemas = [ |
280 | 278 | ResourceSchema::make('res://1', 'Resource1'), |
|
338 | 336 | $resourceContents = [TextContent::make('File content')]; |
339 | 337 |
|
340 | 338 | $this->registry->shouldReceive('getResource')->with($uri)->andReturn($registeredResourceMock); |
341 | | - $registeredResourceMock->shouldReceive('read')->with($this->container, $uri)->andReturn($resourceContents); |
| 339 | + $registeredResourceMock->shouldReceive('read')->with($this->container, $uri, $this->context)->andReturn($resourceContents); |
342 | 340 |
|
343 | 341 | $request = ReadResourceRequest::make(1, $uri); |
344 | | - $result = $this->dispatcher->handleResourceRead($request); |
| 342 | + $result = $this->dispatcher->handleResourceRead($request, $this->context); |
345 | 343 |
|
346 | 344 | expect($result)->toBeInstanceOf(ReadResourceResult::class); |
347 | 345 | expect($result->contents)->toEqual($resourceContents); |
|
350 | 348 | it('can handle resource read request and throw exception if resource not found', function () { |
351 | 349 | $this->registry->shouldReceive('getResource')->with('unknown://uri')->andReturn(null); |
352 | 350 | $request = ReadResourceRequest::make(1, 'unknown://uri'); |
353 | | - $this->dispatcher->handleResourceRead($request); |
| 351 | + $this->dispatcher->handleResourceRead($request, $this->context); |
354 | 352 | })->throws(McpServerException::class, "Resource URI 'unknown://uri' not found."); |
355 | 353 |
|
356 | 354 | it('can handle resource subscribe request and call subscription manager', function () { |
|
396 | 394 | $promptMessages = [PromptMessage::make(Role::User, TextContent::make("Summary for 2024-07-16"))]; |
397 | 395 |
|
398 | 396 | $this->registry->shouldReceive('getPrompt')->with($promptName)->andReturn($registeredPromptMock); |
399 | | - $registeredPromptMock->shouldReceive('get')->with($this->container, $args)->andReturn($promptMessages); |
| 397 | + $registeredPromptMock->shouldReceive('get')->with($this->container, $args, $this->context)->andReturn($promptMessages); |
400 | 398 |
|
401 | 399 | $request = GetPromptRequest::make(1, $promptName, $args); |
402 | | - $result = $this->dispatcher->handlePromptGet($request, $this->session); |
| 400 | + $result = $this->dispatcher->handlePromptGet($request, $this->context); |
403 | 401 |
|
404 | 402 | expect($result)->toBeInstanceOf(GetPromptResult::class); |
405 | 403 | expect($result->messages)->toEqual($promptMessages); |
|
413 | 411 | $this->registry->shouldReceive('getPrompt')->with($promptName)->andReturn($registeredPromptMock); |
414 | 412 |
|
415 | 413 | $request = GetPromptRequest::make(1, $promptName, ['other_arg' => 'value']); // 'topic' is missing |
416 | | - $this->dispatcher->handlePromptGet($request, $this->session); |
| 414 | + $this->dispatcher->handlePromptGet($request, $this->context); |
417 | 415 | })->throws(McpServerException::class, "Missing required argument 'topic' for prompt 'needs-topic'."); |
418 | 416 |
|
419 | 417 |
|
|
0 commit comments