Skip to content

Commit 2197f2e

Browse files
committed
refactor: address reviewer feedback and clean up discovery caching
- Make DiscoveryState class final - Cache DiscoveryState objects directly instead of arrays (no serialization needed) - Rename exportDiscoveryState/importDiscoveryState to getDiscoveryState/setDiscoveryState - Add getDiscoveryState method to ReferenceRegistryInterface - Remove TTL parameter from CachedDiscoverer (no expiration by default) - Remove unused methods from DiscoveryState (toArray, fromArray, merge) - Simplify ServerBuilder to handle decoration internally - Make Discoverer.applyDiscoveryState() internal (no longer public API) - Simplify documentation to focus on user perspective - Remove unnecessary development comments - Update all tests to work with new architecture - All tests pass, PHPStan clean, code formatting applied
1 parent 48939a5 commit 2197f2e

File tree

9 files changed

+63
-402
lines changed

9 files changed

+63
-402
lines changed

docs/discovery-caching.md

Lines changed: 18 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,15 @@
11
# Discovery Caching
22

3-
This document explains the discovery caching feature in the PHP MCP SDK, which improves performance by caching the results of file system operations and reflection during MCP element discovery.
3+
This document explains how to use the discovery caching feature in the PHP MCP SDK to improve performance.
44

55
## Overview
66

7-
The discovery caching system allows you to cache the results of MCP element discovery to avoid repeated file system scanning and reflection operations. This is particularly useful in:
7+
The discovery caching system caches the results of MCP element discovery to avoid repeated file system scanning and reflection operations. This is particularly useful in:
88

99
- **Development environments** where the server is restarted frequently
1010
- **Production environments** where discovery happens on every request
1111
- **Large codebases** with many MCP elements to discover
1212

13-
## Architecture
14-
15-
The caching system is built around a state-based approach that eliminates the need for reflection to access private registry state:
16-
17-
### Core Components
18-
19-
1. **`DiscoveryState`** - A value object that encapsulates all discovered MCP capabilities
20-
2. **`CachedDiscoverer`** - A decorator that wraps the `Discoverer` and provides caching functionality
21-
3. **`Registry`** - Enhanced with `exportDiscoveryState()` and `importDiscoveryState()` methods
22-
4. **`ServerBuilder`** - Updated with `withCache()` method for easy cache configuration
23-
24-
### Key Benefits
25-
26-
- **No Reflection Required**: Uses clean public APIs instead of accessing private state
27-
- **State-Based**: Encapsulates discovered elements in a dedicated state object
28-
- **PSR-16 Compatible**: Works with any PSR-16 SimpleCache implementation
29-
- **Backward Compatible**: Existing code continues to work without changes
30-
3113
## Usage
3214

3315
### Basic Setup
@@ -49,112 +31,30 @@ $server = Server::make()
4931
The caching system works with any PSR-16 SimpleCache implementation. Popular options include:
5032

5133
#### Symfony Cache
34+
5235
```php
5336
use Symfony\Component\Cache\Adapter\ArrayAdapter;
5437
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
55-
use Symfony\Component\Cache\Adapter\RedisAdapter;
5638
use Symfony\Component\Cache\Psr16Cache;
5739

5840
// In-memory cache (development)
5941
$cache = new Psr16Cache(new ArrayAdapter());
6042

6143
// Filesystem cache (production)
62-
$cache = new Psr16Cache(new FilesystemAdapter('cache', 0, __DIR__ . '/var/cache'));
63-
64-
// Redis cache (distributed)
65-
$cache = new Psr16Cache(new RedisAdapter($redisClient));
44+
$cache = new Psr16Cache(new FilesystemAdapter('mcp-discovery', 0, '/var/cache'));
6645
```
6746

6847
#### Other PSR-16 Implementations
48+
6949
```php
7050
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
7151
use Doctrine\Common\Cache\ArrayCache;
7252

73-
// Doctrine cache
74-
$doctrineCache = new ArrayCache();
75-
$cache = DoctrineProvider::wrap($doctrineCache);
76-
```
77-
78-
## Configuration
79-
80-
### Cache TTL (Time To Live)
81-
82-
The default cache TTL is 1 hour (3600 seconds). You can customize this when creating the `CachedDiscoverer`:
83-
84-
```php
85-
use Mcp\Capability\Discovery\CachedDiscoverer;
86-
use Mcp\Capability\Discovery\Discoverer;
87-
88-
$discoverer = new Discoverer($registry, $logger);
89-
$cachedDiscoverer = new CachedDiscoverer(
90-
$discoverer,
91-
$cache,
92-
$logger,
93-
7200 // 2 hours TTL
94-
);
95-
```
96-
97-
### Cache Key Generation
98-
99-
Cache keys are automatically generated based on:
100-
- Base path for discovery
101-
- Directories to scan
102-
- Exclude directories
103-
- File modification times (implicitly through file system state)
104-
105-
This ensures that cache invalidation happens automatically when files change.
106-
107-
## Advanced Usage
108-
109-
### Manual Cache Management
110-
111-
```php
112-
use Mcp\Capability\Discovery\CachedDiscoverer;
113-
114-
$cachedDiscoverer = new CachedDiscoverer($discoverer, $cache, $logger);
115-
116-
// Clear the entire discovery cache
117-
$cachedDiscoverer->clearCache();
118-
119-
// Discovery with caching
120-
$discoveryState = $cachedDiscoverer->discover('/path', ['.'], []);
121-
```
122-
123-
### Custom Discovery State Handling
124-
125-
```php
126-
use Mcp\Capability\Discovery\DiscoveryState;
127-
use Mcp\Capability\Discovery\Discoverer;
128-
129-
$discoverer = new Discoverer($registry, $logger);
130-
131-
// Discover elements
132-
$discoveryState = $discoverer->discover('/path', ['.'], []);
133-
134-
// Check what was discovered
135-
echo "Discovered " . $discoveryState->getElementCount() . " elements\n";
136-
$counts = $discoveryState->getElementCounts();
137-
echo "Tools: {$counts['tools']}, Resources: {$counts['resources']}\n";
138-
139-
// Apply to registry
140-
$discoverer->applyDiscoveryState($discoveryState);
53+
$cache = DoctrineProvider::wrap(new ArrayCache());
14154
```
14255

14356
## Performance Benefits
14457

145-
### Before Caching
146-
- File system scanning on every discovery
147-
- Reflection operations for each MCP element
148-
- Schema generation for each tool/resource
149-
- DocBlock parsing for each method
150-
151-
### After Caching
152-
- File system scanning only on cache miss
153-
- Cached reflection results
154-
- Pre-generated schemas
155-
- Cached docBlock parsing results
156-
157-
### Typical Performance Improvements
15858
- **First run**: Same as without caching
15959
- **Subsequent runs**: 80-95% faster discovery
16060
- **Memory usage**: Slightly higher due to cache storage
@@ -163,6 +63,7 @@ $discoverer->applyDiscoveryState($discoveryState);
16363
## Best Practices
16464

16565
### Development Environment
66+
16667
```php
16768
// Use in-memory cache for fast development cycles
16869
$cache = new Psr16Cache(new ArrayAdapter());
@@ -174,153 +75,35 @@ $server = Server::make()
17475
```
17576

17677
### Production Environment
78+
17779
```php
178-
// Use persistent cache with appropriate TTL
179-
$cache = new Psr16Cache(new FilesystemAdapter('mcp-discovery', 3600, '/var/cache'));
80+
// Use persistent cache
81+
$cache = new Psr16Cache(new FilesystemAdapter('mcp-discovery', 0, '/var/cache'));
18082

18183
$server = Server::make()
18284
->withDiscovery(__DIR__, ['.'])
18385
->withCache($cache)
18486
->build();
18587
```
18688

187-
### Cache Invalidation
89+
## Cache Invalidation
90+
18891
The cache automatically invalidates when:
92+
18993
- Discovery parameters change (base path, directories, exclude patterns)
19094
- Files are modified (detected through file system state)
191-
- Cache TTL expires
19295

193-
For manual invalidation:
194-
```php
195-
$cachedDiscoverer->clearCache();
196-
```
96+
For manual invalidation, restart your application or clear the cache directory.
19797

19898
## Troubleshooting
19999

200100
### Cache Not Working
101+
201102
1. Verify PSR-16 SimpleCache implementation is properly installed
202103
2. Check cache permissions (for filesystem caches)
203-
3. Ensure cache TTL is appropriate for your use case
204-
4. Check logs for cache-related warnings
104+
3. Check logs for cache-related warnings
205105

206106
### Memory Issues
207-
1. Use filesystem or Redis cache instead of in-memory
208-
2. Reduce cache TTL
209-
3. Implement cache size limits in your cache implementation
210-
211-
### Stale Cache
212-
1. Clear cache manually: `$cachedDiscoverer->clearCache()`
213-
2. Reduce cache TTL
214-
3. Implement cache warming strategies
215-
216-
## Example: Complete Implementation
217-
218-
```php
219-
<?php
220-
221-
require_once __DIR__ . '/../bootstrap.php';
222-
223-
use Mcp\Server;
224-
use Mcp\Server\Transport\StdioTransport;
225-
use Symfony\Component\Cache\Adapter\ArrayAdapter;
226-
use Symfony\Component\Cache\Psr16Cache;
227-
228-
// Create cache instance
229-
$cache = new Psr16Cache(new ArrayAdapter());
230-
231-
// Build server with discovery caching
232-
$server = Server::make()
233-
->withServerInfo('Cached Calculator', '1.0.0', 'Calculator with cached discovery')
234-
->withDiscovery(__DIR__, ['.'])
235-
->withLogger(logger())
236-
->withCache($cache) // Enable discovery caching
237-
->build();
238-
239-
// Connect and start serving
240-
$server->connect(new StdioTransport());
241-
```
242-
243-
## Migration Guide
244-
245-
### From Non-Cached to Cached
246-
247-
1. **Add cache dependency**:
248-
```bash
249-
composer require symfony/cache
250-
```
251-
252-
2. **Update server configuration**:
253-
```php
254-
// Before
255-
$server = Server::make()
256-
->withDiscovery(__DIR__, ['.'])
257-
->build();
258-
259-
// After
260-
$server = Server::make()
261-
->withDiscovery(__DIR__, ['.'])
262-
->withCache(new Psr16Cache(new ArrayAdapter()))
263-
->build();
264-
```
265-
266-
3. **No other changes required** - the API remains the same!
267-
268-
## API Reference
269-
270-
### DiscoveryState
271-
272-
```php
273-
class DiscoveryState
274-
{
275-
public function __construct(
276-
array $tools = [],
277-
array $resources = [],
278-
array $prompts = [],
279-
array $resourceTemplates = []
280-
);
281-
282-
public function getTools(): array;
283-
public function getResources(): array;
284-
public function getPrompts(): array;
285-
public function getResourceTemplates(): array;
286-
public function isEmpty(): bool;
287-
public function getElementCount(): int;
288-
public function getElementCounts(): array;
289-
public function merge(DiscoveryState $other): DiscoveryState;
290-
public function toArray(): array;
291-
public static function fromArray(array $data): DiscoveryState;
292-
}
293-
```
294-
295-
### CachedDiscoverer
296-
297-
```php
298-
class CachedDiscoverer
299-
{
300-
public function __construct(
301-
Discoverer $discoverer,
302-
CacheInterface $cache,
303-
LoggerInterface $logger,
304-
int $cacheTtl = 3600
305-
);
306-
307-
public function discover(string $basePath, array $directories, array $excludeDirs = []): DiscoveryState;
308-
public function clearCache(): void;
309-
}
310-
```
311-
312-
### ServerBuilder
313-
314-
```php
315-
class ServerBuilder
316-
{
317-
public function withCache(CacheInterface $cache): self;
318-
// ... other methods
319-
}
320-
```
321-
322-
## Conclusion
323-
324-
Discovery caching provides significant performance improvements for MCP servers, especially in development environments and production deployments with frequent restarts. The state-based architecture ensures clean separation of concerns while maintaining backward compatibility with existing code.
325107

326-
For more examples, see the `examples/10-cached-discovery-stdio/` directory in the SDK.
108+
- Use filesystem cache instead of in-memory cache for large codebases
109+
- Consider using a dedicated cache server (Redis, Memcached) for high-traffic applications

0 commit comments

Comments
 (0)