diff --git a/src/Server/Events/ToolCallFailed.php b/src/Server/Events/ToolCallFailed.php new file mode 100644 index 0000000..d9c161a --- /dev/null +++ b/src/Server/Events/ToolCallFailed.php @@ -0,0 +1,21 @@ +dispatch(new ToolCallStarting($request->params['name'], $request->params['arguments'])); + $result = $tool->handle($request->params['arguments']); + + $events->dispatch(new ToolCallFinished($request->params['name'], $request->params['arguments'])); } catch (ValidationException $e) { + $events->dispatch(new ToolCallFailed($request->params['name'], $request->params['arguments'], $e)); + return JsonRpcResponse::create( $request->id, ToolResult::error($e->getMessage()) diff --git a/tests/Unit/Methods/CallToolTest.php b/tests/Unit/Methods/CallToolTest.php index 85e8b1c..58f0280 100644 --- a/tests/Unit/Methods/CallToolTest.php +++ b/tests/Unit/Methods/CallToolTest.php @@ -2,6 +2,10 @@ namespace Tests\Unit\Methods; +use Illuminate\Events\Dispatcher; +use Laravel\Mcp\Server\Events\ToolCallFailed; +use Laravel\Mcp\Server\Events\ToolCallFinished; +use Laravel\Mcp\Server\Events\ToolCallStarting; use Laravel\Mcp\Server\Methods\CallTool; use Laravel\Mcp\Server\ServerContext; use Laravel\Mcp\Server\Transport\JsonRpcRequest; @@ -9,9 +13,15 @@ use Laravel\Mcp\Tests\Fixtures\ExampleTool; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +use Mockery as m; class CallToolTest extends TestCase { + protected function tearDown(): void + { + m::close(); + } + #[Test] public function it_returns_a_valid_call_tool_response() { @@ -97,4 +107,106 @@ public function it_returns_a_valid_call_tool_response_with_validation_error() 'isError' => true, ], $response->result); } + + #[Test] + public function it_will_call_the_tool_call_starting_and_finished_event() + { + $request = JsonRpcRequest::fromJson(json_encode([ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'example-tool', + 'arguments' => ['name' => 'John Doe'], + ], + ])); + + $context = new ServerContext( + supportedProtocolVersions: ['2025-03-26'], + serverCapabilities: [], + serverName: 'Test Server', + serverVersion: '1.0.0', + instructions: 'Test instructions', + maxPaginationLength: 50, + defaultPaginationLength: 10, + tools: [ExampleTool::class], + resources: [], + prompts: [], + ); + + $dispatcherMock = m::mock(Dispatcher::class); + $dispatcherMock->shouldReceive('dispatch') + ->once() + ->with(m::on(function ($event) { + return $event instanceof ToolCallStarting + && $event->toolName === 'example-tool' + && $event->arguments === ['name' => 'John Doe']; + })); + $dispatcherMock->shouldReceive('dispatch') + ->once() + ->with(m::on(function ($event) { + return $event instanceof ToolCallFinished + && $event->toolName === 'example-tool' + && $event->arguments === ['name' => 'John Doe']; + })); + + app()->instance(Dispatcher::class, $dispatcherMock); + + $method = new CallTool; + + $method->handle($request, $context); + + $this->addToAssertionCount(2); + } + + #[Test] + public function it_will_call_the_tool_call_starting_and_failed_event() + { + $request = JsonRpcRequest::fromJson(json_encode([ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'example-tool', + 'arguments' => [], + ], + ])); + + $context = new ServerContext( + supportedProtocolVersions: ['2025-03-26'], + serverCapabilities: [], + serverName: 'Test Server', + serverVersion: '1.0.0', + instructions: 'Test instructions', + maxPaginationLength: 50, + defaultPaginationLength: 10, + tools: [ExampleTool::class], + resources: [], + prompts: [], + ); + + $dispatcherMock = m::mock(Dispatcher::class); + $dispatcherMock->shouldReceive('dispatch') + ->once() + ->with(m::on(function ($event) { + return $event instanceof ToolCallStarting + && $event->toolName === 'example-tool' + && $event->arguments === []; + })); + $dispatcherMock->shouldReceive('dispatch') + ->once() + ->with(m::on(function ($event) { + return $event instanceof ToolCallFailed + && $event->toolName === 'example-tool' + && $event->arguments === []; + })); + + app()->instance(Dispatcher::class, $dispatcherMock); + + $method = new CallTool; + + $method->handle($request, $context); + + $this->addToAssertionCount(2); + } }