php-mcp / server
Core PHP implementation for the Model Context Protocol (MCP) server
Installs: 293
Dependents: 1
Suggesters: 0
Security: 0
Stars: 153
Watchers: 2
Forks: 6
Open Issues: 1
Requires
- php: >=8.1
- opis/json-schema: ^2.4
- phpdocumentor/reflection-docblock: ^5.6
- psr/container: ^1.0 || ^2.0
- psr/event-dispatcher: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
- psr/simple-cache: ^1.0 || ^2.0 || ^3.0
- react/event-loop: ^1.5
- react/http: ^1.11
- react/stream: ^1.4
- symfony/finder: ^6.4 || ^7.2
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- mockery/mockery: ^1.6
- pestphp/pest: ^2.36.0|^3.5.0
- react/async: ^4.0
- symfony/var-dumper: ^6.4.11|^7.1.5
Suggests
- react/http: Required for using the ReactPHP HTTP transport handler (^1.11 recommended).
README
PHP MCP Server provides a robust and flexible server-side implementation of the Model Context Protocol (MCP) for PHP applications.
Easily expose parts of your application as standardized MCP Tools, Resources, and Prompts, allowing AI assistants (like Anthropic's Claude, Cursor IDE, etc.) to interact with your PHP backend using the MCP standard.
This package simplifies building MCP servers through:
- Attribute-Based Definition: Define MCP elements using PHP 8 Attributes (
#[McpTool]
,#[McpResource]
,#[McpPrompt]
,#[McpResourceTemplate]
) on your methods or invokable classes. - Manual Registration: Programmatically register elements using a fluent builder API.
- Explicit Discovery: Trigger attribute scanning on demand via the
$server->discover()
method. - Metadata Inference: Intelligently generate MCP schemas and descriptions from PHP type hints and DocBlocks.
- Selective Caching: Optionally cache discovered element definitions to speed up startup, while always preserving manually registered elements.
- Flexible Transports: Supports
stdio
andhttp+sse
, separating core logic from network communication. - PSR Compliance: Integrates with PSR-3 (Logging), PSR-11 (Container), and PSR-16 (SimpleCache).
This package currently supports the 2024-11-05
version of the Model Context Protocol.
Requirements
- PHP >= 8.1
- Composer
- (For Http Transport): An event-driven PHP environment capable of handling concurrent requests (see HTTP Transport section).
Installation
composer require php-mcp/server
Note for Laravel Users: While this package works standalone, consider using
php-mcp/laravel
for enhanced framework integration, configuration, and Artisan commands.
Quick Start: Standalone stdio
Server with Discovery
This example creates a server using attribute discovery to find elements and runs via the stdio
transport.
1. Define Your MCP Element:
Create src/MyMcpElements.php
:
<?php namespace App; use PhpMcp\Server\Attributes\McpTool; class MyMcpElements { /** * Adds two numbers together. * @param int $a The first number. * @param int $b The second number. * @return int The sum. */ #[McpTool(name: 'simple_adder')] public function add(int $a, int $b): int { fwrite(STDERR, "Executing simple_adder with a=$a, b=$b\n"); return $a + $b; } }
2. Create the Server Script:
Create mcp-server.php
:
#!/usr/bin/env php <?php declare(strict_types=1); require_once __DIR__ . '/vendor/autoload.php'; use PhpMcp\Server\Server; use PhpMcp\Server\Transports\StdioServerTransport; try { // 1. Build the Server configuration $server = Server::make() ->withServerInfo('My Discovery Server', '1.0.2') ->build(); // 2. **Explicitly run discovery** $server->discover( basePath: __DIR__, scanDirs: ['src'], ); // 3. Create the Stdio Transport $transport = new StdioServerTransport(); // 4. Start Listening (BLOCKING call) $server->listen($transport); exit(0); } catch (\Throwable $e) { fwrite(STDERR, "[MCP SERVER CRITICAL ERROR]\n" . $e . "\n"); exit(1); }
3. Configure Your MCP Client:
Instruct your MCP client (e.g., Cursor, Claude Desktop) to use the stdio
transport by running your script. Make sure to use the absolute path:
// Example: .cursor/mcp.json { "mcpServers": { "my-php-stdio": { "command": "php", "args": ["/full/path/to/your/project/mcp-server.php"] } } }
Flow:
Server::make()->...->build()
: Creates theServer
instance, resolves dependencies, performs manual registrations (if any), and implicitly attempts to load discovered elements from cache (if configured and cache exists).$server->discover(__DIR__, ['src'])
: Explicitly triggers a filesystem scan withinsrc/
. Clears previously discovered/cached elements from the registry, findsMyMcpElements::add
, creates itsToolDefinition
, and registers it. If caching is enabled andsaveToCache
is true, saves this discovered definition to the cache.$server->listen($transport)
: Binds the transport, checks if any elements are registered (in this case, yes), starts the transport listener, and runs the event loop.
Core Architecture
The server uses a decoupled architecture:
ServerBuilder
: Fluent interface (Server::make()->...
) for configuration. Collects server identity, dependencies (Logger, Cache, Container, Loop), capabilities, and manual element registrations. Callsbuild()
to create theServer
instance.Configuration
: A value object holding the resolved configuration and dependencies.Server
: The central object holding the configured state and core logic components (Registry
,Processor
,ClientStateManager
,Configuration
). It's transport-agnostic. Provides methods todiscover()
elements andlisten()
via a specific transport.Registry
: Stores MCP element definitions. Distinguishes between manually registered and discovered elements. Handles optional caching of discovered elements only. Loads cached discovered elements upon instantiation if available.Processor
: Processes parsed JSON-RPC requests/notifications, executes handlers (via DI Container), formats results, handles errors.SchemaGenerator
: Generates JSON Schema for method parameters from PHP type hints and DocBlocks.SchemaValidator
: Validates incoming data against the generated JSON Schema.ClientStateManager
: Manages client runtime state (initialization, subscriptions, activity) using the configured cache.ServerTransportInterface
: Event-driven interface for server-side transports (StdioServerTransport
,HttpServerTransport
). Handles communication, emits events.Protocol
: Internal bridge listening to transport events, interacting withProcessor
andClientStateManager
.discover()
Method: An explicit method on theServer
instance to trigger attribute discovery. Takes path configurations as arguments. By default, it clears previously discovered/cached elements before scanning and saves the new results to cache (if enabled).listen()
Method: Starts the server using a specific transport. Binds theProtocol
, starts the transport listener, and runs the event loop (blocking). Performs a pre-check and warns if no elements are registered and discovery hasn't run.
Defining MCP Elements
Expose your application's functionality as MCP Tools, Resources, or Prompts using attributes or manual registration.
1. Using Attributes (#[Mcp*]
)
Decorate public, non-static methods or invokable classes with #[Mcp*]
attributes to mark them as MCP Elements. After building the server, you must call $server->discover(...)
at least once with the correct paths to find and register these elements. It will also cache the discovered elements if set, so that you can skip discovery on subsequent runs.
$server = ServerBuilder::make()->...->build(); // Scan 'src/Handlers' relative to the project root $server->discover(basePath: __DIR__, scanDirs: ['src/Handlers']);
Attributes:
#[McpTool(name?, description?)
: Defines an action. Parameters/return types/DocBlocks define the MCP schema. Use on public, non-static methods or invokable classes.#[McpResource(uri, name?, description?, mimeType?, size?, annotations?)]
: Defines a static resource instance. Use on public, non-static methods or invokable classes. Method returns resource content.#[McpResourceTemplate(uriTemplate, name?, description?, mimeType?, annotations?)]
: Defines a handler for templated URIs (e.g.,item://{id}
). Use on public, non-static methods or invokable classes. Method parameters must match template variables. Method returns content for the resolved instance.#[McpPrompt(name?, description?)
: Defines a prompt generator. Use on public, non-static methods or invokable classes. Method parameters are prompt arguments. Method returns prompt messages.
(See Attribute Details below for more on parameters and return value formatting)
2. Manual Registration (ServerBuilder->with*
)
Use withTool
, withResource
, withResourceTemplate
, withPrompt
on the ServerBuilder
before calling build()
.
use App\Handlers\MyToolHandler; use App\Handlers\MyResourceHandler; $server = Server::make() ->withServerInfo(...) ->withTool( [MyToolHandler::class, 'processData'], // Handler: [class, method] 'data_processor' // MCP Name (Optional) ) ->withResource( MyResourceHandler::class, // Handler: Invokable class 'config://app/name' // URI (Required) ) // ->withResourceTemplate(...) // ->withPrompt(...) ->build();
- Handlers: Can be
[ClassName::class, 'methodName']
orInvokableHandler::class
. Dependencies are resolved via the configured PSR-11 Container. - Metadata (name, description) is inferred from the handler if not provided explicitly.
- These elements are registered immediately when
build()
is called. - They are never cached by the Registry's caching mechanism.
- They are not removed when
$registry->clearDiscoveredElements()
is called (e.g., at the start of$server->discover()
).
Precedence: Manual vs. Discovered/Cached
If an element is registered both manually (via the builder) and is also found via attribute discovery (or loaded from cache) with the same identifying key (tool name, resource URI, prompt name, template URI):
- The manually registered element always takes precedence.
- The discovered/cached version will be ignored, and a debug message will be logged.
This ensures explicit manual configuration overrides any potentially outdated discovered or cached definitions.
Discovery and Caching
Attribute discovery is an explicit step performed on a built Server
instance.
$server->discover(string $basePath, array $scanDirs = [...], array $excludeDirs = [...], bool $force = false, bool $saveToCache = true)
$basePath
,$scanDirs
,$excludeDirs
: Define where to scan.$force
: Iftrue
, forces a re-scan even if discovery ran earlier in the same script execution. Default isfalse
.$saveToCache
: Iftrue
(default) and a PSR-16 cache was provided to the builder, the results of this scan (discovered elements only) will be saved to the cache, overwriting previous cache content. Iffalse
or no cache is configured, results are not saved.
- Default Behavior: Calling
discover()
performs a fresh scan. It first clears previously discovered items from the cache$saveToCache
is true), then scans the filesystem, registers found elements (marking them as discovered), and finally saves the newly discovered elements to cache if$saveToCache
is true. - Implicit Cache Loading: When
ServerBuilder::build()
creates theRegistry
, theRegistry
constructor automatically attempts to load discovered elements from the cache (if a cache was configured and the cache key exists). Manually registered elements are added after this potential cache load. - Cache Content: Only elements found via discovery are stored in the cache. Manually registered elements are never cached.
Configuration (ServerBuilder
)
You can get a server builder instance by either calling new ServerBuilder
or more conveniently using Server::make()
. The available methods for configuring your server instance include:
withServerInfo(string $name, string $version)
: Required. Server identity.withLogger(LoggerInterface $logger)
: Optional. PSR-3 logger. Defaults toNullLogger
.withCache(CacheInterface $cache, int $ttl = 3600)
: Optional. PSR-16 cache for registry and client state. Defaults toArrayCache
only for the client state manager.withContainer(ContainerInterface $container)
: Optional. PSR-11 container for resolving your handler classes. Defaults toBasicContainer
.withLoop(LoopInterface $loop)
: Optional. ReactPHP event loop. Defaults toLoop::get()
.withCapabilities(Capabilities $capabilities)
: Optional. Configure advertised capabilities (e.g., resource subscriptions). UseCapabilities::forServer(...)
.withTool(...)
,withResource(...)
, etc.: Optional manual registration.
Running the Server (Transports)
The core Server
object doesn't handle network I/O directly. You activate it using a specific transport implementation passed to $server->listen($transport)
.
Stdio Transport
Handles communication over Standard Input/Output. Ideal for servers launched directly by an MCP client (like Cursor).
use PhpMcp\Server\Transports\StdioServerTransport; // ... build $server ... $transport = new StdioServerTransport(); // This blocks until the transport is closed (e.g., SIGINT/SIGTERM) $server->listen($transport);
Warning: When using
StdioServerTransport
, your application code (including tool/resource handlers) MUST NOT write arbitrary output toSTDOUT
(usingecho
,var_dump
, etc.).STDOUT
is reserved for sending framed JSON-RPC messages back to the client. UseSTDERR
for logging or debugging output:fwrite(STDERR, "Debug: Processing tool X\n"); // Or use a PSR-3 logger configured to write to STDERR: // $logger->debug("Processing tool X", ['param' => $value]);
HTTP Transport (HTTP+SSE)
Listens for HTTP connections, handling client messages via POST and sending server messages/notifications via Server-Sent Events (SSE).
use PhpMcp\Server\Transports\HttpServerTransport; // ... build $server ... $transport = new HttpServerTransport( host: '127.0.0.1', // Listen on all interfaces port: 8080, // Port to listen on mcpPathPrefix: 'mcp' // Base path for endpoints (/mcp/sse, /mcp/message) // sslContext: [...] // Optional: ReactPHP socket context for HTTPS ); // This blocks, starting the HTTP server and running the event loop $server->listen($transport);
Concurrency Requirement: The HttpServerTransport
relies on ReactPHP's non-blocking I/O model. It's designed to handle multiple concurrent SSE connections efficiently. Running this transport requires a PHP environment that supports an event loop and non-blocking operations. It will generally NOT work correctly with traditional synchronous web servers like Apache+mod_php or the built-in PHP development server. You should run the listen()
command using the PHP CLI in a persistent process (potentially managed by Supervisor, Docker, etc.).
Endpoints:
- SSE:
GET /{mcpPathPrefix}/sse
(e.g.,GET /mcp/sse
) - Client connects here. - Messages:
POST /{mcpPathPrefix}/message?clientId={clientId}
(e.g.,POST /mcp/message?clientId=sse_abc123
) - Client sends requests here. TheclientId
query parameter is essential for the server to route the message correctly to the state associated with the SSE connection. The server sends the POST path (including the generatedclientId
) via the initialendpoint
SSE event to the client, so you will never have to manually handle this.
Connecting MCP Clients
Instruct clients how to connect to your server:
stdio
: Provide the full command to execute your server script (e.g.,php /path/to/mcp-server.php
). The client needs execute permissions.http
: Provide the full URL to your SSE endpoint (e.g.,http://your.domain:8080/mcp/sse
). Ensure the server process runninglisten()
is accessible.
Refer to specific client documentation (Cursor, Claude Desktop, etc.) for their configuration format.
Attribute Details & Return Formatting {#attribute-details-and-return-formatting}
These attributes mark classes or methods to be found by the ->discover()
process.
#[McpTool]
Marks a method or an invokable class as an MCP Tool. Tools represent actions or functions the client can invoke, often with parameters.
Usage:
- On a Method: Place the attribute directly above a public, non-static method.
- On an Invokable Class: Place the attribute directly above a class definition that contains a public
__invoke
method. The__invoke
method will be treated as the tool's handler.
The attribute accepts the following parameters:
name
(optional): The name of the tool exposed to the client.- When on a method, defaults to the method name (e.g.,
addNumbers
becomesaddNumbers
). - When on an invokable class, defaults to the class's short name (e.g.,
class AdderTool
becomesAdderTool
).
- When on a method, defaults to the method name (e.g.,
description
(optional): A description for the tool. Defaults to the method's DocBlock summary (or the__invoke
method's summary if on a class).
The parameters (including name, type hints, and defaults) of the target method (or __invoke
) define the tool's input schema. The return type hint defines the output schema. DocBlock @param
and @return
descriptions are used for parameter/output descriptions.
Return Value Formatting
The value returned by your method determines the content sent back to the client. The library automatically formats common types:
null
: Returns empty content (if return type hint isvoid
) orTextContent
with(null)
.string
,int
,float
,bool
: Automatically wrapped inPhpMcp\Server\JsonRpc\Contents\TextContent
.array
,object
: Automatically JSON-encoded (pretty-printed) and wrapped inTextContent
.PhpMcp\Server\JsonRpc\Contents\Content
object(s): If you return an instance ofContent
(e.g.,TextContent
,ImageContent
,AudioContent
,ResourceContent
) or an array ofContent
objects, they are used directly. This gives you full control over the output format. Example:return TextContent::code('echo \'Hello\';', 'php');
- Exceptions: If your method throws an exception, a
TextContent
containing the error message and type is returned.
The method's return type hint (@return
tag in DocBlock) is used to generate the tool's output schema, but the actual formatting depends on the value returned at runtime.
Schema Generation
The server automatically generates JSON Schema for tool parameters based on:
- PHP type hints
- DocBlock annotations
- Schema attributes (for enhanced validation)
Examples:
/** * Fetches user details by ID. * * @param int $userId The ID of the user to fetch. * @param bool $includeEmail Include the email address? * @return array{id: int, name: string, email?: string} User details. */ #[McpTool(name: 'get_user')] public function getUserById(int $userId, bool $includeEmail = false): array { // ... implementation returning an array ... } /** * Process user data with nested structures. * * @param array{name: string, contact: array{email: string, phone?: string}} $userData * @param string[] $tags Tags associated with the user * @return array{success: bool, message: string} */ #[McpTool] public function processUserData(array $userData, array $tags): array { // Implementation } /** * Returns PHP code as formatted text. * * @return TextContent */ #[McpTool(name: 'get_php_code')] public function getPhpCode(): TextContent { return TextContent::code('<?php echo \'Hello World\';', 'php'); } /** * An invokable class acting as a tool. */ #[McpTool(description: 'An invokable adder tool.')] class AdderTool { /** * Adds two numbers. * @param int $a First number. * @param int $b Second number. * @return int The sum. */ public function __invoke(int $a, int $b): int { return $a + $b; } }
Additional Validation with #[Schema]
For enhanced schema generation and parameter validation, you can use the Schema
attribute:
use PhpMcp\Server\Attributes\Schema; use PhpMcp\Server\Attributes\Schema\Format; use PhpMcp\Server\Attributes\Schema\ArrayItems; use PhpMcp\Server\Attributes\Schema\Property; /** * Validates user information. */ #[McpTool] public function validateUser( #[Schema(format: 'email')] string $email, #[Schema(minItems: 2, uniqueItems: true)] array $tags ): bool { // Implementation }
The Schema attribute adds JSON Schema constraints like string formats, numeric ranges, array constraints, and object property validations.
#[McpResource]
Marks a method or an invokable class as representing a specific, static MCP Resource instance. Resources represent pieces of content or data identified by a URI. The target method (or __invoke
) will typically be called when a client performs a resources/read
for the specified URI.
Usage:
- On a Method: Place the attribute directly above a public, non-static method.
- On an Invokable Class: Place the attribute directly above a class definition that contains a public
__invoke
method. The__invoke
method will be treated as the resource handler.
The attribute accepts the following parameters:
uri
(required): The unique URI for this resource instance (e.g.,config://app/settings
,file:///data/status.txt
). Must conform to RFC 3986.name
(optional): Human-readable name. Defaults inferred from method name or class short name.description
(optional): Description. Defaults to DocBlock summary of the method or__invoke
.mimeType
(optional): The resource's MIME type (e.g.,text/plain
,application/json
).size
(optional): Resource size in bytes, if known and static.annotations
(optional): Array of MCP annotations (e.g.,['audience' => ['user']]
).
The target method (or __invoke
) should return the content of the resource.
Return Value Formatting
The return value determines the resource content:
string
: Treated as text content. MIME type is taken from the attribute or guessed (text/plain
,application/json
,text/html
).array
: If the attribute'smimeType
isapplication/json
(or containsjson
), the array is JSON-encoded. Otherwise, it attempts JSON encoding with a warning.stream resource
: Content is read from the stream.mimeType
must be provided in the attribute or defaults toapplication/octet-stream
.SplFileInfo
object: Content is read from the file.mimeType
is taken from the attribute or guessed.PhpMcp\Server\JsonRpc\Contents\EmbeddedResource
: Used directly. Gives full control over URI, MIME type, text/blob content.PhpMcp\Server\JsonRpc\Contents\ResourceContent
: The innerEmbeddedResource
is extracted and used.array{'blob': string, 'mimeType'?: string}
: Creates a blob resource.array{'text': string, 'mimeType'?: string}
: Creates a text resource.
#[McpResource(uri: 'status://system/load', mimeType: 'text/plain')] public function getSystemLoad(): string { return file_get_contents('/proc/loadavg'); } /** * An invokable class providing system load resource. */ #[McpResource(uri: 'status://system/load/invokable', mimeType: 'text/plain')] class SystemLoadResource { public function __invoke(): string { return file_get_contents('/proc/loadavg'); } }
#[McpResourceTemplate]
Marks a method or an invokable class that can generate resource instances based on a template URI. This is useful for resources whose URI contains variable parts (like user IDs or document IDs). The target method (or __invoke
) will be called when a client performs a resources/read
matching the template.
Usage:
- On a Method: Place the attribute directly above a public, non-static method.
- On an Invokable Class: Place the attribute directly above a class definition that contains a public
__invoke
method.
The attribute accepts the following parameters:
uriTemplate
(required): The URI template string, conforming to RFC 6570 (e.g.,user://{userId}/profile
,document://{docId}?format={fmt}
).name
,description
,mimeType
,annotations
(optional): Similar to#[McpResource]
, but describe the template itself. Defaults inferred from method/class name and DocBlocks.
The parameters of the target method (or __invoke
) must match the variables defined in the uriTemplate
. The method should return the content for the resolved resource instance.
Return Value Formatting
Same as #[McpResource]
(see above). The returned value represents the content of the resolved resource instance.
/** * Gets a user's profile data. * * @param string $userId The user ID from the URI. * @return array The user profile. */ #[McpResourceTemplate(uriTemplate: 'user://{userId}/profile', name: 'user_profile', mimeType: 'application/json')] public function getUserProfile(string $userId): array { // Fetch user profile for $userId return ['id' => $userId, /* ... */ ]; } /** * An invokable class providing user profiles via template. */ #[McpResourceTemplate(uriTemplate: 'user://{userId}/profile/invokable', name: 'user_profile_invokable', mimeType: 'application/json')] class UserProfileTemplate { /** * Gets a user's profile data. * @param string $userId The user ID from the URI. * @return array The user profile. */ public function __invoke(string $userId): array { // Fetch user profile for $userId return ['id' => $userId, 'source' => 'invokable', /* ... */ ]; } }
#[McpPrompt]
Marks a method or an invokable class as an MCP Prompt generator. Prompts are pre-defined templates or functions that generate conversational messages (like user or assistant turns) based on input parameters.
Usage:
- On a Method: Place the attribute directly above a public, non-static method.
- On an Invokable Class: Place the attribute directly above a class definition that contains a public
__invoke
method.
The attribute accepts the following parameters:
name
(optional): The prompt name. Defaults to method name or class short name.description
(optional): Description. Defaults to DocBlock summary of the method or__invoke
.
Method parameters (or __invoke
parameters) define the prompt's input arguments. The method should return the prompt content, typically an array conforming to the MCP message structure.
Return Value Formatting
Your method should return the prompt messages in one of these formats:
- Array of
PhpMcp\Server\JsonRpc\Contents\PromptMessage
objects: The recommended way for full control.PromptMessage::user(string|Content $content)
PromptMessage::assistant(string|Content $content)
- The
$content
can be a simple string (becomesTextContent
) or anyContent
object (TextContent
,ImageContent
,ResourceContent
, etc.).
- Simple list array:
[['role' => 'user', 'content' => 'Some text'], ['role' => 'assistant', 'content' => $someContentObject]]
role
must be'user'
or'assistant'
.content
can be a string (becomesTextContent
) or aContent
object.content
can also be an array structure like['type' => 'image', 'data' => '...', 'mimeType' => '...']
,['type' => 'text', 'text' => '...']
, or['type' => 'resource', 'resource' => ['uri' => '...', 'text|blob' => '...']]
.
- Simple associative array:
['user' => 'User prompt text', 'assistant' => 'Optional assistant prefix']
(converted to one or twoPromptMessage
s withTextContent
).
/** * Generates a prompt to summarize text. * * @param string $textToSummarize The text to summarize. * @return array The prompt messages. */ #[McpPrompt(name: 'summarize')] public function generateSummaryPrompt(string $textToSummarize): array { return [ ['role' => 'user', 'content' => "Summarize the following text:\n\n{$textToSummarize}"], ]; } /** * An invokable class generating a summary prompt. */ #[McpPrompt(name: 'summarize_invokable')] class SummarizePrompt { /** * Generates a prompt to summarize text. * @param string $textToSummarize The text to summarize. * @return array The prompt messages. */ public function __invoke(string $textToSummarize): array { return [ ['role' => 'user', 'content' => "[Invokable] Summarize: {$textToSummarize}"], ]; } }
Error Handling
The server uses specific exceptions inheriting from PhpMcp\Server\Exception\McpServerException
. The Protocol
catches these and Throwable
during message processing, converting them to appropriate JSON-RPC error responses. Transport-level errors are emitted via the transport's error
event.
Examples
See the examples/
directory:
01-discovery-stdio-calculator/
: Basicstdio
server demonstrating attribute discovery for a simple calculator.02-discovery-http-userprofile/
:http+sse
server using discovery for a user profile service.03-manual-registration-stdio/
:stdio
server showcasing only manual element registration.04-combined-registration-http/
:http+sse
server combining manual and discovered elements, demonstrating precedence.05-stdio-env-variables/
:stdio
server with a tool that uses environment variables passed by the MCP client.06-custom-dependencies-stdio/
:stdio
server showing DI container usage for injecting services into MCP handlers (Task Manager example).07-complex-tool-schema-http/
:http+sse
server with a tool demonstrating complex input schemas (optionals, defaults, enums).
Testing
composer install --dev composer test composer test:coverage # Requires Xdebug
Contributing
Please see CONTRIBUTING.md.
License
The MIT License (MIT). See LICENSE.