Skip to content

Commit b44693a

Browse files
committed
[Server] Throw the SDK's own RuntimeException from FileSessionStore
The constructor guarded its unwritable-directory case with the global `\RuntimeException`, the only throw in `src/` still outside `Mcp\Exception\ExceptionInterface`. A consumer wrapping server setup in a `catch (ExceptionInterface)` missed it and got an unhandled SPL exception on a misconfigured session directory instead. `Mcp\Exception\RuntimeException` extends `\RuntimeException`, so this is additive: existing `catch (\RuntimeException)` blocks keep working. The constructor had no test coverage at all, so this adds one for the throw plus the directory-creation and write/read paths around it.
1 parent 9a6d24c commit b44693a

2 files changed

Lines changed: 92 additions & 1 deletion

File tree

src/Server/Session/FileSessionStore.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Mcp\Server\Session;
1313

14+
use Mcp\Exception\RuntimeException;
1415
use Mcp\Server\NativeClock;
1516
use Psr\Clock\ClockInterface;
1617
use Symfony\Component\Uid\Uuid;
@@ -31,7 +32,7 @@ public function __construct(
3132
}
3233

3334
if (!is_dir($this->directory) || !is_writable($this->directory)) {
34-
throw new \RuntimeException(\sprintf('Session directory "%s" is not writable.', $this->directory));
35+
throw new RuntimeException(\sprintf('Session directory "%s" is not writable.', $this->directory));
3536
}
3637
}
3738

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Tests\Unit\Server\Session;
13+
14+
use Mcp\Exception\ExceptionInterface;
15+
use Mcp\Exception\RuntimeException;
16+
use Mcp\Server\Session\FileSessionStore;
17+
use PHPUnit\Framework\Attributes\TestDox;
18+
use PHPUnit\Framework\TestCase;
19+
use Symfony\Component\Uid\UuidV4;
20+
21+
class FileSessionStoreTest extends TestCase
22+
{
23+
private string $directory;
24+
25+
protected function setUp(): void
26+
{
27+
$this->directory = sys_get_temp_dir().'/mcp-file-session-store-'.bin2hex(random_bytes(6));
28+
}
29+
30+
protected function tearDown(): void
31+
{
32+
if (!is_dir($this->directory)) {
33+
return;
34+
}
35+
36+
@chmod($this->directory, 0775);
37+
38+
foreach (glob($this->directory.'/*') ?: [] as $file) {
39+
@unlink($file);
40+
}
41+
42+
@rmdir($this->directory);
43+
}
44+
45+
#[TestDox('creates the session directory when it does not exist yet')]
46+
public function testCreatesMissingDirectory(): void
47+
{
48+
new FileSessionStore($this->directory);
49+
50+
$this->assertDirectoryExists($this->directory);
51+
}
52+
53+
#[TestDox('round-trips a session payload through the filesystem')]
54+
public function testWriteThenRead(): void
55+
{
56+
$store = new FileSessionStore($this->directory);
57+
$id = new UuidV4();
58+
59+
$store->write($id, 'payload');
60+
61+
$this->assertTrue($store->exists($id));
62+
$this->assertSame('payload', $store->read($id));
63+
}
64+
65+
#[TestDox('rejects an unwritable directory with the SDK\'s own exception')]
66+
public function testUnwritableDirectoryThrowsPackageException(): void
67+
{
68+
mkdir($this->directory, 0775, true);
69+
chmod($this->directory, 0555);
70+
clearstatcache(true, $this->directory);
71+
72+
if (is_writable($this->directory)) {
73+
$this->markTestSkipped('Permission bits do not restrict writes here (running as root, or a filesystem that ignores them).');
74+
}
75+
76+
// The store's only throw must stay inside the package hierarchy, so a
77+
// consumer catching ExceptionInterface sees it rather than a bare SPL
78+
// RuntimeException escaping the SDK.
79+
$this->expectException(RuntimeException::class);
80+
$this->expectExceptionMessage(\sprintf('Session directory "%s" is not writable.', $this->directory));
81+
82+
try {
83+
new FileSessionStore($this->directory);
84+
} catch (RuntimeException $e) {
85+
$this->assertInstanceOf(ExceptionInterface::class, $e);
86+
87+
throw $e;
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)