### Start Example MCP HTTP Server Source: https://github.com/logiscape/mcp-sdk-php/blob/main/webclient/README.md Run this command separately to start an example MCP HTTP server. This server can then be connected to by the web client. ```bash php -S 127.0.0.1:8081 examples/simple_server_http.php ``` -------------------------------- ### Run Apps Server via StdIO Source: https://github.com/logiscape/mcp-sdk-php/blob/main/examples/apps_server/README.md Starts the example server using standard input/output. This method can be used for direct process communication. ```bash php examples/apps_server/apps_server.php ``` -------------------------------- ### Complete Multi-Capability Server Example Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md This PHP script defines and integrates multiple tools, prompts, and resources into a single MCP server instance. It includes examples for estimating reading time, generating tables of contents, writing README files, documenting functions, and providing coding standards and project structure guides. ```php tool( 'estimate-reading-time', 'Estimate reading time for a given text', function (string $text, int $wpm = 200): string { $wordCount = str_word_count($text); $minutes = ceil($wordCount / $wpm); return "{$wordCount} words, approximately {$minutes} minute(s) to read at {$wpm} WPM."; } ) ->tool( 'generate-table-of-contents', 'Extract markdown headings to build a table of contents', function (string $markdown): string { preg_match_all('/^(#{1,6})\s+(.+)$/m', $markdown, $matches, PREG_SET_ORDER); if (empty($matches)) { return 'No headings found in the provided markdown.'; } $toc = "## Table of Contents\n\n"; foreach ($matches as $match) { $level = strlen($match[1]); $title = trim($match[2]); $slug = strtolower(preg_replace('/[^a-z0-9]+/', '-', $title)); $indent = str_repeat(' ', $level - 1); $toc .= "{$indent}- [{$title}](#{$slug})\n"; } return $toc; } ); // --- Prompts --- $server ->prompt( 'write-readme', 'Generate a README.md for a project', function (string $project_name, string $description, string $language = 'PHP'): string { return <<prompt( 'document-function', 'Generate documentation for a function or method', function (string $function_signature, string $purpose): string { return <<resource( uri: 'guide://coding-standards', name: 'Coding Standards', description: 'Team coding standards and conventions', callback: function (): string { return <<<'STANDARDS' # Coding Standards ## PHP - Follow PSR-12 coding style - Use strict_types=1 in all files - Type-hint all parameters and return types - Use named arguments for constructors with 3+ parameters ## Naming - Classes: PascalCase - Methods/Functions: camelCase - Variables: camelCase - Constants: UPPER_SNAKE_CASE - Database tables: snake_case (plural) ## Git - Branch naming: feature/description, fix/description, chore/description - Commit messages: imperative mood ("Add feature" not "Added feature") - Squash merge feature branches STANDARDS; }, mimeType: 'text/markdown' ) ->resource( uri: 'guide://project-structure', name: 'Project Structure', description: 'Recommended directory layout', callback: function (): string { return <<<'STRUCTURE' project/ ├── src/ # Application source code │ ├── Controllers/ # HTTP controllers │ ├── Models/ # Database models │ ├── Services/ # Business logic │ └── Repositories/ # Data access layer ├── tests/ # Test files │ ├── Unit/ │ └── Integration/ ├── config/ # Configuration files ├── public/ # Web root │ └── index.php # Entry point ├── storage/ # Generated files, logs, cache ├── composer.json └── README.md STRUCTURE; }, mimeType: 'text/plain' ); $server->run(); ``` -------------------------------- ### Install Monolog Logging Library Source: https://github.com/logiscape/mcp-sdk-php/blob/main/README.md Use this command to install the Monolog library for logging, which is used in some advanced examples. ```bash composer require monolog/monolog ``` -------------------------------- ### Run Apps Server via HTTP Source: https://github.com/logiscape/mcp-sdk-php/blob/main/examples/apps_server/README.md Starts the example server using PHP's built-in web server for HTTP connections. This is the method hosts typically use to connect. ```bash php -S localhost:8000 examples/apps_server/apps_server.php ``` -------------------------------- ### Run MCP Client Example Source: https://github.com/logiscape/mcp-sdk-php/blob/main/README.md Command to execute the example MCP client script. This assumes `example_client.php` is saved in the current directory. ```bash php example_client.php ``` -------------------------------- ### Install Node.js Dependencies for Conformance Suite Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/testing.md Install the necessary Node.js dependencies for running the MCP Conformance suite. This is a one-time setup. ```bash npm install ``` -------------------------------- ### Install Dependencies with Composer Source: https://github.com/logiscape/mcp-sdk-php/blob/main/webclient/README.md Run this command within the webclient directory to install the SDK and other dependencies. Re-run periodically to update. ```bash composer install ``` -------------------------------- ### Start Development Server Source: https://github.com/logiscape/mcp-sdk-php/blob/main/webclient/README.md Use this command to start a local PHP development server for the web client. The webclient will be accessible at http://127.0.0.1:8080/. ```bash php -S 127.0.0.1:8080 -t webclient ``` -------------------------------- ### Install Conformance Tools Source: https://github.com/logiscape/mcp-sdk-php/blob/main/conformance/README.md Installs the pinned conformance tools using npm. This is a prerequisite for running any conformance tests. ```bash npm install ``` -------------------------------- ### Create a Basic MCP Server Source: https://github.com/logiscape/mcp-sdk-php/blob/main/README.md Example of an MCP server that registers and provides a simple addition tool. Save this as `example_server.php`. ```php tool('add-numbers', 'Adds two numbers together', function (float $a, float $b): string { return 'Sum: ' . ($a + $b); }) // Start the server ->run(); ``` -------------------------------- ### Minimal CLI OAuth Client Setup Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md This script demonstrates the essential setup for a CLI OAuth client. It configures token storage, handles authentication callbacks, and connects to an MCP server. Ensure you have registered your OAuth client with the authorization server beforehand. ```php connect( commandOrUrl: 'https://example.com/mcp-server.php', args: [], env: ['oauth' => $oauthConfig], ); echo "Authenticated and connected to {$session->getInitializeResult()->serverInfo->name}\n"; // Do work... foreach ($session->listTools()->tools as $tool) { echo "- {$tool->name}\n"; } $client->close(); ``` -------------------------------- ### Install Dependencies with Composer and NPM Source: https://github.com/logiscape/mcp-sdk-php/blob/main/CONTRIBUTING.md Install PHP dependencies using Composer and Node.js dependencies for conformance tests using NPM. ```bash composer install npm install # for the conformance tests ``` -------------------------------- ### Create and Run a Basic MCP Server Source: https://github.com/logiscape/mcp-sdk-php/blob/main/AGENTS.md This example demonstrates how to create a new MCP server using the McpServer convenience wrapper. It registers a tool for adding numbers, a prompt for greetings, and a resource for PHP version information, then runs the server. ```php tool('add', 'Add numbers', fn(float $a, float $b) => "Sum: " . ($a + $b)) ->prompt('greet', 'Greeting', fn(string $name) => "Hello, {$name}!") ->resource(uri: 'info://php', name: 'PHP Info', callback: fn() => PHP_VERSION) ->run(); ``` -------------------------------- ### Create Your First MCP Server Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md A basic example of initializing an MCP server and defining a simple 'hello' tool. The `run()` method automatically detects the environment (CLI or web server) and uses the appropriate transport. ```php tool('hello', 'Say hello to someone', function (string $name): string { return "Hello, {$name}! Welcome to MCP."; }) ->run(); ``` -------------------------------- ### Install PHPUnit for Testing Source: https://github.com/logiscape/mcp-sdk-php/blob/main/tests/README.md Execute this command to install PHPUnit as a development dependency. This is necessary if you encounter errors related to not finding the PHPUnit executable. ```bash composer require --dev phpunit/phpunit ``` -------------------------------- ### Create a Basic MCP Client Source: https://github.com/logiscape/mcp-sdk-php/blob/main/README.md Example of an MCP client that connects to a server and calls the 'add-numbers' tool. Save this as `example_client.php` and run it using `php example_client.php`. ```php connect('php', ['example_server.php']); // Call the add-numbers tool with two arguments $result = $session->callTool('add-numbers', ['a' => 5, 'b' => 25]); // Output the result echo $result->content[0]->text . "\n"; } catch (\Exception $e) { echo "Error: " . $e->getMessage() . "\n"; exit(1); } finally { $client->close(); } ``` -------------------------------- ### Minimal Remote MCP Server Setup Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md This is the most basic setup for an MCP server. Deploy this file to your web hosting. It automatically detects the environment and works both locally and remotely. ```php tool('server-time', 'Get the current server time', function (string $timezone = 'UTC'): string { try { $tz = new \DateTimeZone($timezone); } catch (\Exception $e) { throw new \InvalidArgumentException("Invalid timezone: {$timezone}"); } $now = new \DateTime('now', $tz); return $now->format('Y-m-d H:i:s T'); }) ->run(); ``` -------------------------------- ### Install and Update Dependencies Source: https://github.com/logiscape/mcp-sdk-php/blob/main/AGENTS.md Use these commands to manage project dependencies. 'composer install' installs dependencies, 'composer update' updates them to the latest compatible versions, and 'composer require monolog/monolog' adds optional logging support. ```bash # Install dependencies composer install ``` ```bash # Update dependencies composer update ``` ```bash # Install optional logging support (required for webclient and some examples) composer require monolog/monolog ``` -------------------------------- ### PHP Test Structure Example Source: https://github.com/logiscape/mcp-sdk-php/blob/main/tests/README.md Demonstrates the standard Arrange-Act-Assert pattern for writing PHP tests. Use this structure for new test methods. ```php public function testFeatureWorksCorrectly(): void { // Arrange: Set up test conditions $session = new FakeSession(); // Act: Execute the functionality being tested $result = $session->doSomething(); // Assert: Verify expected outcomes $this->assertInstanceOf(ExpectedType::class, $result); } ``` -------------------------------- ### Configure Claude Desktop for Local Server Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md Example configuration for Claude Desktop to connect to a local MCP server. Specify the command and arguments to run your server script. ```json { "mcpServers": { "my-server": { "command": "php", "args": ["/absolute/path/to/server.php"] } } } ``` -------------------------------- ### Install MCP SDK PHP via Composer Source: https://github.com/logiscape/mcp-sdk-php/blob/main/README.md Use this command to install the MCP SDK for PHP using Composer. Ensure you have PHP 8.1 or higher and the required extensions. ```bash composer require logiscape/mcp-sdk-php ``` -------------------------------- ### Signaling List Changes Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md This example shows how to advertise the capability to send list change notifications and then emit specific notifications when tool, resource, or prompt lists are modified. This ensures the client's cache is updated. ```php httpOptions(['enable_sse' => true]); // Step 1: advertise that this server may send list-changed notifications. $server->notifyOnChanges( resourcesChanged: true, toolsChanged: true, promptsChanged: true, ); // Step 2: each tool calls the matching send…ListChanged() method after it // changes the corresponding catalog. The three methods are independent -- // emit only the one(s) whose list actually changed. $server->tool('enable-beta-tools', 'Turn on the beta tool set', function () use ($server): string { // ... register or unregister tools based on application state ... // The TOOL list changed -> notify so the client refetches tools/list. $session = $server->getServer()->getSession(); $session?->sendToolListChanged(); return 'Beta tools enabled.'; }); $server->tool('mount-dataset', 'Expose a new dataset as readable resources', function () use ($server): string { // ... add resources for the newly mounted dataset ... // The RESOURCE list changed -> notify so the client refetches resources/list. $session = $server->getServer()->getSession(); $session?->sendResourceListChanged(); return 'Dataset mounted.'; }); $server->tool('install-prompt-pack', 'Add a pack of prompt templates', function () use ($server): string { // ... register the new prompts ... // The PROMPT list changed -> notify so the client refetches prompts/list. $session = $server->getServer()->getSession(); $session?->sendPromptListChanged(); return 'Prompt pack installed.'; }); $server->run(); ``` -------------------------------- ### Registering and Linking a UI with a Tool Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md This snippet demonstrates how to use the `ui()` helper to register a UI resource, link it to a tool, and declare the Apps extension in the server's capabilities. Always return a meaningful text content fallback for non-UI hosts. ```php $server ->tool('get_weather', 'Get the current weather', function (string $city): CallToolResult { $data = ['city' => $city, 'temperatureC' => 21, 'condition' => 'Sunny']; return new CallToolResult( content: [new TextContent(text: "Weather in {$city}: {$data['condition']}")], // text fallback structuredContent: $data, // data for the UI ); }) ->ui( tool: 'get_weather', uri: 'ui://weather/dashboard', name: 'Weather Dashboard', html: file_get_contents(__DIR__ . '/dashboard.html'), ); ``` -------------------------------- ### Basic Server-Initiated Sampling with PHP SDK Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md A tool that forwards a user-supplied prompt to the client's LLM and returns the completion. Ensure the client supports sampling and check the result for null or unexpected content types. ```php tool( name: 'summarize', description: 'Ask the client LLM to summarize a block of text in one sentence', callback: function (string $text, SamplingContext $sampling): string { if (!$sampling->supportsSampling()) { return 'This client does not support sampling -- cannot summarize.'; } $result = $sampling->prompt( text: "Summarize the following in one sentence:\n\n{$text}", maxTokens: 200, ); if ($result === null) { return 'Summarization is unavailable right now.'; } // A plain prompt() returns a single text content block. if ($result->content instanceof TextContent) { return $result->content->text; } return 'Received an unexpected content type from the LLM.'; } ); $server->run(); ``` -------------------------------- ### Disable Standalone GET SSE Stream Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md Configure the client to not automatically open the standalone GET SSE stream by setting 'autoSse' to false. This is useful for short-lived web requests. ```php $session = $client->connect( 'https://example.com/mcp-server.php', [], ['autoSse' => false], ); ``` -------------------------------- ### Implement Custom Token Validator with Introspection Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md Implement TokenValidatorInterface for custom authentication flows, such as using an introspection endpoint. This example uses cURL to call the introspection endpoint. ```php introspectionUrl); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query([ 'token' => $token, 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200 || $response === false) { return new TokenValidationResult( valid: false, error: 'Token introspection request failed' ); } $data = json_decode($response, true); if (!($data['active'] ?? false)) { return new TokenValidationResult( valid: false, error: 'Token is not active' ); } return new TokenValidationResult( valid: true, claims: $data ); } } $server = new McpServer('custom-auth-server'); $validator = new IntrospectionTokenValidator( introspectionUrl: 'https://your-auth-server.com/oauth2/introspect', clientId: 'your-client-id', clientSecret: 'your-client-secret' ); $server ->withAuth($validator, 'https://your-auth-server.com/', 'https://yoursite.com/mcp-server.php') ->tool('whoami', 'Show the authenticated user info', function (): string { return 'You are authenticated.'; }) ->run(); ``` -------------------------------- ### Client::connect() Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md Establishes a connection to the MCP service. It can connect via a command executable or a URL, with configurable arguments, environment variables, and timeouts. ```APIDOC ## Client::connect() ### Description Establishes a connection to the MCP service. It can connect via a command executable or a URL, with configurable arguments, environment variables, and timeouts. ### Parameters #### `commandOrUrl` (string) - **Description**: Executable to run (for Stdio) or MCP endpoint URL (for HTTP). - **Required**: Yes #### `args` (array) - **Description**: Arguments for the executable (for Stdio) or HTTP headers (`['Header' => 'value']`) (for HTTP). - **Required**: No #### `env` (array, nullable) - **Description**: Subprocess environment variables (for Stdio) or HTTP options array (for HTTP). - **Required**: No #### `readTimeout` (float, nullable) - **Description**: Per-request read timeout in seconds. - **Required**: No ``` -------------------------------- ### Checking Server Capabilities Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md This snippet demonstrates how to connect to an MCP server and check its advertised capabilities using the `getInitializeResult()` method. Always gate calls on advertised capabilities to avoid errors. ```php connect('https://example.com/mcp-server.php'); $caps = $session->getInitializeResult()->capabilities; if ($caps->tools !== null) { echo "Server supports tools (listChanged: " . var_export($caps->tools->listChanged ?? null, true) . ")\n"; } if ($caps->prompts !== null) { echo "Server supports prompts\n"; } if ($caps->resources !== null) { echo "Server supports resources"; if ($caps->resources->subscribe ?? false) { echo " (with subscribe)"; } echo "\n"; } if ($caps->logging !== null) { echo "Server can emit logging messages\n"; } if ($caps->completions !== null) { echo "Server supports argument completion\n"; } $client->close(); ``` -------------------------------- ### Simple Form Example Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md Implement a tool that sends a personalized greeting. It uses `requiresForm` to ask for the user's name if it's not provided, ensuring a name is available for the greeting. ```php tool( name: 'send-greeting', description: 'Send a personalized greeting, asking the user for their name if needed', callback: function (ElicitationContext $elicit, string $name = ''): string { // If the model didn't supply a name, ask the user directly. if ($name === '') { try { $result = $elicit->requiresForm( message: 'What name should I use for the greeting?', requestedSchema: [ 'type' => 'object', 'properties' => [ 'name' => [ 'type' => 'string', 'title' => 'Your name', 'minLength' => 1, ], ], 'required' => ['name'], ], ); $name = $result->content['name']; } catch (ElicitationDeclinedException $e) { return 'No greeting sent -- a name is required.'; } } return "Hello, {$name}!"; } ); $server->run(); ``` -------------------------------- ### List Available Tools Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md Fetches and displays a list of all available tools and their input schemas. This is useful for discovering what operations are supported by the MCP server. ```php connect('https://example.com/mcp-server.php'); $result = $session->listTools(); foreach ($result->tools as $tool) { echo "- {$tool->name}\n"; if (isset($tool->description)) { echo " {$tool->description}\n"; } // The input schema describes what arguments the tool expects. Each // property definition is forwarded as decoded JSON, which is typically // an associative array (servers occasionally hand back stdClass), so // handle both shapes when reading fields. if (isset($tool->inputSchema, $tool->inputSchema->properties)) { $required = $tool->inputSchema->required ?? []; foreach ($tool->inputSchema->properties as $name => $prop) { $req = in_array($name, $required, true) ? 'required' : 'optional'; $type = is_array($prop) ? ($prop['type'] ?? 'unknown') : ($prop->type ?? 'unknown'); echo " - {$name} ({$type}, {$req})\n"; } } } $client->close(); ``` -------------------------------- ### Initiate and Persist Request ID for Cancellation Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md This snippet starts a long-running tool call and captures the request ID for later cancellation. It also snapshots the session state for resuming. ```php connect( commandOrUrl: 'https://example.com/mcp-server.php', args: [], env: ['autoSse' => false], ); // Capture the request ID *before* sending. getNextRequestId() returns the // integer the SDK will assign to the next sendRequest() call, which is // exactly the value the cancel needs to reference. $_SESSION['inflight_request_id'] = $session->getNextRequestId(); // Kick off the long-running call. (In a real app you'd run this in a way // that doesn't block the request -- a queue worker, a fork, etc. The // snapshot below assumes a separate worker is now driving the session.) // $session->callTool('long-running-search', ['query' => 'widgets']); // Snapshot the session so the cancel endpoint can resume into it. $transport = $client->getTransport(); if ($transport instanceof StreamableHttpTransport) { $_SESSION['mcp'] = [ 'sessionManagerState' => $transport->getSessionManager()->toArray(), 'initResult' => json_encode( $session->getInitializeResult(), JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR ), 'protocolVersion' => $session->getNegotiatedProtocolVersion(), 'nextRequestId' => $session->getNextRequestId(), 'serverUrl' => 'https://example.com/mcp-server.php', ]; } $client->detach(); ``` -------------------------------- ### Get a Specific Prompt and its Messages Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md Fetches a specific prompt by name and provides arguments. It then iterates through the returned messages, distinguishing between user and assistant roles and handling text content. ```php connect('https://example.com/mcp-server.php'); // Prompt arguments must be strings -- they come from a UI form, not from JSON Schema. $result = $session->getPrompt('code-review', [ 'language' => 'php', 'code' => "function add(\\$a, \\$b) { return \\$a + \\$b; }", ]); echo "Description: " . ($result->description ?? '(none)') . "\n\n"; foreach ($result->messages as $message) { echo "[{$message->role->value}]\n"; if ($message->content instanceof TextContent) { echo $message->content->text . "\n\n"; } else { echo "(non-text content: " . get_class($message->content) . ")\n\n"; } } $client->close(); ?> ``` -------------------------------- ### Listing Tools Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md Demonstrates how to list available tools and inspect their input schemas using the `listTools()` method. ```APIDOC ## listTools() ### Description Lists all available tools and their associated input schemas. ### Method `listTools()` ### Parameters None ### Response - **tools** (array) - An array of tool objects, each containing `name`, `description`, and `inputSchema`. ### Request Example ```php connect('https://example.com/mcp-server.php'); $result = $session->listTools(); foreach ($result->tools as $tool) { echo "- {$tool->name}\n"; if (isset($tool->description)) { echo " {$tool->description}\n"; } if (isset($tool->inputSchema, $tool->inputSchema->properties)) { $required = $tool->inputSchema->required ?? []; foreach ($tool->inputSchema->properties as $name => $prop) { $req = in_array($name, $required, true) ? 'required' : 'optional'; $type = is_array($prop) ? ($prop['type'] ?? 'unknown') : ($prop->type ?? 'unknown'); echo " - {$name} ({$type}, {$req})\n"; } } } $client->close(); ?> ``` ``` -------------------------------- ### Multi-Field Form Example Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/server-dev.md Implement a tool to archive a project, requiring user confirmation and a reason. It uses `requiresForm` to collect multiple fields: a confirmation checkbox, a reason string, and a visibility setting. ```php tool( name: 'archive-project', description: 'Archive a project after confirming with the user', callback: function (string $projectId, ElicitationContext $elicit): string { try { $result = $elicit->requiresForm( message: "Archive project '{$projectId}'? This cannot be undone from the client.", requestedSchema: [ 'type' => 'object', 'properties' => [ 'confirm' => [ 'type' => 'boolean', 'title' => 'Confirm archive', 'description' => 'Must be checked to proceed', 'default' => false, ], 'reason' => [ 'type' => 'string', 'title' => 'Reason', 'description' => 'Why are you archiving this project?', 'minLength' => 3, 'maxLength' => 200, ], 'visibility' => [ 'type' => 'string', 'title' => 'Post-archive visibility', 'enum' => ['hidden', 'read-only', 'public'], 'default' => 'hidden', ], ], 'required' => ['confirm', 'reason'], ], ); } catch (ElicitationDeclinedException $e) { return "Archive cancelled ({$e->action})."; } if (!$result->content['confirm']) { return 'Archive cancelled -- confirmation checkbox was not ticked.'; } // In a real server, archive the project here. return sprintf( "Archived '%s' (visibility: %s). Reason: %s", $projectId, $result->content['visibility'], $result->content['reason'], ); } ); $server->run(); ``` -------------------------------- ### Detecting Negotiated Protocol Features Source: https://github.com/logiscape/mcp-sdk-php/blob/main/docs/client-dev.md This example shows how to check the negotiated MCP protocol version and specific feature support using `getNegotiatedProtocolVersion()` and `supportsFeature()`. This is distinct from checking advertised server capabilities. ```php $session = $client->connect('https://example.com/mcp-server.php'); // Hard version string (e.g. "2025-11-25"). $version = $session->getNegotiatedProtocolVersion(); // Boolean checks for individual features. if ($session->supportsFeature('elicitation')) { // The negotiated protocol version defines elicitation/create. } if ($session->supportsFeature('url_elicitation')) { // Negotiated 2025-11-25 or newer; URL-mode elicitation is defined. } if ($session->supportsFeature('structured_content')) { // The negotiated version defines structuredContent on tool results. } ```