Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions examples/http_server.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
#[ToolAttribute(name: "echo", description: "Echoes back the provided message.")]
class EchoTool extends BaseTool
{
/**
* @return \MCP\Server\Tool\Content\ContentItemInterface
*/
protected function doExecute(
#[Parameter(name: 'message', type: 'string', description: 'The message to echo.')]
array $arguments
): array {
): \MCP\Server\Tool\Content\ContentItemInterface {
$message = $arguments['message'] ?? 'Default message if not provided';
return [$this->createTextContent("Echo: " . $message)];
return $this->text("Echo: " . $message);
}
}

Expand Down
7 changes: 5 additions & 2 deletions examples/stdio_server.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
#[ToolAttribute(name: "echo", description: "Echoes back the provided message.")]
class EchoTool extends BaseTool
{
/**
* @return \MCP\Server\Tool\Content\ContentItemInterface
*/
protected function doExecute(
#[Parameter(name: 'message', type: 'string', description: 'The message to echo.')]
array $arguments
): array {
): \MCP\Server\Tool\Content\ContentItemInterface {
$message = $arguments['message'] ?? 'Default message if not provided';
return [$this->createTextContent("Echo: " . $message)];
return $this->text("Echo: " . $message);
}
}

Expand Down
37 changes: 20 additions & 17 deletions src/Tool/Tool.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,16 @@ public function shutdown(): void
final public function execute(array $arguments): array
{
$this->validateArguments($arguments);
$contentItems = $this->doExecute($arguments);
$returnedContent = $this->doExecute($arguments); // Can be single item or array
$contentItems = is_array($returnedContent) ? $returnedContent : [$returnedContent];
$resultArray = [];

foreach ($contentItems as $item) {
if (!$item instanceof Content\ContentItemInterface) {
// Or throw an exception, depending on how strict we want to be
// For now, let's assume doExecute correctly returns
// ContentItemInterface objects and skip invalid items if any
// for robustness.
// A stricter approach might be:
// throw new \LogicException(
// 'doExecute must return an array of ContentItemInterface objects.'
// );
continue;
// Throw an exception for stricter validation, ensuring all items are correct
throw new \LogicException(
'All items returned by doExecute must be instances of ContentItemInterface.'
);
}
$resultArray[] = $item->toArray();
}
Expand Down Expand Up @@ -278,14 +275,20 @@ private function validateType($value, string $type): bool
* Executes the core logic of the tool.
*
* This method must be implemented by concrete tool classes. It receives
* validated arguments and should return an array of ContentItemInterface
* validated arguments and should return one or more ContentItemInterface
* objects representing the tool's output.
*
* If a single ContentItemInterface object is returned, the `execute()` method
* will automatically wrap it in an array. This ensures that the final tool
* response sent by the server adheres to the protocol, which expects an array
* of content items.
*
* @param array<string,mixed> $arguments Validated arguments for the tool, matching the defined parameters.
* @return Content\ContentItemInterface[] An array of content items (e.g., TextContent, ImageContent)
* @return Content\ContentItemInterface[]|Content\ContentItemInterface An array of content items
* (e.g., TextContent, ImageContent) or a single content item
* representing the tool's response.
*/
abstract protected function doExecute(array $arguments): array;
abstract protected function doExecute(array $arguments): array|Content\ContentItemInterface;

// Content Creation Helper Methods

Expand All @@ -297,7 +300,7 @@ abstract protected function doExecute(array $arguments): array;
* @param Content\Annotations|null $annotations Optional annotations for the text content.
* @return Content\TextContent The created TextContent item.
*/
final protected function createTextContent(
final protected function text(
string $text,
?Content\Annotations $annotations = null
): Content\TextContent {
Expand All @@ -314,7 +317,7 @@ final protected function createTextContent(
* @param Content\Annotations|null $annotations Optional annotations for the image content.
* @return Content\ImageContent The created ImageContent item.
*/
final protected function createImageContent(
final protected function image(
string $rawData,
string $mimeType,
?Content\Annotations $annotations = null
Expand All @@ -336,7 +339,7 @@ final protected function createImageContent(
* @param Content\Annotations|null $annotations Optional annotations for the audio content.
* @return Content\AudioContent The created AudioContent item.
*/
final protected function createAudioContent(
final protected function audio(
string $rawData,
string $mimeType,
?Content\Annotations $annotations = null
Expand All @@ -357,7 +360,7 @@ final protected function createAudioContent(
* @param Content\Annotations|null $annotations Optional annotations for the embedded resource.
* @return Content\EmbeddedResource The created EmbeddedResource item.
*/
final protected function createEmbeddedResource(
final protected function embeddedResource(
array $resourceData,
?Content\Annotations $annotations = null
): Content\EmbeddedResource {
Expand Down
5 changes: 5 additions & 0 deletions tests/Capability/FailingMockTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@

use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('failing', 'Failing Tool')]
class FailingMockTool extends Tool
{
/**
* @return array<ContentItemInterface>
* @throws \RuntimeException Always throws.
*/
protected function doExecute(array $arguments): array
{
throw new \RuntimeException('Tool execution failed');
Expand Down
4 changes: 4 additions & 0 deletions tests/Capability/InvalidSuggestionsTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('invalidSuggestionsTool', 'Tool that returns invalid suggestions')]
class InvalidSuggestionsTool extends Tool
Expand All @@ -18,6 +19,9 @@ public function __construct(array $suggestionsToReturn)
$this->suggestionsToReturn = $suggestionsToReturn;
}

/**
* @return array<ContentItemInterface>
*/
protected function doExecute(array $arguments): array
{
// Not used for completion tests
Expand Down
8 changes: 6 additions & 2 deletions tests/Capability/MockTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Attribute\Parameter as ParameterAttribute;
use MCP\Server\Tool\Attribute\ToolAnnotations;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAnnotations(title: 'Mock Test Tool', readOnlyHint: true)]
#[ToolAttribute('test', 'Test Tool')]
class MockTool extends Tool
{
/**
* @return ContentItemInterface
*/
protected function doExecute(
#[ParameterAttribute('data', type: 'string', description: 'Input data')]
array $arguments
): array {
return [$this->createTextContent('Result: ' . $arguments['data'])];
): \MCP\Server\Tool\Content\ContentItemInterface {
return $this->text('Result: ' . $arguments['data']);
}

/**
Expand Down
20 changes: 16 additions & 4 deletions tests/Capability/ToolsCapabilityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,12 @@ public function shutdown(): void
{
$this->shutdownCountRef++;
}
protected function doExecute(array $arguments): array
/**
* @return Content\ContentItemInterface
*/
protected function doExecute(array $arguments): \MCP\Server\Tool\Content\ContentItemInterface
{
return [$this->createTextContent('done')];
return $this->text('done');
}
};

Expand Down Expand Up @@ -312,9 +315,12 @@ public function testHandleCompleteWithNullIdIsNotification(): void
public function testHandleCompleteDefaultSuggestions(): void
{
$basicTool = new #[ToolAttribute('basic', 'Basic Tool')] class extends Tool {
protected function doExecute(array $arguments): array
/**
* @return Content\ContentItemInterface
*/
protected function doExecute(array $arguments): \MCP\Server\Tool\Content\ContentItemInterface
{
return [$this->createTextContent('done')];
return $this->text('done');
}
};
$this->capability->addTool($basicTool); // Add to the existing capability instance
Expand Down Expand Up @@ -447,6 +453,9 @@ public function testHandleCompleteWithToolReturningInvalidSuggestionsValuesType(
// if constructor validation is robust.
$customBadTool = new #[ToolAttribute('customBadValuesTool', 'Tool with bad values type')] class extends Tool
{
/**
* @return array<Content\ContentItemInterface>
*/
protected function doExecute(array $arguments): array
{
return [];
Expand Down Expand Up @@ -481,6 +490,9 @@ public function getCompletionSuggestions(string $argumentName, mixed $currentVal
public function testHandleCompleteWithToolReturningNonArraySuggestions(): void
{
$nonArraySuggestionsTool = new #[ToolAttribute('nonArraySuggestionsTool', 'Tool returning non-array suggestions')] class extends Tool {
/**
* @return array<Content\ContentItemInterface>
*/
protected function doExecute(array $arguments): array
{
return []; // Not called in this test path
Expand Down
10 changes: 7 additions & 3 deletions tests/Tool/ArrayParamTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@
use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Attribute\Parameter as ParameterAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('array-tool', 'Tests array parameters')]
class ArrayParamTool extends Tool
{
/**
* @return ContentItemInterface
*/
protected function doExecute(
#[ParameterAttribute('numbers', type: 'array', description: 'List of numbers')]
#[ParameterAttribute('enabled', type: 'boolean', description: 'Whether processing is enabled')]
array $arguments
): array {
): \MCP\Server\Tool\Content\ContentItemInterface {
if (!$arguments['enabled']) {
return [$this->createTextContent('Processing disabled')];
return $this->text('Processing disabled');
}
$sum = array_sum($arguments['numbers']);
return [$this->createTextContent("Sum: $sum")];
return $this->text("Sum: $sum");
}
}
8 changes: 6 additions & 2 deletions tests/Tool/CalculatorTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@
use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Attribute\Parameter as ParameterAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('calculator', 'A calculator tool')]
class CalculatorTool extends Tool
{
/**
* @return ContentItemInterface
*/
protected function doExecute(
#[ParameterAttribute('operation', type: 'string', description: 'Operation to perform (add/subtract)')]
#[ParameterAttribute('a', type: 'number', description: 'First number')]
#[ParameterAttribute('b', type: 'number', description: 'Second number')]
array $arguments
): array {
): \MCP\Server\Tool\Content\ContentItemInterface {
$result = match ($arguments['operation']) {
'add' => $arguments['a'] + $arguments['b'],
'subtract' => $arguments['a'] - $arguments['b'],
default => throw new \InvalidArgumentException('Invalid operation')
};

return [$this->createTextContent((string)$result)];
return $this->text((string)$result);
}
}
4 changes: 4 additions & 0 deletions tests/Tool/Helpers/AbstractTestTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@

use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('abstract-test-tool', 'Abstract Test Tool')]
abstract class AbstractTestTool extends Tool
{
/**
* @return array<ContentItemInterface>
*/
abstract protected function doExecute(array $arguments): array;
}
4 changes: 4 additions & 0 deletions tests/Tool/Helpers/NoAttributeTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
namespace MCP\Server\Tests\Tool\Helpers;

use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Content\ContentItemInterface;

class NoAttributeTool extends Tool
{
/**
* @return array<ContentItemInterface>
*/
protected function doExecute(array $arguments): array
{
return []; // Dummy implementation
Expand Down
8 changes: 6 additions & 2 deletions tests/Tool/MockTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Attribute\Parameter as ParameterAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('test', 'Test Tool')]
class MockTool extends Tool
{
protected function doExecute(array $arguments): array
/**
* @return ContentItemInterface
*/
protected function doExecute(array $arguments): \MCP\Server\Tool\Content\ContentItemInterface
{
return [$this->createTextContent('Hello World')];
return $this->text('Hello World');
}
}
10 changes: 7 additions & 3 deletions tests/Tool/MultiOutputTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Attribute\Parameter as ParameterAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('multi-output', 'Tests multiple output types')]
class MultiOutputTool extends Tool
{
/**
* @return ContentItemInterface
*/
protected function doExecute(
#[ParameterAttribute('format', type: 'string', description: 'Output format (text/image)')]
array $arguments
): array {
): \MCP\Server\Tool\Content\ContentItemInterface {
if ($arguments['format'] === 'text') {
return [$this->createTextContent('Hello world')];
return $this->text('Hello world');
}
return [$this->createImageContent('fake-image-data', 'image/png')];
return $this->image('fake-image-data', 'image/png');
}
}
8 changes: 6 additions & 2 deletions tests/Tool/OptionalParamTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
use MCP\Server\Tool\Tool;
use MCP\Server\Tool\Attribute\Tool as ToolAttribute;
use MCP\Server\Tool\Attribute\Parameter as ParameterAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('greeter', 'A friendly greeter')]
class OptionalParamTool extends Tool
{
/**
* @return ContentItemInterface
*/
protected function doExecute(
#[ParameterAttribute('name', type: 'string', description: 'Name to greet')]
#[ParameterAttribute('title', type: 'string', description: 'Optional title', required: false)]
array $arguments
): array {
): \MCP\Server\Tool\Content\ContentItemInterface {
$title = $arguments['title'] ?? 'friend';
return [$this->createTextContent("Hello {$title} {$arguments['name']}")];
return $this->text("Hello {$title} {$arguments['name']}");
}
}
8 changes: 6 additions & 2 deletions tests/Tool/OtherMockTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
// ParameterAttribute is not used in this class, so it's not strictly necessary to import,
// but kept for consistency if it might be added later or to avoid confusion.
use MCP\Server\Tool\Attribute\Parameter as ParameterAttribute;
use MCP\Server\Tool\Content\ContentItemInterface;

#[ToolAttribute('other', 'Other Tool')]
class OtherMockTool extends Tool
{
protected function doExecute(array $arguments): array
/**
* @return ContentItemInterface
*/
protected function doExecute(array $arguments): \MCP\Server\Tool\Content\ContentItemInterface
{
return [$this->createTextContent('Other Result')];
return $this->text('Other Result');
}
}
Loading