[Examples] Demo pre-built instance handlers + inspector coverage (#390)
* [Tests] Bump MCP Inspector to 0.22.0 for 2025-11-25 protocol The pinned inspector 0.17.2 predates the protocol version bump to 2025-11-25 (v0.6.0) and rejects the handshake with "Server's protocol version is not supported", failing every inspector snapshot test. 0.22.0 negotiates the current protocol; the full inspector suite passes against it (95 tests, 7 pre-existing skips). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [Examples] Demo pre-built instance handler in combined-registration Extends the combined-registration example with a third handler style: a pre-built object instance `[$instance, 'method']`, alongside the existing discovered and manual class-string handlers. PreconfiguredGreeter takes a scalar constructor argument neither the container-less `new $className()` fallback nor the auto-wiring container can build, so it can only be registered as an already-constructed instance — exercising the resolver path added in the parent branch. Adds an inspector snapshot case (tools/call instance_greeter) and updates the tools/list snapshot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d187002d87
commit
1fed089d71
8 changed files with 99 additions and 7 deletions
|
|
@ -217,14 +217,20 @@ npx @modelcontextprotocol/inspector http://localhost:8000
|
|||
**What it demonstrates:**
|
||||
- Mixing attribute discovery with manual registration
|
||||
- HTTP server with both discovered and manual capabilities
|
||||
- Flexible registration patterns
|
||||
- All three handler styles: discovered, `[Class::class, 'method']`, and a pre-built `[$instance, 'method']`
|
||||
- Pre-built instance handlers for classes the container cannot auto-wire (e.g. constructor scalars)
|
||||
|
||||
**Key Features:**
|
||||
```php
|
||||
// Built here so its constructor dependencies are injected before registration;
|
||||
// the SDK invokes this very instance instead of constructing one itself.
|
||||
$preconfiguredGreeter = new PreconfiguredGreeter('Willkommen', logger());
|
||||
|
||||
$server = Server::builder()
|
||||
->setDiscovery(__DIR__, ['.']) // Automatic discovery
|
||||
->addTool([ManualHandlers::class, 'manualGreeter']) // Manual registration
|
||||
->addResource([ManualHandlers::class, 'getPriorityConfig'], 'config://priority')
|
||||
->setDiscovery(__DIR__, ['.']) // Automatic discovery
|
||||
->addTool([ManualHandlers::class, 'manualGreeter']) // Manual class-string handler
|
||||
->addTool([$preconfiguredGreeter, 'greet'], 'instance_greeter') // Pre-built instance handler
|
||||
->addResource([ManualHandlers::class, 'getPriorityConfigManual'], 'config://priority')
|
||||
```
|
||||
|
||||
### Complex Tool Schema
|
||||
|
|
|
|||
|
|
@ -277,8 +277,8 @@ Register MCP elements programmatically without using attributes. The handler is
|
|||
**Handler** can be any PHP callable:
|
||||
|
||||
1. **Closure**: `function(int $a, int $b): int { return $a + $b; }`
|
||||
2. **Class and method name pair**: `[ClassName::class, 'methodName']` - class must be constructable through the container
|
||||
3. **Class instance and method name**: `[$instance, 'methodName']`
|
||||
2. **Class and method name pair**: `[ClassName::class, 'methodName']` - the class is instantiated lazily on first call, so it must be constructable through the container (or have a no-arg constructor)
|
||||
3. **Class instance and method name**: `[$instance, 'methodName']` - the given, already-constructed object is invoked as-is. Use this for handlers the container cannot build, e.g. those with scalar constructor arguments or dependencies wired at runtime
|
||||
4. **Invokable class name**: `InvokableClass::class` - class must be constructable through the container and have `__invoke` method
|
||||
|
||||
### Manual Tool Registration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
<?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\Example\Server\CombinedRegistration;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* A handler whose constructor takes a scalar the container cannot auto-wire, so
|
||||
* it can only be registered as a pre-built object instance:
|
||||
* `->addTool([new PreconfiguredGreeter('...', ...), 'greet'], 'instance_greeter')`.
|
||||
*
|
||||
* Neither the container-less `new $className()` fallback nor the auto-wiring
|
||||
* container can build this class, since the required `string $greeting` has no
|
||||
* default and is not a resolvable service.
|
||||
*/
|
||||
final class PreconfiguredGreeter
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $greeting,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A tool registered as a pre-built object instance.
|
||||
*
|
||||
* @param string $name the name to greet
|
||||
*
|
||||
* @return string greeting
|
||||
*/
|
||||
public function greet(string $name): string
|
||||
{
|
||||
$this->logger->info("Instance tool 'instance_greeter' called for {$name}");
|
||||
|
||||
return "{$this->greeting}, {$name}!";
|
||||
}
|
||||
}
|
||||
|
|
@ -14,9 +14,15 @@ require_once dirname(__DIR__).'/bootstrap.php';
|
|||
chdir(__DIR__);
|
||||
|
||||
use Mcp\Example\Server\CombinedRegistration\ManualHandlers;
|
||||
use Mcp\Example\Server\CombinedRegistration\PreconfiguredGreeter;
|
||||
use Mcp\Server;
|
||||
use Mcp\Server\Session\FileSessionStore;
|
||||
|
||||
// Built here so its constructor dependencies (a scalar the container cannot
|
||||
// auto-wire) are injected before registration; the SDK invokes this very
|
||||
// instance instead of trying to construct one itself.
|
||||
$preconfiguredGreeter = new PreconfiguredGreeter('Willkommen', logger());
|
||||
|
||||
$server = Server::builder()
|
||||
->setServerInfo('Combined HTTP Server', '1.0.0')
|
||||
->setLogger(logger())
|
||||
|
|
@ -24,6 +30,7 @@ $server = Server::builder()
|
|||
->setSession(new FileSessionStore(__DIR__.'/sessions'))
|
||||
->setDiscovery(__DIR__)
|
||||
->addTool([ManualHandlers::class, 'manualGreeter'])
|
||||
->addTool([$preconfiguredGreeter, 'greet'], 'instance_greeter')
|
||||
->addResource(
|
||||
[ManualHandlers::class, 'getPriorityConfigManual'],
|
||||
'config://priority',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ final class HttpCombinedRegistrationTest extends HttpInspectorSnapshotTestCase
|
|||
],
|
||||
'testName' => 'manual_greeter',
|
||||
],
|
||||
'Instance Greeter Tool (pre-built object handler)' => [
|
||||
'method' => 'tools/call',
|
||||
'options' => [
|
||||
'toolName' => 'instance_greeter',
|
||||
'toolArgs' => ['name' => 'HTTP Test User'],
|
||||
],
|
||||
'testName' => 'instance_greeter',
|
||||
],
|
||||
'Discovered Status Check Tool' => [
|
||||
'method' => 'tools/call',
|
||||
'options' => [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Willkommen, HTTP Test User!"
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
|
|
@ -16,6 +16,22 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "instance_greeter",
|
||||
"description": "A tool registered as a pre-built object instance.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "the name to greet"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "discovered_status_check",
|
||||
"description": "A tool discovered via attributes.",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use Symfony\Component\Process\Process;
|
|||
|
||||
abstract class InspectorSnapshotTestCase extends TestCase
|
||||
{
|
||||
private const INSPECTOR_VERSION = '0.17.2';
|
||||
private const INSPECTOR_VERSION = '0.22.0';
|
||||
|
||||
/** @param array<string, mixed> $options */
|
||||
#[DataProvider('provideMethods')]
|
||||
|
|
|
|||
Loading…
Reference in a new issue