[Server] Fix: standardize error handling across handlers with MCP specificatio… (#124)
* fix: standardize error handling across handlers with MCP specification compliance * Update mcp-elements.md Co-authored-by: Christopher Hertel <mail@christopher-hertel.de> * docs: clarify error handling behavior for all element types * docs: remove unnecessary whitespace in mcp-elements.md * fix: simplify exception signatures to accept simple messages * refactor: use specific exceptions in examples for better error handling * feat: throw exceptions from reference provider instead of returning null --------- Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
This commit is contained in:
parent
8885d29a4e
commit
ca18caf29d
4 changed files with 278 additions and 185 deletions
|
|
@ -13,9 +13,9 @@ namespace Mcp\Server\Handler\Request;
|
|||
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\ToolCallException;
|
||||
use Mcp\Exception\ToolNotFoundException;
|
||||
use Mcp\Schema\Content\TextContent;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
|
|
@ -59,9 +59,6 @@ final class CallToolHandler implements RequestHandlerInterface
|
|||
|
||||
try {
|
||||
$reference = $this->referenceProvider->getTool($toolName);
|
||||
if (null === $reference) {
|
||||
throw new ToolNotFoundException($request);
|
||||
}
|
||||
|
||||
$arguments['_session'] = $session;
|
||||
|
||||
|
|
@ -77,17 +74,19 @@ final class CallToolHandler implements RequestHandlerInterface
|
|||
]);
|
||||
|
||||
return new Response($request->getId(), $result);
|
||||
} catch (ToolNotFoundException $e) {
|
||||
$this->logger->error('Tool not found', ['name' => $toolName]);
|
||||
|
||||
return new Error($request->getId(), Error::METHOD_NOT_FOUND, $e->getMessage());
|
||||
} catch (ToolCallException|ExceptionInterface $e) {
|
||||
} catch (ToolCallException $e) {
|
||||
$this->logger->error(\sprintf('Error while executing tool "%s": "%s".', $toolName, $e->getMessage()), [
|
||||
'tool' => $toolName,
|
||||
'arguments' => $arguments,
|
||||
]);
|
||||
|
||||
return Error::forInternalError('Error while executing tool', $request->getId());
|
||||
$errorContent = [new TextContent($e->getMessage())];
|
||||
|
||||
return new Response($request->getId(), CallToolResult::error($errorContent));
|
||||
} catch (ToolNotFoundException $e) {
|
||||
$this->logger->error('Tool not found', ['name' => $toolName]);
|
||||
|
||||
return new Error($request->getId(), Error::METHOD_NOT_FOUND, $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Unhandled error during tool execution', [
|
||||
'name' => $toolName,
|
||||
|
|
|
|||
|
|
@ -13,10 +13,14 @@ namespace Mcp\Server\Handler\Request;
|
|||
|
||||
use Mcp\Capability\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\PromptNotFoundException;
|
||||
use Mcp\Exception\ResourceNotFoundException;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\PromptReference;
|
||||
use Mcp\Schema\Request\CompletionCompleteRequest;
|
||||
use Mcp\Schema\ResourceReference;
|
||||
use Mcp\Schema\Result\CompletionCompleteResult;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
|
@ -51,41 +55,38 @@ final class CompletionCompleteHandler implements RequestHandlerInterface
|
|||
$name = $request->argument['name'] ?? '';
|
||||
$value = $request->argument['value'] ?? '';
|
||||
|
||||
$reference = match ($request->ref->type) {
|
||||
'ref/prompt' => $this->referenceProvider->getPrompt($request->ref->name),
|
||||
'ref/resource' => $this->referenceProvider->getResourceTemplate($request->ref->uri),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (null === $reference) {
|
||||
return new Response($request->getId(), new CompletionCompleteResult([]));
|
||||
}
|
||||
|
||||
$providers = $reference->completionProviders;
|
||||
$provider = $providers[$name] ?? null;
|
||||
if (null === $provider) {
|
||||
return new Response($request->getId(), new CompletionCompleteResult([]));
|
||||
}
|
||||
|
||||
if (\is_string($provider)) {
|
||||
if (!class_exists($provider)) {
|
||||
return Error::forInternalError('Invalid completion provider', $request->getId());
|
||||
}
|
||||
$provider = $this->container?->has($provider) ? $this->container->get($provider) : new $provider();
|
||||
}
|
||||
|
||||
if (!$provider instanceof ProviderInterface) {
|
||||
return Error::forInternalError('Invalid completion provider type', $request->getId());
|
||||
}
|
||||
|
||||
try {
|
||||
$reference = match (true) {
|
||||
$request->ref instanceof PromptReference => $this->referenceProvider->getPrompt($request->ref->name),
|
||||
$request->ref instanceof ResourceReference => $this->referenceProvider->getResource($request->ref->uri),
|
||||
};
|
||||
|
||||
$providers = $reference->completionProviders;
|
||||
$provider = $providers[$name] ?? null;
|
||||
if (null === $provider) {
|
||||
return new Response($request->getId(), new CompletionCompleteResult([]));
|
||||
}
|
||||
|
||||
if (\is_string($provider)) {
|
||||
if (!class_exists($provider)) {
|
||||
return Error::forInternalError('Invalid completion provider', $request->getId());
|
||||
}
|
||||
$provider = $this->container?->has($provider) ? $this->container->get($provider) : new $provider();
|
||||
}
|
||||
|
||||
if (!$provider instanceof ProviderInterface) {
|
||||
return Error::forInternalError('Invalid completion provider type', $request->getId());
|
||||
}
|
||||
|
||||
$completions = $provider->getCompletions($value);
|
||||
$total = \count($completions);
|
||||
$hasMore = $total > 100;
|
||||
$paged = \array_slice($completions, 0, 100);
|
||||
|
||||
return new Response($request->getId(), new CompletionCompleteResult($paged, $total, $hasMore));
|
||||
} catch (\Throwable) {
|
||||
} catch (PromptNotFoundException|ResourceNotFoundException $e) {
|
||||
return Error::forResourceNotFound($e->getMessage(), $request->getId());
|
||||
} catch (\Throwable $e) {
|
||||
return Error::forInternalError('Error while handling completion request', $request->getId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ namespace Mcp\Server\Handler\Request;
|
|||
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\PromptGetException;
|
||||
use Mcp\Exception\PromptNotFoundException;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
|
|
@ -56,9 +55,6 @@ final class GetPromptHandler implements RequestHandlerInterface
|
|||
|
||||
try {
|
||||
$reference = $this->referenceProvider->getPrompt($promptName);
|
||||
if (null === $reference) {
|
||||
throw new PromptNotFoundException($request);
|
||||
}
|
||||
|
||||
$arguments['_session'] = $session;
|
||||
|
||||
|
|
@ -67,18 +63,18 @@ final class GetPromptHandler implements RequestHandlerInterface
|
|||
$formatted = $reference->formatResult($result);
|
||||
|
||||
return new Response($request->getId(), new GetPromptResult($formatted));
|
||||
} catch (PromptGetException $e) {
|
||||
$this->logger->error(\sprintf('Error while handling prompt "%s": "%s".', $promptName, $e->getMessage()));
|
||||
|
||||
return Error::forInternalError($e->getMessage(), $request->getId());
|
||||
} catch (PromptNotFoundException $e) {
|
||||
$this->logger->error('Prompt not found', ['prompt_name' => $promptName]);
|
||||
|
||||
return new Error($request->getId(), Error::METHOD_NOT_FOUND, $e->getMessage());
|
||||
} catch (PromptGetException|ExceptionInterface $e) {
|
||||
$this->logger->error('Error while handling prompt', ['prompt_name' => $promptName]);
|
||||
|
||||
return Error::forInternalError('Error while handling prompt: '.$e->getMessage(), $request->getId());
|
||||
return Error::forResourceNotFound($e->getMessage(), $request->getId());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Error while handling prompt', ['prompt_name' => $promptName]);
|
||||
$this->logger->error(\sprintf('Unexpected error while handling prompt "%s": "%s".', $promptName, $e->getMessage()));
|
||||
|
||||
return Error::forInternalError('Error while handling prompt: '.$e->getMessage(), $request->getId());
|
||||
return Error::forInternalError('Error while handling prompt', $request->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
|||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Capability\Registry\ResourceTemplateReference;
|
||||
use Mcp\Exception\ResourceNotFoundException;
|
||||
use Mcp\Exception\ResourceReadException;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
|
|
@ -56,9 +57,6 @@ final class ReadResourceHandler implements RequestHandlerInterface
|
|||
|
||||
try {
|
||||
$reference = $this->referenceProvider->getResource($uri);
|
||||
if (null === $reference) {
|
||||
throw new ResourceNotFoundException($request);
|
||||
}
|
||||
|
||||
$arguments = [
|
||||
'uri' => $uri,
|
||||
|
|
@ -77,12 +75,16 @@ final class ReadResourceHandler implements RequestHandlerInterface
|
|||
}
|
||||
|
||||
return new Response($request->getId(), new ReadResourceResult($formatted));
|
||||
} catch (ResourceReadException $e) {
|
||||
$this->logger->error(\sprintf('Error while reading resource "%s": "%s".', $uri, $e->getMessage()));
|
||||
|
||||
return Error::forInternalError($e->getMessage(), $request->getId());
|
||||
} catch (ResourceNotFoundException $e) {
|
||||
$this->logger->error('Resource not found', ['uri' => $uri]);
|
||||
|
||||
return new Error($request->getId(), Error::RESOURCE_NOT_FOUND, $e->getMessage());
|
||||
return Error::forResourceNotFound($e->getMessage(), $request->getId());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error(\sprintf('Error while reading resource "%s": "%s".', $uri, $e->getMessage()));
|
||||
$this->logger->error(\sprintf('Unexpected error while reading resource "%s": "%s".', $uri, $e->getMessage()));
|
||||
|
||||
return Error::forInternalError('Error while reading resource', $request->getId());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue