php-sdk/CHANGELOG.md
Mateu Aguiló Bosch 0347dc85b4
[Server] Allow runtime-resolved element handlers (#294)
* [Server] feat: allow runtime handlers

Adds Mcp\Server\Handler\RunTimeHandlerInterface so integrators
(e.g. configuration-driven element sources) can register handlers
whose arguments and execution are resolved at runtime rather than
via PHP reflection of a callable.

ReferenceHandler::handle() dispatches instanceof RunTimeHandler
ahead of the callable/array/string branches, calls filterArguments
to narrow the argument map, and forwards execute() a ClientGateway
built from the active session.

The Registry-layer surface (ElementReference and the four
*Reference subclasses, RegistryInterface, Registry::register*,
ArrayLoader's handler-description helper) is widened to accept the
new type. The Builder/ArrayLoader path keeps its narrow callable
union; exposing runtime handlers there requires reworking
HandlerResolver and is left for a follow-up.

* feat(server): add runtime handler metadata API

Extend RunTimeHandlerInterface with four nullable metadata
accessors (getInputSchema, getOutputSchema, getPromptArguments,
getCompletionProviders) so the loader can obtain element
metadata without reflection.

Add RunTimeHandlerTrait providing null defaults for all four
accessors so single-purpose handlers can override only the
methods relevant to their element kind.

* feat(server): expose runtime handlers via Builder

Widen Builder::addTool, addResource, addResourceTemplate, and
addPrompt to accept RunTimeHandlerInterface instances alongside
the existing closure/array/string handler forms.

Branch ArrayLoader::load() on instanceof RunTimeHandlerInterface
in each of the four registration loops so runtime handlers skip
the reflection-based HandlerResolver/HandlerSchema path and pull
metadata from the interface accessors instead. Require explicit
name and description for runtime handlers and raise
ConfigurationException when missing or when a runtime tool has
no input schema source.

Replace direct $data['handler'] use in error log context with
getHandlerDescription() to avoid serializing large runtime
handler object graphs into log records.

Document the runtime handler API in docs/server-builder.md with a
new Runtime Handlers subsection and add a cross-reference from
docs/mcp-elements.md.

* test(server): cover runtime handler Builder path

Add ArrayLoaderRunTimeHandlerTest covering:
- successful tool registration with handler-supplied input schema,
  with kwarg input schema (precedence over the handler), and the
  same precedence rule for output schema
- ConfigurationException for tools missing name, description, or
  any input schema source
- successful resource registration via Builder kwargs and the
  matching missing-name failure mode
- successful resource template registration with completion
  providers sourced from the handler and the missing-description
  failure mode
- successful prompt registration with arguments and completion
  providers sourced from the handler, plus the missing-name
  failure mode
- a trait-only fixture verifying the four metadata accessors
  return null by default

Update existing ReferenceHandlerTest anonymous classes to use
RunTimeHandlerTrait so they satisfy the widened interface.

* refactor(server): split runtime handler interface

Splits RunTimeHandlerInterface into a minimal base contract (only
execute()) plus element-specific subtypes: RunTimeToolHandlerInterface,
RunTimePromptHandlerInterface, and
RunTimeResourceTemplateHandlerInterface. Each subtype declares only
the metadata accessors relevant to its element kind, so a tool
handler no longer has to no-op out prompt or template methods.

Drops the now-redundant RunTimeHandlerTrait and removes
filterArguments; execute() receives the full argument map directly.

Updates ArrayLoader, ReferenceHandler, and Builder type signatures
to use the per-element interfaces, and rewrites the Runtime
Handlers documentation accordingly.

* refactor(server): rename runtime handler interfaces to Runtime casing

Renames RunTimeHandlerInterface and its element-specific subtypes to
Runtime casing, adds a dedicated RuntimeResourceHandlerInterface so
addResource() rejects mismatched runtime handlers at the type level,
and strips _session and _request from arguments forwarded to runtime
handlers. Hardens ArrayLoader to re-throw ConfigurationException
without wrapping, and extracts inline test fixtures into
tests/Fixtures/Runtime/.

* chore: please the linting gods

* test(server): use Uuid::v4 for symfony/uid 5.4 compatibility

Uuid::v7() was added in symfony/uid 6.2 and breaks the Symfony 5.4
matrix job. Match the convention used elsewhere in the suite.

* refactor(server): split ArrayLoader registration

Extract per-element preparation into dedicated private prepare*
methods for tools, resources, templates, and prompts. Factor the
shared required-field assertion and reflected name/description
resolution into helpers.

* refactor(server): non-null runtime handler types

Drop nullable return types on RuntimeToolHandlerInterface::
getInputSchema(), RuntimePromptHandlerInterface::
getPromptArguments()/getCompletionProviders(), and
RuntimeResourceTemplateHandlerInterface::
getCompletionProviders() in favor of plain arrays.
ArrayLoader no longer needs the null-coalescing
fallbacks, and NullSchemaToolHandler plus its
ConfigurationException test become redundant.

* doc: update docs/server-builder.md

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>

* feat: peer revie feedback

Introduce a new `Builder::add(...)` pattern for a more generic runtime
handler.

* refactor(server): apply PR 294 review feedback

Move per-interface handler dispatch from ReferenceHandler into
ExplicitElementLoader by wrapping each ElementHandlerInterface in a
Closure that satisfies the existing callable contract, revert the
*Reference / Registry handler-type widening PR 294 introduced, throw
Mcp\Exception\InvalidArgumentException (not \TypeError) on mismatched
Builder::add() pairings, and rename two classes for clarity:

* Mcp\Capability\Registry\Loader\ArrayLoader -> ReflectedElementLoader
* Mcp\Schema\Resource -> Mcp\Schema\ResourceDefinition (no alias)

ReferenceHandler::prepareArguments() now injects ClientGateway by type
and binds an `array $arguments` / `array $variables` closure parameter
from the leftover inbound arguments. This addresses Antoine's coupling
concern (ReferenceHandler no longer knows about the four handler
interfaces) and Chris's package-exception preference, and clears the
phpdoc reserved-word collision Antoine flagged on the resource schema
class. Existing tests are re-targeted to exercise the closure path
through ExplicitElementLoader / ReferenceHandler.

* doc: address PR 294 review feedback

Apply Chris's line-level suggestions on docs/server-builder.md
(rewrap to 120 chars, smoothed Drupal mcp module name-drop in the
explicit-element registration section, exception name reads
Mcp\Exception\InvalidArgumentException), rewrap docs/mcp-elements.md
to 120 chars, and add 0.6.0 CHANGELOG entries covering the
Builder::add() entry point, the new handler interfaces, the new
ExplicitElementLoader, plus three BC breaks (Resource ->
ResourceDefinition rename, ArrayLoader -> ReflectedElementLoader
rename, mismatched-pairing exception swap).

* refactor: address self-review feedback

* CHANGELOG: drop spurious [BC Break] entry for the exception
  type Builder::add() raises (method is new in this PR).
* docs/server-builder: revert wrap-only edits to pre-existing
  main content; keep 120ch wrapping only for new sections.
* ExplicitElementLoader: trim verbose class docblock.
* ReferenceHandler: rename SPLAT_PARAMETER_NAMES constant to
  ARGUMENT_BAG_PARAMETERS.

* refactor: rename CallableHandler to Handler

* style: fix phpdoc param alignment

* refactor: inline $reference->handler access

* fix: maintainer review feedback

docs: mention ExplicitElementLoader in comment

docs: move Drupal reference to README list

docs: drop implementation-only changelog entry

docs: revert rewrap noise from mcp-elements.md

* style: fix phpdoc param alignment

---------

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
Co-authored-by: soyuka <soyuka@users.noreply.github.com>
2026-05-29 07:16:12 +02:00

7 KiB

Changelog

All notable changes to mcp/sdk will be documented in this file.

0.6.0

  • Add Builder::add(Tool|ResourceDefinition|ResourceTemplate|Prompt $definition, ElementHandlerInterface $handler) for explicit registration of elements whose schema is only known at runtime.
  • Add handler interfaces ToolHandlerInterface, ResourceHandlerInterface, ResourceTemplateHandlerInterface, PromptHandlerInterface, and the ElementHandlerInterface marker.
  • [BC Break] Renamed Mcp\Schema\Resource to Mcp\Schema\ResourceDefinition. No alias.
  • [BC Break] Renamed Mcp\Capability\Registry\Loader\ArrayLoader to Mcp\Capability\Registry\Loader\ReflectedElementLoader.
  • [BC Break] Bump default protocol version to 2025-11-25
  • Allow overriding the default name pattern for Discovery
  • Add configurable session garbage collection (gcProbability/gcDivisor)
  • Add ChainLoader to compose multiple LoaderInterface implementations via explicit ordering.
  • Add RegistryInterface::unregisterTool(), unregisterResource(), unregisterResourceTemplate(), unregisterPrompt() — idempotent removals.
  • Add RegistryInterface::hasTool(), hasResource(), hasResourceTemplate(), hasPrompt() — by-name existence checks.
  • DiscoveryLoader now refreshes only its own previously written entries; manual registrations (via Builder::addTool() etc. or runtime $registry->registerTool() calls) survive rediscovery, and a same-name manual registration takes precedence over discovery on collision.
  • [BC Break] Removed ElementReference::$isManual public property and the bool $isManual parameter from all *Reference constructors. Origin tracking is no longer carried on the element; manual-over-discovered precedence is encoded by loader execution order.
  • [BC Break] RegistryInterface::registerTool(), registerResource(), registerResourceTemplate(), registerPrompt() lost their trailing bool $isManual = false parameter. Callers using positional arguments must drop the flag.
  • [BC Break] Removed RegistryInterface::clear(), getDiscoveryState(), setDiscoveryState(). Rediscovery now goes through DiscoveryLoader::load() directly.
  • Registry::register*() semantics changed to plain last-write-wins (overwrites silently) and the methods now return the stored *Reference. The previous "discovered registration is ignored when a manual one already exists" precedence rule still applies, but is now enforced by DiscoveryLoader via reference-identity tracking — and still emits a debug log when a discovery is skipped due to a conflicting registration.
  • Add optional title parameter to Builder::addResource() and Builder::addResourceTemplate() for MCP spec compliance
  • [BC Break] Builder::addResource() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.
  • [BC Break] Builder::addResourceTemplate() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.
  • Add CorsMiddleware, DnsRebindingProtectionMiddleware, and ProtocolVersionMiddleware for StreamableHttpTransport, composed automatically as the default stack via StreamableHttpTransport::defaultMiddleware()
  • [BC BREAK] StreamableHttpTransport constructor: $corsHeaders parameter removed; CORS is now configured via CorsMiddleware. The $middleware parameter is nullable — null (or omitted) installs the default stack; [] disables all defaults. Default Access-Control-Allow-Origin is no longer set (was *).

0.5.0

  • Add built-in authentication middleware for HTTP transport using OAuth
  • Add client component for building MCP clients
  • Add Builder::setReferenceHandler() to allow custom ReferenceHandlerInterface implementations (e.g. authorization decorators)
  • Add elicitation enum schema types per SEP-1330: TitledEnumSchemaDefinition, MultiSelectEnumSchemaDefinition, TitledMultiSelectEnumSchemaDefinition
  • [BC break] Make Symfony Finder component optional. Users would need to install symfony/finder now themselves
  • Add LenientOidcDiscoveryMetadataPolicy for identity providers that omit code_challenge_methods_supported (e.g. FusionAuth, Microsoft Entra ID)
  • Add OAuth 2.0 Dynamic Client Registration middleware (RFC 7591)
  • Add optional title field to Prompt and McpPrompt for MCP spec compliance
  • [BC Break] Builder::addPrompt() signature changed — $title parameter added between $name and $description. Callers using positional arguments for $description must switch to named arguments.
  • Add optional title field to Tool and McpTool for MCP spec compliance
  • [BC Break] Tool::__construct() signature changed — $title parameter added between $name and $inputSchema. Callers using positional arguments must switch to named arguments or pass null for $title.
  • [BC Break] McpTool attribute signature changed — $title parameter added between $name and $description. Callers using positional arguments for $description must switch to named arguments.
  • [BC Break] Builder::addTool() signature changed — $title parameter added between $name and $description. Callers using positional arguments for $description must switch to named arguments.

0.4.0

  • Rename Mcp\Server\Session\Psr16StoreSession to Mcp\Server\Session\Psr16SessionStore
  • Add missing handlers for resource subscribe/unsubscribe and persist subscriptions via session
  • Introduce SessionManager to encapsulate session handling (replaces SessionFactory) and move garbage collection logic from Protocol.

0.3.0

  • Add output schema support to MCP tools
  • Add validation of the input parameters given to a Tool.
  • Rename Mcp\Capability\Registry\ResourceReference::$schema to Mcp\Capability\Registry\ResourceReference::$resource.
  • Introduce SchemaGeneratorInterface and DiscovererInterface to allow custom schema generation and discovery implementations.
  • Remove DocBlockParser::getSummary() method, use DocBlockParser::getDescription() instead.

0.2.2

  • Throw exception when trying to inject parameter with the unsupported names $_session or $_request.
  • Throwable objects are passed to log context instead of the exception message.

0.2.1

  • Add RunnerControl for StdioTransport to allow break out from continuously listening for new input.
  • Open range of supported Symfony versions to include v5.4

0.2.0

  • Make Protocol stateless by decouple if from TransportInterface. Removed Protocol::getTransport().
  • Change signature of Builder::addLoaders(...$loaders) to Builder::addLoaders(iterable $loaders).
  • Removed ClientAwareInterface in favor of injecting a RequestContext with argument injection.
  • The ClientGateway cannot be injected with argument injection anymore. Use RequestContext instead.
  • Removed ClientAwareTrait
  • Removed Protocol::getTransport()
  • Added parameter for TransportInterface to Protocol::processInput()

0.1.0

  • First tagged release of package
  • Support for implementing MCP server