php-sdk/src/Schema/ServerCapabilities.php
Pavel Buchnev 99bf56eef8
Registry Architecture Refactoring - Enhanced Separation of Concerns (#46)
* refactor: Separate Registry concerns following SOLID principles [WIP]

- Extract ReferenceProvider and ReferenceRegistryInterface interfaces
- Create DefaultToolExecutor with ReferenceHandlerInterface
- Remove execution responsibility from Registry class
- Enable custom handler and executor implementations

* refactor: implement SOLID principles with separated execution concerns

* Create DefaultResourceReader, DefaultPromptGetter
* Refactor JsonRpc Handler and RequestHandlers to use dedicated executors
* Update ServerBuilder to support custom executors via dependency injection

* chore: remove extra comments

* feat: introduce DispatchableRegistry for enhanced tool and resource management

* refactor: use proper interfaces

* refactor: implement HandlerInterface for improved abstraction and flexibility

* test: add unit tests for DispatchableRegistry and Registry classes

* refactor: cover with unit tests Resource reader, Prompt getter and Tool executor

* cs fix

* phpstan fix

* test: add unit tests for CallToolHandler, GetPromptHandler, PingHandler, and ReadResourceHandler

* refactor: revert Handler

* chore: remove style guide for tests

* refactor: remove "Default" prefix from classes

* refactor: remove DispatchableRegistry

* refactor: use package specific exception classes

* refactor: remove DispatchableRegistry

* refactor: add logger support to PromptGetter and ResourceReader classes

* Update src/Capability/Prompt/PromptGetter.php

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

* refactor: Use FQN for Resource class to avoid cs-fixer misinterpreting it as `resource`

* refactor: rename ToolExecutor to ToolCaller and related classes for clarity and consistency

* refactor: add missed docblock

* refactor: rename ToolExecutor references to ToolCaller

* refactor: rename ToolCallerTest property and variable references

* cs fix

* ignore some phpstan errors

---------

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
2025-09-14 20:44:18 +02:00

164 lines
6.6 KiB
PHP

<?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\Schema;
/**
* Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed
* set: any server can define its own, additional capabilities.
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
class ServerCapabilities implements \JsonSerializable
{
/**
* @param ?bool $tools server exposes callable tools
* @param ?bool $toolsListChanged server supports list changed notifications for tools
* @param ?bool $resources server provides readable resources
* @param ?bool $resourcesSubscribe server supports subscribing to changes in the list of resources
* @param ?bool $resourcesListChanged server supports list changed notifications for resources
* @param ?bool $prompts server provides prompts templates
* @param ?bool $promptsListChanged server supports list changed notifications for prompts
* @param ?bool $logging server emits structured log messages
* @param ?bool $completions Server supports argument autocompletion
* @param ?array<string, mixed> $experimental experimental, non-standard features that the server supports
*/
public function __construct(
public readonly ?bool $tools = true,
public readonly ?bool $toolsListChanged = false,
public readonly ?bool $resources = true,
public readonly ?bool $resourcesSubscribe = false,
public readonly ?bool $resourcesListChanged = false,
public readonly ?bool $prompts = true,
public readonly ?bool $promptsListChanged = false,
public readonly ?bool $logging = false,
public readonly ?bool $completions = false,
public readonly ?array $experimental = null,
) {
}
/**
* @param array{
* logging?: mixed,
* completions?: mixed,
* prompts?: array{listChanged?: bool}|object,
* resources?: array{listChanged?: bool, subscribe?: bool}|object,
* tools?: object|array{listChanged?: bool},
* experimental?: array<string, mixed>,
* } $data
*/
public static function fromArray(array $data): self
{
$loggingEnabled = isset($data['logging']);
$completionsEnabled = isset($data['completions']);
$toolsEnabled = isset($data['tools']);
$promptsEnabled = isset($data['prompts']);
$resourcesEnabled = isset($data['resources']);
$promptsListChanged = null;
if (isset($data['prompts'])) {
if (\is_array($data['prompts']) && \array_key_exists('listChanged', $data['prompts'])) {
$promptsListChanged = (bool) $data['prompts']['listChanged'];
} elseif (\is_object($data['prompts']) && property_exists($data['prompts'], 'listChanged')) {
$promptsListChanged = (bool) $data['prompts']->listChanged;
}
}
$resourcesSubscribe = null;
$resourcesListChanged = null;
if (isset($data['resources'])) {
if (\is_array($data['resources']) && \array_key_exists('subscribe', $data['resources'])) {
$resourcesSubscribe = (bool) $data['resources']['subscribe'];
} elseif (\is_object($data['resources']) && property_exists($data['resources'], 'subscribe')) {
$resourcesSubscribe = (bool) $data['resources']->subscribe;
}
if (\is_array($data['resources']) && \array_key_exists('listChanged', $data['resources'])) {
$resourcesListChanged = (bool) $data['resources']['listChanged'];
} elseif (\is_object($data['resources']) && property_exists($data['resources'], 'listChanged')) {
$resourcesListChanged = (bool) $data['resources']->listChanged;
}
}
$toolsListChanged = null;
if (isset($data['tools'])) {
if (\is_array($data['tools']) && \array_key_exists('listChanged', $data['tools'])) {
$toolsListChanged = (bool) $data['tools']['listChanged'];
} elseif (\is_object($data['tools']) && property_exists($data['tools'], 'listChanged')) {
$toolsListChanged = (bool) $data['tools']->listChanged;
}
}
return new self(
tools: $toolsEnabled,
toolsListChanged: $toolsListChanged,
resources: $resourcesEnabled,
resourcesSubscribe: $resourcesSubscribe,
resourcesListChanged: $resourcesListChanged,
prompts: $promptsEnabled,
promptsListChanged: $promptsListChanged,
logging: $loggingEnabled,
completions: $completionsEnabled,
experimental: $data['experimental'] ?? null,
);
}
/**
* @return array{
* logging?: object,
* completions?: object,
* prompts?: object,
* resources?: object,
* tools?: object,
* experimental?: object,
* }
*/
public function jsonSerialize(): array
{
$data = [];
if ($this->logging) {
$data['logging'] = new \stdClass();
}
if ($this->completions) {
$data['completions'] = new \stdClass();
}
if ($this->prompts || $this->promptsListChanged) {
$data['prompts'] = new \stdClass();
if ($this->promptsListChanged) {
$data['prompts']->listChanged = $this->promptsListChanged;
}
}
if ($this->resources || $this->resourcesSubscribe || $this->resourcesListChanged) {
$data['resources'] = new \stdClass();
if ($this->resourcesSubscribe) {
$data['resources']->subscribe = $this->resourcesSubscribe;
}
if ($this->resourcesListChanged) {
$data['resources']->listChanged = $this->resourcesListChanged;
}
}
if ($this->tools || $this->toolsListChanged) {
$data['tools'] = new \stdClass();
if ($this->toolsListChanged) {
$data['tools']->listChanged = $this->toolsListChanged;
}
}
if ($this->experimental) {
$data['experimental'] = (object) $this->experimental;
}
return $data;
}
}