Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions src/Server/Session/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,20 @@
class Session implements SessionInterface
{
/**
* @param array<string, mixed> $data Stores all session data.
* Keys are snake_case by convention for MCP-specific data.
*
* Official keys are:
* - initialized: bool
* - client_info: array|null
* - protocol_version: string|null
* - log_level: string|null
*
* @var array<string, mixed>
*/
private array $data;

public function __construct(
protected SessionStoreInterface $store,
protected Uuid $id = new UuidV4(),
protected array $data = [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parameter was used only if we could not read from the store...

Seams weird. It is a BC break to remove it (I've updated the label)

private SessionStoreInterface $store,
private Uuid $id = new UuidV4(),
) {
if ($rawData = $this->store->read($this->id)) {
$this->data = json_decode($rawData, true) ?? [];
}
}

public function getId(): Uuid
Expand All @@ -59,7 +56,7 @@ public function save(): bool
public function get(string $key, mixed $default = null): mixed
{
$key = explode('.', $key);
$data = $this->data;
$data = $this->readData();

foreach ($key as $segment) {
if (\is_array($data) && \array_key_exists($segment, $data)) {
Expand All @@ -75,6 +72,7 @@ public function get(string $key, mixed $default = null): mixed
public function set(string $key, mixed $value, bool $overwrite = true): void
{
$segments = explode('.', $key);
$this->readData();
$data = &$this->data;

while (\count($segments) > 1) {
Expand All @@ -94,7 +92,7 @@ public function set(string $key, mixed $value, bool $overwrite = true): void
public function has(string $key): bool
{
$key = explode('.', $key);
$data = $this->data;
$data = $this->readData();

foreach ($key as $segment) {
if (\is_array($data) && \array_key_exists($segment, $data)) {
Expand All @@ -112,6 +110,7 @@ public function has(string $key): bool
public function forget(string $key): void
{
$segments = explode('.', $key);
$this->readData();
$data = &$this->data;

while (\count($segments) > 1) {
Expand Down Expand Up @@ -143,7 +142,7 @@ public function pull(string $key, mixed $default = null): mixed

public function all(): array
{
return $this->data;
return $this->readData();
}

public function hydrate(array $attributes): void
Expand All @@ -156,4 +155,22 @@ public function jsonSerialize(): array
{
return $this->all();
}

/**
* @return array<string, mixed>
*/
private function readData(): array
{
if (isset($this->data)) {
return $this->data;
}

$rawData = $this->store->read($this->id);

if (false === $rawData) {
return $this->data = [];
}

return $this->data = json_decode($rawData, true, flags: \JSON_THROW_ON_ERROR);
}
}
36 changes: 36 additions & 0 deletions tests/Unit/Server/Session/SessionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Mcp\Tests\Unit\Server\Session;

use Mcp\Server\Session\InMemorySessionStore;
use Mcp\Server\Session\Session;
use PHPUnit\Framework\TestCase;

class SessionTest extends TestCase
{
public function testAll()
{
$store = $this->getMockBuilder(InMemorySessionStore::class)
->disableOriginalConstructor()
->onlyMethods(['read'])
->getMock();
$store->expects($this->once())->method('read')->willReturn(json_encode(['foo' => 'bar']));

$session = new Session($store);
$result = $session->all();
$this->assertEquals(['foo' => 'bar'], $result);

// Call again to make sure we dont read from Store
$result = $session->all();
$this->assertEquals(['foo' => 'bar'], $result);
}
}