Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
71.23% covered (warning)
71.23%
52 / 73
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ToolRunner
71.23% covered (warning)
71.23%
52 / 73
33.33% covered (danger)
33.33%
1 / 3
25.71
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
45.71% covered (danger)
45.71%
16 / 35
0.00% covered (danger)
0.00%
0 / 1
11.76
 actionResponseToToolResult
94.59% covered (success)
94.59%
35 / 37
0.00% covered (danger)
0.00%
0 / 1
11.02
1<?php
2namespace Apie\McpServer\Tool;
3
4use Apie\Common\ApieFacade;
5use Apie\Console\ConsoleCliStorage;
6use Apie\Core\Actions\ActionResponse;
7use Apie\Core\Actions\ActionResponseStatus;
8use Apie\Core\ContextBuilders\ContextBuilderFactory;
9use Apie\Core\ContextConstants;
10use Apie\Core\ValueObjects\Utils;
11use Mcp\Types\CallToolResult;
12use Mcp\Types\TextContent;
13use Mcp\Types\Tool;
14use Psr\Http\Message\ServerRequestInterface;
15use Psr\Log\LoggerInterface;
16use ReflectionClass;
17
18class ToolRunner
19{
20    public function __construct(
21        private readonly ContextBuilderFactory $contextBuilder,
22        private readonly ApieFacade $apieFacade,
23        private readonly LoggerInterface $logger,
24        private readonly ?ConsoleCliStorage $consoleCliStorage = null
25    ) {
26    }
27
28    public function run(Tool $tool, array $params, ?ServerRequestInterface $request = null): CallToolResult
29    {
30        try {
31            $meta = Utils::toArray($tool->_meta);
32            $action = new ReflectionClass($meta["x-definition"]);
33            $fields = Utils::toArray($meta["x-fields"]);
34            if (isset($meta['x-bounded-context-id'])) {
35                $fields[ContextConstants::BOUNDED_CONTEXT_ID] = $meta['x-bounded-context-id'];
36            }
37            if ($this->consoleCliStorage) {
38                $fields[ConsoleCliStorage::class] = $this->consoleCliStorage;
39            }
40            $fields[ContextConstants::MCP_SERVER] = true;
41            $fields[Tool::class] = true;
42            $fields[ContextConstants::RAW_CONTENTS] = $params;
43            if (isset($params['id'])) {
44                $fields[ContextConstants::RESOURCE_ID] = $params['id'];
45            }
46            $context = $request
47                ? $this->contextBuilder->createFromRequest($request, $fields)
48                : $this->contextBuilder->createGeneralContext($fields);
49            $action = $this->apieFacade->createAction($context);
50            /** @var ActionResponse $data */
51            $data = ($action)($context, $params);
52            return $this->actionResponseToToolResult($data);
53        } catch (\Throwable $e) {
54            $this->logger->error('Error while executing tool: "' . $e->getMessage() . '"', [
55                'tool' => $tool,
56                'params' => $params,
57                'exception' => $e,
58            ]);
59            return new CallToolResult(
60                [
61                    new TextContent(json_encode([
62                        'status' => 'error',
63                        'message' => $e->getMessage(),
64                        'exception' => get_class($e),
65                        'trace' => $e->getTraceAsString(),
66                    ])),
67                ],
68                true
69            );
70        }
71    }
72
73    private function actionResponseToToolResult(ActionResponse $input): CallToolResult
74    {
75        switch ($input->status) {
76            case ActionResponseStatus::CLIENT_ERROR:
77            case ActionResponseStatus::AUTHORIZATION_ERROR:
78            case ActionResponseStatus::OUTPUT_ERROR:
79            case ActionResponseStatus::PERISTENCE_ERROR:
80            case ActionResponseStatus::SERVER_ERROR:
81                return new CallToolResult(
82                    [
83                        new TextContent(json_encode($input->getResultAsNativeData())),
84                    ],
85                    true
86                );
87
88            case ActionResponseStatus::DELETED:
89                return new CallToolResult(
90                    [
91                        new TextContent(json_encode("Resource was deleted correctly.")),
92                    ],
93                    false
94                );
95            case ActionResponseStatus::NOT_FOUND:
96                return new CallToolResult(
97                    [
98                        new TextContent(json_encode("Resource was not found.")),
99                    ],
100                    true
101                );
102            
103            case ActionResponseStatus::CREATED:
104            case ActionResponseStatus::SUCCESS:
105                if (isset($input->error)) {
106                    throw $input->error;
107                }
108                return new CallToolResult(
109                    [
110                        new TextContent(json_encode($input->getResultAsNativeData())),
111                    ],
112                    false
113                );
114        }
115        throw new \LogicException('Unknown status ' . $input->status->value);
116    }
117}