Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
/tests export-ignore
/.php-cs-fixer.dist.php export-ignore
/Makefile export-ignore
/mkdocs.yml export-ignore
/requirements-docs.txt export-ignore
/phpdoc.dist.xml
/phpstan* export-ignore
/phpunit.xml.dist export-ignore
76 changes: 63 additions & 13 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,47 @@
name: Deploy Documentation
name: Documentation

# The site is built by `make docs`: Zensical renders the guides under docs/
# (see mkdocs.yml) and phpDocumentor renders the API reference into /api/.
# Pull requests build only — the build is strict, so a broken internal link
# fails CI instead of shipping a dead link to the site.
#
# NOTE: deployment uses the official GitHub Pages actions, so the repository's
# Pages source must be set to "GitHub Actions" (Settings → Pages) instead of
# the gh-pages branch this workflow published to before.

on:
release:
types: [published]
push:
branches: [main]
# GitHub Actions does not support YAML anchors, so this list is repeated
# for pull_request below — keep the two in sync.
paths:
- docs/**
- mkdocs.yml
- requirements-docs.txt
- phpdoc.dist.xml
- src/**
- Makefile
- .github/workflows/docs.yml
pull_request:
paths:
- docs/**
- mkdocs.yml
- requirements-docs.txt
- phpdoc.dist.xml
- src/**
- Makefile
- .github/workflows/docs.yml
workflow_dispatch:

permissions:
contents: write
contents: read

concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -21,16 +53,34 @@ jobs:
php-version: '8.4'
coverage: "none"

- name: Install Composer
- name: Install Composer dependencies
uses: "ramsey/composer-install@v4"

- name: Generate Documentation
- name: Install uv
# setup-uv publishes no floating major tag; pin the exact release.
uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true

- name: Build documentation
run: make docs

- name: Deploy to gh-pages branch
uses: peaceiris/actions-gh-pages@v4
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./.phpdoc/build
enable_jekyll: false
cname: php.sdk.modelcontextprotocol.io
path: ./site

deploy:
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
4 changes: 3 additions & 1 deletion .github/workflows/pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,7 @@ jobs:
- name: PHPStan
run: vendor/bin/phpstan analyse

# Only the phpDocumentor half: this job is PHP-only, and the Zensical
# guides are built (strictly) by the Documentation workflow.
- name: Documentation
run: make docs
run: make docs-api
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ tests/Conformance/logs/*.log
# phpDocumentor
.phpdoc/build/
.phpdoc/cache/

# Documentation site (make docs)
/site/
/.cache/
2 changes: 1 addition & 1 deletion .phpdoc/template/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{% set topMenu = {
"menu": [
{ "name": "Guides", "url": "docs/index.html"},
{ "name": "Guides", "url": "/"},
{ "name": "Specification", "url": "https://modelcontextprotocol.io/" }
],
"social": [
Expand Down
2 changes: 1 addition & 1 deletion .phpdoc/template/components/header-title.html.twig
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h1 class="phpdocumentor-title">
<a href="{{ path('/docs/index.html') }}" class="phpdocumentor-title__link">
<a href="/" class="phpdocumentor-title__link">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 195 195" fill="none">
<path d="M25 97.8528L92.8823 29.9706C102.255 20.598 117.451 20.598 126.823 29.9706V29.9706C136.196 39.3431 136.196 54.5391 126.823 63.9117L75.5581 115.177" stroke="black" stroke-width="12" stroke-linecap="round"/>
<path d="M76.2653 114.47L126.823 63.9117C136.196 54.5391 151.392 54.5391 160.765 63.9117L161.118 64.2652C170.491 73.6378 170.491 88.8338 161.118 98.2063L99.7248 159.6C96.6006 162.724 96.6006 167.789 99.7248 170.913L112.331 183.52" stroke="black" stroke-width="12" stroke-linecap="round"/>
Expand Down
27 changes: 22 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
.PHONY: deps-stable deps-low cs phpstan tests unit-tests inspector-tests coverage ci ci-stable ci-lowest conformance-tests conformance-server conformance-client docs
.PHONY: deps-stable deps-low cs phpstan tests unit-tests inspector-tests coverage ci ci-stable ci-lowest conformance-tests conformance-server conformance-client docs docs-guides docs-api docs-serve

# The documentation toolchain is Python (Zensical, see requirements-docs.txt),
# run through uv so no virtualenv has to be managed by hand:
# https://docs.astral.sh/uv/getting-started/installation/
DOCS_RUN = uv run --no-project --with-requirements requirements-docs.txt --

deps-stable:
composer update --prefer-stable
Expand Down Expand Up @@ -46,7 +51,19 @@ ci-stable: deps-stable cs phpstan tests

ci-lowest: deps-low cs phpstan tests

docs:
vendor/bin/phpdoc
@grep -q 'No errors have been found' .phpdoc/build/reports/errors.html || \
(echo "Documentation errors found. See build/docs/reports/errors.html" && exit 1)
# The published site is the guides (Zensical) with the phpDocumentor API
# reference mounted at /api/. `zensical build` wipes site/, so it runs first.
docs: docs-guides docs-api
rm -rf site/api
cp -a .phpdoc/build/api site/api

docs-guides:
$(DOCS_RUN) zensical build --strict

docs-api:
vendor/bin/phpdoc --no-interaction
@grep -q 'No errors have been found' .phpdoc/build/api/reports/errors.html || \
(echo "Documentation errors found. See .phpdoc/build/api/reports/errors.html" && exit 1)

docs-serve:
$(DOCS_RUN) zensical serve
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ $server = Server::builder()
->build();
```

[→ Server Documentation](docs/server-builder.md)
[→ Server Documentation](https://php.sdk.modelcontextprotocol.io/run/server-builder/)

## Client SDK

Expand Down Expand Up @@ -272,24 +272,25 @@ $transport = new HttpTransport('http://localhost:8000');
$client->connect($transport);
```

[→ Client Documentation](docs/client.md)
[→ Client Documentation](https://php.sdk.modelcontextprotocol.io/client/)

## Documentation

The full documentation is published at **[php.sdk.modelcontextprotocol.io](https://php.sdk.modelcontextprotocol.io/)**.

### Core Concepts

- **[Server Builder](docs/server-builder.md)** — Complete ServerBuilder reference and configuration
- **[Client](docs/client.md)** — Client SDK for connecting to and communicating with MCP servers
- **[Transports](docs/transports.md)** — STDIO and HTTP transport setup and usage
- **[MCP Elements](docs/mcp-elements.md)** — Creating tools, resources, prompts, and templates
- **[Server-Client Communication](docs/server-client-communication.md)** — Sampling, logging, progress, and notifications
- **[Protocol Extensions](docs/extensions.md)** — Opt-in protocol extensions announced during capability negotiation, including MCP Apps (HTML UI resources)
- **[Authorization](docs/authorization.md)** — OAuth and authorization setup for HTTP transport
- **[Events](docs/events.md)** — Hooking into server lifecycle with events
- **[Get started](https://php.sdk.modelcontextprotocol.io/get-started/)** — Install the SDK and build your first server
- **[Servers](https://php.sdk.modelcontextprotocol.io/servers/)** — Tools, resources, resource templates, prompts, and how to register them
- **[Inside your handler](https://php.sdk.modelcontextprotocol.io/handlers/)** — Sampling, logging, progress, and notifications from within a handler
- **[Running your server](https://php.sdk.modelcontextprotocol.io/run/)** — Server builder, STDIO and HTTP transports, framework integration, sessions, authorization
- **[Clients](https://php.sdk.modelcontextprotocol.io/client/)** — Client SDK for connecting to and communicating with MCP servers
- **[Advanced](https://php.sdk.modelcontextprotocol.io/advanced/)** — Events, protocol extensions (including MCP Apps), and custom message handlers
- **[API Reference](https://php.sdk.modelcontextprotocol.io/api/)** — Generated class reference

### Learning & Examples

- **[Examples](docs/examples.md)** — Comprehensive example walkthroughs for servers and clients
- **[Examples](https://php.sdk.modelcontextprotocol.io/examples/)** — Comprehensive example walkthroughs for servers and clients
- **[ROADMAP.md](ROADMAP.md)** — Planned features and development roadmap

## External Resources
Expand Down
2 changes: 1 addition & 1 deletion adr/0001-oauth-authorization-server-out-of-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,5 @@ If you need an authorization server (token issuance, client registration, login,
validator seams. The MCP server validates the tokens it issues; it does not issue them
itself.

See [`../docs/authorization.md`](../docs/authorization.md) for the supported Resource Server
See [`../docs/run/authorization.md`](../docs/run/authorization.md) for the supported Resource Server
and delegation setup.
1 change: 1 addition & 0 deletions docs/.overrides/.icons/mcp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
php.sdk.modelcontextprotocol.io
97 changes: 97 additions & 0 deletions docs/advanced/custom-handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Custom Message Handlers

**Low-level escape hatch.** Custom message handlers run before the SDK's built-in handlers and give you total control over
individual JSON-RPC messages. They do not receive the builder's registry, container, or discovery output unless you pass
those dependencies in yourself.

> **Warning**: Custom message handlers bypass discovery, manual capability registration, and container lookups (unless
> you explicitly pass them). Tools, resources, and prompts you register elsewhere will not show up unless your handler
> loads and executes them manually. Reach for this API only when you need that level of control and are comfortable
> taking on the additional plumbing.

## Request Handlers

Handle JSON-RPC requests (messages with an `id` that expect a response). Request handlers **must** return either a
`Response` or an `Error` object.

Attach request handlers with `addRequestHandler()` (single) or `addRequestHandlers()` (multiple). You can call these
methods as many times as needed; each call prepends the handlers so they execute before the defaults:

```php
$server = Server::builder()
->addRequestHandler(new CustomListToolsHandler())
->addRequestHandlers([
new CustomCallToolHandler(),
new CustomGetPromptHandler(),
])
->build();
```

Request handlers implement `RequestHandlerInterface`:

```php
use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\Request;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Server\Handler\Request\RequestHandlerInterface;
use Mcp\Server\Session\SessionInterface;

interface RequestHandlerInterface
{
public function supports(Request $request): bool;

public function handle(Request $request, SessionInterface $session): Response|Error;
}
```

- `supports()` decides if the handler should process the incoming request
- `handle()` **must** return a `Response` (on success) or an `Error` (on failure)

## Notification Handlers

Handle JSON-RPC notifications (messages without an `id` that don't expect a response). Notification handlers **do not**
return anything - they perform side effects only.

Attach notification handlers with `addNotificationHandler()` (single) or `addNotificationHandlers()` (multiple):

```php
// Handlers are your own classes implementing NotificationHandlerInterface;
// the SDK ships only its internal ones.
$server = Server::builder()
->addNotificationHandler(new AuditNotificationHandler($auditLog))
->addNotificationHandlers([
new MetricsNotificationHandler($metrics),
new CancellationNotificationHandler(),
])
->build();
```

Notification handlers implement `NotificationHandlerInterface`:

```php
use Mcp\Schema\JsonRpc\Notification;
use Mcp\Server\Handler\Notification\NotificationHandlerInterface;
use Mcp\Server\Session\SessionInterface;

interface NotificationHandlerInterface
{
public function supports(Notification $notification): bool;

public function handle(Notification $notification, SessionInterface $session): void;
}
```

- `supports()` decides if the handler should process the incoming notification
- `handle()` performs side effects but **does not** return a value (notifications have no response)

## Key Differences

| Handler Type | Interface | Returns | Use Case |
|-------------|-----------|---------|----------|
| Request Handler | `RequestHandlerInterface` | `Response\|Error` | Handle requests that need responses (e.g., `tools/list`, `tools/call`) |
| Notification Handler | `NotificationHandlerInterface` | `void` | Handle fire-and-forget notifications (e.g., `notifications/initialized`, `notifications/progress`) |

## Example

Check out `examples/server/custom-method-handlers/server.php` for a complete example showing how to implement
custom `tools/list` and `tools/call` request handlers independently of the registry.
13 changes: 2 additions & 11 deletions docs/events.md → docs/advanced/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@

The MCP SDK provides a PSR-14 compatible event system that allows you to hook into the server's lifecycle. Events enable request/response modification, and other user-defined behaviors.

## Table of Contents

- [Setup](#setup)
- [Protocol Events](#protocol-events)
- [RequestEvent](#requestevent)
- [ResponseEvent](#responseevent)
- [ErrorEvent](#errorevent)
- [NotificationEvent](#notificationevent)
- [List Change Events](#list-change-events)

## Setup

Configure an event dispatcher when building your server:

```php
use Mcp\Event\RequestEvent;
use Mcp\Server;
use Symfony\Component\EventDispatcher\EventDispatcher;

Expand Down Expand Up @@ -67,7 +58,7 @@ The SDK dispatches 4 broad event types at the protocol level, allowing you to ob
**Properties**:
- `getError(): Error` - The error being sent
- `setError(Error $error): void` - Modify the error before sending
- `getRequest(): Request` - The original request (null for parse errors)
- `getRequest(): Request` - The original request. Messages that fail to parse are rejected before this event, so a listener never sees them.
- `getThrowable(): ?\Throwable` - The exception that caused the error (if any)
- `getSession(): SessionInterface` - The current session

Expand Down
2 changes: 1 addition & 1 deletion docs/extensions.md → docs/advanced/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,6 @@ handshake:
See the [`ext-apps` repository][ext-apps] for the full protocol, official
TypeScript SDK (`@modelcontextprotocol/ext-apps`), and view-side examples. A
working minimal view is included in
[`examples/server/mcp-apps/weather-app.html`](../examples/server/mcp-apps/weather-app.html).
[`examples/server/mcp-apps/weather-app.html`](https://github.com/modelcontextprotocol/php-sdk/blob/main/examples/server/mcp-apps/weather-app.html).

[ext-apps]: https://github.com/modelcontextprotocol/ext-apps
10 changes: 10 additions & 0 deletions docs/advanced/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Advanced

Everything here is optional. A working server needs none of it.

* **[Events](events.md)** — PSR-14 events dispatched around every request, response,
error, and notification. Useful for metrics, audit logs, and debugging.
* **[Protocol extensions](extensions.md)** — opt-in extensions announced during
capability negotiation, including MCP Apps (HTML UI resources).
* **[Custom message handlers](custom-handlers.md)** — taking over a JSON-RPC method the
SDK does not implement, or overriding one it does.
Loading