add3a9a21f
Tools now return CallToolResult directly instead of Content arrays, preventing the MCP SDK from auto-generating structuredContent as a JSON array (which Claude Code rejects — expects a JSON object/record). Also adds Accept header to test helpers and SSE response parsing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
56 lines
1.5 KiB
PHP
56 lines
1.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Mcp\Tool;
|
|
|
|
use Mcp\Schema\Content\TextContent;
|
|
use Mcp\Schema\Result\CallToolResult;
|
|
use RuntimeException;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
|
|
trait McpToolHelper
|
|
{
|
|
private function requireRole(Security $security, string $role): void
|
|
{
|
|
if (!$security->isGranted($role)) {
|
|
throw new RuntimeException("Permission denied: {$role} required.");
|
|
}
|
|
}
|
|
|
|
private function jsonResponse(array $data): CallToolResult
|
|
{
|
|
return new CallToolResult(
|
|
content: [new TextContent(text: json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE))],
|
|
);
|
|
}
|
|
|
|
private function mcpError(string $category, string $message): never
|
|
{
|
|
throw new RuntimeException("{$category}: {$message}");
|
|
}
|
|
|
|
/**
|
|
* @return array{page: int, limit: int, offset: int}
|
|
*/
|
|
private function paginationParams(int $page = 1, int $limit = 30): array
|
|
{
|
|
$page = max(1, $page);
|
|
$limit = min(100, max(1, $limit));
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
return ['page' => $page, 'limit' => $limit, 'offset' => $offset];
|
|
}
|
|
|
|
private function paginatedResponse(array $items, int $total, int $page, int $limit): CallToolResult
|
|
{
|
|
return $this->jsonResponse([
|
|
'items' => $items,
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'limit' => $limit,
|
|
'pageCount' => (int) ceil($total / max(1, $limit)),
|
|
]);
|
|
}
|
|
}
|