Drop CustomCallToolHandler in favour of mcp/sdk RunTimeHandlerInterface
### Problem/Motivation
Today `mcp_server` works around `mcp/sdk` by registering a bespoke request handler — `Drupal\mcp_server\Handler\CustomCallToolHandler` — that intercepts every `CallToolRequest` *before* the SDK dispatches it, then dispatches an `McpAuthorizeCallEvent` and ultimately invokes our `ToolPluginInterface::executeTool()` directly. This bypass exists because the SDK's `Mcp\Capability\Registry\ReferenceHandler` only knew how to invoke handlers shaped as a `\Closure`, a `[$objectOrClass, $method]` pair, or a class string — there was no way to register an object that *itself* knows how to execute and how to filter the inbound argument bag.
The cost of the workaround:
- We register every tool with a `static fn() => NULL` placeholder handler purely so the SDK accepts the registration. The real call path lives outside the SDK in `CustomCallToolHandler::handle()`.
- Authorization, gateway construction, and argument filtering are duplicated in our handler instead of riding on the SDK's normal dispatch.
- It diverges from the SDK's design: anyone reading the code has to learn that `addRequestHandler()` plus a no-op tool registration is what actually wires tools up.
- Two adjacent classes (`McpAuthorizeCallEvent`, `McpOperationDescriptor`) only exist to serve the bespoke handler, expanding the API surface for no upstream benefit.
We have prototyped a small, additive change to `mcp/sdk` that introduces a new handler shape: `Mcp\Server\Handler\RunTimeHandlerInterface`. When `ReferenceHandler::handle()` finds an instance of this interface as the registered handler, it calls `$handler->execute($handler->filterArguments($arguments), new ClientGateway($session))` and we're done — no custom request handler, no placeholder closures, no parallel dispatch path.
### Proposed resolution
1. Land the SDK change upstream (or carry it as a Composer patch in the meantime). The patch adds:
- `Mcp\Server\Handler\RunTimeHandlerInterface` with `filterArguments(array): array` and `execute(array, ClientGateway): mixed`.
- A new branch in `Mcp\Capability\Registry\ReferenceHandler::handle()` that detects the interface and dispatches to it.
- The `Handler` phpstan type alias on `ElementReference` extended to include the new interface.
2. Update `Drupal\mcp_server\Plugin\ToolPluginInterface` to extend `RunTimeHandlerInterface` and remove the legacy `executeTool(?ToolRegistration, string, array, ?ClientGateway)` method.
3. Provide a default `filterArguments()` implementation on `ToolPluginBase` that strips keys beginning with `_` (the convention we already use for internal/system arguments).
4. Refactor every shipped tool plugin (`EchoTool`, `ContentLookupTool`, `ToolApi`) to implement `execute(array $arguments, ClientGateway $gateway): mixed` instead of `executeTool()`.
5. In `McpServerFactory::registerTools()`, instantiate each `ToolPluginInterface` and pass the *plugin instance itself* as the handler to `Builder::addTool()` — replacing the placeholder closure registration.
6. Have `ToolPluginManager::createInstance()` resolve and inject the matching `ToolRegistration` into the plugin's configuration so plugins that need it (e.g. `ToolApi`, which keys behaviour off `McpToolConfig`) can reach it via `$this->getConfiguration()['registration']`.
7. Delete `Drupal\mcp_server\Handler\CustomCallToolHandler`, `McpAuthorizeCallEvent`, and `McpOperationDescriptor`. Remove the `addRequestHandler()` call from `McpServerFactory::buildServer()`.
8. Move the authorization concern (previously raised via `McpAuthorizeCallEvent`) into the existing per-plugin access checks (`ToolPluginInterface::access()` / `accessOperation()`), or expose an equivalent SDK-friendly hook if a request-time event is still required.
9. Update `references/tools/index.md` and `references/auth/index.md` to describe the new flow and drop references to the deleted classes/events.
### Remaining tasks
- Open the upstream PR against `mcp/sdk` and link it here.
- Decide whether `mcp_server_oauth` needs a replacement for `McpAuthorizeCallEvent` or whether the per-plugin `access()` path is sufficient.
- Update tests that subscribed to `McpAuthorizeCallEvent` (if any) to use the new access path.
- Verify HTTP `/_mcp` and Drush STDIO transports still execute tools correctly end-to-end.
### User interface changes
None.
### API changes
- `ToolPluginInterface::executeTool()` is removed. Implementers must provide `execute(array $arguments, ClientGateway $gateway): mixed` instead.
- `McpAuthorizeCallEvent` and `McpOperationDescriptor` are removed. Subscribers must move authorization logic into the plugin's `access()` / `accessOperation()` methods.
- These are breaking changes; per `AGENTS.md` the 2.x branch carries no backwards-compatibility shims and they ship as-is.
### Data model changes
None.
issue