Skip to content

Commit 8543966

Browse files
committed
Add ToolExecutionExceptionInterface for custom tool error handling
1 parent b1e54f1 commit 8543966

File tree

6 files changed

+70
-4
lines changed

6 files changed

+70
-4
lines changed

src/Capability/Registry/ReferenceHandlerInterface.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ interface ReferenceHandlerInterface
2727
*
2828
* @return mixed the result of the element execution
2929
*
30-
* @throws \Mcp\Exception\InvalidArgumentException if the handler is invalid
31-
* @throws \Mcp\Exception\RegistryException if execution fails
30+
* @throws \Mcp\Exception\InvalidArgumentException if the handler is invalid
31+
* @throws \Mcp\Exception\ToolExecutionExceptionInterface if the tool reports an error during its execution
32+
* @throws \Mcp\Exception\RegistryException if execution fails
3233
*/
3334
public function handle(ElementReference $reference, array $arguments): mixed;
3435
}

src/Capability/Tool/ToolCaller.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Mcp\Capability\Registry\ReferenceHandlerInterface;
1515
use Mcp\Capability\Registry\ReferenceProviderInterface;
1616
use Mcp\Exception\ToolCallException;
17+
use Mcp\Exception\ToolExecutionExceptionInterface;
1718
use Mcp\Exception\ToolNotFoundException;
1819
use Mcp\Schema\Content\AudioContent;
1920
use Mcp\Schema\Content\EmbeddedResource;
@@ -68,6 +69,11 @@ public function call(CallToolRequest $request): CallToolResult
6869
]);
6970

7071
return new CallToolResult($formattedResult);
72+
} catch (ToolExecutionExceptionInterface $e) {
73+
return CallToolResult::error(array_map(
74+
fn (string $message): TextContent => new TextContent($message),
75+
$e->getErrorMessages(),
76+
));
7177
} catch (\Throwable $e) {
7278
$this->logger->error('Tool execution failed', [
7379
'name' => $toolName,

src/Capability/Tool/ToolCallerInterface.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Mcp\Capability\Tool;
1313

1414
use Mcp\Exception\ToolCallException;
15+
use Mcp\Exception\ToolExecutionExceptionInterface;
1516
use Mcp\Exception\ToolNotFoundException;
1617
use Mcp\Schema\Request\CallToolRequest;
1718
use Mcp\Schema\Result\CallToolResult;
@@ -22,8 +23,9 @@
2223
interface ToolCallerInterface
2324
{
2425
/**
25-
* @throws ToolCallException if the tool execution fails
26-
* @throws ToolNotFoundException if the tool is not found
26+
* @throws ToolCallException if the tool execution fails
27+
* @throws ToolNotFoundException if the tool is not found
28+
* @throws ToolExecutionExceptionInterface if the tool reports an error during its execution
2729
*/
2830
public function call(CallToolRequest $request): CallToolResult;
2931
}

src/Capability/ToolChain.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Mcp\Capability\Tool\ToolCallerInterface;
1818
use Mcp\Exception\InvalidCursorException;
1919
use Mcp\Exception\ToolCallException;
20+
use Mcp\Exception\ToolExecutionExceptionInterface;
2021
use Mcp\Exception\ToolNotFoundException;
2122
use Mcp\Schema\Request\CallToolRequest;
2223
use Mcp\Schema\Result\CallToolResult;
@@ -67,6 +68,10 @@ public function call(CallToolRequest $request): CallToolResult
6768
try {
6869
return $item->call($request);
6970
} catch (\Throwable $e) {
71+
if ($e instanceof ToolExecutionExceptionInterface) {
72+
throw $e;
73+
}
74+
7075
throw new ToolCallException($request, $e);
7176
}
7277
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Exception;
13+
14+
interface ToolExecutionExceptionInterface extends \Throwable
15+
{
16+
/**
17+
* @return non-empty-list<non-empty-string>
18+
*/
19+
public function getErrorMessages(): array;
20+
}

tests/Capability/Tool/ToolCallerTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,38 @@ public function testCallThrowsToolNotFoundExceptionWhenToolNotFound(): void
175175
$this->toolCaller->call($request);
176176
}
177177

178+
public function testCallThrowsToolExecutionException(): void
179+
{
180+
$request = new CallToolRequest('test_tool', ['param' => 'value']);
181+
$tool = $this->createValidTool('test_tool');
182+
$exception = new class extends \Exception implements \Mcp\Exception\ToolExecutionExceptionInterface {
183+
public function getErrorMessages(): array
184+
{
185+
return ['test error'];
186+
}
187+
};
188+
$toolReference = new ToolReference($tool, fn () => throw $exception);
189+
190+
$this->referenceProvider
191+
->expects($this->once())
192+
->method('getTool')
193+
->with('test_tool')
194+
->willReturn($toolReference);
195+
196+
$this->referenceHandler
197+
->expects($this->once())
198+
->method('handle')
199+
->with($toolReference, ['param' => 'value'])
200+
->willThrowException($exception);
201+
202+
$result = $this->toolCaller->call($request);
203+
204+
$this->assertCount(1, $result->content);
205+
$this->assertInstanceOf(TextContent::class, $result->content[0]);
206+
$this->assertEquals('test error', $result->content[0]->text);
207+
$this->assertTrue($result->isError);
208+
}
209+
178210
public function testCallThrowsToolExecutionExceptionWhenHandlerThrowsException(): void
179211
{
180212
$request = new CallToolRequest('failing_tool', ['param' => 'value']);

0 commit comments

Comments
 (0)