* [Schema][Client][Server] Deprecate Roots, Sampling and Logging (SEP-2577) * Emit runtime deprecation notices and document the migration (SEP-2596)
892 lines
27 KiB
Markdown
892 lines
27 KiB
Markdown
# MCP Elements
|
|
|
|
MCP elements are the core capabilities of your server: Tools, Resources, Resource Templates, and Prompts. These elements
|
|
define what your server can do and how clients can interact with it. The PHP MCP SDK provides both attribute-based
|
|
discovery and manual registration methods.
|
|
|
|
## Table of Contents
|
|
|
|
- [Overview](#overview)
|
|
- [Tools](#tools)
|
|
- [Resources](#resources)
|
|
- [Resource Templates](#resource-templates)
|
|
- [Prompts](#prompts)
|
|
- [Logging](#logging)
|
|
- [Completion Providers](#completion-providers)
|
|
- [Schema Generation and Validation](#schema-generation-and-validation)
|
|
- [Discovery vs Manual Registration](#discovery-vs-manual-registration)
|
|
|
|
## Overview
|
|
|
|
MCP defines four types of capabilities:
|
|
|
|
- **Tools**: Functions that can be called by clients to perform actions
|
|
- **Resources**: Data sources that clients can read (static URIs)
|
|
- **Resource Templates**: URI templates for dynamic resources with variables
|
|
- **Prompts**: Template generators for AI prompts
|
|
|
|
### Registration Methods
|
|
|
|
Each capability can be registered using two methods:
|
|
|
|
1. **Attribute-Based Discovery**: Use PHP attributes (`#[McpTool]`, `#[McpResource]`, etc.) on methods or classes. The
|
|
server automatically discovers and registers them.
|
|
|
|
2. **Manual Registration**: Explicitly register capabilities using `ServerBuilder` methods (`addTool()`, `addResource()`, etc.).
|
|
|
|
**Priority**: Manual registrations **always override** discovered elements with the same identifier:
|
|
- **Tools**: Same `name`
|
|
- **Resources**: Same `uri`
|
|
- **Resource Templates**: Same `uriTemplate`
|
|
- **Prompts**: Same `name`
|
|
|
|
For manual registration details, see [Server Builder Manual Registration](server-builder.md#manual-capability-registration).
|
|
|
|
For runtime, config-driven elements whose shape is not known at compile time, see
|
|
[Explicit element registration](server-builder.md#explicit-element-registration) in the Server Builder docs.
|
|
|
|
## Tools
|
|
|
|
Tools are callable functions that perform actions and return results.
|
|
|
|
```php
|
|
use Mcp\Capability\Attribute\McpTool;
|
|
|
|
class Calculator
|
|
{
|
|
/**
|
|
* Performs arithmetic operations with validation.
|
|
*/
|
|
#[McpTool(name: 'calculate')]
|
|
public function performCalculation(float $a, float $b, string $operation): float
|
|
{
|
|
return match($operation) {
|
|
'add' => $a + $b,
|
|
'subtract' => $a - $b,
|
|
'multiply' => $a * $b,
|
|
'divide' => $b != 0 ? $a / $b : throw new \InvalidArgumentException('Division by zero'),
|
|
default => throw new \InvalidArgumentException('Invalid operation')
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### Parameters
|
|
|
|
- **`name`** (optional): Tool identifier. Defaults to method name if not provided.
|
|
- **`title`** (optional): Human-readable display title shown in client UI. Distinct from `name`.
|
|
- **`description`** (optional): Tool description. Defaults to docblock summary if not provided, otherwise uses method name.
|
|
- **`annotations`** (optional): `ToolAnnotations` object for additional metadata.
|
|
- **`icons`** (optional): Array of `Icon` objects for visual representation.
|
|
- **`meta`** (optional): Arbitrary key-value pairs for custom metadata.
|
|
|
|
**Priority for name/description**: Attribute parameters → DocBlock content → Method name
|
|
|
|
For tool parameter validation and JSON schema generation, see [Schema Generation and Validation](#schema-generation-and-validation).
|
|
|
|
### Tool Return Values
|
|
|
|
Tools can return any data type and the SDK will automatically wrap them in appropriate MCP content types.
|
|
|
|
#### Automatic Content Wrapping
|
|
|
|
```php
|
|
// Primitive types → TextContent
|
|
public function getString(): string { return "Hello"; } // TextContent
|
|
public function getNumber(): int { return 42; } // TextContent
|
|
public function getBool(): bool { return true; } // TextContent
|
|
public function getArray(): array { return ['key' => 'value']; } // TextContent (JSON)
|
|
|
|
// Special cases
|
|
public function getNull(): ?string { return null; } // TextContent("(null)")
|
|
public function returnVoid(): void { /* no return */ } // Empty content
|
|
```
|
|
|
|
#### Explicit Content Types
|
|
|
|
For fine control over output formatting:
|
|
|
|
```php
|
|
use Mcp\Schema\Content\{TextContent, ImageContent, AudioContent, ResourceLink, EmbeddedResource};
|
|
|
|
public function getFormattedCode(): TextContent
|
|
{
|
|
return TextContent::code('<?php echo "Hello";', 'php');
|
|
}
|
|
|
|
public function getMarkdown(): TextContent
|
|
{
|
|
return new TextContent('# Title\n\nMarkdown content');
|
|
}
|
|
|
|
public function getImage(): ImageContent
|
|
{
|
|
return new ImageContent(
|
|
data: base64_encode(file_get_contents('image.png')),
|
|
mimeType: 'image/png'
|
|
);
|
|
}
|
|
|
|
public function getAudio(): AudioContent
|
|
{
|
|
return new AudioContent(
|
|
data: base64_encode(file_get_contents('audio.mp3')),
|
|
mimeType: 'audio/mpeg'
|
|
);
|
|
}
|
|
|
|
public function getEmbeddedResource(): EmbeddedResource
|
|
{
|
|
return new EmbeddedResource(
|
|
type: 'resource',
|
|
resource: ['uri' => 'file://data.json', 'text' => 'File content']
|
|
);
|
|
}
|
|
|
|
public function getResourceLink(): ResourceLink
|
|
{
|
|
// Reference a resource by URI without embedding its contents, e.g. when
|
|
// a tool result would otherwise need to inline many or large resources.
|
|
return new ResourceLink(
|
|
uri: 'file://data.json',
|
|
name: 'data.json',
|
|
mimeType: 'application/json'
|
|
);
|
|
}
|
|
```
|
|
|
|
#### Multiple Content Items
|
|
|
|
Return an array of content items:
|
|
|
|
```php
|
|
public function getMultipleContent(): array
|
|
{
|
|
return [
|
|
new TextContent('Here is the analysis:'),
|
|
TextContent::code($code, 'php'),
|
|
new TextContent('And here is the summary.')
|
|
];
|
|
}
|
|
```
|
|
|
|
#### Structured Output
|
|
|
|
Besides the human-readable `content`, a tool result can carry a machine-readable `structuredContent` value. Declare its
|
|
shape with `outputSchema`, a JSON Schema of type `object`:
|
|
|
|
```php
|
|
#[McpTool(
|
|
name: 'get_weather',
|
|
outputSchema: [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'temperature' => ['type' => 'number'],
|
|
'conditions' => ['type' => 'string'],
|
|
],
|
|
'required' => ['temperature', 'conditions'],
|
|
]
|
|
)]
|
|
public function getWeather(string $city): array
|
|
{
|
|
// Sent as `structuredContent`, and JSON-encoded into `content` for clients that ignore it
|
|
return ['temperature' => 22.5, 'conditions' => 'sunny'];
|
|
}
|
|
```
|
|
|
|
The same schema can be passed to manual registration:
|
|
|
|
```php
|
|
$builder->addTool([WeatherHandler::class, 'getWeather'], outputSchema: [/* ... */]);
|
|
```
|
|
|
|
The SDK fills `structuredContent` whenever the return value qualifies — `outputSchema` is what tells clients to expect it
|
|
and lets them validate it. What qualifies depends on the protocol revision the call is served under:
|
|
|
|
| Return value | `structuredContent` |
|
|
|---|---|
|
|
| Associative array (`['temperature' => 22.5]`) | The array |
|
|
| Object (`stdClass`, DTO, `JsonSerializable`) that serializes to a JSON object | Its JSON representation |
|
|
| List (`[1, 2, 3]`, `[['id' => 1], ['id' => 2]]`), or an object serializing to one | Omitted before `2026-07-28`, kept from it on |
|
|
| Array holding `Content` instances | Omitted (already carried in `content`) |
|
|
| Scalars, `null`, `Content` instances | Omitted |
|
|
|
|
Up to revision `2025-11-25`, `structuredContent` had to be a JSON object, so a PHP list — which serializes to a JSON
|
|
array — was not emittable and strict clients rejected the whole tool call over one. [SEP-2106][sep-2106], part of
|
|
revision `2026-07-28`, widened `outputSchema` to any JSON Schema 2020-12 and `structuredContent` to any JSON value
|
|
conforming to it. The SDK picks the rule from the revision negotiated for the call, so a tool serving both eras needs the
|
|
object shape to produce structured output everywhere. Wrap the list in a key for that:
|
|
|
|
```php
|
|
// Structured content only from 2026-07-28 on: a bare list is not a JSON object
|
|
public function listUsersFlat(): array
|
|
{
|
|
return [['id' => 1], ['id' => 2]];
|
|
}
|
|
|
|
#[McpTool(outputSchema: [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'items' => ['type' => 'array', 'items' => ['type' => 'object']],
|
|
],
|
|
'required' => ['items']
|
|
])]
|
|
public function listUsers(): array
|
|
{
|
|
return ['items' => [['id' => 1], ['id' => 2]]];
|
|
}
|
|
```
|
|
|
|
Either way the data reaches the client: a return value with no structured representation is still JSON-encoded into
|
|
`content` as a `TextContent`. When a tool declares an `outputSchema` but returns something that cannot be sent as
|
|
`structuredContent`, the SDK logs a warning — the value is not silently dropped.
|
|
|
|
A tool that wants to branch on the revision itself can read it from the injected `RequestContext`, see
|
|
[Client Communication](server-client-communication.md#client-gateway).
|
|
|
|
[sep-2106]: https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content
|
|
|
|
#### Error Handling
|
|
|
|
Tool handlers can throw any exception, but the type determines how it's handled:
|
|
|
|
- **`ToolCallException`**: Converted to JSON-RPC response with `CallToolResult` where `isError: true`, allowing the LLM to see the error message and self-correct
|
|
- **Any other exception**: Converted to JSON-RPC error response, but with a generic error message
|
|
|
|
```php
|
|
use Mcp\Exception\ToolCallException;
|
|
|
|
#[McpTool]
|
|
public function divideNumbers(float $a, float $b): float
|
|
{
|
|
if ($b === 0.0) {
|
|
throw new ToolCallException('Division by zero is not allowed');
|
|
}
|
|
|
|
return $a / $b;
|
|
}
|
|
|
|
#[McpTool]
|
|
public function processFile(string $filename): string
|
|
{
|
|
if (!file_exists($filename)) {
|
|
throw new ToolCallException("File not found: {$filename}");
|
|
}
|
|
|
|
return file_get_contents($filename);
|
|
}
|
|
```
|
|
|
|
**Recommendation**: Use `ToolCallException` when you want to communicate specific errors to clients. Any other exception will still be converted to JSON-RPC compliant errors but with generic error messages.
|
|
|
|
|
|
## Resources
|
|
|
|
Resources provide access to static data that clients can read.
|
|
|
|
```php
|
|
use Mcp\Capability\Attribute\McpResource;
|
|
|
|
class ConfigProvider
|
|
{
|
|
/**
|
|
* Provides the current application configuration.
|
|
*/
|
|
#[McpResource(uri: 'config://app/settings', name: 'app_settings')]
|
|
public function getSettings(): array
|
|
{
|
|
return [
|
|
'version' => '1.0.0',
|
|
'debug' => false,
|
|
'features' => ['auth', 'logging']
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### Parameters
|
|
|
|
- **`uri`** (required): Unique resource identifier. Must comply with [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986).
|
|
- **`name`** (optional): Short resource identifier. Defaults to method name if not provided.
|
|
- **`title`** (optional): Human-readable display title shown in client UI. Distinct from `name`.
|
|
- **`description`** (optional): Resource description. Defaults to docblock summary if not provided.
|
|
- **`mimeType`** (optional): MIME type of the resource content.
|
|
- **`size`** (optional): Size in bytes if known.
|
|
- **`annotations`** (optional): Additional metadata.
|
|
- **`icons`** (optional): Array of `Icon` objects for visual representation.
|
|
- **`meta`** (optional): Arbitrary key-value pairs for custom metadata.
|
|
|
|
**Standard Protocol URI Schemes**: `https://` (web resources), `file://` (filesystem), `git://` (version control).
|
|
**Custom schemes**: `config://`, `data://`, `db://`, `api://` or any RFC 3986 compliant scheme.
|
|
|
|
### Resource Return Values
|
|
|
|
Resource handlers can return various data types that are automatically formatted into appropriate MCP resource content types.
|
|
|
|
#### Supported Return Types
|
|
|
|
```php
|
|
// String content - converted to text resource
|
|
public function getTextFile(): string
|
|
{
|
|
return "File content here";
|
|
}
|
|
|
|
// Array content - converted to JSON
|
|
public function getConfig(): array
|
|
{
|
|
return ['debug' => true, 'version' => '1.0'];
|
|
}
|
|
|
|
// Stream resource - read and converted to blob
|
|
public function getImageStream(): resource
|
|
{
|
|
return fopen('image.png', 'r');
|
|
}
|
|
|
|
// SplFileInfo - file content with MIME type detection
|
|
public function getFileInfo(): \SplFileInfo
|
|
{
|
|
return new \SplFileInfo('document.pdf');
|
|
}
|
|
```
|
|
|
|
**Explicit resource content types**
|
|
|
|
```php
|
|
use Mcp\Schema\Content\{TextResourceContents, BlobResourceContents};
|
|
|
|
public function getExplicitText(): TextResourceContents
|
|
{
|
|
return new TextResourceContents(
|
|
uri: 'config://app/settings',
|
|
mimeType: 'application/json',
|
|
text: json_encode(['setting' => 'value'])
|
|
);
|
|
}
|
|
|
|
public function getExplicitBlob(): BlobResourceContents
|
|
{
|
|
return new BlobResourceContents(
|
|
uri: 'file://image.png',
|
|
mimeType: 'image/png',
|
|
blob: base64_encode(file_get_contents('image.png'))
|
|
);
|
|
}
|
|
```
|
|
|
|
**Special Array Formats**
|
|
|
|
```php
|
|
// Array with 'text' key - used as text content
|
|
public function getTextArray(): array
|
|
{
|
|
return ['text' => 'Content here', 'mimeType' => 'text/plain'];
|
|
}
|
|
|
|
// Array with 'blob' key - used as blob content
|
|
public function getBlobArray(): array
|
|
{
|
|
return ['blob' => base64_encode($data), 'mimeType' => 'image/png'];
|
|
}
|
|
|
|
// Multiple resource contents
|
|
public function getMultipleResources(): array
|
|
{
|
|
return [
|
|
new TextResourceContents('file://readme.txt', 'text/plain', 'README content'),
|
|
new TextResourceContents('file://config.json', 'application/json', '{"key": "value"}')
|
|
];
|
|
}
|
|
```
|
|
|
|
#### Error Handling
|
|
|
|
Resource handlers can throw any exception, but the type determines how it's handled:
|
|
|
|
- **`ResourceReadException`**: Converted to JSON-RPC error response with the actual exception message
|
|
- **Any other exception**: Converted to JSON-RPC error response, but with a generic error message
|
|
|
|
```php
|
|
use Mcp\Exception\ResourceReadException;
|
|
|
|
#[McpResource(uri: 'file://{path}')]
|
|
public function getFile(string $path): string
|
|
{
|
|
if (!file_exists($path)) {
|
|
throw new ResourceReadException("File not found: {$path}");
|
|
}
|
|
|
|
if (!is_readable($path)) {
|
|
throw new ResourceReadException("File not readable: {$path}");
|
|
}
|
|
|
|
return file_get_contents($path);
|
|
}
|
|
```
|
|
|
|
**Recommendation**: Use `ResourceReadException` when you want to communicate specific errors to clients. Any other exception will still be converted to JSON-RPC compliant errors but with generic error messages.
|
|
|
|
## Resource Templates
|
|
|
|
Resource templates are **dynamic resources** that use parameterized URIs with variables. They follow all the same rules
|
|
as static resources (URI schemas, return values, MIME types, etc.) but accept variables using [RFC 6570 URI template syntax](https://datatracker.ietf.org/doc/html/rfc6570).
|
|
|
|
```php
|
|
use Mcp\Capability\Attribute\McpResourceTemplate;
|
|
|
|
class UserProvider
|
|
{
|
|
/**
|
|
* Retrieves user profile information by ID.
|
|
*/
|
|
#[McpResourceTemplate(
|
|
uriTemplate: 'user://{userId}/profile/{section}',
|
|
name: 'user_profile',
|
|
description: 'User profile data by section',
|
|
mimeType: 'application/json'
|
|
)]
|
|
public function getUserProfile(string $userId, string $section): array
|
|
{
|
|
return $this->users[$userId][$section] ?? throw new \InvalidArgumentException("Profile section not found");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Parameters
|
|
|
|
- **`uriTemplate`** (required): URI template with `{variables}` using RFC 6570 syntax. Must comply with RFC 3986.
|
|
- **`name`** (optional): Short resource template identifier. Defaults to method name if not provided.
|
|
- **`title`** (optional): Human-readable display title shown in client UI. Distinct from `name`.
|
|
- **`description`** (optional): Template description. Defaults to docblock summary if not provided.
|
|
- **`mimeType`** (optional): MIME type of the resource content.
|
|
- **`annotations`** (optional): Additional metadata.
|
|
|
|
### Variable Rules
|
|
|
|
1. **Variable names must match exactly** between URI template and method parameters
|
|
2. **Parameter order matters** - variables are passed in the order they appear in the URI template
|
|
3. **All variables are required** - no optional parameters supported
|
|
4. **Type hints work normally** - parameters can be typed (string, int, etc.)
|
|
|
|
**Example mapping**: `user://123/profile/settings` → `getUserProfile("123", "settings")`
|
|
|
|
## Prompts
|
|
|
|
Prompts generate templates for AI interactions.
|
|
|
|
```php
|
|
use Mcp\Capability\Attribute\McpPrompt;
|
|
|
|
class PromptGenerator
|
|
{
|
|
/**
|
|
* Generates a code review request prompt.
|
|
*/
|
|
#[McpPrompt(name: 'code_review')]
|
|
public function reviewCode(string $language, string $code, string $focus = 'general'): array
|
|
{
|
|
return [
|
|
['role' => 'system', 'content' => 'You are an expert code reviewer.'],
|
|
['role' => 'user', 'content' => "Review this {$language} code focusing on {$focus}:\n\n```{$language}\n{$code}\n```"]
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### Parameters
|
|
|
|
- **`name`** (optional): Prompt identifier. Defaults to method name if not provided.
|
|
- **`title`** (optional): Human-readable display title shown in client UI. Distinct from `name`.
|
|
- **`description`** (optional): Prompt description. Defaults to docblock summary if not provided.
|
|
- **`icons`** (optional): Array of `Icon` objects for visual representation.
|
|
- **`meta`** (optional): Arbitrary key-value pairs for custom metadata.
|
|
|
|
### Prompt Return Values
|
|
|
|
Prompt handlers must return an array of message structures that are automatically formatted into MCP prompt messages.
|
|
|
|
#### Supported Return Formats
|
|
|
|
```php
|
|
// Array of message objects with role and content
|
|
public function basicPrompt(): array
|
|
{
|
|
return [
|
|
['role' => 'assistant', 'content' => 'You are a helpful assistant'],
|
|
['role' => 'user', 'content' => 'Hello, how are you?']
|
|
];
|
|
}
|
|
|
|
// Single message (automatically wrapped in array)
|
|
public function singleMessage(): array
|
|
{
|
|
return [
|
|
['role' => 'user', 'content' => 'Write a poem about PHP']
|
|
];
|
|
}
|
|
|
|
// Associative array with user/assistant keys
|
|
public function userAssistantFormat(): array
|
|
{
|
|
return [
|
|
'user' => 'Explain how arrays work in PHP',
|
|
'assistant' => 'Arrays in PHP are ordered maps...'
|
|
];
|
|
}
|
|
|
|
// Mixed content types in messages
|
|
use Mcp\Schema\Content\{TextContent, ImageContent};
|
|
|
|
public function mixedContent(): array
|
|
{
|
|
return [
|
|
[
|
|
'role' => 'user',
|
|
'content' => [
|
|
new TextContent('Analyze this image:'),
|
|
new ImageContent(data: $imageData, mimeType: 'image/png')
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
// Using explicit PromptMessage objects
|
|
use Mcp\Schema\PromptMessage;
|
|
use Mcp\Schema\Enum\Role;
|
|
|
|
public function explicitMessages(): array
|
|
{
|
|
return [
|
|
new PromptMessage(Role::Assistant, [new TextContent('System instructions')]),
|
|
new PromptMessage(Role::User, [new TextContent('User question')])
|
|
];
|
|
}
|
|
```
|
|
|
|
The SDK automatically validates that all messages have valid roles and converts the result into the appropriate MCP prompt message format.
|
|
|
|
#### Valid Message Roles
|
|
|
|
- **`user`**: User input or questions
|
|
- **`assistant`**: Assistant responses/system
|
|
|
|
#### Error Handling
|
|
|
|
Prompt handlers can throw any exception, but the type determines how it's handled:
|
|
- **`PromptGetException`**: Converted to JSON-RPC error response with the actual exception message
|
|
- **Any other exception**: Converted to JSON-RPC error response, but with a generic error message
|
|
|
|
```php
|
|
use Mcp\Exception\PromptGetException;
|
|
|
|
#[McpPrompt]
|
|
public function generatePrompt(string $topic, string $style): array
|
|
{
|
|
$validStyles = ['casual', 'formal', 'technical'];
|
|
|
|
if (!in_array($style, $validStyles)) {
|
|
throw new PromptGetException(
|
|
"Invalid style '{$style}'. Must be one of: " . implode(', ', $validStyles)
|
|
);
|
|
}
|
|
|
|
return [
|
|
['role' => 'user', 'content' => "Write about {$topic} in a {$style} style"]
|
|
];
|
|
}
|
|
```
|
|
|
|
**Recommendation**: Use `PromptGetException` when you want to communicate specific errors to clients. Any other exception will still be converted to JSON-RPC compliant errors but with generic error messages.
|
|
|
|
## Logging
|
|
|
|
> **Deprecated** since protocol revision `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), earliest removal `2027-07-28`. Logging keeps working until then; new integrations should log to stderr (stdio) or use OpenTelemetry instead.
|
|
|
|
The SDK provides support to send structured log messages to clients. All standard PSR-3 log levels are supported.
|
|
Level **warning** as the default level.
|
|
|
|
### Usage
|
|
|
|
The SDK automatically injects a `RequestContext` instance into handlers. This can be used to create a `ClientLogger`.
|
|
|
|
```php
|
|
use Mcp\Capability\Logger\ClientLogger;
|
|
use Mcp\Server\RequestContext;
|
|
|
|
#[McpTool]
|
|
public function processData(string $input, RequestContext $context): array {
|
|
$logger = $context->getClientLogger();
|
|
|
|
$logger->info('Processing started', ['input' => $input]);
|
|
$logger->warning('Deprecated API used');
|
|
|
|
// ... processing logic ...
|
|
|
|
$logger->info('Processing completed');
|
|
return ['result' => 'processed'];
|
|
}
|
|
```
|
|
|
|
## Completion Providers
|
|
|
|
Completion providers help MCP clients offer auto-completion suggestions for Resource Templates and Prompts. Unlike Tools and static Resources (which can be listed via `tools/list` and `resources/list`), Resource Templates and Prompts have dynamic parameters that benefit from completion hints.
|
|
|
|
### Completion Provider Types
|
|
|
|
#### 1. Value Lists
|
|
|
|
Provide a static list of possible values:
|
|
|
|
```php
|
|
use Mcp\Capability\Attribute\CompletionProvider;
|
|
|
|
#[McpPrompt]
|
|
public function generateContent(
|
|
#[CompletionProvider(values: ['blog', 'article', 'tutorial', 'guide'])]
|
|
string $contentType,
|
|
|
|
#[CompletionProvider(values: ['beginner', 'intermediate', 'advanced'])]
|
|
string $difficulty
|
|
): array
|
|
{
|
|
return [
|
|
['role' => 'user', 'content' => "Create a {$difficulty} level {$contentType}"]
|
|
];
|
|
}
|
|
```
|
|
|
|
#### 2. Enum Classes
|
|
|
|
Use enum values for completion:
|
|
|
|
```php
|
|
enum Priority: string
|
|
{
|
|
case LOW = 'low';
|
|
case MEDIUM = 'medium';
|
|
case HIGH = 'high';
|
|
}
|
|
|
|
enum Status // Unit enum
|
|
{
|
|
case DRAFT;
|
|
case PUBLISHED;
|
|
case ARCHIVED;
|
|
}
|
|
|
|
#[McpResourceTemplate(uriTemplate: 'tasks/{taskId}')]
|
|
public function getTask(
|
|
string $taskId,
|
|
|
|
#[CompletionProvider(enum: Priority::class)] // Uses backing values
|
|
string $priority,
|
|
|
|
#[CompletionProvider(enum: Status::class)] // Uses case names
|
|
string $status
|
|
): array
|
|
{
|
|
// Implementation
|
|
}
|
|
```
|
|
|
|
#### 3. Custom Provider Classes
|
|
|
|
For dynamic completion logic:
|
|
|
|
```php
|
|
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
|
|
|
class UserIdCompletionProvider implements ProviderInterface
|
|
{
|
|
public function __construct(private DatabaseService $db) {}
|
|
|
|
public function getCompletions(string $currentValue): array
|
|
{
|
|
// Return dynamic completions based on current input
|
|
return $this->db->searchUserIds($currentValue);
|
|
}
|
|
}
|
|
|
|
#[McpResourceTemplate(uriTemplate: 'user://{userId}/profile')]
|
|
public function getUserProfile(
|
|
#[CompletionProvider(provider: UserIdCompletionProvider::class)]
|
|
string $userId
|
|
): array
|
|
{
|
|
// Implementation
|
|
}
|
|
```
|
|
|
|
**Provider Resolution:**
|
|
- **Class strings** (`Provider::class`) → Resolved from PSR-11 container
|
|
- **Instances** (`new Provider()`) → Used directly
|
|
- **Values** (`['a', 'b']`) → Wrapped in `ListCompletionProvider`
|
|
- **Enums** (`MyEnum::class`) → Wrapped in `EnumCompletionProvider`
|
|
|
|
> **Important**
|
|
>
|
|
> Completion providers only offer **suggestions** to users. Users can still input any value, so **always validate
|
|
> parameters** in your handlers. Providers don't enforce validation - they're purely for UX improvement.
|
|
|
|
## Schema Generation and Validation
|
|
|
|
The SDK automatically generates JSON schemas for **tool parameters** using a sophisticated priority system. Schema
|
|
generation applies to both attribute-discovered and manually registered tools.
|
|
|
|
### Schema Generation Priority
|
|
|
|
The server follows this order of precedence:
|
|
|
|
1. **`#[Schema]` attribute with `definition`** - Complete schema override (highest priority)
|
|
2. **Parameter-level `#[Schema]` attribute** - Parameter-specific enhancements
|
|
3. **Method-level `#[Schema]` attribute** - Method-wide configuration
|
|
4. **PHP type hints + docblocks** - Automatic inference (lowest priority)
|
|
|
|
### Automatic Schema from PHP Types
|
|
|
|
```php
|
|
#[McpTool]
|
|
public function processUser(
|
|
string $email, // Required string
|
|
int $age, // Required integer
|
|
?string $name = null, // Optional string
|
|
bool $active = true // Boolean with default
|
|
): array
|
|
{
|
|
// Schema auto-generated from method signature
|
|
}
|
|
```
|
|
|
|
### Parameter-Level Schema Enhancement
|
|
|
|
Add validation rules to specific parameters:
|
|
|
|
```php
|
|
use Mcp\Capability\Attribute\Schema;
|
|
|
|
#[McpTool]
|
|
public function validateUser(
|
|
#[Schema(format: 'email')]
|
|
string $email,
|
|
|
|
#[Schema(minimum: 18, maximum: 120)]
|
|
int $age,
|
|
|
|
#[Schema(
|
|
pattern: '^[A-Z][a-z]+$',
|
|
description: 'Capitalized first name'
|
|
)]
|
|
string $firstName
|
|
): bool
|
|
{
|
|
// PHP types provide base validation
|
|
// Schema attributes add constraints
|
|
}
|
|
```
|
|
|
|
### Method-Level Schema
|
|
|
|
Add validation for complex object structures:
|
|
|
|
```php
|
|
#[McpTool]
|
|
#[Schema(
|
|
properties: [
|
|
'userData' => [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'name' => ['type' => 'string', 'minLength' => 2],
|
|
'email' => ['type' => 'string', 'format' => 'email'],
|
|
'age' => ['type' => 'integer', 'minimum' => 18]
|
|
],
|
|
'required' => ['name', 'email']
|
|
]
|
|
],
|
|
required: ['userData']
|
|
)]
|
|
public function createUser(array $userData): array
|
|
{
|
|
// Method-level schema adds object structure validation
|
|
// PHP array type provides base type
|
|
}
|
|
```
|
|
|
|
### Complete Schema Override
|
|
|
|
**Use sparingly** - bypasses all automatic inference:
|
|
|
|
```php
|
|
#[McpTool]
|
|
#[Schema(definition: [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'endpoint' => ['type' => 'string', 'format' => 'uri'],
|
|
'method' => ['type' => 'string', 'enum' => ['GET', 'POST', 'PUT', 'DELETE']],
|
|
'headers' => [
|
|
'type' => 'object',
|
|
'patternProperties' => [
|
|
'^[A-Za-z0-9-]+$' => ['type' => 'string']
|
|
]
|
|
]
|
|
],
|
|
'required' => ['endpoint', 'method']
|
|
])]
|
|
public function makeApiRequest(string $endpoint, string $method, array $headers): array
|
|
{
|
|
// Complete definition override - PHP types ignored
|
|
}
|
|
```
|
|
|
|
**Warning:** Only use complete schema override if you're well-versed with JSON Schema specification and have complex
|
|
validation requirements that cannot be achieved through the priority system.
|
|
|
|
## Discovery vs Manual Registration
|
|
|
|
### Attribute-Based Discovery
|
|
|
|
**Advantages:**
|
|
- Declarative and readable
|
|
- Automatic parameter inference
|
|
- DocBlock integration
|
|
- Type-safe by default
|
|
- Caching support
|
|
|
|
**Example:**
|
|
```php
|
|
$server = Server::builder()
|
|
->setDiscovery(__DIR__, ['.']) // Automatic discovery
|
|
->build();
|
|
```
|
|
|
|
### Manual Registration
|
|
|
|
**Advantages:**
|
|
- Fine-grained control
|
|
- Runtime configuration
|
|
- Conditional registration
|
|
- External handler support
|
|
|
|
**Example:**
|
|
```php
|
|
$server = Server::builder()
|
|
->addTool([Calculator::class, 'add'], 'add_numbers')
|
|
->addResource([Config::class, 'get'], 'config://app')
|
|
->addPrompt([Prompts::class, 'email'], 'write_email')
|
|
->build();
|
|
```
|
|
|
|
For detailed information on manual registration, see [Server Builder](server-builder.md#manual-capability-registration).
|
|
|
|
### Hybrid Approach
|
|
|
|
Combine both methods for maximum flexibility:
|
|
|
|
```php
|
|
$server = Server::builder()
|
|
->setDiscovery(__DIR__, ['.']) // Discover most capabilities
|
|
->addTool([ExternalService::class, 'process'], 'external') // Add specific ones
|
|
->build();
|
|
```
|
|
|
|
Manual registrations always take precedence over discovered elements with the same identifier.
|