Lines 90.00% 36 / 40
Methods 60.00% 3 / 5
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 runConsole 0.00% 0 / 1 0.00% 0 / 1 2
 getServerInfo 100.00% 1 / 1 100.00% 1 / 1 1
 faqSearch 100.00% 14 / 14 100.00% 1 / 1 4
 buildServer 86.95% 20 / 23 0.00% 0 / 1 3.02
28final readonly class McpSdkRuntime implements McpServerRuntimeInterface
29{
30    /**
31     * @param array<string, mixed> $serverInfo
32     */
33    public function __construct(
34        private FaqSearchTool $faqSearchTool,
35        private array $serverInfo,
36    ) {
37    }
38
39    public function runConsole(InputInterface $input, OutputInterface $output): void
40    {
41        $this->buildServer()->run(new StdioTransport());
42    }
43
44    public function getServerInfo(): array
45    {
46        return $this->serverInfo;
47    }
48
49    /**
50     * Adapter method for mcp/sdk manual tool registration.
51     *
52     * Returns the decoded JSON payload for successful searches and a string for
53     * user-facing errors, matching the intent of the current Symfony MCP runtime.
54     *
55     * @return array<string, mixed>|string
56     */
57    public function faqSearch(
58        string $query,
59        ?int $category_id = null,
60        int $limit = 10,
61        bool $all_languages = false,
62    ): array|string {
63        $result = $this->faqSearchTool->execute([
64            'query' => $query,
65            'category_id' => $category_id,
66            'limit' => $limit,
67            'all_languages' => $all_languages,
68        ]);
69
70        $content = (string) ($result['content'] ?? '');
71        $decoded = json_decode($content, associative: true);
72        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
73            $normalized = [];
74            foreach ($decoded as $decodedKey => $decodedValue) {
75                $normalized[(string) $decodedKey] = $decodedValue;
76            }
77
78            return $normalized;
79        }
80
81        return $content;
82    }
83
84    private function buildServer(): Server
85    {
86        if (!class_exists(Server::class) || !class_exists(StdioTransport::class)) {
87            throw new RuntimeException(
88                'The mcp/sdk package is not installed or does not expose the expected server classes.',
89            );
90        }
91
92        $definition = $this->faqSearchTool->getDefinition();
93
94        return Server::builder()
95            ->setServerInfo(
96                (string) $this->serverInfo['name'],
97                (string) $this->serverInfo['version'],
98                (string) ($this->serverInfo['description'] ?? null),
99            )
100            ->addTool(
101                $this->faqSearch(...),
102                $definition->name,
103                null,
104                $definition->description,
105                null,
106                $definition->inputSchema,
107                null,
108                null,
109                $definition->outputSchema,
110            )
111            ->build();
112    }
113}