MCP PHP SDK

repository·main·Indexed 23 days ago

https://github.com/modelcontextprotocol/php-sdk

A framework-agnostic implementation of the Model Context Protocol for PHP. It enables developers to build MCP servers that expose tools, resources, and prompts to AI agents, as well as MCP clients to consume those capabilities. The SDK supports both STDIO and HTTP transports and includes examples for OAuth 2.0 / OpenID Connect integration using Keycloak and Microsoft Entra ID.

Tokens
44.7K
Snippets
95
Records
163
Agent score
82%

What's inside modelcontextprotocol-php-sdk

  1. Overview of the MCP Client SDK

    main

    The MCP Client SDK provides a synchronous, framework-agnostic API for PHP applications to communicate with MCP servers. It manages connection lifecycles, request/response correlation, server-initiated requests (sampling), and real-time notifications.

    Key features include:

    • Connection management via Transports.
    • Synchronous API for tools, resources, and prompts.
    • Support for server-initiated communication (sampling and notifications).
    • Progress notification support for long-running operations.
    use Mcp\
    Client;
    use Mcp\Client\Transport\StdioTransport;
    
    // Build and configure the client
    $client = Client::builder()
        ->setClientInfo('My Client', '1.0.0')
        ->setInitTimeout(30)
        ->setRequestTimeout(120)
        ->build();
    
    // Create a transport
    $transport = new StdioTransport(
        command: 'php',
        args: ['/path/to/server.php'],
    );
    
    // Connect and use the server
    $client->connect($transport);
    $tools = $client->listTools();
    $client->disconnect();
  2. Overview of MCP Elements

    main

    MCP elements are the core capabilities of an MCP server. They define how clients interact with your server. The four primary types are:

    • Tools: Callable functions for performing actions.
    • Resources: Static data sources identified by URIs.
    • Resource Templates: URI templates for dynamic resources using variables.
    • Prompts: Template generators for AI prompts.

    You can register these elements using Attribute-Based Discovery (using PHP attributes like #[McpTool] or #[McpResource]) or Manual Registration via ServerBuilder.

    Note on Priority: Manual registrations always override discovered elements if they share the same identifier (name for Tools/Prompts, uri for Resources, and uriTemplate for Resource Templates).

  3. How the MCP App View and Host handshake works

    main

    When an MCP App is rendered in a client (the Host), the View (the HTML iframe) and the Host communicate by exchanging JSONRPCMessage objects via window.parent.postMessage.

    Before the Host can forward tool calls (tools/call), tool inputs (tool-input), or tool results (tool-result), the View must complete the following handshake sequence:

    1. View → Host: ui/initialize request
    2. Host → View: Response containing hostCapabilities, hostInfo, and hostContext
    3. View → Host: ui/notifications/initialized notification
    4. View → Host: ui/notifications/size-changed notification (sent whenever the iframe needs to resize)
  4. Configure Discovery for MCP Attributes

    main

    If you use PHP attributes like #[McpTool], #[McpResource], #[McpResourceTemplate], or #[McpPrompt] to define your server elements, you must configure discovery using setDiscovery() so the server knows where to scan for them.

    Parameters:

    • $basePath (string): The base directory for discovery (typically __DIR__).
    • $scanDirs (array): Directories to recursively scan. Defaults to ['.', 'src'].
    • $excludeDirs (array): Directory names to skip during the recursive scan.
    • $cache (CacheInterface|null): An optional PSR-16 cache to store discovered elements. Highly recommended for production to avoid filesystem scanning on every startup.
    • $namePatterns (array): File name patterns to match (e.g., ['*.php']).
    // Production setup with caching
    use Symfony\Component\Cache\Adapter\FilesystemAdapter;
    use Symfony\Component\Cache\Psr16Cache;
    
    $cache = new Psr16Cache(new FilesystemAdapter('mcp-discovery'));
    
    $server = Server::builder()
        ->setDiscovery(
            basePath: __DIR__,
            scanDirs: ['src', 'lib'],
            excludeDirs: ['vendor', 'tests', 'temp'],
            cache: $cache
        )
        ->build();
  5. Register MCP Server Capabilities

    main

    The SDK supports three ways to register capabilities (Tools, Resources, Prompts) on a server:

    1. Attribute-Based Discovery: Use #[McpTool] and #[McpResource] attributes on class methods. Use setDiscovery(path, directories) on the builder to enable this.
    2. Manual Registration: Use the builder methods addTool() and addResource() to register specific class methods programmatically.
    3. Hybrid Approach: Combine attribute discovery with manual registration for specific external services.
    // 1. Attribute-Based
    #[McpTool]
    public function generateReport(): string { /* ... */ }
    
    #[McpResource(uri: 'config://app/settings')]
    public function getConfig(): array { /* ... */ }
    
    // 2. Manual Registration
    $server = Server::builder()
        ->addTool([Calculator::class, 'add'], 'add_numbers')
        ->addResource([Config::class, 'get'], 'config://app')
        ->build();
    
    // 3. Hybrid
    $server = Server::builder()
        ->setDiscovery(__DIR__, ['.'])
        ->addTool([ExternalService::class, 'process'], 'external')
        ->build();
  6. Register MCP tools and resources using Attributes

    main

    You can use PHP attributes to automatically discover tools and resources within a class. This is an alternative to manual registration.

    #[McpTool(name: 'calculate')]
    public function calculate(float $a, float $b, string $operation): float|string
    
    #[McpResource(
        uri: 'config://calculator/settings',
        name: 'calculator_config',
        mimeType: 'application/json'
    )]
    public function getConfiguration(): array
  7. Understand the OAuth 2.1 role of the MCP PHP SDK

    main

    The MCP PHP SDK is designed to function as an OAuth 2.1 Resource Server, not an Authorization Server (Identity Provider).

    What the SDK DOES:

    • Validates bearer tokens: Uses JwtTokenValidator to verify incoming credentials.
    • Serves Metadata: Provides Protected Resource Metadata (RFC 9728).
    • Handles Challenges: Emits WWW-Authenticate headers.
    • Delegates Authentication: Uses OAuthProxyMiddleware to forward /authorize and /token requests to an external Identity Provider (IdP).

    What the SDK DOES NOT DO:

    • Mint tokens: It will not issue, sign, or rotate access or refresh tokens.
    • Manage Identity: It does not handle login UIs, consent flows, or client registration as an issuer.
    • Store credentials: It does not manage authorization codes or refresh token persistence.
  8. Communicate with clients using ClientGateway

    main

    To communicate back to a client outside of the standard request-response flow (such as logging, sampling, or progress updates), use the Mcp\Server\ClientGateway.

    To access the gateway within your tool or resource methods, you must use method argument injection by type-hinting Mcp\Server\RequestContext. The SDK will automatically inject the request context, allowing you to retrieve the gateway via $context->getClientGateway().

    use Mcp\Capability\Attribute\McpTool;
    use Mcp\Server\RequestContext;
    
    class MyService
    {
        #[McpTool(name: 'my_tool', description: 'My Tool Description')]
        public function myTool(RequestContext $context): string
        {
            $context->getClientGateway()->log(...);
        }
    }
  9. Use Protocol Events to intercept requests, responses, and errors

    main

    The SDK dispatches four main protocol-level events that allow you to observe or modify server operations:

    RequestEvent

    Dispatched: When a request is received, before processing.

    • getRequest(): Request: Get the incoming request.
    • setRequest(Request $request): void: Modify the request before it reaches handlers.
    • getMethod(): string: Get the request method.
    • getSession(): SessionInterface: Get the current session.

    ResponseEvent

    Dispatched: When a successful response is ready, after handler execution.

    • getResponse(): Response: Get the response being sent.
    • setResponse(Response $response): void: Modify the response before sending.
    • getMethod(): string: Get the request method.
    • getSession(): SessionInterface: Get the current session.

    ErrorEvent

    Dispatched: When an error occurs during request processing.

    • getError(): Error: Get the error being sent.
    • setError(Error $error): void: Modify the error before sending.
    • getThrowable(): ?\Throwable: Get the exception that caused the error.
    • getRequest(): ?Request: Get the original request (null for parse errors).
    • getSession(): SessionInterface: Get the current session.

    NotificationEvent

    Dispatched: When a notification is received, before processing.

    • getNotification(): Notification: Get the incoming notification.
    • setNotification(Notification $notification): void: Modify the notification before processing.
    • getMethod(): string: Get the notification method.
    • getSession(): SessionInterface: Get the current session.
  10. Implement Custom Message Handlers

    main

    Custom message handlers are a low-level escape hatch that allow you to intercept and process individual JSON-RPC messages before the SDK's built-in handlers.

    Warning: Custom handlers bypass discovery and manual capability registration. Tools and resources registered via the builder will not be visible unless your custom handler manually loads them.

    Request Handlers

    Used for messages with an id that expect a response (e.g., tools/call).

    • Interface: Mcp\Server\Handler\Request\RequestHandlerInterface
    • Methods: supports(Request $request): bool and handle(Request $request, SessionInterface $session): Response|Error
    • Requirement: Must return a Response or Error object.

    Notification Handlers

    Used for messages without an id (fire-and-forget) (e.g., notifications/initialized).

    • Interface: Mcp\Server\Handler\Notification\NotificationHandlerInterface
    • Methods: supports(Notification $notification): bool and handle(Notification $notification, SessionInterface $session): void
    • Requirement: Must return void.
    // Request Handler Example
    use Mcp\Schema\JsonRpc\Response;
    use Mcp\Schema\JsonRpc\Request;
    use Mcp\Server\Handler\Request\RequestHandlerInterface;
    use Mcp\Server\Session\SessionInterface;
    
    class CustomListToolsHandler implements RequestHandlerInterface
    {
        public function supports(Request $request): bool
        {
            return $request->getMethod() === 'tools/list';
        }
    
        public function handle(Request $request, SessionInterface $session): Response|Error
        {
            // Implementation logic
        }
    }
    
    // Attaching to the builder
    $server = Server::builder()
        ->addRequestHandler(new CustomListToolsHandler())
        ->build();
  11. Generate JSON schemas for tool parameters

    main

    The SDK automatically generates JSON schemas for tool parameters using a priority system. This allows you to define validation rules for your MCP tools using PHP attributes. The system follows a specific order of precedence:

    1. #[Schema] attribute with definition: A complete schema override (highest priority).
    2. Parameter-level #[Schema] attribute: Enhances specific parameters.
    3. Method-level #[Schema] attribute: Configures the entire method.
    4. PHP type hints + docblocks: Automatic inference (lowest priority).

    Use automatic inference for simple types, parameter-level attributes for constraints like format or minimum, and method-level attributes for complex object structures.

    use Mcp\
    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
    }
  12. Perform server-initiated elicitation requests

    main

    Elicitation allows a server to request interactive user input during tool execution.

    Requirements:

    • The client must support elicitation (check via $context->getClientGateway()->supportsElicitation()).
    • A session store (e.g., FileSessionStore) must be configured to persist the request.

    Workflow:

    1. Build an ElicitationSchema defining the required fields (string, number, boolean, enum).
    2. Call $client->elicit() with a message and the schema.
    3. Handle the result using $result->isAccepted(), $result->isDeclined(), or $result->isCancelled().
    // 1. Check support
    if (!$context->getClientGateway()->supportsElicitation()) {
        return ['status' => 'error', 'message' => 'Client does not support elicitation'];
    }
    
    // 2. Build schema
    $schema = new ElicitationSchema(
        properties: [
            'party_size' => new NumberSchemaDefinition(
                title: 'Party Size',
                integerOnly: true,
                minimum: 1,
                maximum: 20
            ),
            'date' => new StringSchemaDefinition(
                title: 'Reservation Date',
                format: 'date'
            ),
            'dietary' => new EnumSchemaDefinition(
                title: 'Dietary Restrictions',
                enum: ['none', 'vegetarian', 'vegan'],
                enumNames: ['None', 'Vegetarian', 'Vegan']
            ),
        ],
        required: ['party_size', 'date']
    );
    
    // 3. Send request
    $result = $client->elicit(
        message: 'Please provide your reservation details',
        requestedSchema: $schema
    );
    
    // 4. Handle response
    if ($result->isAccepted()) {
        $data = $result->content; // User-provided data
    } elseif ($result->isDeclined() || $result->isCancelled()) {
        // Handle decline/cancel
    }