Introduce Capability Registry & Discovery

This commit is contained in:
Christopher Hertel 2025-08-09 00:36:43 +02:00
commit da0d5a87a0
No known key found for this signature in database
GPG key ID: 111F6BFF7B6B5AA3
6 changed files with 7063 additions and 8 deletions

View file

@ -0,0 +1,38 @@
<?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\Attribute;
use Mcp\Capability\Prompt\Completion\ProviderInterface;
use Mcp\Exception\InvalidArgumentException;
/**
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class CompletionProvider
{
/**
* @param class-string<ProviderInterface>|ProviderInterface|null $provider if a class-string, it will be resolved
* from the container at the point of use
* @param ?array<int, int|float|string> $values a list of values to use for completion
*/
public function __construct(
public ?string $providerClass = null,
public string|ProviderInterface|null $provider = null,
public ?array $values = null,
public ?string $enum = null,
) {
if (1 !== \count(array_filter([$provider, $values, $enum]))) {
throw new InvalidArgumentException('Only one of provider, values, or enum can be set.');
}
}
}

View file

@ -0,0 +1,32 @@
<?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\Attribute;
/**
* Marks a PHP method as an MCP Prompt generator.
* The method should return the prompt messages, potentially using arguments for templating.
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
final class McpPrompt
{
/**
* @param ?string $name overrides the prompt name (defaults to method name)
* @param ?string $description Optional description of the prompt. Defaults to method DocBlock summary.
*/
public function __construct(
public ?string $name = null,
public ?string $description = null,
) {
}
}

View file

@ -0,0 +1,42 @@
<?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\Attribute;
use Mcp\Schema\Annotations;
/**
* Marks a PHP class as representing or handling a specific MCP Resource instance.
* Used primarily for the 'resources/list' discovery.
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
final class McpResource
{
/**
* @param string $uri The specific URI identifying this resource instance. Must be unique within the server.
* @param ?string $name A human-readable name for this resource. If null, a default might be generated from the method name.
* @param ?string $description An optional description of the resource. Defaults to class DocBlock summary.
* @param ?string $mimeType the MIME type, if known and constant for this resource
* @param ?int $size the size in bytes, if known and constant
* @param Annotations|null $annotations optional annotations describing the resource
*/
public function __construct(
public string $uri,
public ?string $name = null,
public ?string $description = null,
public ?string $mimeType = null,
public ?int $size = null,
public ?Annotations $annotations = null,
) {
}
}

View file

@ -0,0 +1,40 @@
<?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\Attribute;
use Mcp\Schema\Annotations;
/**
* Marks a PHP class definition as representing an MCP Resource Template.
* This is informational, used for 'resources/templates/list'.
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
final class McpResourceTemplate
{
/**
* @param string $uriTemplate the URI template string (RFC 6570)
* @param ?string $name A human-readable name for the template type. If null, a default might be generated from the method name.
* @param ?string $description Optional description. Defaults to class DocBlock summary.
* @param ?string $mimeType optional default MIME type for matching resources
* @param ?Annotations $annotations optional annotations describing the resource template
*/
public function __construct(
public string $uriTemplate,
public ?string $name = null,
public ?string $description = null,
public ?string $mimeType = null,
public ?Annotations $annotations = null,
) {
}
}

View file

@ -0,0 +1,33 @@
<?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\Attribute;
use Mcp\Schema\ToolAnnotations;
/**
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
class McpTool
{
/**
* @param string|null $name The name of the tool (defaults to the method name)
* @param string|null $description The description of the tool (defaults to the DocBlock/inferred)
* @param ToolAnnotations|null $annotations Optional annotations describing tool behavior
*/
public function __construct(
public ?string $name = null,
public ?string $description = null,
public ?ToolAnnotations $annotations = null,
) {
}
}

View file

@ -0,0 +1,258 @@
<?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\Attribute;
/**
* Defines a JSON Schema for a method's input or an individual parameter.
*
* When used at the method level, it describes an object schema where properties
* correspond to the method's parameters.
*
* When used at the parameter level, it describes the schema for that specific parameter.
* If 'type' is omitted at the parameter level, it will be inferred.
*
* @phpstan-type SchemaAttributeData array{
* definition?: array<string, mixed>,
* type?: string,
* description?: string,
* enum?: array<int, float|string|int>,
* gormat?: string,
* minLength?: int,
* maxLength?: int,
* pattern?: string,
* minimum?: int,
* maximum?: int,
* exclusiveMinimum?: int,
* exclusiveMaximum?: int,
* multipleOf?: int|float,
* items?: array<string, mixed>,
* minItems?: int,
* maxItems?: int,
* uniqueItems?: bool,
* properties?: array<string, mixed>,
* required?: array<int, string>,
* additionalProperties?: bool|array<string, mixed>,
* }
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)]
class Schema
{
/**
* The complete JSON schema array.
* If provided, it takes precedence over individual properties like $type, $properties, etc.
*
* @var ?array<string, mixed>
*/
public ?array $definition = null;
/**
* Alternatively, provide individual top-level schema keywords.
* These are used if $definition is null.
*/
public ?string $type = null;
public ?string $description = null;
public mixed $default = null;
/**
* @var ?array<int, float|string|int>
*/
public ?array $enum = null; // list of allowed values
public ?string $format = null; // e.g., 'email', 'date-time'
// Constraints for string
public ?int $minLength = null;
public ?int $maxLength = null;
public ?string $pattern = null;
// Constraints for number/integer
public int|float|null $minimum = null;
public int|float|null $maximum = null;
public ?bool $exclusiveMinimum = null;
public ?bool $exclusiveMaximum = null;
public int|float|null $multipleOf = null;
// Constraints for array
/**
* @var ?array<string, mixed>
*/
public ?array $items = null; // JSON schema for array items
public ?int $minItems = null;
public ?int $maxItems = null;
public ?bool $uniqueItems = null;
// Constraints for object (primarily used when Schema is on a method or an object-typed parameter)
/**
* @var ?array<string, mixed>
*/
public ?array $properties = null; // [propertyName => [schema array], ...]
/**
* @var ?array<int, string>
*/
public ?array $required = null; // [propertyName, ...]
/**
* @var bool|array<string, mixed>|null
*/
public bool|array|null $additionalProperties = null; // true, false, or a schema array
/**
* @param ?array<string, mixed> $definition A complete JSON schema array. If provided, other parameters are ignored.
* @param ?string $type the JSON schema type
* @param ?string $description description of the element
* @param ?array<int, float|string|int> $enum allowed enum values
* @param ?string $format String format (e.g., 'date-time', 'email').
* @param ?int $minLength minimum length for strings
* @param ?int $maxLength maximum length for strings
* @param ?string $pattern regex pattern for strings
* @param int|float|null $minimum minimum value for numbers/integers
* @param int|float|null $maximum maximum value for numbers/integers
* @param ?bool $exclusiveMinimum exclusive minimum
* @param ?bool $exclusiveMaximum exclusive maximum
* @param int|float|null $multipleOf must be a multiple of this value
* @param ?array<string, mixed> $items JSON Schema for items if type is 'array'
* @param ?int $minItems minimum items for an array
* @param ?int $maxItems maximum items for an array
* @param ?bool $uniqueItems whether array items must be unique
* @param ?array<string, mixed> $properties Property definitions if type is 'object'. [name => schema_array].
* @param ?array<int, string> $required list of required properties for an object
* @param bool|array<string, mixed>|null $additionalProperties policy for additional properties in an object
*/
public function __construct(
?array $definition = null,
?string $type = null,
?string $description = null,
?array $enum = null,
?string $format = null,
?int $minLength = null,
?int $maxLength = null,
?string $pattern = null,
int|float|null $minimum = null,
int|float|null $maximum = null,
?bool $exclusiveMinimum = null,
?bool $exclusiveMaximum = null,
int|float|null $multipleOf = null,
?array $items = null,
?int $minItems = null,
?int $maxItems = null,
?bool $uniqueItems = null,
?array $properties = null,
?array $required = null,
bool|array|null $additionalProperties = null,
) {
if (null !== $definition) {
$this->definition = $definition;
} else {
$this->type = $type;
$this->description = $description;
$this->enum = $enum;
$this->format = $format;
$this->minLength = $minLength;
$this->maxLength = $maxLength;
$this->pattern = $pattern;
$this->minimum = $minimum;
$this->maximum = $maximum;
$this->exclusiveMinimum = $exclusiveMinimum;
$this->exclusiveMaximum = $exclusiveMaximum;
$this->multipleOf = $multipleOf;
$this->items = $items;
$this->minItems = $minItems;
$this->maxItems = $maxItems;
$this->uniqueItems = $uniqueItems;
$this->properties = $properties;
$this->required = $required;
$this->additionalProperties = $additionalProperties;
}
}
/**
* Converts the attribute's definition to a JSON schema array.
*
* @return SchemaAttributeData
*/
public function toArray(): array
{
if (null !== $this->definition) {
return [
'definition' => $this->definition,
];
}
$schema = [];
if (null !== $this->type) {
$schema['type'] = $this->type;
}
if (null !== $this->description) {
$schema['description'] = $this->description;
}
if (null !== $this->enum) {
$schema['enum'] = $this->enum;
}
if (null !== $this->format) {
$schema['format'] = $this->format;
}
// String
if (null !== $this->minLength) {
$schema['minLength'] = $this->minLength;
}
if (null !== $this->maxLength) {
$schema['maxLength'] = $this->maxLength;
}
if (null !== $this->pattern) {
$schema['pattern'] = $this->pattern;
}
// Numeric
if (null !== $this->minimum) {
$schema['minimum'] = $this->minimum;
}
if (null !== $this->maximum) {
$schema['maximum'] = $this->maximum;
}
if (null !== $this->exclusiveMinimum) {
$schema['exclusiveMinimum'] = $this->exclusiveMinimum;
}
if (null !== $this->exclusiveMaximum) {
$schema['exclusiveMaximum'] = $this->exclusiveMaximum;
}
if (null !== $this->multipleOf) {
$schema['multipleOf'] = $this->multipleOf;
}
// Array
if (null !== $this->items) {
$schema['items'] = $this->items;
}
if (null !== $this->minItems) {
$schema['minItems'] = $this->minItems;
}
if (null !== $this->maxItems) {
$schema['maxItems'] = $this->maxItems;
}
if (null !== $this->uniqueItems) {
$schema['uniqueItems'] = $this->uniqueItems;
}
// Object
if (null !== $this->properties) {
$schema['properties'] = $this->properties;
}
if (null !== $this->required) {
$schema['required'] = $this->required;
}
if (null !== $this->additionalProperties) {
$schema['additionalProperties'] = $this->additionalProperties;
}
return $schema;
}
}