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
This commit is contained in:
Mateu Aguiló Bosch 2026-01-18 22:17:44 +01:00 • committed by GitHub
commit ab4f686e18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 727 additions and 595 deletions

View file

@ -0,0 +1,49 @@
<?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\Tests\Unit\Capability\Formatter;
use Mcp\Capability\Formatter\PromptResultFormatter;
use Mcp\Schema\Content\PromptMessage;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Enum\Role;
use PHPUnit\Framework\TestCase;
class PromptResultFormatterTest extends TestCase
{
public function testFormatPromptMessage(): void
{
$message = new PromptMessage(Role::User, new TextContent('hello'));
$result = (new PromptResultFormatter())->format($message);
$this->assertCount(1, $result);
$this->assertSame($message, $result[0]);
}
public function testFormatUserAssistantShorthand(): void
{
$result = (new PromptResultFormatter())->format([
'user' => 'Hello',
'assistant' => 'Hi there',
]);
$this->assertCount(2, $result);
$this->assertSame(Role::User, $result[0]->role);
$this->assertSame(Role::Assistant, $result[1]->role);
}
public function testFormatRoleContentArray(): void
{
$result = (new PromptResultFormatter())->format([
['role' => 'user', 'content' => 'Hello'],
]);
$this->assertCount(1, $result);
$this->assertSame(Role::User, $result[0]->role);
}
}

View file

@ -0,0 +1,40 @@
<?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\Tests\Unit\Capability\Formatter;
use Mcp\Capability\Formatter\ResourceResultFormatter;
use Mcp\Schema\Content\TextResourceContents;
use PHPUnit\Framework\TestCase;
class ResourceResultFormatterTest extends TestCase
{
public function testFormatStringResult(): void
{
$result = (new ResourceResultFormatter())->format('content', 'file://test');
$this->assertCount(1, $result);
$this->assertInstanceOf(TextResourceContents::class, $result[0]);
}
public function testFormatResourceContents(): void
{
$contents = new TextResourceContents('file://test', 'text/plain', 'content');
$result = (new ResourceResultFormatter())->format($contents, 'file://test');
$this->assertSame([$contents], $result);
}
public function testFormatWithMimeType(): void
{
$result = (new ResourceResultFormatter())->format('content', 'file://test', 'text/html');
$this->assertCount(1, $result);
$this->assertSame('text/html', $result[0]->mimeType);
}
}

View file

@ -0,0 +1,58 @@
<?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\Tests\Unit\Capability\Formatter;
use Mcp\Capability\Formatter\ToolResultFormatter;
use Mcp\Schema\Content\TextContent;
use PHPUnit\Framework\TestCase;
class ToolResultFormatterTest extends TestCase
{
public function testFormatStringResult(): void
{
$result = (new ToolResultFormatter())->format('hello');
$this->assertCount(1, $result);
$this->assertInstanceOf(TextContent::class, $result[0]);
$this->assertSame('hello', $result[0]->text);
}
public function testFormatContentResult(): void
{
$content = new TextContent('test');
$result = (new ToolResultFormatter())->format($content);
$this->assertSame([$content], $result);
}
public function testFormatArrayResult(): void
{
$result = (new ToolResultFormatter())->format(['key' => 'value']);
$this->assertCount(1, $result);
$this->assertInstanceOf(TextContent::class, $result[0]);
$this->assertStringContainsString('value', $result[0]->text);
}
public function testFormatNullResult(): void
{
$result = (new ToolResultFormatter())->format(null);
$this->assertCount(1, $result);
$this->assertInstanceOf(TextContent::class, $result[0]);
$this->assertSame('(null)', $result[0]->text);
}
public function testFormatBoolResult(): void
{
$result = (new ToolResultFormatter())->format(true);
$this->assertCount(1, $result);
$this->assertInstanceOf(TextContent::class, $result[0]);
$this->assertSame('true', $result[0]->text);
}
}