* [Schema] Fix Implementation::fromArray rejecting falsy-but-valid name/version
`empty()` treats the string "0" as empty, so a valid clientInfo/serverInfo
with `"version": "0"` (or `"name": "0"`) was rejected with an
InvalidArgumentException. During initialize this aborted request parsing and
surfaced as a misleading "A valid session id is REQUIRED" error.
Replace the `empty()` guards with explicit isset/is_string/empty-string checks
so genuinely missing, non-string, and empty values are still rejected while
"0" is accepted. Adds ImplementationTest covering the regression and the
existing validation paths.
Fixes#392
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [Schema] Suppress PHPStan argument.type on invalid-input tests
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add MCP Apps extension (io.modelcontextprotocol/ui) support
Adds support for the MCP Apps protocol extension, which lets servers
expose interactive HTML UIs as resources rendered by spec-conforming
clients (e.g. Goose) in sandboxed iframes.
- Generic extension plumbing: ServerExtensionInterface and
Builder::enableExtension(), with extensions advertised under
capabilities.extensions during the initialize handshake.
ClientCapabilities/ServerCapabilities gain an extensions field.
- Schema DTOs under Mcp\Schema\Extension\Apps: the McpApps marker,
UiToolMeta, ToolVisibility enum, UiResourceContentMeta,
UiResourceCsp, and UiResourcePermissions (with empty-object
presence markers per spec).
- A worked weather example under examples/server/mcp-apps/ with a
minimal spec-conforming view (ui/initialize handshake, postMessage
JSONRPCMessage objects, and ResizeObserver-driven size reporting).
- docs/extensions.md guide with pointers from index.md and
examples.md to the ext-apps repository for the TypeScript SDK and
richer view-side patterns.
- Unit and inspector snapshot test coverage.
Spec: https://github.com/modelcontextprotocol/ext-apps
* Type UiToolMeta visibility as ToolVisibility enum
Also fix UiResourceContentMeta::jsonSerialize() return phpdoc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address review nitpicks for MCP Apps extension
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Apply PR review feedback for MCP Apps extension
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add regression tests and CHANGELOG entry for capabilities extensions
- Pin the is_array() guards on extensions in ServerCapabilities and
ClientCapabilities::fromArray() with explicit non-array payload tests.
- Pin the isset()-rejects-null contract in UiResourcePermissions::fromArray().
- Surface the new extensions field on both capability classes in CHANGELOG.
- Inline the ?: idiom in Builder::build() to match the surrounding style.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [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>
The MCP specification (2025-11-25) defines an optional title field for
Resource and ResourceTemplate as a human-readable display label, distinct
from name (a short identifier). Tool and Prompt already implement this
field; Resource and ResourceTemplate were missing it.
Changes:
- Add title parameter to Resource and ResourceTemplate constructors
- Add title to fromArray(), jsonSerialize(), and PHPStan type arrays
- Add title to McpResource and McpResourceTemplate attributes
- Pass title through Discoverer when constructing schema objects
- Add tests for deserialization, serialization, and null omission
Fixes#296
* fix: allow URIs without double slash in Resource and ResourceTemplate (RFC 3986)
The URI regex required "://" after the scheme, rejecting valid URIs
like urn:isbn:123, mailto:user@example.com, config:key, and data:
URIs. RFC 3986 only requires ":" after the scheme; the "//" is part
of the authority component which is optional.
Changed both Resource.URI_PATTERN and ResourceTemplate.URI_TEMPLATE_PATTERN
to require ":" instead of "://".
Adds data-provider tests for URIs without double slash in both classes.
Fixes#293
* Reword unit tests
---------
Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
* feat(schema): add top-level title to Tool
Mirror PR #278's Prompt change for Tool:
- Add optional ?string $title to Mcp\Schema\Tool between $name
and $inputSchema. fromArray() now reads $data['title'];
jsonSerialize() emits 'title' right after 'name'. The
@phpstan-type ToolData gains title?: string.
- Add ?string $title to the #[McpTool] attribute between $name
and $description.
- Note ToolAnnotations::$title as deprecated-for-display in
favor of Tool::$title per MCP spec 2025-06-18 (PHPDoc only,
field retained for BC).
Phase 1 of plan 01--tool-title-spec-compliance. Later phases
update Discoverer, Builder::addTool(), ArrayLoader, and the
conformance fixture to match the new signature.
* feat(server): thread title through discovery
- Discoverer::processMethod() now propagates McpTool::$title
into every discovered Tool via named arguments.
- Builder::addTool() accepts ?string $title between $name and
$description. [BC Break] positional callers passing the old
$description as 3rd positional argument must switch to named
arguments, matching the precedent set for addPrompt() in
0.5.0.
- tests/Conformance/server.php migrated to named-argument form
for every addTool() call.
Phase 2 of plan 01--tool-title-spec-compliance.
* feat(loader): forward title in ArrayLoader
ArrayLoader now reads 'title' from each tool config entry and
passes it into the Tool constructor, completing the wiring
from Builder::addTool() through array-driven registration.
The $tools @param array-shape typedef gains title: ?string
between name and description.
Phase 3 of plan 01--tool-title-spec-compliance.
* test(schema): cover Tool.title end-to-end
- New tests/Unit/Schema/ToolTest.php: constructor round-trip,
jsonSerialize key-order when title set vs null, fromArray
tolerance.
- New tests/Unit/Capability/Discovery/DiscovererToolTitleTest
plus a TitleFixture class verifying McpTool(title: ...)
propagates through discovery into the registered Tool.
- New tests/Unit/Capability/Registry/Loader/ArrayLoader...
test verifying array-driven title propagation.
- Extend McpToolTest for title named-arg and default-null.
- Extend BuilderTest with addTool(title: 't') propagation.
- Update tests/Unit/.../RegistryTest, CallToolHandlerTest,
ListToolsHandlerTest and examples/.../server.php
new Tool(...) call sites to pass title (named args) so
they match the new constructor signature.
- CHANGELOG 0.6.0 section: feature bullet + [BC Break]
note for addTool() signature, mirroring 0.5.0 Prompt
entries.
- docs/server-builder.md: addTool() row and prose now
list title? between name and description.
Phase 4 of plan 01--tool-title-spec-compliance. All 730
phpunit tests pass; remaining phpstan warnings are
pre-existing and unrelated to Tool.title.
* refactor(schema): make Tool.title non-BC-breaking
Move `title` to the end of the Tool, Builder::addTool, and McpTool
attribute constructors with a default of null. This preserves
positional-argument compatibility and removes the need for
`title: null` boilerplate at call sites. JSON serialization order
(name, title, inputSchema, ...) is unchanged.
Also tighten the new tests: parameterize ToolTest with data
providers, fold the Discoverer title fixture into the existing
DiscoverableToolHandler, and drop the reflection-heavy Builder
test that overlapped with ArrayLoaderToolTitleTest.
* fix: move title after name
---------
Co-authored-by: DDEV User <nobody@example.com>
* style: apply php-cs-fixer to test files
Addresses drift flagged by the default PHP-CS-Fixer config — mostly
adds missing `: void` return types on pre-existing test methods.
No behavioural changes.
* fix: resolve phpstan errors surfaced by qa
- Drop the obsolete `Method ::test*() has no return type specified`
ignore rule — every test method now declares a return type, so
the pattern never matches and phpstan reports it as unmatched.
- Remove `?? []` from two reads of `CallToolRequest::$arguments`;
the property is typed `array` (non-nullable), so the coalesce is
a no-op that phpstan flags.
---------
Co-authored-by: DDEV User <nobody@example.com>
- Add ElicitAction enum (accept/decline/cancel)
- Add schema definitions for elicitation fields (string, number, boolean, enum)
- Add ElicitationSchema wrapper for requestedSchema
- Add ElicitRequest and ElicitResult for elicitation/create method
- Update ClientCapabilities to support elicitation capability
- Add elicit() method to ClientGateway
- Store client capabilities in session during initialization
- Add comprehensive unit tests
- Add elicitation example server with book_restaurant, confirm_action, and collect_feedback tools