Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
69.39% |
34 / 49 |
|
50.00% |
1 / 2 |
CRAP | |
0.00% |
0 / 1 |
RemoteMcpController | |
69.39% |
34 / 49 |
|
50.00% |
1 / 2 |
7.03 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__invoke | |
68.75% |
33 / 48 |
|
0.00% |
0 / 1 |
5.76 |
1 | <?php |
2 | namespace Apie\McpServer\Controllers; |
3 | |
4 | use Apie\Common\IntegrationTestLogger; |
5 | use Apie\Core\ApieLib; |
6 | use Apie\McpServer\Tool\ToolFactory; |
7 | use Apie\McpServer\Tool\ToolRunner; |
8 | use Mcp\Server\HttpServerRunner; |
9 | use Mcp\Server\Server; |
10 | use Mcp\Server\Transport\Http\HttpMessage; |
11 | use Mcp\Server\Transport\Http\SessionStoreInterface; |
12 | use Nyholm\Psr7\Response; |
13 | use Nyholm\Psr7\Stream; |
14 | use Psr\Http\Message\ServerRequestInterface; |
15 | use Psr\Log\LoggerInterface; |
16 | |
17 | class RemoteMcpController |
18 | { |
19 | public function __construct( |
20 | private readonly ToolFactory $toolFactory, |
21 | private readonly ToolRunner $toolRunner, |
22 | private readonly LoggerInterface $logger, |
23 | private readonly ?SessionStoreInterface $sessionStore = null |
24 | ) { |
25 | } |
26 | public function __invoke(ServerRequestInterface $request): Response |
27 | { |
28 | $server = new Server('apie-server'); |
29 | $hadError = false; |
30 | $server->registerHandler('tools/list', function ($params) use (&$hadError) { |
31 | try { |
32 | $res = $this->toolFactory->createList(); |
33 | $hadError = false; |
34 | return $res; |
35 | } catch (\Throwable $e) { |
36 | $hadError = true; |
37 | IntegrationTestLogger::logException($e); |
38 | $this->logger->error('Error in tools/list: ' . $e->getMessage() . ': ' . $e->getTraceAsString(), ['exception' => $e]); |
39 | throw $e; |
40 | } |
41 | }); |
42 | $server->registerHandler('tools/call', function ($params) use ($request, &$hadError) { |
43 | try { |
44 | $name = $params->name; |
45 | $tool = $this->toolFactory->findByName($name); |
46 | $res = $this->toolRunner->run($tool, $params, $request); |
47 | $hadError = false; |
48 | return $res; |
49 | } catch (\Throwable $e) { |
50 | $hadError = true; |
51 | IntegrationTestLogger::logException($e); |
52 | $this->logger->error('Error in tools/call: ' . $e->getMessage() . ': ' . $e->getTraceAsString(), ['exception' => $e]); |
53 | throw $e; |
54 | } |
55 | }); |
56 | // TODO: configurable options? |
57 | $httpOptions = [ |
58 | 'session_timeout' => 1800, |
59 | 'max_queue_size' => 500, |
60 | 'enable_sse' => false, |
61 | 'shared_hosting' => true, |
62 | 'server_header' => 'Apie-MCP-Server/' . ApieLib::VERSION, |
63 | ]; |
64 | |
65 | $runner = new HttpServerRunner( |
66 | $server, |
67 | $server->createInitializationOptions(), |
68 | $httpOptions, |
69 | $this->logger, |
70 | $this->sessionStore |
71 | ); |
72 | $message = new HttpMessage($request->getBody()->getContents()); |
73 | $message->setMethod($request->getMethod()); |
74 | foreach ($request->getHeaders() as $name => $values) { |
75 | $message->setHeader($name, implode(', ', $values)); |
76 | } |
77 | $result = $runner->handleRequest($message); |
78 | return new Response( |
79 | $hadError ? 500 : $result->getStatusCode(), |
80 | $result->getHeaders(), |
81 | Stream::create($result->getBody()) |
82 | ); |
83 | } |
84 | } |