Skip to content

Commit 959e0e1

Browse files
chr-hertelclaude
andcommitted
[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>
1 parent da56587 commit 959e0e1

7 files changed

Lines changed: 98 additions & 6 deletions

File tree

docs/examples.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,20 @@ npx @modelcontextprotocol/inspector http://localhost:8000
217217
**What it demonstrates:**
218218
- Mixing attribute discovery with manual registration
219219
- HTTP server with both discovered and manual capabilities
220-
- Flexible registration patterns
220+
- All three handler styles: discovered, `[Class::class, 'method']`, and a pre-built `[$instance, 'method']`
221+
- Pre-built instance handlers for classes the container cannot auto-wire (e.g. constructor scalars)
221222

222223
**Key Features:**
223224
```php
225+
// Built here so its constructor dependencies are injected before registration;
226+
// the SDK invokes this very instance instead of constructing one itself.
227+
$preconfiguredGreeter = new PreconfiguredGreeter('Willkommen', logger());
228+
224229
$server = Server::builder()
225-
->setDiscovery(__DIR__, ['.']) // Automatic discovery
226-
->addTool([ManualHandlers::class, 'manualGreeter']) // Manual registration
227-
->addResource([ManualHandlers::class, 'getPriorityConfig'], 'config://priority')
230+
->setDiscovery(__DIR__, ['.']) // Automatic discovery
231+
->addTool([ManualHandlers::class, 'manualGreeter']) // Manual class-string handler
232+
->addTool([$preconfiguredGreeter, 'greet'], 'instance_greeter') // Pre-built instance handler
233+
->addResource([ManualHandlers::class, 'getPriorityConfigManual'], 'config://priority')
228234
```
229235

230236
### Complex Tool Schema

docs/server-builder.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,8 @@ Register MCP elements programmatically without using attributes. The handler is
277277
**Handler** can be any PHP callable:
278278

279279
1. **Closure**: `function(int $a, int $b): int { return $a + $b; }`
280-
2. **Class and method name pair**: `[ClassName::class, 'methodName']` - class must be constructable through the container
281-
3. **Class instance and method name**: `[$instance, 'methodName']`
280+
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)
281+
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
282282
4. **Invokable class name**: `InvokableClass::class` - class must be constructable through the container and have `__invoke` method
283283

284284
### Manual Tool Registration
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Example\Server\CombinedRegistration;
13+
14+
use Psr\Log\LoggerInterface;
15+
16+
/**
17+
* A handler whose constructor takes a scalar the container cannot auto-wire, so
18+
* it can only be registered as a pre-built object instance:
19+
* `->addTool([new PreconfiguredGreeter('...', ...), 'greet'], 'instance_greeter')`.
20+
*
21+
* Neither the container-less `new $className()` fallback nor the auto-wiring
22+
* container can build this class, since the required `string $greeting` has no
23+
* default and is not a resolvable service.
24+
*/
25+
final class PreconfiguredGreeter
26+
{
27+
public function __construct(
28+
private readonly string $greeting,
29+
private readonly LoggerInterface $logger,
30+
) {
31+
}
32+
33+
/**
34+
* A tool registered as a pre-built object instance.
35+
*
36+
* @param string $name the name to greet
37+
*
38+
* @return string greeting
39+
*/
40+
public function greet(string $name): string
41+
{
42+
$this->logger->info("Instance tool 'instance_greeter' called for {$name}");
43+
44+
return "{$this->greeting}, {$name}!";
45+
}
46+
}

examples/server/combined-registration/server.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,23 @@
1414
chdir(__DIR__);
1515

1616
use Mcp\Example\Server\CombinedRegistration\ManualHandlers;
17+
use Mcp\Example\Server\CombinedRegistration\PreconfiguredGreeter;
1718
use Mcp\Server;
1819
use Mcp\Server\Session\FileSessionStore;
1920

21+
// Built here so its constructor dependencies (a scalar the container cannot
22+
// auto-wire) are injected before registration; the SDK invokes this very
23+
// instance instead of trying to construct one itself.
24+
$preconfiguredGreeter = new PreconfiguredGreeter('Willkommen', logger());
25+
2026
$server = Server::builder()
2127
->setServerInfo('Combined HTTP Server', '1.0.0')
2228
->setLogger(logger())
2329
->setContainer(container())
2430
->setSession(new FileSessionStore(__DIR__.'/sessions'))
2531
->setDiscovery(__DIR__)
2632
->addTool([ManualHandlers::class, 'manualGreeter'])
33+
->addTool([$preconfiguredGreeter, 'greet'], 'instance_greeter')
2734
->addResource(
2835
[ManualHandlers::class, 'getPriorityConfigManual'],
2936
'config://priority',

tests/Inspector/Http/HttpCombinedRegistrationTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ public static function provideMethods(): array
2525
],
2626
'testName' => 'manual_greeter',
2727
],
28+
'Instance Greeter Tool (pre-built object handler)' => [
29+
'method' => 'tools/call',
30+
'options' => [
31+
'toolName' => 'instance_greeter',
32+
'toolArgs' => ['name' => 'HTTP Test User'],
33+
],
34+
'testName' => 'instance_greeter',
35+
],
2836
'Discovered Status Check Tool' => [
2937
'method' => 'tools/call',
3038
'options' => [
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"content": [
3+
{
4+
"type": "text",
5+
"text": "Willkommen, HTTP Test User!"
6+
}
7+
],
8+
"isError": false
9+
}

tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-tools_list.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@
1616
]
1717
}
1818
},
19+
{
20+
"name": "instance_greeter",
21+
"description": "A tool registered as a pre-built object instance.",
22+
"inputSchema": {
23+
"type": "object",
24+
"properties": {
25+
"name": {
26+
"type": "string",
27+
"description": "the name to greet"
28+
}
29+
},
30+
"required": [
31+
"name"
32+
]
33+
}
34+
},
1935
{
2036
"name": "discovered_status_check",
2137
"description": "A tool discovered via attributes.",

0 commit comments

Comments
 (0)