Skip to content

Commit e114855

Browse files
feat: Add pagination limit configuration
- Introduced a new pagination limit parameter in the Configuration class. - Updated ServerBuilder to support pagination limit configuration. - Refactored RequestProcessor to utilize the configured pagination limit for list methods.
1 parent c33202c commit e114855

File tree

9 files changed

+91
-78
lines changed

9 files changed

+91
-78
lines changed

src/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Configuration
2626
* @param CacheInterface|null $cache Optional PSR-16 Cache instance for registry/state.
2727
* @param ContainerInterface $container PSR-11 DI Container for resolving handlers/dependencies.
2828
* @param int $definitionCacheTtl TTL in seconds for cached definitions (if cache is provided).
29+
* @param int $paginationLimit Maximum number of items to return for list methods.
2930
*/
3031
public function __construct(
3132
public readonly string $serverName,
@@ -36,5 +37,6 @@ public function __construct(
3637
public readonly ?CacheInterface $cache,
3738
public readonly ContainerInterface $container,
3839
public readonly int $definitionCacheTtl = 3600,
40+
public readonly int $paginationLimit = 50,
3941
) {}
4042
}

src/Registry.php

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class Registry
2828

2929
private ?ClientStateManager $clientStateManager = null;
3030

31-
// Main collections hold BOTH manual and discovered/cached elements
3231
/** @var ArrayObject<string, ToolDefinition> */
3332
private ArrayObject $tools;
3433

@@ -41,7 +40,6 @@ class Registry
4140
/** @var ArrayObject<string, ResourceTemplateDefinition> */
4241
private ArrayObject $resourceTemplates;
4342

44-
// Track keys/names of MANUALLY registered elements
4543
/** @var array<string, true> */
4644
private array $manualToolNames = [];
4745

@@ -56,7 +54,6 @@ class Registry
5654

5755
private bool $discoveredElementsLoaded = false;
5856

59-
// --- Notification Callbacks ---
6057
/** @var callable|null */
6158
private $notifyToolsChanged = null;
6259

@@ -151,8 +148,6 @@ private function initializeDefaultNotifiers(): void
151148
};
152149
}
153150

154-
// --- Notifier Methods ---
155-
156151
public function setToolsChangedNotifier(?callable $notifier): void
157152
{
158153
$this->notifyToolsChanged = $notifier;
@@ -168,8 +163,6 @@ public function setPromptsChangedNotifier(?callable $notifier): void
168163
$this->notifyPromptsChanged = $notifier;
169164
}
170165

171-
// --- Registration Methods ---
172-
173166
public function registerTool(ToolDefinition $tool, bool $isManual = false): void
174167
{
175168
$toolName = $tool->getName();
@@ -195,7 +188,7 @@ public function registerTool(ToolDefinition $tool, bool $isManual = false): void
195188
}
196189

197190
if (! $exists && $this->notifyToolsChanged) {
198-
($this->notifyToolsChanged)();
191+
($this->notifyToolsChanged)($tool);
199192
}
200193
}
201194

@@ -277,8 +270,6 @@ public function registerPrompt(PromptDefinition $prompt, bool $isManual = false)
277270
}
278271
}
279272

280-
// --- Cache Handling Methods ---
281-
282273
public function loadDiscoveredElementsFromCache(bool $force = false): void
283274
{
284275
if ($this->cache === null) {
@@ -428,7 +419,6 @@ public function clearDiscoveredElements(bool $deleteFromCache = true): void
428419
{
429420
$this->logger->debug('Clearing discovered elements...', ['deleteCacheFile' => $deleteFromCache]);
430421

431-
// Clear cache file if requested
432422
if ($deleteFromCache && $this->cache !== null) {
433423
try {
434424
$this->cache->delete(self::DISCOVERED_ELEMENTS_CACHE_KEY);
@@ -438,7 +428,6 @@ public function clearDiscoveredElements(bool $deleteFromCache = true): void
438428
}
439429
}
440430

441-
// Clear internal collections of non-manual items
442431
$clearCount = 0;
443432

444433
foreach ($this->tools as $name => $tool) {
@@ -466,12 +455,10 @@ public function clearDiscoveredElements(bool $deleteFromCache = true): void
466455
}
467456
}
468457

469-
$this->discoveredElementsLoaded = false; // Mark as needing discovery/cache load again
458+
$this->discoveredElementsLoaded = false;
470459
$this->logger->debug("Removed {$clearCount} discovered elements from internal registry.");
471460
}
472461

473-
// --- Finder Methods ---
474-
475462
public function findTool(string $name): ?ToolDefinition
476463
{
477464
return $this->tools[$name] ?? null;
@@ -513,8 +500,6 @@ public function findResourceTemplateByUri(string $uri): ?array
513500
return null;
514501
}
515502

516-
// --- Getter Methods ---
517-
518503
/** @return ArrayObject<string, ToolDefinition> */
519504
public function allTools(): ArrayObject
520505
{

src/ServerBuilder.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ final class ServerBuilder
3434

3535
private ?LoopInterface $loop = null;
3636

37-
private ?int $definitionCacheTtl = 3600; // Default TTL
37+
private ?int $definitionCacheTtl = 3600;
38+
39+
private ?int $paginationLimit = 50;
3840

3941
// Temporary storage for manual registrations
4042
private array $manualTools = [];
@@ -68,6 +70,16 @@ public function withCapabilities(Capabilities $capabilities): self
6870
return $this;
6971
}
7072

73+
/**
74+
* Configures the server's pagination limit.
75+
*/
76+
public function withPaginationLimit(int $paginationLimit): self
77+
{
78+
$this->paginationLimit = $paginationLimit;
79+
80+
return $this;
81+
}
82+
7183
/**
7284
* Provides a PSR-3 logger instance. Defaults to NullLogger.
7385
*/
@@ -176,7 +188,8 @@ public function build(): Server
176188
loop: $loop,
177189
cache: $cache,
178190
container: $container,
179-
definitionCacheTtl: $this->definitionCacheTtl ?? 3600
191+
definitionCacheTtl: $this->definitionCacheTtl ?? 3600,
192+
paginationLimit: $this->paginationLimit ?? 50
180193
);
181194

182195
$clientStateManager = new ClientStateManager($configuration->logger, $configuration->cache, 'mcp_state_', $configuration->definitionCacheTtl);

src/Support/RequestProcessor.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ private function handleNotificationInitialized(array $params, string $clientId):
229229
private function handleToolList(array $params): ListToolsResult
230230
{
231231
$cursor = $params['cursor'] ?? null;
232-
$limit = 50; // $this->configuration->paginationLimit ?? 50;
232+
$limit = $this->configuration->paginationLimit;
233233
$offset = $this->decodeCursor($cursor);
234234
$allItems = $this->registry->allTools()->getArrayCopy();
235235
$pagedItems = array_slice($allItems, $offset, $limit);
@@ -241,7 +241,7 @@ private function handleToolList(array $params): ListToolsResult
241241
private function handleResourcesList(array $params): ListResourcesResult
242242
{
243243
$cursor = $params['cursor'] ?? null;
244-
$limit = 50; // $this->configuration->paginationLimit ?? 50;
244+
$limit = $this->configuration->paginationLimit;
245245
$offset = $this->decodeCursor($cursor);
246246
$allItems = $this->registry->allResources()->getArrayCopy();
247247
$pagedItems = array_slice($allItems, $offset, $limit);
@@ -253,7 +253,7 @@ private function handleResourcesList(array $params): ListResourcesResult
253253
private function handleResourceTemplateList(array $params): ListResourceTemplatesResult
254254
{
255255
$cursor = $params['cursor'] ?? null;
256-
$limit = 50; // $this->configuration->paginationLimit ?? 50;
256+
$limit = $this->configuration->paginationLimit;
257257
$offset = $this->decodeCursor($cursor);
258258
$allItems = $this->registry->allResourceTemplates()->getArrayCopy();
259259
$pagedItems = array_slice($allItems, $offset, $limit);
@@ -265,7 +265,7 @@ private function handleResourceTemplateList(array $params): ListResourceTemplate
265265
private function handlePromptsList(array $params): ListPromptsResult
266266
{
267267
$cursor = $params['cursor'] ?? null;
268-
$limit = 50; // $this->configuration->paginationLimit ?? 50;
268+
$limit = $this->configuration->paginationLimit;
269269
$offset = $this->decodeCursor($cursor);
270270
$allItems = $this->registry->allPrompts()->getArrayCopy();
271271
$pagedItems = array_slice($allItems, $offset, $limit);

tests/Unit/ConfigurationTest.php

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Mockery;
66
use PhpMcp\Server\Configuration;
7-
use PhpMcp\Server\Model\Capabilities; // Import the Capabilities model
7+
use PhpMcp\Server\Model\Capabilities;
88
use Psr\Container\ContainerInterface;
99
use Psr\Log\LoggerInterface;
1010
use Psr\SimpleCache\CacheInterface;
@@ -17,7 +17,6 @@
1717
$this->loop = Mockery::mock(LoopInterface::class);
1818
$this->cache = Mockery::mock(CacheInterface::class);
1919
$this->container = Mockery::mock(ContainerInterface::class);
20-
// Create a default Capabilities object for testing
2120
$this->capabilities = Capabilities::forServer();
2221
});
2322

@@ -27,61 +26,73 @@
2726

2827
it('constructs configuration object with all properties', function () {
2928
$ttl = 1800;
30-
// Pass the capabilities object to the constructor
29+
$paginationLimit = 100;
3130
$config = new Configuration(
3231
serverName: $this->name,
3332
serverVersion: $this->version,
34-
capabilities: $this->capabilities, // Pass capabilities
33+
capabilities: $this->capabilities,
3534
logger: $this->logger,
3635
loop: $this->loop,
3736
cache: $this->cache,
3837
container: $this->container,
39-
definitionCacheTtl: $ttl
38+
definitionCacheTtl: $ttl,
39+
paginationLimit: $paginationLimit
4040
);
4141

4242
expect($config->serverName)->toBe($this->name);
4343
expect($config->serverVersion)->toBe($this->version);
44-
expect($config->capabilities)->toBe($this->capabilities); // Assert capabilities
44+
expect($config->capabilities)->toBe($this->capabilities);
4545
expect($config->logger)->toBe($this->logger);
4646
expect($config->loop)->toBe($this->loop);
4747
expect($config->cache)->toBe($this->cache);
4848
expect($config->container)->toBe($this->container);
4949
expect($config->definitionCacheTtl)->toBe($ttl);
50+
expect($config->paginationLimit)->toBe($paginationLimit);
5051
});
5152

5253
it('constructs configuration object with default TTL', function () {
53-
// Pass capabilities object
5454
$config = new Configuration(
5555
serverName: $this->name,
5656
serverVersion: $this->version,
57-
capabilities: $this->capabilities, // Pass capabilities
57+
capabilities: $this->capabilities,
5858
logger: $this->logger,
5959
loop: $this->loop,
6060
cache: $this->cache,
6161
container: $this->container
62-
// No TTL provided
6362
);
6463

6564
expect($config->definitionCacheTtl)->toBe(3600); // Default value
6665
});
6766

67+
it('constructs configuration object with default pagination limit', function () {
68+
$config = new Configuration(
69+
serverName: $this->name,
70+
serverVersion: $this->version,
71+
capabilities: $this->capabilities,
72+
logger: $this->logger,
73+
loop: $this->loop,
74+
cache: $this->cache,
75+
container: $this->container
76+
);
77+
78+
expect($config->paginationLimit)->toBe(50); // Default value
79+
});
80+
6881
it('constructs configuration object with null cache', function () {
69-
// Pass capabilities object
7082
$config = new Configuration(
7183
serverName: $this->name,
7284
serverVersion: $this->version,
73-
capabilities: $this->capabilities, // Pass capabilities
85+
capabilities: $this->capabilities,
7486
logger: $this->logger,
7587
loop: $this->loop,
76-
cache: null, // Explicitly null cache
88+
cache: null,
7789
container: $this->container
7890
);
7991

8092
expect($config->cache)->toBeNull();
8193
});
8294

8395
it('constructs configuration object with specific capabilities', function () {
84-
// Create specific capabilities
8596
$customCaps = Capabilities::forServer(
8697
resourcesSubscribe: true,
8798
loggingEnabled: true,
@@ -91,7 +102,7 @@
91102
$config = new Configuration(
92103
serverName: $this->name,
93104
serverVersion: $this->version,
94-
capabilities: $customCaps, // Pass custom capabilities
105+
capabilities: $customCaps,
95106
logger: $this->logger,
96107
loop: $this->loop,
97108
cache: null,

tests/Unit/RegistryTest.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,29 @@
1818

1919
function createTestTool(string $name = 'test-tool'): ToolDefinition
2020
{
21-
return new ToolDefinition('TestClass', 'toolMethod', $name, 'Desc '.$name, ['type' => 'object']);
21+
return new ToolDefinition('TestClass', 'toolMethod', $name, 'Desc ' . $name, ['type' => 'object']);
2222
}
2323
function createTestResource(string $uri = 'test://res', string $name = 'test-res'): ResourceDefinition
2424
{
25-
return new ResourceDefinition('TestClass', 'resourceMethod', $uri, $name, 'Desc '.$name, 'text/plain', 100, []);
25+
return new ResourceDefinition('TestClass', 'resourceMethod', $uri, $name, 'Desc ' . $name, 'text/plain', 100, []);
2626
}
2727
function createTestPrompt(string $name = 'test-prompt'): PromptDefinition
2828
{
29-
return new PromptDefinition('TestClass', 'promptMethod', $name, 'Desc '.$name, []);
29+
return new PromptDefinition('TestClass', 'promptMethod', $name, 'Desc ' . $name, []);
3030
}
3131
function createTestTemplate(string $uriTemplate = 'tmpl://{id}', string $name = 'test-tmpl'): ResourceTemplateDefinition
3232
{
33-
return new ResourceTemplateDefinition('TestClass', 'templateMethod', $uriTemplate, $name, 'Desc '.$name, 'application/json', []);
33+
return new ResourceTemplateDefinition('TestClass', 'templateMethod', $uriTemplate, $name, 'Desc ' . $name, 'application/json', []);
3434
}
3535

3636
beforeEach(function () {
3737
/** @var MockInterface&LoggerInterface */
3838
$this->logger = Mockery::mock(LoggerInterface::class)->shouldIgnoreMissing();
3939
/** @var MockInterface&CacheInterface */
4040
$this->cache = Mockery::mock(CacheInterface::class);
41-
41+
/** @var MockInterface&ClientStateManager */
4242
$this->clientStateManager = Mockery::mock(ClientStateManager::class)->shouldIgnoreMissing();
4343

44-
// Default cache behaviors
4544
$this->cache->allows('get')->with(DISCOVERED_CACHE_KEY)->andReturn(null)->byDefault();
4645
$this->cache->allows('set')->with(DISCOVERED_CACHE_KEY, Mockery::any())->andReturn(true)->byDefault();
4746
$this->cache->allows('delete')->with(DISCOVERED_CACHE_KEY)->andReturn(true)->byDefault();
@@ -316,7 +315,7 @@ function getRegistryProperty(Registry $reg, string $propName)
316315
$this->cache->shouldReceive('get')->with(DISCOVERED_CACHE_KEY)->once()->andReturn('invalid string data');
317316

318317
// Act
319-
$registry = new Registry($this->logger, $this->cache, $this->clientStateManager); // Load happens here
318+
$registry = new Registry($this->logger, $this->cache, $this->clientStateManager);
320319

321320
// Assert
322321
expect($registry->discoveryRanOrCached())->toBeFalse(); // Marked loaded
@@ -330,7 +329,7 @@ function getRegistryProperty(Registry $reg, string $propName)
330329
$this->cache->shouldReceive('get')->with(DISCOVERED_CACHE_KEY)->once()->andReturn($cachedData);
331330

332331
// Act
333-
$registry = new Registry($this->logger, $this->cache, $this->clientStateManager); // Load happens here
332+
$registry = new Registry($this->logger, $this->cache, $this->clientStateManager);
334333

335334
// Assert
336335
expect($registry->discoveryRanOrCached())->toBeFalse();
@@ -385,8 +384,7 @@ function getRegistryProperty(Registry $reg, string $propName)
385384
$resource = createTestResource('notify://res');
386385
$prompt = createTestPrompt('notify-prompt');
387386

388-
// Set expectations on the ClientStateManager mock
389-
$this->clientStateManager->shouldReceive('queueMessageForAll')->times(3)->with(Mockery::type(Notification::class));
387+
$this->clientStateManager->shouldReceive('queueMessageForAll')->times(3)->with(Mockery::type('string'));
390388

391389
// Act
392390
$this->registry->registerTool($tool);

0 commit comments

Comments
 (0)