Every example lives in examples/
and runs against the dependencies installed in the repository root. Each server example is a
server.php whose bootstrap picks the transport from the SAPI it runs under:
# STDIO transport
php examples/server/discovery-calculator/server.php
# Streamable HTTP transport
php -S 127.0.0.1:8000 examples/server/discovery-userprofile/server.php
# Interactive testing with the MCP Inspector
npx @modelcontextprotocol/inspector php examples/server/discovery-calculator/server.php| Example | What it shows | Docs |
|---|---|---|
discovery-calculator |
Attribute discovery of tools, resources and prompts; ResourceLink content |
Tools |
discovery-userprofile |
Resource templates with completion providers; FileSessionStore |
Resource templates, Completions |
explicit-registration |
Manual addTool()/addResource()/addResourceTemplate()/addPrompt() without discovery |
Registering elements |
combined-registration |
Discovery and manual registration combined, and which wins on conflict | Registering elements |
cached-discovery |
Caching the discovery scan in a PSR-16 cache | Server builder |
custom-dependencies |
Handlers pulling services from a PSR-11 container | Server builder |
complex-tool-schema |
Rich input schemas from typed parameters, PHP enums and defaults | Schema generation |
schema-showcase |
#[Schema] constraint attributes: formats, patterns, ranges |
Schema generation |
env-variables |
Configuring a server through environment variables | Server builder |
client-communication |
Sampling, roots, progress and log messages from inside a handler | Talking back to the client |
client-logging |
Structured log notifications through the ClientLogger |
Logging |
elicitation |
Asking the user for input mid-call with ClientGateway::elicit() and typed elicitation schemas, on either protocol era |
Asking for input |
custom-method-handlers |
Registering handlers for custom JSON-RPC methods | Custom message handlers |
mcp-apps |
The MCP Apps extension: a tool that ships an interactive HTML view | Protocol extensions |
stateless-lifecycle |
Revision 2026-07-28: cache policy, request state, notification bus |
Serving both eras, Caching, Subscriptions |
oauth-keycloak |
OAuth authorization against a Keycloak instance (own README) | Authorization |
oauth-microsoft |
OAuth authorization against Microsoft Entra ID (own README) | Authorization |
| Example | What it shows | Docs |
|---|---|---|
stdio_discovery_calculator.php |
Connecting over STDIO, listing and calling tools, reading resources | Connecting to a server |
http_discovery_calculator.php |
The same conversation over the Streamable HTTP transport | Transports |
stdio_client_communication.php |
Answering server-initiated sampling, log and progress messages | Server-initiated requests |
http_client_communication.php |
The same handlers over HTTP — see the note on PHP's built-in server below | Server-initiated requests |
stdio_elicitation.php |
Answering elicitation requests from an interactive prompt | Server-initiated requests |
stdio_roots.php |
Exposing workspace roots and signalling roots/list_changed |
Server-initiated requests |
stateless_lifecycle_client.php |
A client pinned to revision 2026-07-28 — see Modern-era client |
Connecting to a server |
Client examples run directly:
php examples/client/stdio_discovery_calculator.phpNote: PHP's built-in development server handles one request at a time, so the sampling round-trip in
http_client_communication.phpwill not complete under a plainphp -S. Start it with worker processes instead:PHP_CLI_SERVER_WORKERS=2 php -S 127.0.0.1:8000 ….
stateless-lifecycle/server.php answers both eras on the same URL. On revision 2026-07-28
every request carries its own protocol version and client capabilities, so a call is a single
POST with no handshake before it:
php -S 127.0.0.1:8000 examples/server/stateless-lifecycle/server.phpcurl -sS http://127.0.0.1:8000/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: server/discover' \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}}}}'The example's header comments document the equivalent handshake-era conversation against the same endpoint. Serving both eras explains how the routing works.
stateless_lifecycle_client.php drives the server above from PHP: it pins
ProtocolVersion::V2026_07_28, skips the handshake, discovers the server through
server/discover and calls a tool — one process, no session:
php -S 127.0.0.1:8000 examples/server/stateless-lifecycle/server.php &
php examples/client/stateless_lifecycle_client.phpSee Clients on the modern revision for the API it uses.