-
Notifications
You must be signed in to change notification settings - Fork 140
[Server] Allow runtime-resolved element handlers #294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7a0a5ae
710c251
913c5cf
2d9b29b
68842bf
d0cadd6
0d56bee
acc3100
3876e61
09b5667
59c4163
a781354
7634887
25ad401
944059e
e89882a
7fcbcdc
994bd02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -398,6 +398,62 @@ the handler's method name and docblock. | |
|
|
||
| For more details on MCP elements, handlers, and attribute-based discovery, see [MCP Elements](mcp-elements.md). | ||
|
|
||
| ### Explicit element registration | ||
|
|
||
| When an element's name, schema, or description is only known at runtime (for example, the Drupal `mcp` module exposing | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move the Drupal reference to README section "PHP Libraries Using the MCP SDK" |
||
| configuration or database entities as MCP elements), pair an `Mcp\Schema\*` value object with one of the four handler | ||
| interfaces below and register it through `Builder::add()`. | ||
|
|
||
| | Element kind | Handler interface | | ||
| |-------------------|-------------------------------------------------------| | ||
| | Tool | `Mcp\Server\Handler\ToolHandlerInterface` | | ||
| | Resource | `Mcp\Server\Handler\ResourceHandlerInterface` | | ||
| | Resource template | `Mcp\Server\Handler\ResourceTemplateHandlerInterface` | | ||
| | Prompt | `Mcp\Server\Handler\PromptHandlerInterface` | | ||
|
|
||
| Each handler interface declares a single execution method. Tool and prompt handlers receive an arguments map and a | ||
| `ClientGateway`. Resource handlers receive the requested URI; resource template handlers additionally receive the parsed | ||
| template variables. | ||
|
|
||
| ```php | ||
| use Mcp\Schema\Tool; | ||
| use Mcp\Server; | ||
| use Mcp\Server\ClientGateway; | ||
| use Mcp\Server\Handler\ToolHandlerInterface; | ||
|
|
||
| final class WeatherHandler implements ToolHandlerInterface | ||
| { | ||
| public function execute(array $arguments, ClientGateway $gateway): mixed | ||
| { | ||
| return ['temperature' => 21, 'unit' => 'C']; | ||
| } | ||
| } | ||
|
|
||
| $tool = new Tool( | ||
| name: 'get_weather', | ||
| title: null, | ||
| inputSchema: [ | ||
| 'type' => 'object', | ||
| 'properties' => ['city' => ['type' => 'string']], | ||
| 'required' => ['city'], | ||
| ], | ||
| description: 'Returns the current weather for a city.', | ||
| annotations: null, | ||
| ); | ||
|
|
||
| $server = Server::builder() | ||
| ->add($tool, new WeatherHandler()) | ||
| ->build(); | ||
| ``` | ||
|
|
||
| `Builder::add()` validates the pairing at registration time. Pairing a `Tool` definition with, for example, a | ||
| `PromptHandlerInterface` raises `Mcp\Exception\InvalidArgumentException`. The schema value object validates its own | ||
| inputs (name pattern, schema shape, etc.), so passing an incomplete definition fails before `add()` returns. | ||
|
|
||
| Use `add()` when the metadata cannot be inferred from a handler class via reflection. For statically-known elements, | ||
| prefer `addTool/addResource/addResourceTemplate/addPrompt`, which can derive metadata from the handler's signature and | ||
| docblock. | ||
|
|
||
| ## Service Dependencies | ||
|
|
||
| ### Container | ||
|
|
@@ -619,4 +675,5 @@ $server = Server::builder() | |
| | `addResource()` | handler, uri, name?, description?, mimeType?, size?, annotations? | Register resource | | ||
| | `addResourceTemplate()` | handler, uriTemplate, name?, description?, mimeType?, annotations? | Register resource template | | ||
| | `addPrompt()` | handler, name?, description? | Register prompt | | ||
| | `add()` | definition, handler | Register an element from a schema VO + handler pair | | ||
| | `build()` | - | Create the server instance | | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| <?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\Capability\Registry\Loader; | ||
|
|
||
| use Mcp\Capability\RegistryInterface; | ||
| use Mcp\Schema\Prompt; | ||
| use Mcp\Schema\ResourceDefinition; | ||
| use Mcp\Schema\ResourceTemplate; | ||
| use Mcp\Schema\Tool; | ||
| use Mcp\Server\ClientGateway; | ||
| use Mcp\Server\Handler\PromptHandlerInterface; | ||
| use Mcp\Server\Handler\ResourceHandlerInterface; | ||
| use Mcp\Server\Handler\ResourceTemplateHandlerInterface; | ||
| use Mcp\Server\Handler\ToolHandlerInterface; | ||
|
|
||
| /** | ||
| * Translates `Builder::add()` definition+handler pairs into Registry entries. | ||
| * | ||
| * @author Mateu Aguiló Bosch <mateu.aguilo.bosch@gmail.com> | ||
| */ | ||
| final class ExplicitElementLoader implements LoaderInterface | ||
|
e0ipso marked this conversation as resolved.
|
||
| { | ||
| /** | ||
| * @param list<array{definition: Tool, handler: ToolHandlerInterface}> $tools | ||
| * @param list<array{definition: ResourceDefinition, handler: ResourceHandlerInterface}> $resources | ||
| * @param list<array{definition: ResourceTemplate, handler: ResourceTemplateHandlerInterface}> $resourceTemplates | ||
| * @param list<array{definition: Prompt, handler: PromptHandlerInterface}> $prompts | ||
| */ | ||
| public function __construct( | ||
| private readonly array $tools = [], | ||
| private readonly array $resources = [], | ||
| private readonly array $resourceTemplates = [], | ||
| private readonly array $prompts = [], | ||
| ) { | ||
| } | ||
|
|
||
| public function load(RegistryInterface $registry): void | ||
| { | ||
| foreach ($this->tools as $entry) { | ||
| $handler = $entry['handler']; | ||
| $registry->registerTool( | ||
| $entry['definition'], | ||
| static fn (array $arguments, ClientGateway $client) => $handler->execute($arguments, $client), | ||
| ); | ||
| } | ||
|
|
||
| foreach ($this->resources as $entry) { | ||
| $handler = $entry['handler']; | ||
| $registry->registerResource( | ||
| $entry['definition'], | ||
| static fn (string $uri, ClientGateway $client) => $handler->read($uri, $client), | ||
| ); | ||
| } | ||
|
|
||
| foreach ($this->resourceTemplates as $entry) { | ||
| $handler = $entry['handler']; | ||
| $registry->registerResourceTemplate( | ||
| $entry['definition'], | ||
| static fn (string $uri, array $variables, ClientGateway $client) => $handler->read($uri, $variables, $client), | ||
| ); | ||
|
Comment on lines
+66
to
+69
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Third argument
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't we just add the completionProvider? there'd be a feature gap between the two loaders if not?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was my thought as well, but we only have i don't think it should be part of the |
||
| } | ||
|
|
||
| foreach ($this->prompts as $entry) { | ||
| $handler = $entry['handler']; | ||
| $registry->registerPrompt( | ||
| $entry['definition'], | ||
| static fn (array $arguments, ClientGateway $client) => $handler->get($arguments, $client), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.