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
88 lines
3 KiB
PHP
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;
|
|
}
|
|
}
|