1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Server\Prompt;
15:
16: use Nexus\Mcp\Core\Schema\ContentBlock\TextContent;
17: use Nexus\Mcp\Core\Schema\Enum\Role;
18: use Nexus\Mcp\Core\Schema\Prompt\PromptMessage;
19: use Nexus\Mcp\Core\Schema\Result\GetPromptResult;
20: use Nexus\Mcp\Core\Schema\Result\InputRequiredResult;
21: use Nexus\Mcp\Server\Discovery\ArgumentBinder;
22: use Nexus\Mcp\Server\Exception\UnsupportedReturnValueException;
23: use Nexus\Mcp\Server\ServerContext;
24:
25: /**
26: * Adapts an attribute-discovered handler method to the `PromptRendererInterface` contract.
27: */
28: final readonly class ReflectedPromptRenderer implements PromptRendererInterface
29: {
30: public function __construct(
31: private object $handler,
32: private \ReflectionMethod $method,
33: private ArgumentBinder $binder = new ArgumentBinder(),
34: ) {
35: }
36:
37: #[\Override]
38: public function render(?array $arguments, ServerContext $context): GetPromptResult|InputRequiredResult
39: {
40: $bound = $this->binder->bind($this->method, $arguments ?? [], $context);
41:
42: return $this->adapt($this->method->invokeArgs($this->handler, $bound));
43: }
44:
45: private function adapt(mixed $result): GetPromptResult|InputRequiredResult
46: {
47: if ($result instanceof GetPromptResult || $result instanceof InputRequiredResult) {
48: return $result;
49: }
50:
51: if (\is_string($result)) {
52: return new GetPromptResult(messages: [
53: new PromptMessage(role: Role::User, content: new TextContent(text: $result)),
54: ]);
55: }
56:
57: if ($result instanceof PromptMessage) {
58: return new GetPromptResult(messages: [$result]);
59: }
60:
61: if (\is_array($result)) {
62: return new GetPromptResult(messages: $this->buildMessageList($result));
63: }
64:
65: throw $this->buildUnsupportedError($this->method, $result);
66: }
67:
68: /**
69: * @param array<array-key, mixed> $result
70: *
71: * @return list<PromptMessage>
72: */
73: private function buildMessageList(array $result): array
74: {
75: if (! array_is_list($result) || [] === $result) {
76: throw $this->buildUnsupportedError($this->method, $result);
77: }
78:
79: $messages = [];
80:
81: foreach ($result as $item) {
82: if (! $item instanceof PromptMessage) {
83: throw $this->buildUnsupportedError($this->method, $result);
84: }
85:
86: $messages[] = $item;
87: }
88:
89: return $messages;
90: }
91:
92: private function buildUnsupportedError(\ReflectionMethod $method, mixed $result): UnsupportedReturnValueException
93: {
94: return new UnsupportedReturnValueException(
95: $method->getDeclaringClass()->getName(),
96: $method->getName(),
97: \sprintf('a %s, a string, or prompt messages', GetPromptResult::class),
98: $result,
99: );
100: }
101: }
102: