Skip to content

Latest commit

 

History

History
247 lines (196 loc) · 8.8 KB

File metadata and controls

247 lines (196 loc) · 8.8 KB

Registering elements

Every tool, resource, resource template, and prompt has to reach the server's registry somehow. There are three ways to get it there, and they mix freely.

Attribute-Based Discovery

Advantages:

  • Declarative and readable
  • Automatic parameter inference
  • DocBlock integration
  • Type-safe by default
  • Caching support

Example:

$server = Server::builder()
    ->setDiscovery(__DIR__, ['.'], excludeDirs: ['vendor'])  // Automatic discovery
    ->build();

Manual Registration

Register MCP elements programmatically without using attributes. The handler is the most important parameter and can be any PHP callable.

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();

Handler Types

Handler can be any PHP callable:

  1. Closure: function(int $a, int $b): int { return $a + $b; }
  2. Class and method name pair: [ClassName::class, 'methodName'] - the class is instantiated lazily on first call, so it must be constructable through the container (or have a no-arg constructor)
  3. Class instance and method name: [$instance, 'methodName'] - the given, already-constructed object is invoked as-is. Use this for handlers the container cannot build, e.g. those with scalar constructor arguments or dependencies wired at runtime
  4. Invokable class name: InvokableClass::class - class must be constructable through the container and have __invoke method

Manual Tool Registration

$server = Server::builder()
    // Using closure
    ->addTool(
        handler: function(int $a, int $b): int { return $a + $b; },
        name: 'add_numbers',
        description: 'Adds two numbers together'
    )

    // Using class method pair
    ->addTool(
        handler: [Calculator::class, 'multiply'],
        name: 'multiply_numbers'
        // name and description are optional - derived from method name and docblock
    )

    // Using instance method
    ->addTool(
        handler: [$calculatorInstance, 'divide']
    )

    // Using invokable class
    ->addTool(
        handler: InvokableCalculator::class
    );

Parameters

  • handler (callable|string): The tool handler
  • name (string|null): Optional tool name
  • title (string|null): Optional human-readable title for display in UI
  • description (string|null): Optional tool description
  • annotations (ToolAnnotations|null): Optional annotations for the tool
  • inputSchema (array|null): Optional input schema for the tool
  • icons (Icon[]|null): Optional array of icons for the tool
  • meta (array|null): Optional metadata for the tool
  • outputSchema (array|null): Optional JSON schema describing the tool's structuredContent

Manual Resource Registration

Register static resources:

$server = Server::builder()
    ->addResource(
        handler: [Config::class, 'getSettings'],
        uri: 'config://app/settings',
        name: 'app_config',
        description: 'Application configuration',
        mimeType: 'application/json'
    );

Parameters

  • handler (callable|string): The resource handler
  • uri (string): The resource URI
  • name (string|null): Optional resource name
  • title (string|null): Optional human-readable title for display in UI
  • description (string|null): Optional resource description
  • mimeType (string|null): Optional MIME type of the resource
  • size (int|null): Optional size of the resource in bytes
  • annotations (Annotations|null): Optional annotations for the resource
  • icons (Icon[]|null): Optional array of icons for the resource
  • meta (array|null): Optional metadata for the resource

Manual Resource Template Registration

Register dynamic resources with URI templates:

$server = Server::builder()
    ->addResourceTemplate(
        handler: [UserService::class, 'getUserProfile'],
        uriTemplate: 'user://{userId}/profile',
        name: 'user_profile',
        description: 'User profile by ID',
        mimeType: 'application/json'
    );

Parameters

  • handler (callable|string): The resource template handler
  • uriTemplate (string): The resource URI template
  • name (string|null): Optional resource template name
  • title (string|null): Optional human-readable title for display in UI
  • description (string|null): Optional resource template description
  • mimeType (string|null): Optional MIME type of the resource
  • annotations (Annotations|null): Optional annotations for the resource template
  • meta (array|null): Optional metadata for the resource template

Manual Prompt Registration

Register prompt generators:

$server = Server::builder()
    ->addPrompt(
        handler: [PromptService::class, 'generatePrompt'],
        name: 'custom_prompt',
        description: 'A custom prompt generator'
    );

Parameters

  • handler (callable|string): The prompt handler
  • name (string|null): Optional prompt name
  • title (string|null): Optional human-readable title for display in UI
  • description (string|null): Optional prompt description
  • icons (Icon[]|null): Optional array of icons for the prompt
  • meta (array|null): Optional metadata for the prompt

Note: name and description are optional when the handler is a method or an invokable class — they are then derived from the method name and its docblock. A closure handler has neither, so it gets a generated name (closure_tool_<id>) and no description; name your closures explicitly.

For more details on the elements themselves, see Tools, Resources, Resource templates, and Prompts.

Explicit element registration

When an element's name, schema, or description is only known at runtime, pair an Mcp\Schema\* value object with one of the four handler interfaces below and register it through Builder::add().

Element kind Handler interface
Tool Mcp\Server\Handler\ToolHandlerInterface
Resource Mcp\Server\Handler\ResourceHandlerInterface
Resource template Mcp\Server\Handler\ResourceTemplateHandlerInterface
Prompt Mcp\Server\Handler\PromptHandlerInterface

Each handler interface declares a single execution method. Tool and prompt handlers receive an arguments map and a ClientGateway. Resource handlers receive the requested URI; resource template handlers additionally receive the parsed template variables.

use Mcp\Schema\Tool;
use Mcp\Server;
use Mcp\Server\ClientGateway;
use Mcp\Server\Handler\ToolHandlerInterface;

final class WeatherHandler implements ToolHandlerInterface
{
    public function execute(array $arguments, ClientGateway $gateway): mixed
    {
        return ['temperature' => 21, 'unit' => 'C'];
    }
}

$tool = new Tool(
    name: 'get_weather',
    title: null,
    inputSchema: [
        'type' => 'object',
        'properties' => ['city' => ['type' => 'string']],
        'required' => ['city'],
    ],
    description: 'Returns the current weather for a city.',
    annotations: null,
);

$server = Server::builder()
    ->add($tool, new WeatherHandler())
    ->build();

Builder::add() validates the pairing at registration time. Pairing a Tool definition with, for example, a PromptHandlerInterface raises Mcp\Exception\InvalidArgumentException. The schema value objects validate some of their own input as well — Tool requires an object-typed input schema, ResourceDefinition and ResourceTemplate check the name pattern and URI — but an invalid tool or prompt name is not rejected, it is only logged as a warning when the element is registered.

Use add() when the metadata cannot be inferred from a handler class via reflection. For statically-known elements, prefer addTool/addResource/addResourceTemplate/addPrompt, which can derive metadata from the handler's signature and docblock.

Hybrid Approach

Combine both methods for maximum flexibility:

$server = Server::builder()
    ->setDiscovery(__DIR__, ['.'], excludeDirs: ['vendor'])  // Discover most capabilities
    ->addTool([ExternalService::class, 'process'], 'external')  // Add specific ones
    ->build();

Manual registrations always take precedence over discovered elements with the same identifier — same name for tools and prompts, same uri for resources, same uriTemplate for resource templates.

For runtime, config-driven elements whose shape is not known at compile time, see Explicit element registration.