Skip to content

Commit ff57fe6

Browse files
committed
feat: add tool call events
1 parent c4d627a commit ff57fe6

File tree

5 files changed

+160
-0
lines changed

5 files changed

+160
-0
lines changed

src/Server/Events/ToolCallFailed.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Laravel\Mcp\Server\Events;
4+
5+
use Throwable;
6+
7+
class ToolCallFailed
8+
{
9+
/**
10+
* Create a new event instance.
11+
*/
12+
public function __construct(
13+
public string $toolName,
14+
public array $arguments,
15+
public Throwable $exception,
16+
) {}
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Laravel\Mcp\Server\Events;
4+
5+
class ToolCallFinished
6+
{
7+
/**
8+
* Create a new event instance.
9+
*/
10+
public function __construct(
11+
public string $toolName,
12+
public array $arguments,
13+
) {}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Laravel\Mcp\Server\Events;
4+
5+
class ToolCallStarting
6+
{
7+
/**
8+
* Create a new event instance.
9+
*/
10+
public function __construct(
11+
public string $toolName,
12+
public array $arguments,
13+
) {}
14+
}

src/Server/Methods/CallTool.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
use Generator;
88
use Illuminate\Container\Container;
99
use Illuminate\Contracts\Support\Arrayable;
10+
use Illuminate\Events\Dispatcher;
1011
use Illuminate\Support\ItemNotFoundException;
1112
use Illuminate\Validation\ValidationException;
1213
use Laravel\Mcp\Request;
1314
use Laravel\Mcp\Server\Contracts\Method;
15+
use Laravel\Mcp\Server\Events\ToolCallFailed;
16+
use Laravel\Mcp\Server\Events\ToolCallFinished;
17+
use Laravel\Mcp\Server\Events\ToolCallStarting;
1418
use Laravel\Mcp\Server\ServerContext;
1519
use Laravel\Mcp\Server\Tools\ToolNotification;
1620
use Laravel\Mcp\Server\Tools\ToolResult;
@@ -36,13 +40,21 @@ public function handle(JsonRpcRequest $request, ServerContext $context)
3640
);
3741
}
3842

43+
$events = app(Dispatcher::class);
44+
3945
try {
46+
$events->dispatch(new ToolCallStarting($request->params['name'], $request->params['arguments']));
47+
4048
$result = Container::getInstance()->call([$tool, 'handle'], [
4149
'request' => new Request(
4250
$request->params['arguments'],
4351
),
4452
]);
53+
54+
$events->dispatch(new ToolCallFinished($request->params['name'], $request->params['arguments']));
4555
} catch (ValidationException $e) {
56+
$events->dispatch(new ToolCallFailed($request->params['name'], $request->params['arguments'], $e));
57+
4658
$result = ToolResult::error(ValidationMessages::from($e));
4759
}
4860

tests/Unit/Methods/CallToolTest.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
<?php
22

3+
use Illuminate\Events\Dispatcher;
4+
use Laravel\Mcp\Server\Events\ToolCallFailed;
5+
use Laravel\Mcp\Server\Events\ToolCallFinished;
6+
use Laravel\Mcp\Server\Events\ToolCallStarting;
37
use Laravel\Mcp\Server\Methods\CallTool;
48
use Laravel\Mcp\Server\ServerContext;
59
use Laravel\Mcp\Server\Transport\JsonRpcRequest;
610
use Laravel\Mcp\Server\Transport\JsonRpcResponse;
11+
use Mockery as m;
712
use Tests\Fixtures\CurrentTimeTool;
813
use Tests\Fixtures\ExampleTool;
914

@@ -124,3 +129,101 @@
124129
->and($type)->toEqual('text')
125130
->and($text)->toContain('The current time is ');
126131
});
132+
133+
it('will call the tool call starting and finished event', function () {
134+
$request = JsonRpcRequest::fromJson(json_encode([
135+
'jsonrpc' => '2.0',
136+
'id' => 1,
137+
'method' => 'tools/call',
138+
'params' => [
139+
'name' => 'example-tool',
140+
'arguments' => ['name' => 'John Doe'],
141+
],
142+
]));
143+
144+
$context = new ServerContext(
145+
supportedProtocolVersions: ['2025-03-26'],
146+
serverCapabilities: [],
147+
serverName: 'Test Server',
148+
serverVersion: '1.0.0',
149+
instructions: 'Test instructions',
150+
maxPaginationLength: 50,
151+
defaultPaginationLength: 10,
152+
tools: [ExampleTool::class],
153+
resources: [],
154+
prompts: [],
155+
);
156+
157+
$dispatcherMock = m::mock(Dispatcher::class);
158+
$dispatcherMock->shouldReceive('dispatch')
159+
->once()
160+
->with(m::on(function ($event) {
161+
return $event instanceof ToolCallStarting
162+
&& $event->toolName === 'example-tool'
163+
&& $event->arguments === ['name' => 'John Doe'];
164+
}));
165+
$dispatcherMock->shouldReceive('dispatch')
166+
->once()
167+
->with(m::on(function ($event) {
168+
return $event instanceof ToolCallFinished
169+
&& $event->toolName === 'example-tool'
170+
&& $event->arguments === ['name' => 'John Doe'];
171+
}));
172+
173+
app()->instance(Dispatcher::class, $dispatcherMock);
174+
175+
$method = new CallTool;
176+
177+
$method->handle($request, $context);
178+
179+
$this->addToAssertionCount(2);
180+
});
181+
182+
it('will call the tool call starting and failed event', function () {
183+
$request = JsonRpcRequest::fromJson(json_encode([
184+
'jsonrpc' => '2.0',
185+
'id' => 1,
186+
'method' => 'tools/call',
187+
'params' => [
188+
'name' => 'example-tool',
189+
'arguments' => [],
190+
],
191+
]));
192+
193+
$context = new ServerContext(
194+
supportedProtocolVersions: ['2025-03-26'],
195+
serverCapabilities: [],
196+
serverName: 'Test Server',
197+
serverVersion: '1.0.0',
198+
instructions: 'Test instructions',
199+
maxPaginationLength: 50,
200+
defaultPaginationLength: 10,
201+
tools: [ExampleTool::class],
202+
resources: [],
203+
prompts: [],
204+
);
205+
206+
$dispatcherMock = m::mock(Dispatcher::class);
207+
$dispatcherMock->shouldReceive('dispatch')
208+
->once()
209+
->with(m::on(function ($event) {
210+
return $event instanceof ToolCallStarting
211+
&& $event->toolName === 'example-tool'
212+
&& $event->arguments === [];
213+
}));
214+
$dispatcherMock->shouldReceive('dispatch')
215+
->once()
216+
->with(m::on(function ($event) {
217+
return $event instanceof ToolCallFailed
218+
&& $event->toolName === 'example-tool'
219+
&& $event->arguments === [];
220+
}));
221+
222+
app()->instance(Dispatcher::class, $dispatcherMock);
223+
224+
$method = new CallTool;
225+
226+
$method->handle($request, $context);
227+
228+
$this->addToAssertionCount(2);
229+
});

0 commit comments

Comments
 (0)