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: $messages = array_filter($result, static fn(mixed $item): bool => $item instanceof PromptMessage);
76:
77: if (! array_is_list($result) || [] === $result || \count($messages) !== \count($result)) {
78: throw $this->buildUnsupportedError($this->method, $result);
79: }
80:
81: return array_values($messages);
82: }
83:
84: private function buildUnsupportedError(\ReflectionMethod $method, mixed $result): UnsupportedReturnValueException
85: {
86: return new UnsupportedReturnValueException(
87: $method->getDeclaringClass()->getName(),
88: $method->getName(),
89: \sprintf('a %s, a string, or prompt messages', GetPromptResult::class),
90: $result,
91: );
92: }
93: }
94: