diff --git a/README.md b/README.md index 8d4aaa81..394cd37c 100644 --- a/README.md +++ b/README.md @@ -190,23 +190,42 @@ $response = $server->run($transport); By default, the SDK uses in-memory sessions. You can configure different session stores: ```php -use Mcp\Server\Session\InMemorySessionStore; use Mcp\Server\Session\FileSessionStore; +use Mcp\Server\Session\InMemorySessionStore; +use Mcp\Server\Session\Psr16StoreSession; +use Symfony\Component\Cache\Psr16Cache; +use Symfony\Component\Cache\Adapter\RedisAdapter; -// Use default in-memory sessions (TTL only) +// Use default in-memory sessions with custom TTL $server = Server::builder() ->setSession(ttl: 7200) // 2 hours ->build(); -// Use file-based sessions +// Override with file-based storage $server = Server::builder() ->setSession(new FileSessionStore(__DIR__ . '/sessions')) ->build(); -// Use in-memory with custom TTL +// Override with in-memory storage and custom TTL $server = Server::builder() ->setSession(new InMemorySessionStore(3600)) ->build(); + +// Override with PSR-16 cache-based storage +// Requires psr/simple-cache and symfony/cache (or any other PSR-16 implementation) +// composer require psr/simple-cache symfony/cache +$redisAdapter = new RedisAdapter( + RedisAdapter::createConnection('redis://localhost:6379'), + 'mcp_sessions' +); + +$server = Server::builder() + ->setSession(new Psr16StoreSession( + cache: new Psr16Cache($redisAdapter), + prefix: 'mcp-', + ttl: 3600 + )) + ->build(); ``` ### Discovery Caching diff --git a/docs/server-builder.md b/docs/server-builder.md index 03fcaece..ac131374 100644 --- a/docs/server-builder.md +++ b/docs/server-builder.md @@ -139,6 +139,9 @@ Configure session storage and lifecycle. By default, the SDK uses `InMemorySessi ```php use Mcp\Server\Session\FileSessionStore; use Mcp\Server\Session\InMemorySessionStore; +use Mcp\Server\Session\Psr16StoreSession; +use Symfony\Component\Cache\Psr16Cache; +use Symfony\Component\Cache\Adapter\RedisAdapter; // Use default in-memory sessions with custom TTL $server = Server::builder() @@ -147,18 +150,35 @@ $server = Server::builder() // Override with file-based storage $server = Server::builder() - ->setSession(new FileSessionStore('/tmp/mcp-sessions')) + ->setSession(new FileSessionStore(__DIR__ . '/sessions')) ->build(); // Override with in-memory storage and custom TTL $server = Server::builder() ->setSession(new InMemorySessionStore(3600)) ->build(); + +// Override with PSR-16 cache-based storage +// Requires psr/simple-cache and symfony/cache (or any other PSR-16 implementation) +// composer require psr/simple-cache symfony/cache +$redisAdapter = new RedisAdapter( + RedisAdapter::createConnection('redis://localhost:6379'), + 'mcp_sessions' +); + +$server = Server::builder() + ->setSession(new Psr16StoreSession( + cache: new Psr16Cache($redisAdapter), + prefix: 'mcp-', + ttl: 3600 + )) + ->build(); ``` **Available Session Stores:** - `InMemorySessionStore`: Fast in-memory storage (default) - `FileSessionStore`: Persistent file-based storage +- `Psr16StoreSession`: PSR-16 compliant cache-based storage **Custom Session Stores:** diff --git a/src/Server/Session/Psr16StoreSession.php b/src/Server/Session/Psr16StoreSession.php new file mode 100644 index 00000000..aacf0ffc --- /dev/null +++ b/src/Server/Session/Psr16StoreSession.php @@ -0,0 +1,81 @@ + + * + * PSR-16 compliant cache-based session store. + * + * This implementation uses any PSR-16 compliant cache as the storage backend + * for session data. Each session is stored with a prefixed key using the session ID. + */ +class Psr16StoreSession implements SessionStoreInterface +{ + public function __construct( + private readonly CacheInterface $cache, + private readonly string $prefix = 'mcp-', + private readonly int $ttl = 3600, + ) { + } + + public function exists(Uuid $id): bool + { + try { + return $this->cache->has($this->getKey($id)); + } catch (\Throwable) { + return false; + } + } + + public function read(Uuid $id): string|false + { + try { + return $this->cache->get($this->getKey($id), false); + } catch (\Throwable) { + return false; + } + } + + public function write(Uuid $id, string $data): bool + { + try { + return $this->cache->set($this->getKey($id), $data, $this->ttl); + } catch (\Throwable) { + return false; + } + } + + public function destroy(Uuid $id): bool + { + try { + return $this->cache->delete($this->getKey($id)); + } catch (\Throwable) { + return false; + } + } + + public function gc(): array + { + return []; + } + + private function getKey(Uuid $id): string + { + return $this->prefix.$id; + } +}