Skip to content

Commit b4f98ac

Browse files
authored
feat: handle JSON exceptions (#15)
* feat: handle json exceptions * fix: ci
1 parent c06d5f9 commit b4f98ac

File tree

8 files changed

+106
-13
lines changed

8 files changed

+106
-13
lines changed

.github/workflows/pipeline.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ jobs:
4242
- name: Checkout
4343
uses: actions/checkout@v4
4444

45-
- name: Conventional Commit
46-
uses: ytanikin/[email protected]
47-
with:
48-
task_types: '["feat", "fix", "docs", "test", "ci", "style", "refactor", "perf", "chore", "revert"]'
49-
add_label: 'false'
50-
custom_labels: '{"feat": "feature", "fix": "bug", "docs": "documentation", "test": "test", "ci": "CI/CD", "style": "codestyle", "refactor": "refactor", "perf": "performance", "chore": "chore", "revert": "revert"}'
51-
5245
- name: Setup PHP
5346
uses: shivammathur/setup-php@v2
5447
with:
@@ -72,3 +65,10 @@ jobs:
7265

7366
- name: PHPStan
7467
run: vendor/bin/phpstan analyse
68+
69+
- name: Conventional Commit
70+
uses: ytanikin/[email protected]
71+
with:
72+
task_types: '["feat", "fix", "docs", "test", "ci", "style", "refactor", "perf", "chore", "revert"]'
73+
add_label: 'true'
74+
custom_labels: '{"feat": "feature", "fix": "bug", "docs": "documentation", "test": "test", "ci": "CI/CD", "style": "codestyle", "refactor": "refactor", "perf": "performance", "chore": "chore", "revert": "revert"}'

src/Server.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ public function connect(Transport $transport): void
2828
continue;
2929
}
3030

31-
$response = $this->jsonRpcHandler->process($message);
31+
try {
32+
$response = $this->jsonRpcHandler->process($message);
33+
} catch (\JsonException $e) {
34+
$this->logger->error('Failed to process message', [
35+
'message' => $message,
36+
'exception' => $e,
37+
]);
38+
continue;
39+
}
3240

3341
if (null === $response) {
3442
continue;

src/Server/JsonRpcHandler.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
use PhpLlm\McpSdk\Message\Response;
1212
use Psr\Log\LoggerInterface;
1313

14-
final readonly class JsonRpcHandler
14+
/**
15+
* @final
16+
*/
17+
readonly class JsonRpcHandler
1518
{
1619
/**
1720
* @var array<int, RequestHandler>
@@ -37,6 +40,9 @@ public function __construct(
3740
$this->notificationHandlers = $notificationHandlers instanceof \Traversable ? iterator_to_array($notificationHandlers) : $notificationHandlers;
3841
}
3942

43+
/**
44+
* @throws \JsonException
45+
*/
4046
public function process(string $message): ?string
4147
{
4248
$this->logger->info('Received message to process', ['message' => $message]);
@@ -57,7 +63,8 @@ public function process(string $message): ?string
5763

5864
try {
5965
return $message instanceof Notification
60-
? $this->handleNotification($message) : $this->encodeResponse($this->handleRequest($message));
66+
? $this->handleNotification($message)
67+
: $this->encodeResponse($this->handleRequest($message));
6168
} catch (\DomainException) {
6269
return null;
6370
} catch (\InvalidArgumentException $e) {

tests/Fixtures/InMemoryTransport.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace PhpLlm\McpSdk\Tests\Fixtures;
4+
5+
use PhpLlm\McpSdk\Server\Transport;
6+
7+
class InMemoryTransport implements Transport
8+
{
9+
private bool $connected = true;
10+
11+
/**
12+
* @param list<string> $messages
13+
*/
14+
public function __construct(
15+
private readonly array $messages = [],
16+
) {
17+
}
18+
19+
public function initialize(): void
20+
{
21+
}
22+
23+
public function isConnected(): bool
24+
{
25+
return $this->connected;
26+
}
27+
28+
public function receive(): \Generator
29+
{
30+
yield from $this->messages;
31+
$this->connected = false;
32+
}
33+
34+
public function send(string $data): void
35+
{
36+
}
37+
38+
public function close(): void
39+
{
40+
}
41+
}

tests/Message/ErrorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace App\Tests\Mcp\Message;
5+
namespace PhpLlm\McpSdk\Tests\Message;
66

77
use PhpLlm\McpSdk\Message\Error;
88
use PHPUnit\Framework\TestCase;

tests/Message/FactoryTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace App\Tests\Mcp\Message;
5+
namespace PhpLlm\McpSdk\Tests\Message;
66

77
use PhpLlm\McpSdk\Message\Factory;
88
use PhpLlm\McpSdk\Message\Notification;

tests/Message/ResponseTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace App\Tests\Mcp\Message;
5+
namespace PhpLlm\McpSdk\Tests\Message;
66

77
use PhpLlm\McpSdk\Message\Response;
88
use PHPUnit\Framework\TestCase;

tests/ServerTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace PhpLlm\McpSdk\Tests;
4+
5+
use PhpLlm\McpSdk\Server;
6+
use PhpLlm\McpSdk\Server\JsonRpcHandler;
7+
use PhpLlm\McpSdk\Tests\Fixtures\InMemoryTransport;
8+
use PHPUnit\Framework\MockObject\Stub\Exception;
9+
use PHPUnit\Framework\TestCase;
10+
use Psr\Log\NullLogger;
11+
12+
class ServerTest extends TestCase
13+
{
14+
public function testJsonExceptions(): void
15+
{
16+
$logger = $this->getMockBuilder(NullLogger::class)
17+
->disableOriginalConstructor()
18+
->onlyMethods(['error'])
19+
->getMock();
20+
$logger->expects($this->once())->method('error');
21+
22+
$handler = $this->getMockBuilder(JsonRpcHandler::class)
23+
->disableOriginalConstructor()
24+
->onlyMethods(['process'])
25+
->getMock();
26+
$handler->expects($this->exactly(2))->method('process')->willReturnOnConsecutiveCalls(new Exception(new \JsonException('foobar')), 'success');
27+
28+
$transport = $this->getMockBuilder(InMemoryTransport::class)
29+
->setConstructorArgs([['foo', 'bar']])
30+
->onlyMethods(['send'])
31+
->getMock();
32+
$transport->expects($this->once())->method('send')->with('success');
33+
34+
$server = new Server($handler, $logger);
35+
$server->connect($transport);
36+
}
37+
}

0 commit comments

Comments
 (0)