Skip to content

Commit 4e64d39

Browse files
committed
refactor: exception handling
1 parent e83485a commit 4e64d39

File tree

9 files changed

+78
-137
lines changed

9 files changed

+78
-137
lines changed

src/Attribute/Guarded.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Butschster\ContextGenerator\McpServer\Attribute;
6+
7+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
8+
final readonly class Guarded
9+
{
10+
public function __construct(
11+
private ?array $scopes = null,
12+
) {}
13+
}

src/McpServerBootloader.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ public function defineSingletons(): array
3939
RouteRegistrar::class => RouteRegistrar::class,
4040
McpItemsRegistry::class => McpItemsRegistry::class,
4141
StrategyInterface::class => McpResponseStrategy::class,
42-
Router::class => static function (StrategyInterface $strategy, #[Proxy] ContainerInterface $container) {
42+
Router::class => static function (
43+
StrategyInterface $strategy,
44+
#[Proxy] ContainerInterface $container,
45+
) {
4346
$router = new Router();
4447
\assert($strategy instanceof McpResponseStrategy);
4548
$strategy->setContainer($container);

src/McpServerCoreBootloader.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use Spiral\Boot\DirectoriesInterface;
4545
use Spiral\Boot\EnvironmentInterface;
4646
use Spiral\Core\FactoryInterface;
47+
use Spiral\Exceptions\ExceptionReporterInterface;
4748
use Spiral\McpServer\Bootloader\ValinorMapperBootloader;
4849
use Spiral\McpServer\MiddlewareManager;
4950
use Spiral\McpServer\MiddlewareRegistryInterface;
@@ -208,9 +209,20 @@ private function createDispatcher(
208209
private function createProtocol(
209210
FactoryInterface $factory,
210211
LoggerInterface $logger,
212+
ExceptionReporterInterface $reporter,
211213
): Protocol {
212214
return $factory->make(Protocol::class, [
213215
'logger' => $logger,
216+
'reporter' => new class($reporter) implements \Mcp\Server\Exception\ExceptionReporterInterface {
217+
public function __construct(
218+
private readonly ExceptionReporterInterface $reporter,
219+
) {}
220+
221+
public function report(\Throwable $e): void
222+
{
223+
$this->reporter->report($e);
224+
}
225+
},
214226
]);
215227
}
216228

src/Routing/ActionCaller.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
namespace Butschster\ContextGenerator\McpServer\Routing;
66

7+
use Butschster\ContextGenerator\McpServer\Attribute\Guarded;
78
use Butschster\ContextGenerator\McpServer\Attribute\InputSchema;
9+
use Mcp\Server\Authentication\Contract\UserProviderInterface;
10+
use Mcp\Server\Authentication\Error\InvalidTokenError;
811
use Psr\Http\Message\ServerRequestInterface;
912
use Spiral\Core\Attribute\Proxy;
1013
use Spiral\Core\InvokerInterface;
@@ -16,6 +19,7 @@
1619
{
1720
public function __construct(
1821
#[Proxy] private ScopeInterface $container,
22+
#[Proxy] private UserProviderInterface $userProvider,
1923
private SchemaMapperInterface $schemaMapper,
2024
private string $class,
2125
) {}
@@ -32,13 +36,18 @@ public function __invoke(ServerRequestInterface $request): mixed
3236
$inputSchema = $inputSchemaClass->newInstance();
3337

3438
$input = $this->schemaMapper->toObject(
35-
json: \json_encode((array)($request->getParsedBody() ?? [])),
39+
json: \json_encode((array)($request->getParsedBody() ?? []), \JSON_FORCE_OBJECT),
3640
class: $inputSchema->class,
3741
);
3842

3943
$bindings[$inputSchema->class] = $input;
4044
}
4145

46+
$authRequired = $reflection->getAttributes(Guarded::class)[0] ?? null;
47+
if ($authRequired !== null && $this->userProvider->getUser() === null) {
48+
throw new InvalidTokenError();
49+
}
50+
4251
return $this->container->runScope(
4352
bindings: new Scope(
4453
name: 'mcp-server-request',

src/Routing/McpResponseStrategy.php

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,19 @@ public function __construct(
2222
#[\Override]
2323
public function invokeRouteCallable(Route $route, ServerRequestInterface $request): ResponseInterface
2424
{
25-
try {
26-
$this->logger->info('Invoking route callable', [
27-
'route' => $route->getName(),
28-
'method' => $request->getMethod(),
29-
'uri' => (string) $request->getUri(),
30-
]);
31-
32-
$controller = $route->getCallable($this->getContainer());
33-
$response = $controller($request, $route->getVars());
34-
35-
if ($response instanceof ResponseInterface) {
36-
return $response;
37-
}
38-
39-
return new JsonResponse($response);
40-
} catch (\Throwable $e) {
41-
$this->logger->error('Error while handling request', [
42-
'exception' => $e,
43-
'request' => $request,
44-
]);
45-
46-
$this->reporter->report($e);
47-
48-
return new JsonResponse([
49-
'error' => 'Internal Server Error',
50-
'message' => $e->getMessage(),
51-
], 500);
25+
$this->logger->info('Invoking route callable', [
26+
'route' => $route->getName(),
27+
'method' => $request->getMethod(),
28+
'uri' => (string)$request->getUri(),
29+
]);
30+
31+
$controller = $route->getCallable($this->getContainer());
32+
$response = $controller($request, $route->getVars());
33+
34+
if ($response instanceof ResponseInterface) {
35+
return $response;
5236
}
37+
38+
return new JsonResponse($response);
5339
}
5440
}

src/Routing/Routes/PromptRoute.php

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,11 @@ private function handlePromptsList(ListPromptsRequest $request): ListPromptsResu
6363
$request = $this->requestFactory->createPsrRequest($method, []);
6464

6565
// Dispatch the request through the router
66-
try {
67-
$response = $this->router->dispatch($request);
68-
\assert($response instanceof JsonResponse);
69-
70-
// Convert the response back to appropriate MCP type
71-
return $response->getPayload();
72-
} catch (\Throwable $e) {
73-
$this->logger->error('Route handling error', [
74-
'method' => $method,
75-
'error' => $e->getMessage(),
76-
'trace' => $e->getTraceAsString(),
77-
]);
78-
79-
throw new McpServerException(
80-
message: 'Failed to list prompts.',
81-
previous: $e,
82-
);
83-
}
66+
$response = $this->router->dispatch($request);
67+
\assert($response instanceof JsonResponse);
68+
69+
// Convert the response back to appropriate MCP type
70+
return $response->getPayload();
8471
}
8572

8673
/**
@@ -101,21 +88,10 @@ private function handlePromptGet(GetPromptRequest $request, Context $context): G
10188

10289
$request = $this->requestFactory->createPsrRequest($method, $arguments);
10390

104-
try {
105-
$response = $this->router->dispatch($request);
106-
\assert($response instanceof JsonResponse);
107-
108-
// Convert the response back to appropriate MCP type
109-
return $response->getPayload();
110-
} catch (McpServerException $e) {
111-
throw $e;
112-
} catch (\Throwable $e) {
113-
$this->logger->error('Prompt get error', [
114-
'prompt' => $name,
115-
'error' => $e->getMessage(),
116-
]);
117-
118-
throw McpServerException::promptGenerationFailed($name, $e);
119-
}
91+
$response = $this->router->dispatch($request);
92+
\assert($response instanceof JsonResponse);
93+
94+
// Convert the response back to appropriate MCP type
95+
return $response->getPayload();
12096
}
12197
}

src/Routing/Routes/ResourceRoute.php

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,10 @@ private function handleResourcesList(ListResourcesRequest $request): ListResourc
8787
$request = $this->requestFactory->createPsrRequest($method, $params);
8888

8989
// Dispatch the request through the router
90-
try {
91-
$response = $this->router->dispatch($request);
92-
\assert($response instanceof JsonResponse);
90+
$response = $this->router->dispatch($request);
91+
\assert($response instanceof JsonResponse);
9392

94-
return $response->getPayload();
95-
} catch (\Throwable $e) {
96-
$this->logger->error('Route handling error', [
97-
'method' => $method,
98-
'error' => $e->getMessage(),
99-
'trace' => $e->getTraceAsString(),
100-
]);
101-
102-
throw new McpServerException(
103-
message: 'Failed to list resources.',
104-
previous: $e,
105-
);
106-
}
93+
return $response->getPayload();
10794
}
10895

10996
private function handleResourceTemplateList(ListResourceTemplatesRequest $request): ListResourceTemplatesResult
@@ -117,23 +104,10 @@ private function handleResourceTemplateList(ListResourceTemplatesRequest $reques
117104
$request = $this->requestFactory->createPsrRequest($method, $params);
118105

119106
// Dispatch the request through the router
120-
try {
121-
$response = $this->router->dispatch($request);
122-
\assert($response instanceof JsonResponse);
107+
$response = $this->router->dispatch($request);
108+
\assert($response instanceof JsonResponse);
123109

124-
return $response->getPayload();
125-
} catch (\Throwable $e) {
126-
$this->logger->error('Route handling error', [
127-
'method' => $method,
128-
'error' => $e->getMessage(),
129-
'trace' => $e->getTraceAsString(),
130-
]);
131-
132-
throw new McpServerException(
133-
message: 'Failed to list resources.',
134-
previous: $e,
135-
);
136-
}
110+
return $response->getPayload();
137111
}
138112

139113
/**
@@ -165,15 +139,6 @@ private function handleResourceRead(ReadResourceRequest $request, Context $conte
165139
} catch (\JsonException $e) {
166140
$this->logger->warning('Failed to JSON encode resource content.', ['exception' => $e, 'uri' => $uri]);
167141
throw McpServerException::internalError("Failed to serialize resource content for '{$uri}'.", $e);
168-
} catch (McpServerException $e) {
169-
throw $e;
170-
} catch (\Throwable $e) {
171-
$this->logger->error('Resource read error', [
172-
'resource' => $uri,
173-
'error' => $e->getMessage(),
174-
]);
175-
176-
throw McpServerException::resourceReadFailed($uri, $e);
177142
}
178143
}
179144

src/Routing/Routes/ToolRoute.php

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
use Mcp\Server\Context;
1111
use Mcp\Server\Contracts\RouteInterface;
1212
use Mcp\Server\Dispatcher\RequestMethod;
13-
use Mcp\Server\Exception\McpServerException;
14-
use PhpMcp\Schema\Content\TextContent;
1513
use PhpMcp\Schema\JsonRpc\Notification;
1614
use PhpMcp\Schema\JsonRpc\Request;
1715
use PhpMcp\Schema\JsonRpc\Result;
@@ -62,30 +60,16 @@ private function handleToolList(ListToolsRequest $request): ListToolsResult
6260
$request = $this->requestFactory->createPsrRequest($method, $params);
6361

6462
// Dispatch the request through the router
65-
try {
66-
$response = $this->router->dispatch($request);
67-
\assert($response instanceof JsonResponse);
68-
69-
return $response->getPayload();
70-
} catch (\Throwable $e) {
71-
$this->logger->error('Route handling error', [
72-
'method' => $method,
73-
'error' => $e->getMessage(),
74-
'trace' => $e->getTraceAsString(),
75-
]);
76-
77-
throw new McpServerException(
78-
message: 'Failed to list tools.',
79-
previous: $e,
80-
);
81-
}
63+
$response = $this->router->dispatch($request);
64+
\assert($response instanceof JsonResponse);
65+
66+
return $response->getPayload();
8267
}
8368

8469
private function handleToolCall(CallToolRequest $request, Context $context): CallToolResult
8570
{
8671
$method = 'tools/call/' . $request->name;
8772
$arguments = $request->arguments ?? [];
88-
$toolName = $request->name;
8973

9074
$this->logger->debug('Handling tool call', [
9175
'tool' => $request->name,
@@ -96,17 +80,9 @@ private function handleToolCall(CallToolRequest $request, Context $context): Cal
9680
// Create PSR request with the tool name in the path and arguments as POST body
9781
$request = $this->requestFactory->createPsrRequest($method, $arguments);
9882

99-
try {
100-
$response = $this->router->dispatch($request);
101-
\assert($response instanceof JsonResponse);
102-
103-
return $response->getPayload();
104-
} catch (\Throwable $e) {
105-
$this->logger->error('Tool call error', [
106-
'tool' => $toolName,
107-
'error' => $e->getMessage(),
108-
]);
109-
return new CallToolResult([new TextContent(text: $e->getMessage())], isError: true);
110-
}
83+
$response = $this->router->dispatch($request);
84+
\assert($response instanceof JsonResponse);
85+
86+
return $response->getPayload();
11187
}
11288
}

src/ServerRunner.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Butschster\ContextGenerator\McpServer\Registry\McpItemsRegistry;
99
use Butschster\ContextGenerator\McpServer\Routing\RouteRegistrar;
1010
use Mcp\Server\Contracts\ServerTransportInterface;
11+
use Mcp\Server\Server;
1112
use Spiral\Core\Attribute\Proxy;
1213
use Spiral\Core\Attribute\Singleton;
1314
use Spiral\Core\Scope;
@@ -46,7 +47,7 @@ public function run(string $name): void
4647
RouteRegistrar $registrar,
4748
McpItemsRegistry $registry,
4849
ExceptionReporterInterface $reporter,
49-
\Mcp\Server\Server $server,
50+
Server $server,
5051
ServerTransportInterface $transport,
5152
) use ($name): void {
5253
// Register all classes with MCP item attributes. Should be before registering controllers!

0 commit comments

Comments
 (0)