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\Completion;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Prompt\PromptReference;
18: use Nexus\Mcp\Core\Schema\Resource\ResourceTemplateReference;
19: use Nexus\Mcp\Core\Schema\Result\CompleteResult;
20: use Nexus\Mcp\Server\ServerContext;
21:
22: /**
23: * In-memory implementation of `CompletionStoreInterface`.
24: *
25: * @phpstan-type ArgumentMap array<int|non-empty-string, (\Closure(string, ?array<array-key, string>, ServerContext): CompleteResult)|CompletionProviderInterface>
26: * @phpstan-type ProviderMap array<int|non-empty-string, array<int|non-empty-string, CompletionProviderInterface>>
27: */
28: final readonly class CompletionStore implements CompletionStoreInterface
29: {
30: /**
31: * @var ProviderMap
32: */
33: private array $promptCompletions;
34:
35: /**
36: * @var ProviderMap
37: */
38: private array $templateCompletions;
39:
40: /**
41: * @param array<int|non-empty-string, ArgumentMap> $promptCompletions
42: * @param array<int|non-empty-string, ArgumentMap> $templateCompletions
43: */
44: public function __construct(array $promptCompletions = [], array $templateCompletions = [])
45: {
46: Assert::that($promptCompletions)
47: ->keys()
48: ->isIntOrNonEmptyString('Completion store prompt key must be a non-empty string.')
49: ;
50: Assert::that($templateCompletions)
51: ->keys()
52: ->isIntOrNonEmptyString('Completion store template key must be a non-empty string.')
53: ;
54:
55: $this->promptCompletions = $this->normalize($promptCompletions);
56: $this->templateCompletions = $this->normalize($templateCompletions);
57: }
58:
59: #[\Override]
60: public function complete(
61: PromptReference|ResourceTemplateReference $ref,
62: string $argumentName,
63: string $argumentValue,
64: ?array $contextArguments,
65: ServerContext $context,
66: ): CompleteResult {
67: if ($ref instanceof PromptReference) {
68: $providers = $this->promptCompletions[$ref->name] ?? null;
69: } else {
70: $providers = $this->templateCompletions[$ref->uri] ?? null;
71: }
72:
73: if (null === $providers || ! isset($providers[$argumentName])) {
74: return new CompleteResult(completion: ['values' => []]);
75: }
76:
77: return $providers[$argumentName]->complete($argumentValue, $contextArguments, $context);
78: }
79:
80: /**
81: * @param array<int|non-empty-string, ArgumentMap> $completions
82: *
83: * @return ProviderMap
84: */
85: private function normalize(array $completions): array
86: {
87: $normalized = [];
88:
89: foreach ($completions as $key => $providers) {
90: Assert::that($providers)
91: ->keys()
92: ->isIntOrNonEmptyString('Completion store argument key must be a non-empty string.')
93: ;
94:
95: foreach ($providers as $argument => $provider) {
96: if (! $provider instanceof CompletionProviderInterface) {
97: Assert::that($provider)->isInstanceOf(
98: \Closure::class,
99: 'Completion provider must be a closure or implement CompletionProviderInterface, {type} given.',
100: );
101: $provider = new ClosureCompletionProvider($provider);
102: }
103:
104: $normalized[$key][$argument] = $provider;
105: }
106: }
107:
108: return $normalized;
109: }
110: }
111: