Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This package simplifies building MCP servers through:
* **Attribute-Based Definition:** Define MCP elements using PHP 8 Attributes (`#[McpTool]`, `#[McpResource]`, `#[McpPrompt]`, `#[McpResourceTemplate]`) on your methods or invokable classes.
* **Manual Registration:** Programmatically register elements using a fluent builder API.
* **Explicit Discovery:** Trigger attribute scanning on demand via the `$server->discover()` method.
* **Metadata Inference:** Intelligently generate MCP schemas and descriptions from type hints and DocBlocks.
* **Metadata Inference:** Intelligently generate MCP schemas and descriptions from PHP type hints and DocBlocks.
* **Selective Caching:** Optionally cache *discovered* element definitions to speed up startup, while always preserving manually registered elements.
* **Flexible Transports:** Supports `stdio` and `http+sse`, separating core logic from network communication.
* **PSR Compliance:** Integrates with PSR-3 (Logging), PSR-11 (Container), and PSR-16 (SimpleCache).
Expand Down Expand Up @@ -138,6 +138,8 @@ The server uses a decoupled architecture:
* **`Server`:** The central object holding the configured state and core logic components (`Registry`, `Processor`, `ClientStateManager`, `Configuration`). It's transport-agnostic. Provides methods to `discover()` elements and `listen()` via a specific transport.
* **`Registry`:** Stores MCP element definitions. **Distinguishes between manually registered and discovered elements.** Handles optional caching of *discovered* elements only. Loads cached discovered elements upon instantiation if available.
* **`Processor`:** Processes parsed JSON-RPC requests/notifications, executes handlers (via DI Container), formats results, handles errors.
* **`SchemaGenerator`:** Generates JSON Schema for method parameters from PHP type hints and DocBlocks.
* **`SchemaValidator`:** Validates incoming data against the generated JSON Schema.
* **`ClientStateManager`:** Manages client runtime state (initialization, subscriptions, activity) using the configured cache.
* **`ServerTransportInterface`:** Event-driven interface for server-side transports (`StdioServerTransport`, `HttpServerTransport`). Handles communication, emits events.
* **`Protocol`:** Internal bridge listening to transport events, interacting with `Processor` and `ClientStateManager`.
Expand Down Expand Up @@ -324,6 +326,16 @@ The value returned by your method determines the content sent back to the client

The method's return type hint (`@return` tag in DocBlock) is used to generate the tool's output schema, but the actual formatting depends on the *value* returned at runtime.

**Schema Generation**

The server automatically generates JSON Schema for tool parameters based on:

1. PHP type hints
2. DocBlock annotations
3. Schema attributes (for enhanced validation)

**Examples:**

```php
/**
* Fetches user details by ID.
Expand All @@ -338,6 +350,18 @@ public function getUserById(int $userId, bool $includeEmail = false): array
// ... implementation returning an array ...
}

/**
* Process user data with nested structures.
*
* @param array{name: string, contact: array{email: string, phone?: string}} $userData
* @param string[] $tags Tags associated with the user
* @return array{success: bool, message: string}
*/
#[McpTool]
public function processUserData(array $userData, array $tags): array {
// Implementation
}

/**
* Returns PHP code as formatted text.
*
Expand Down Expand Up @@ -366,6 +390,33 @@ class AdderTool {
}
```

**Additional Validation with `#[Schema]`**

For enhanced schema generation and parameter validation, you can use the `Schema` attribute:

```php
use PhpMcp\Server\Attributes\Schema;
use PhpMcp\Server\Attributes\Schema\Format;
use PhpMcp\Server\Attributes\Schema\ArrayItems;
use PhpMcp\Server\Attributes\Schema\Property;

/**
* Validates user information.
*/
#[McpTool]
public function validateUser(
#[Schema(format: 'email')]
string $email,

#[Schema(minItems: 2, uniqueItems: true)]
array $tags
): bool {
// Implementation
}
```

The Schema attribute adds JSON Schema constraints like string formats, numeric ranges, array constraints, and object property validations.

#### `#[McpResource]`

Marks a method **or an invokable class** as representing a specific, static MCP Resource instance. Resources represent pieces of content or data identified by a URI. The target method (or `__invoke`) will typically be called when a client performs a `resources/read` for the specified URI.
Expand Down
111 changes: 111 additions & 0 deletions src/Attributes/Schema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace PhpMcp\Server\Attributes;

use Attribute;
use PhpMcp\Server\Attributes\Schema\ArrayItems;
use PhpMcp\Server\Attributes\Schema\Property;

#[Attribute(Attribute::TARGET_PARAMETER)]
class Schema
{
/** @var Property[] */
protected array $properties = [];

/**
* @param string|null $format String format (email, date-time, uri, etc.)
* @param int|null $minLength Minimum string length
* @param int|null $maxLength Maximum string length
* @param string|null $pattern Regular expression pattern
* @param int|float|null $minimum Minimum numeric value
* @param int|float|null $maximum Maximum numeric value
* @param bool|null $exclusiveMinimum Whether minimum is exclusive
* @param bool|null $exclusiveMaximum Whether maximum is exclusive
* @param int|float|null $multipleOf Value must be multiple of this number
* @param ArrayItems|null $items Schema for array items
* @param int|null $minItems Minimum array items
* @param int|null $maxItems Maximum array items
* @param bool|null $uniqueItems Whether array items must be unique
* @param Property[] $properties Properties for object validation
* @param string[]|null $required Required properties for objects
* @param bool|Schema|null $additionalProperties Whether additional properties are allowed
* @param mixed|null $enum List of allowed values
* @param mixed|null $default Default value
*/
public function __construct(
public ?string $format = null,
public ?int $minLength = null,
public ?int $maxLength = null,
public ?string $pattern = null,
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,
public ?ArrayItems $items = null,
public ?int $minItems = null,
public ?int $maxItems = null,
public ?bool $uniqueItems = null,
array $properties = [],
public ?array $required = null,
public bool|Schema|null $additionalProperties = null,
public mixed $enum = null,
public mixed $default = null,
) {
$this->properties = $properties;
}

/**
* Convert to JSON Schema array
*/
public function toArray(): array
{
$schema = [];

// String constraints
if ($this->format !== null) $schema['format'] = $this->format;
if ($this->minLength !== null) $schema['minLength'] = $this->minLength;
if ($this->maxLength !== null) $schema['maxLength'] = $this->maxLength;
if ($this->pattern !== null) $schema['pattern'] = $this->pattern;

// Numeric constraints
if ($this->minimum !== null) $schema['minimum'] = $this->minimum;
if ($this->maximum !== null) $schema['maximum'] = $this->maximum;
if ($this->exclusiveMinimum !== null) $schema['exclusiveMinimum'] = $this->exclusiveMinimum;
if ($this->exclusiveMaximum !== null) $schema['exclusiveMaximum'] = $this->exclusiveMaximum;
if ($this->multipleOf !== null) $schema['multipleOf'] = $this->multipleOf;

// Array constraints
if ($this->items !== null) $schema['items'] = $this->items->toArray();
if ($this->minItems !== null) $schema['minItems'] = $this->minItems;
if ($this->maxItems !== null) $schema['maxItems'] = $this->maxItems;
if ($this->uniqueItems !== null) $schema['uniqueItems'] = $this->uniqueItems;

// Object constraints
if (!empty($this->properties)) {
$props = [];
foreach ($this->properties as $property) {
$props[$property->name] = $property->toArray();
}
$schema['properties'] = $props;
}

if ($this->required !== null) $schema['required'] = $this->required;

if ($this->additionalProperties !== null) {
if ($this->additionalProperties instanceof self) {
$schema['additionalProperties'] = $this->additionalProperties->toArray();
} else {
$schema['additionalProperties'] = $this->additionalProperties;
}
}

// General constraints
if ($this->enum !== null) $schema['enum'] = $this->enum;
if ($this->default !== null) $schema['default'] = $this->default;

return $schema;
}
}
58 changes: 58 additions & 0 deletions src/Attributes/Schema/ArrayItems.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace PhpMcp\Server\Attributes\Schema;

use PhpMcp\Server\Attributes\Schema;

/**
* Array item schema - extends Schema to inherit all schema properties
*/
class ArrayItems extends Schema
{
/**
* Creates a schema definition for array items
*/
public function __construct(
?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,
?ArrayItems $items = null,
?int $minItems = null,
?int $maxItems = null,
?bool $uniqueItems = null,
array $properties = [],
?array $required = null,
bool|Schema|null $additionalProperties = null,
mixed $enum = null,
mixed $default = null,
) {
parent::__construct(
format: $format,
minLength: $minLength,
maxLength: $maxLength,
pattern: $pattern,
minimum: $minimum,
maximum: $maximum,
exclusiveMinimum: $exclusiveMinimum,
exclusiveMaximum: $exclusiveMaximum,
multipleOf: $multipleOf,
items: $items,
minItems: $minItems,
maxItems: $maxItems,
uniqueItems: $uniqueItems,
properties: $properties,
required: $required,
additionalProperties: $additionalProperties,
enum: $enum,
default: $default,
);
}
}
32 changes: 32 additions & 0 deletions src/Attributes/Schema/Format.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace PhpMcp\Server\Attributes\Schema;

/**
* Common string formats supported by JSON Schema
*/
class Format
{
// String formats
public const DATE = 'date';
public const TIME = 'time';
public const DATE_TIME = 'date-time';
public const DURATION = 'duration';
public const EMAIL = 'email';
public const IDN_EMAIL = 'idn-email';
public const HOSTNAME = 'hostname';
public const IDN_HOSTNAME = 'idn-hostname';
public const IPV4 = 'ipv4';
public const IPV6 = 'ipv6';
public const URI = 'uri';
public const URI_REFERENCE = 'uri-reference';
public const IRI = 'iri';
public const IRI_REFERENCE = 'iri-reference';
public const URI_TEMPLATE = 'uri-template';
public const JSON_POINTER = 'json-pointer';
public const RELATIVE_JSON_POINTER = 'relative-json-pointer';
public const REGEX = 'regex';
public const UUID = 'uuid';
}
59 changes: 59 additions & 0 deletions src/Attributes/Schema/Property.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace PhpMcp\Server\Attributes\Schema;

use PhpMcp\Server\Attributes\Schema;

/**
* Property definition for object schemas
*/
class Property extends Schema
{
/**
* @param string $name Property name
*/
public function __construct(
public string $name,
?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,
?ArrayItems $items = null,
?int $minItems = null,
?int $maxItems = null,
?bool $uniqueItems = null,
array $properties = [],
?array $required = null,
bool|Schema|null $additionalProperties = null,
mixed $enum = null,
mixed $default = null,
) {
parent::__construct(
format: $format,
minLength: $minLength,
maxLength: $maxLength,
pattern: $pattern,
minimum: $minimum,
maximum: $maximum,
exclusiveMinimum: $exclusiveMinimum,
exclusiveMaximum: $exclusiveMaximum,
multipleOf: $multipleOf,
items: $items,
minItems: $minItems,
maxItems: $maxItems,
uniqueItems: $uniqueItems,
properties: $properties,
required: $required,
additionalProperties: $additionalProperties,
enum: $enum,
default: $default,
);
}
}
Loading