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
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:
- Attribute-Based Discovery
: Use PHP attributes (
#[McpTool],#[McpResource], etc.) on methods or classes. The server automatically discovers and registers them. - Manual Registration
: Explicitly register capabilities using
ServerBuildermethods (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.
Tools
Tools are callable functions that perform actions and return results.
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.description(optional): Tool description. Defaults to docblock summary if not provided, otherwise uses method name.annotations(optional):ToolAnnotationsobject for additional metadata.icons(optional): Array ofIconobjects 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.
Tool Return Values
Tools can return any data type and the SDK will automatically wrap them in appropriate MCP content types.
Automatic Content Wrapping
// 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:
use Mcp\Schema\Content\{TextContent, ImageContent, AudioContent, 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']
);
}
Multiple Content Items
Return an array of content items:
public function getMultipleContent(): array
{
return [
new TextContent('Here is the analysis:'),
TextContent::code($code, 'php'),
new TextContent('And here is the summary.')
];
}
Error Handling
Tool handlers can throw any exception, but the type determines how it's handled:
ToolCallException: Converted to JSON-RPC response withCallToolResultwhereisError: 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
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.
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.name(optional): Human-readable name. Defaults to method name if not provided.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 ofIconobjects 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
// 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
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
// 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
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.
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): Human-readable name. Defaults to method name if not provided.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
- Variable names must match exactly between URI template and method parameters
- Parameter order matters - variables are passed in the order they appear in the URI template
- All variables are required - no optional parameters supported
- 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.
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.description(optional): Prompt description. Defaults to docblock summary if not provided.icons(optional): Array ofIconobjects 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
// 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 questionsassistant: 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
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
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
.
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:
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:
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:
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 inListCompletionProvider - Enums
(
MyEnum::class) → Wrapped inEnumCompletionProvider
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:
#[Schema]attribute withdefinition- Complete schema override (highest priority)- Parameter-level
#[Schema]attribute - Parameter-specific enhancements - Method-level
#[Schema]attribute - Method-wide configuration - PHP type hints + docblocks - Automatic inference (lowest priority)
Automatic Schema from PHP Types
#[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:
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:
#[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:
#[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:
$server = Server::builder()
->setDiscovery(__DIR__, ['.']) // Automatic discovery
->build();
Manual Registration
Advantages:
- Fine-grained control
- Runtime configuration
- Conditional registration
- External handler support
Example:
$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.
Hybrid Approach
Combine both methods for maximum flexibility:
$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.