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.phpClient examples run directly:
php examples/client/stdio_discovery_calculator.phpstateless-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. By default, a client probes
the server with server/discover and falls back to the initialize handshake when the server
doesn't speak 2026-07-28. The example turns that fallback off with
setFallbackProtocolVersion(null), so it only connects on 2026-07-28. Then it lists and calls
tools without a session:
php -S 127.0.0.1:8000 examples/server/stateless-lifecycle/server.php &
php examples/client/stateless_lifecycle_client.phpSee Protocol version for the API it uses.
