php-sdk/src/Capability/Registry/ToolReference.php
Mateu Aguiló Bosch ab4f686e18
refactor: put formatters into dedicated classes (#224)
Split formatting logic from Reference classes into dedicated formatter classes:
- ToolResultFormatter: Formats tool execution results into Content items
- PromptResultFormatter: Formats prompt results into PromptMessage items
- ResourceResultFormatter: Formats resource results into ResourceContents items

This refactor:
- Eliminates 122 lines of code duplication between ResourceReference and
  ResourceTemplateReference (identical formatting logic)
- Improves code organization by separating formatting concerns from
  capability reference management
- Makes formatters independently testable and reusable
- Reduces Reference classes by ~450 lines while maintaining all functionality

Changes:
- Create src/Capability/Formatter/* with three formatter classes
- Update Reference classes to delegate to formatters instead of inline logic
- Add comprehensive test coverage for all formatter classes
- All existing tests pass; no behavioral changes
2026-01-18 22:17:44 +01:00

88 lines
3 KiB
PHP

<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Capability\Registry;
use Mcp\Capability\Formatter\ToolResultFormatter;
use Mcp\Schema\Content\Content;
use Mcp\Schema\Tool;
/**
* @phpstan-import-type Handler from ElementReference
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
class ToolReference extends ElementReference
{
/**
* @param Handler $handler
*/
public function __construct(
public readonly Tool $tool,
callable|array|string $handler,
bool $isManual = false,
) {
parent::__construct($handler, $isManual);
}
/**
* Formats the result of a tool execution into an array of MCP Content items.
*
* - If the result is already a Content object, it's wrapped in an array.
* - If the result is an array:
* - If all elements are Content objects, the array is returned as is.
* - If it's a mixed array (Content and non-Content items), non-Content items are
* individually formatted (scalars to TextContent, others to JSON TextContent).
* - If it's an array with no Content items, the entire array is JSON-encoded into a single TextContent.
* - Scalars (string, int, float, bool) are wrapped in TextContent.
* - null is represented as TextContent('(null)').
* - Other objects are JSON-encoded and wrapped in TextContent.
*
* @param mixed $toolExecutionResult the raw value returned by the tool's PHP method
*
* @return Content[] the content items for CallToolResult
*
* @throws \JsonException if JSON encoding fails for non-Content array/object results
*/
public function formatResult(mixed $toolExecutionResult): array
{
return (new ToolResultFormatter())->format($toolExecutionResult);
}
/**
* Extracts structured content from a tool result using the output schema.
*
* @param mixed $toolExecutionResult the raw value returned by the tool's PHP method
*
* @return array<string, mixed>|null the structured content, or null if not extractable
*
* @throws \JsonException if JSON encoding fails for non-Content array/object results
*/
public function extractStructuredContent(mixed $toolExecutionResult): ?array
{
if (\is_array($toolExecutionResult)) {
return $toolExecutionResult;
}
if (\is_object($toolExecutionResult) && !($toolExecutionResult instanceof Content)) {
$jsonResult = json_encode(
$toolExecutionResult,
\JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR | \JSON_INVALID_UTF8_SUBSTITUTE
);
return json_decode(
$jsonResult, true, 512, \JSON_THROW_ON_ERROR
);
}
return null;
}
}