From a92750f4272de5306f0629d9477b8886e7ea759c Mon Sep 17 00:00:00 2001 From: Nariman Gardi <110671428+NarimanGardi@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:42:53 +0300 Subject: [PATCH] [Server] Fix completion/complete crash for plain ref/resource A non-template resource resolves to a ResourceReference, which has no completionProviders property; only prompts and resource templates do. Read providers from those instead, returning an empty completion result when a resource has none. --- .../Request/CompletionCompleteHandler.php | 18 +++- .../Request/CompletionCompleteHandlerTest.php | 97 +++++++++++++++++++ 2 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 tests/Unit/Server/Handler/Request/CompletionCompleteHandlerTest.php diff --git a/src/Server/Handler/Request/CompletionCompleteHandler.php b/src/Server/Handler/Request/CompletionCompleteHandler.php index 29c41fb7..b3c4d043 100644 --- a/src/Server/Handler/Request/CompletionCompleteHandler.php +++ b/src/Server/Handler/Request/CompletionCompleteHandler.php @@ -12,6 +12,7 @@ namespace Mcp\Server\Handler\Request; use Mcp\Capability\Completion\ProviderInterface; +use Mcp\Capability\Registry\ResourceTemplateReference; use Mcp\Capability\RegistryInterface; use Mcp\Exception\PromptNotFoundException; use Mcp\Exception\ResourceNotFoundException; @@ -56,12 +57,11 @@ public function handle(Request $request, SessionInterface $session): Response|Er $value = $request->argument['value'] ?? ''; try { - $reference = match (true) { - $request->ref instanceof PromptReference => $this->registry->getPrompt($request->ref->name), - $request->ref instanceof ResourceReference => $this->registry->getResource($request->ref->uri), + $providers = match (true) { + $request->ref instanceof PromptReference => $this->registry->getPrompt($request->ref->name)->completionProviders, + $request->ref instanceof ResourceReference => $this->resourceCompletionProviders($request->ref->uri), }; - $providers = $reference->completionProviders; $provider = $providers[$name] ?? null; if (null === $provider) { return new Response($request->getId(), new CompletionCompleteResult([])); @@ -90,4 +90,14 @@ public function handle(Request $request, SessionInterface $session): Response|Er return Error::forInternalError('Error while handling completion request', $request->getId()); } } + + /** + * @return array + */ + private function resourceCompletionProviders(string $uri): array + { + $reference = $this->registry->getResource($uri); + + return $reference instanceof ResourceTemplateReference ? $reference->completionProviders : []; + } } diff --git a/tests/Unit/Server/Handler/Request/CompletionCompleteHandlerTest.php b/tests/Unit/Server/Handler/Request/CompletionCompleteHandlerTest.php new file mode 100644 index 00000000..63eefba3 --- /dev/null +++ b/tests/Unit/Server/Handler/Request/CompletionCompleteHandlerTest.php @@ -0,0 +1,97 @@ +registry = $this->createMock(RegistryInterface::class); + $this->session = $this->createMock(SessionInterface::class); + + $this->handler = new CompletionCompleteHandler($this->registry); + } + + public function testReturnsEmptyCompletionForResourceWithoutTemplate(): void + { + $uri = 'file://static/readme.txt'; + $request = $this->createCompletionRequest($uri, ['name' => 'arg', 'value' => 'a']); + + $this->registry + ->expects($this->once()) + ->method('getResource') + ->with($uri) + ->willReturn($this->createMock(ResourceReference::class)); + + $response = $this->handler->handle($request, $this->session); + + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals($request->getId(), $response->id); + $this->assertEquals(new CompletionCompleteResult([]), $response->result); + } + + public function testReturnsCompletionsForResourceTemplate(): void + { + $uri = 'file://users/alice'; + $request = $this->createCompletionRequest($uri, ['name' => 'id', 'value' => 'al']); + + $templateReference = new ResourceTemplateReference( + new ResourceTemplate('file://users/{id}', 'user'), + static fn () => null, + ['id' => new CompletionProviderFixture()], + ); + + $this->registry + ->expects($this->once()) + ->method('getResource') + ->with($uri) + ->willReturn($templateReference); + + $response = $this->handler->handle($request, $this->session); + + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals(new CompletionCompleteResult(['alpha'], 1, false), $response->result); + } + + /** + * @param array{ name: string, value: string } $argument + */ + private function createCompletionRequest(string $uri, array $argument): CompletionCompleteRequest + { + return CompletionCompleteRequest::fromArray([ + 'jsonrpc' => '2.0', + 'method' => CompletionCompleteRequest::getMethod(), + 'id' => 'test-completion-'.uniqid(), + 'params' => [ + 'ref' => ['type' => 'ref/resource', 'uri' => $uri], + 'argument' => $argument, + ], + ]); + } +}