Compare commits

..
470 changed files with 4779 additions and 37216 deletions

2
.gitattributes vendored
View file

@ -5,8 +5,6 @@
/tests export-ignore /tests export-ignore
/.php-cs-fixer.dist.php export-ignore /.php-cs-fixer.dist.php export-ignore
/Makefile export-ignore /Makefile export-ignore
/mkdocs.yml export-ignore
/requirements-docs.txt export-ignore
/phpdoc.dist.xml /phpdoc.dist.xml
/phpstan* export-ignore /phpstan* export-ignore
/phpunit.xml.dist export-ignore /phpunit.xml.dist export-ignore

View file

@ -1,189 +0,0 @@
name: conformance-weekly
# Tracks the newest published @modelcontextprotocol/conformance; the
# on:pull_request pipeline pins an exact version instead. Also publishes
# score badges to the orphan `badges` branch, consumed by the README.
on:
schedule:
- cron: '0 6 * * 1' # Mondays 06:00 UTC
workflow_dispatch:
permissions:
contents: write
issues: write
jobs:
server:
name: conformance / server (${{ matrix.spec-version }}, ${{ matrix.dist-tag }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- spec-version: '2025-11-25'
dist-tag: latest
path: '/'
baseline: conformance-baseline-2025-11-25.yml
- spec-version: '2026-07-28'
dist-tag: alpha
path: '/'
baseline: conformance-baseline-2026-07-28.yml
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '22'
- run: composer install --prefer-dist --no-progress --no-interaction
- name: Start conformance server
run: |
mkdir -p tests/Conformance/sessions tests/Conformance/logs
chmod -R 777 tests/Conformance/sessions tests/Conformance/logs
docker compose -f tests/Conformance/Fixtures/docker-compose.yml up -d
sleep 5
- name: Run conformance tests
working-directory: ./tests/Conformance
run: >-
npx --yes @modelcontextprotocol/conformance@${{ matrix.dist-tag }} server
--url http://localhost:8000${{ matrix.path }}
--suite all
--spec-version ${{ matrix.spec-version }}
--expected-failures ${{ matrix.baseline }}
--output-dir results
- name: Generate score badge
if: always()
run: php tests/Conformance/score.php server ${{ matrix.spec-version }}
- name: Show docker logs on failure
if: failure()
run: docker compose -f tests/Conformance/Fixtures/docker-compose.yml logs
- name: Upload conformance results
if: failure()
uses: actions/upload-artifact@v7
with:
name: conformance-server-results-${{ matrix.spec-version }}
path: |
tests/Conformance/logs
tests/Conformance/results
- name: Upload score badge
if: always()
uses: actions/upload-artifact@v7
with:
name: badge-server-${{ matrix.spec-version }}
path: tests/Conformance/server-conformance-${{ matrix.spec-version }}.json
client:
name: conformance / client (${{ matrix.spec-version }}, ${{ matrix.dist-tag }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- spec-version: '2025-11-25'
dist-tag: latest
baseline: conformance-baseline-2025-11-25.yml
- spec-version: '2026-07-28'
dist-tag: alpha
baseline: conformance-baseline-2026-07-28.yml
steps:
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none
- uses: actions/setup-node@v7
with:
node-version: '22'
- run: composer install --prefer-dist --no-progress --no-interaction
- run: mkdir -p tests/Conformance/logs
- name: Run conformance tests
working-directory: ./tests/Conformance
run: >-
npx --yes @modelcontextprotocol/conformance@${{ matrix.dist-tag }} client
--command "php ${{ github.workspace }}/tests/Conformance/client.php"
--suite all
--spec-version ${{ matrix.spec-version }}
--expected-failures ${{ matrix.baseline }}
--output-dir results
- name: Generate score badge
if: always()
run: php tests/Conformance/score.php client ${{ matrix.spec-version }}
- name: Upload conformance results
if: failure()
uses: actions/upload-artifact@v7
with:
name: conformance-client-results-${{ matrix.spec-version }}
path: |
tests/Conformance/logs
tests/Conformance/results
- name: Upload score badge
if: always()
uses: actions/upload-artifact@v7
with:
name: badge-client-${{ matrix.spec-version }}
path: tests/Conformance/client-conformance-${{ matrix.spec-version }}.json
notify:
name: Open issue on failure
runs-on: ubuntu-latest
needs: [server, client]
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: File or comment tracking issue
run: |
existing=$(gh issue list --label conformance-weekly --state open --json number --jq '.[0].number // empty')
if [ -n "$existing" ]; then
gh issue comment "$existing" --body "New failure on $(date -u +%FT%TZ): $RUN_URL"
else
gh issue create \
--title '[conformance] Weekly conformance run failed' \
--label conformance-weekly \
--body "Conformance against the newest published \`@modelcontextprotocol/conformance\` failed.
- Run: $RUN_URL
- Trigger: $EVENT_NAME
- Date: $(date -u +%FT%TZ)
Upstream likely published a release whose scenarios the SDK does not satisfy. Either fix the SDK, update the conformance fixtures, or add the new failure to the matching \`tests/Conformance/conformance-baseline-<spec-version>.yml\`."
fi
publish:
name: Publish conformance badges
runs-on: ubuntu-latest
needs: [server, client]
if: ${{ !cancelled() && github.repository == 'modelcontextprotocol/php-sdk' }}
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
pattern: badge-*
path: badges-in
merge-multiple: true
- name: Publish to badges branch
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git ls-remote --exit-code --heads origin badges >/dev/null 2>&1; then
git fetch origin badges
git worktree add badges-wt badges
else
git worktree add --detach badges-wt
git -C badges-wt checkout --orphan badges
git -C badges-wt rm -rf --quiet . >/dev/null 2>&1 || true
fi
rm -f badges-wt/server-conformance.json badges-wt/client-conformance.json
cp badges-in/*.json badges-wt/
cd badges-wt
git add -A
if git diff --cached --quiet; then
echo "Conformance scores unchanged."
else
git commit -m "Update conformance score badges"
git push origin badges
fi

View file

@ -1,29 +1,19 @@
name: Documentation name: Deploy 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.
on: on:
push: release:
branches: [main] types: [published]
pull_request:
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: read contents: write
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
build: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -31,33 +21,15 @@ jobs:
php-version: '8.4' php-version: '8.4'
coverage: "none" coverage: "none"
- name: Install Composer dependencies - name: Install Composer
uses: "ramsey/composer-install@v4" uses: "ramsey/composer-install@v4"
- name: Install uv - name: Generate Documentation
uses: astral-sh/setup-uv@v10.1.0
with:
enable-cache: true
- name: Build documentation
run: make docs run: make docs
- name: Upload Pages artifact - name: Deploy to gh-pages branch
uses: actions/upload-pages-artifact@v5 uses: peaceiris/actions-gh-pages@v4
with: with:
path: ./site github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./.phpdoc/build
deploy: enable_jekyll: false
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

View file

@ -5,10 +5,6 @@ permissions:
contents: read contents: read
pull-requests: write pull-requests: write
env:
# Pinned exact version; conformance-weekly.yaml tracks upstream releases.
CONFORMANCE_PKG: "@modelcontextprotocol/conformance@0.2.0-alpha.11"
jobs: jobs:
unit: unit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -36,7 +32,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -53,33 +49,11 @@ jobs:
- name: Tests - name: Tests
run: vendor/bin/phpunit --testsuite=unit run: vendor/bin/phpunit --testsuite=unit
integration:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.1', '8.2', '8.3', '8.4', '8.5']
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: "none"
- name: Install Composer
uses: "ramsey/composer-install@v4"
- name: Tests
run: vendor/bin/phpunit --testsuite=integration
inspector: inspector:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -88,7 +62,7 @@ jobs:
coverage: "none" coverage: "none"
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v7 uses: actions/setup-node@v6
with: with:
node-version: '22' node-version: '22'
@ -99,25 +73,14 @@ jobs:
run: vendor/bin/phpunit --testsuite=inspector run: vendor/bin/phpunit --testsuite=inspector
conformance-server: conformance-server:
name: conformance / server (${{ matrix.spec-version }}) name: conformance / server
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- spec-version: '2025-11-25'
path: '/'
baseline: conformance-baseline-2025-11-25.yml
- spec-version: '2026-07-28'
path: '/'
baseline: conformance-baseline-2026-07-28.yml
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v7 uses: actions/setup-node@v6
with: with:
node-version: '22' node-version: '22'
@ -133,12 +96,7 @@ jobs:
- name: Run conformance tests - name: Run conformance tests
working-directory: ./tests/Conformance working-directory: ./tests/Conformance
run: >- run: npx @modelcontextprotocol/conformance server --url http://localhost:8000/ --expected-failures conformance-baseline.yml
npx --yes ${{ env.CONFORMANCE_PKG }} server
--url http://localhost:8000${{ matrix.path }}
--suite all
--spec-version ${{ matrix.spec-version }}
--expected-failures ${{ matrix.baseline }}
- name: Show logs on failure - name: Show logs on failure
if: failure() if: failure()
@ -166,20 +124,11 @@ jobs:
run: docker compose -f tests/Conformance/Fixtures/docker-compose.yml down run: docker compose -f tests/Conformance/Fixtures/docker-compose.yml down
conformance-client: conformance-client:
name: conformance / client (${{ matrix.spec-version }}) name: conformance / client
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- spec-version: '2025-11-25'
baseline: conformance-baseline-2025-11-25.yml
- spec-version: '2026-07-28'
baseline: conformance-baseline-2026-07-28.yml
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -188,7 +137,7 @@ jobs:
coverage: "none" coverage: "none"
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v7 uses: actions/setup-node@v6
with: with:
node-version: '22' node-version: '22'
@ -200,12 +149,7 @@ jobs:
- name: Run client conformance tests - name: Run client conformance tests
working-directory: ./tests/Conformance working-directory: ./tests/Conformance
run: >- run: npx @modelcontextprotocol/conformance client --command "php ${{ github.workspace }}/tests/Conformance/client.php" --suite all --expected-failures conformance-baseline.yml
npx --yes ${{ env.CONFORMANCE_PKG }} client
--command "php ${{ github.workspace }}/tests/Conformance/client.php"
--suite all
--spec-version ${{ matrix.spec-version }}
--expected-failures ${{ matrix.baseline }}
- name: Show logs on failure - name: Show logs on failure
if: failure() if: failure()
@ -224,7 +168,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -244,7 +188,5 @@ jobs:
- name: PHPStan - name: PHPStan
run: vendor/bin/phpstan analyse 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 - name: Documentation
run: make docs-api run: make docs

8
.gitignore vendored
View file

@ -6,16 +6,10 @@ vendor
examples/**/dev.log examples/**/dev.log
examples/**/cache examples/**/cache
examples/**/sessions examples/**/sessions
tests/Conformance/client-conformance.json tests/Conformance/results
tests/Conformance/server-conformance.json
tests/Conformance/results*
tests/Conformance/sessions tests/Conformance/sessions
tests/Conformance/logs/*.log tests/Conformance/logs/*.log
# phpDocumentor # phpDocumentor
.phpdoc/build/ .phpdoc/build/
.phpdoc/cache/ .phpdoc/cache/
# Documentation site (make docs)
/site/
/.cache/

View file

@ -2,7 +2,7 @@
{% set topMenu = { {% set topMenu = {
"menu": [ "menu": [
{ "name": "Guides", "url": "/"}, { "name": "Guides", "url": "docs/index.html"},
{ "name": "Specification", "url": "https://modelcontextprotocol.io/" } { "name": "Specification", "url": "https://modelcontextprotocol.io/" }
], ],
"social": [ "social": [

View file

@ -1,5 +1,5 @@
<h1 class="phpdocumentor-title"> <h1 class="phpdocumentor-title">
<a href="/" class="phpdocumentor-title__link"> <a href="{{ path('/docs/index.html') }}" class="phpdocumentor-title__link">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 195 195" fill="none"> <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="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"/> <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"/>

View file

@ -2,89 +2,6 @@
All notable changes to `mcp/sdk` will be documented in this file. All notable changes to `mcp/sdk` will be documented in this file.
0.9.0
-----
* [BC Break] Remove the `providerClass` argument of `#[CompletionProvider]`. Use `provider:`, which takes the same class-string and is now the first positional argument.
* Add `HttpTransport::getSessionId()` to read the server-minted `Mcp-Session-Id`: a request-scoped caller can persist it and pass it back through the constructor's `$headers` on a later transport. Always `null` on `2026-07-28`, which removed protocol-level sessions.
* Fix OIDC discovery rejecting issuers with a trailing slash (e.g. Authentik, Auth0).
0.8.0
-----
* Support for MCP spec version 2026-07-28 incl. stateless HTTP transport
* Negotiate the protocol revision during `initialize`: the server counter-offers a revision it supports and the client fails the handshake instead of continuing on an unagreed one. Adds `Client::getProtocolVersion()`.
* Serve both protocol eras from one endpoint: `StreamableHttpTransport` classifies each request (`InboundClassifier`) and routes it to the matching dispatcher, so one URL answers modern and handshake-era clients alike. `Builder::withoutModernEra()` opts out, `Builder::setModernVersions()` narrows the modern leg.
* Speak the lifecycle from the client: `Client` opens with `server/discover`, stamps `_meta` with version, capabilities and client info, and sends the `Mcp-Method`/`Mcp-Name`/`Mcp-Param-*` headers (via `Client\Stateless\ToolCatalog`). `Schema\Wire\McpHeader` holds the shared header names.
* Add multi round-trip requests (SEP-2322): a handler returning `InputRequiredResult` yields `resultType: "input_required"` with an opaque, signed `requestState` (`RequestStateCodec`, key via `Builder::setRequestState()`); the client retries with `inputResponses`, read through `RequestContext::getInputContext()` and its typed `elicitResult()`/`samplingResult()`/`rootsResult()`. On the client, `InputRequestResolver` answers such results automatically from the host's elicitation, sampling and roots handlers.
* Serve `ClientGateway::elicit()`/`elicitUrl()` on every revision: where the client cannot be asked mid-request, `Server\Stateless\ElicitationReplay` turns the ask into `input_required` and resumes once re-sent — the handler is entered once per ask. `InputRequiredShim` does the reverse for handshake-era clients. `sample()`/`listRoots()` raise a `LogicException` on `2026-07-28`, which removed them.
* Run stateless handlers in a fiber so `$gateway->progress()`/`log()` stream over SSE when the handler emits something and the client accepts `text/event-stream`; honour `io.modelcontextprotocol/logLevel` (SEP-2575). Adds `LoggingLevel::severity()`/`isAtLeast()`.
* Deliver notifications on `subscriptions/listen` (SEP-2575) via `NotificationBusInterface` — `InMemoryNotificationBus` for persistent runtimes, `Psr16NotificationBus` for PHP-FPM — set with `Builder::setNotificationBus()`; `Builder::setSubscriptionLifetime()` replaces the hard-coded 30s ceiling.
* Validate the standard request headers (SEP-2243) with `StandardHeaderValidator` (`Builder::setHeaderValidator()`), answering `-32020` when they contradict the body.
* Add `Wire\CachePolicy` (`Builder::setCachePolicy()`) for SEP-2549 caching hints; defaults to `ttlMs: 0, cacheScope: private`. A `ReadResourceResult` may override with its own values.
* Carry W3C trace context (SEP-414): `traceparent`/`tracestate`/`baggage` from `_meta` are exposed via `RequestContext::getTraceContext()` and echoed onto the request's notifications.
* Close the schema gaps for `2025-06-18`/`2025-11-25` and add the `2026-07-28` surface (SEP-2106): url-mode elicitation (`ClientGateway::elicitUrl()`/`supportsElicitationUrl()`), `Implementation::title`, and `outputSchema`/`structuredContent` accepting any JSON value.
* Deprecate Roots, Sampling and Logging (SEP-2577, earliest removal `2027-07-28`); they keep working but trigger a deprecation notice.
* [BC Break] Answer a not-found subject with `-32602` instead of `-32002` (SEP-2164): `resources/read` picks the code by revision (`-32602` from `2026-07-28` on), `prompts/get`, `completion/complete` and `tools/call` switch on every revision. Adds `ProtocolVersion::usesInvalidParamsForResourceNotFound()`.
* [BC Break] A list-shaped tool result is only sent as `structuredContent` on `2026-07-28`+; older revisions keep the JSON-encoded value in `content`.
* [BC Break] Add the extensions framework (SEP-2133) MCP Apps sits on: `ExtensionInterface::getId()` returns an `ExtensionIdentifier` value object, and the interface gains `getMessages()`/`getRequestHandlers()` (extend `AbstractExtension` to skip both). `MessageFactory::make()` takes an `$additional` message list; `RequestHandlerInterface`'s result template is covariant. `ServerExtensionInterface` is replaced by the side-agnostic `Schema\Extension\ExtensionInterface`.
* Add client-side extension negotiation: `ClientGateway::supportsExtension()`, `Client\Builder::enableExtension()`, `ClientCapabilities::withExtensions()`.
* Add sampling-with-tools: sampling requests can carry tools and tool-choice preferences, messages support tool-use/tool-result blocks, and clients advertise `sampling.context`/`sampling.tools` (`ClientGateway::supportsSamplingTools()`/`supportsSamplingContext()`). Requests violating the tool-flow rules are rejected with a JSON-RPC error.
* [BC Break] `SamplingMessage::$content` and `CreateSamplingMessageResult::$content` may be a list of content blocks — use `getContentBlocks()`. `CreateSamplingMessageResult` rejects roles other than `assistant` and empty content.
* Add client-side Roots support (`RootsCallbackInterface`, `Client::sendRootsListChanged()`) and server-side `ClientGateway::listRoots()`/`supportsRoots()`/`supportsSampling()`.
* Add `Schema\Content\ResourceLink` to reference a resource by URI in tool results and prompt messages.
* [BC Break] `Schema\JsonRpc\Error` accepts `null` as `$id`; an unreadable id now omits the member instead of sending `"id": ""`. `MessageFactory` decodes a missing or null id as an id-less error.
* Preserve the request `id` on an invalid-but-parseable message (`-32600`) via `InvalidInputMessageException::getRequestId()`.
* [BC Break] Drop the SDK-only name pattern on `ResourceDefinition`/`ResourceTemplate` `$name`; the spec allows any string.
* Log expected tool failures (`ToolCallException`) at debug level instead of error.
* Add `annotations` to `ImageContent`.
* Fix empty tool/resource schemas serializing as `[]` instead of `{}`.
* Fix `PromptResultFormatter` dropping `annotations`, `_meta` and `mimeType` for plain-array content.
0.7.0
-----
* Add client-side elicitation support: `ElicitationCallbackInterface`, `ElicitationRequestHandler`, and `ElicitationException` let clients respond to server elicitation requests.
* Defer element loading to the first registry read: loaders now run at request time (first `has*`/`get*` call) instead of eagerly at `Builder::build()`, fixing empty registries under persistent runtimes (e.g. FrankenPHP worker mode) where a loader's data source is not ready at build time. Adds `Builder::setLazyLoading()` (default on), a public `Registry::load()`, and an optional `LoaderInterface` constructor argument on `Registry`.
* [BC Break] Element loading is lazy by default: loader failures now surface on the first request rather than at `Builder::build()`, and `initialize` advertises capabilities from the configured sources rather than the loaded registry. Call `Builder::setLazyLoading(false)` to restore eager build-time loading.
* Allow `[$instance, 'methodName']` as an element handler in `Builder::addTool()`, `addResource()`, `addResourceTemplate()`, and `addPrompt()`. Unblocks handlers with constructor dependencies that the container-less `new $className()` fallback cannot build.
* Always emit an `items` schema for array tool parameters: untyped arrays get `items: {}` and nullable typed arrays (e.g. `string[]|null`) keep their element type. Fixes strict clients rejecting tools with "array type must have items" (#151).
* Harden JSON-RPC input parsing: single-message vs batch is now decided from the decoded JSON type (object → single, list array → batch) instead of the raw first byte. Scalars, empty payloads, and non-object batch elements are surfaced as `InvalidInputMessageException` entries instead of triggering warnings or a `TypeError`.
* Add `maxBatchSize` (default `100`) to `MessageFactory` — oversized JSON-RPC batches are rejected before any message is constructed, guarding against amplification.
* Add `maxBodyBytes` (default 4 MiB) to `StreamableHttpTransport` — POST bodies exceeding the cap are rejected with `413`. Unknown-size/chunked bodies are read incrementally and stopped at the cap so they cannot exhaust memory.
* Reject malformed `Mcp-Session-Id` headers with a `400` response: a repeated header or a value that is not a valid UUID is now rejected up front instead of surfacing as an uncaught `Uuid::fromString()` error.
* Extract RFC 9728 metadata serving into `ProtectedResourceMetadataHandler`, a transport-neutral PSR-15 `RequestHandlerInterface` that can be mounted directly as a Symfony/Laravel controller; `ProtectedResourceMetadataMiddleware` now delegates to it (no BC break).
0.6.0
-----
* Add `Builder::add(Tool|ResourceDefinition|ResourceTemplate|Prompt $definition, ElementHandlerInterface $handler)` for explicit registration of elements whose schema is only known at runtime.
* Add handler interfaces `ToolHandlerInterface`, `ResourceHandlerInterface`, `ResourceTemplateHandlerInterface`, `PromptHandlerInterface`, and the `ElementHandlerInterface` marker.
* [BC Break] Renamed `Mcp\Schema\Resource` to `Mcp\Schema\ResourceDefinition`. No alias.
* [BC Break] Renamed `Mcp\Capability\Registry\Loader\ArrayLoader` to `Mcp\Capability\Registry\Loader\ReflectedElementLoader`.
* [BC Break] Bump default protocol version to `2025-11-25`
* Add support for MCP Apps extension in schema and server
* Add `extensions` to `ServerCapabilities` and `ClientCapabilities` and `Builder::enableExtension()`
* Allow overriding the default name pattern for Discovery
* Add configurable session garbage collection (`gcProbability`/`gcDivisor`)
* Add optional `title` field to `ResourceDefinition` and `ResourceTemplate` for MCP spec compliance
* Add `ChainLoader` to compose multiple `LoaderInterface` implementations via explicit ordering.
* Add `RegistryInterface::unregisterTool()`, `unregisterResource()`, `unregisterResourceTemplate()`, `unregisterPrompt()` — idempotent removals.
* Add `RegistryInterface::hasTool()`, `hasResource()`, `hasResourceTemplate()`, `hasPrompt()` — by-name existence checks.
* `DiscoveryLoader` now refreshes only its own previously written entries; manual registrations (via `Builder::addTool()` etc. or runtime `$registry->registerTool()` calls) survive rediscovery, and a same-name manual registration takes precedence over discovery on collision.
* [BC Break] Removed `ElementReference::$isManual` public property and the `bool $isManual` parameter from all `*Reference` constructors. Origin tracking is no longer carried on the element; manual-over-discovered precedence is encoded by loader execution order.
* [BC Break] `RegistryInterface::registerTool()`, `registerResource()`, `registerResourceTemplate()`, `registerPrompt()` lost their trailing `bool $isManual = false` parameter. Callers using positional arguments must drop the flag.
* [BC Break] Removed `RegistryInterface::clear()`, `getDiscoveryState()`, `setDiscoveryState()`. Rediscovery now goes through `DiscoveryLoader::load()` directly.
* `Registry::register*()` semantics changed to plain last-write-wins (overwrites silently) and the methods now return the stored `*Reference`. The previous "discovered registration is ignored when a manual one already exists" precedence rule still applies, but is now enforced by `DiscoveryLoader` via reference-identity tracking — and still emits a debug log when a discovery is skipped due to a conflicting registration.
* Add optional `title` parameter to `Builder::addResource()` and `Builder::addResourceTemplate()` for MCP spec compliance
* [BC Break] `Builder::addResource()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
* [BC Break] `Builder::addResourceTemplate()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
* Add `CorsMiddleware`, `DnsRebindingProtectionMiddleware`, and `ProtocolVersionMiddleware` for `StreamableHttpTransport`, composed automatically as the default stack via `StreamableHttpTransport::defaultMiddleware()`
* [BC BREAK] `StreamableHttpTransport` constructor: `$corsHeaders` parameter removed; CORS is now configured via `CorsMiddleware`. The `$middleware` parameter is nullable — `null` (or omitted) installs the default stack; `[]` disables all defaults. Default `Access-Control-Allow-Origin` is no longer set (was `*`).
* [BC Break] `ResourceDefinition::__construct()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
* [BC Break] `ResourceTemplate::__construct()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
* [BC Break] `McpResource` and `McpResourceTemplate` attribute signatures changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
0.5.0 0.5.0
----- -----

View file

@ -1,84 +0,0 @@
# Contributing
Thanks for considering a contribution to the PHP SDK for the Model Context Protocol. This is a
collaboration between [the PHP Foundation](https://thephp.foundation/) and the
[Symfony project](https://symfony.com/), and it follows Symfony's conventions throughout.
## Ways to contribute
- **Report a bug** or **propose a feature** by [opening an issue](https://github.com/modelcontextprotocol/php-sdk/issues).
Check existing issues first — many spec-driven changes are already tracked under the relevant
`2026-07-28`-style release label.
- **Send a pull request** for a fix, a new capability, or a docs improvement.
- **Improve the guides** under `docs/` — see [Documentation](#documentation) below for how they're built.
- **Help close a conformance gap** — `make conformance-tests` runs the official MCP conformance
suite against this SDK; a failing scenario there is a concrete, well-scoped contribution.
## Development setup
Requires PHP 8.1+.
```bash
composer install
```
## Before opening a pull request
Run the full CI suite locally:
```bash
make ci
```
This runs, in order: `make cs` (PHP CS Fixer, auto-fixes style), `make phpstan` (static analysis),
and `make tests` (unit + inspector tests). All three must pass. If your change touches
protocol-observable behavior, also run:
```bash
make conformance-tests # requires Docker
```
## Coding standards
This project follows [Symfony's coding standards](https://symfony.com/doc/current/contributing/code/standards.html)
and [backward compatibility promise](https://symfony.com/doc/current/contributing/code/bc.html). In short:
See [CLAUDE.md](CLAUDE.md) for a fuller tour of the codebase's architecture and layout.
## Tests
New capabilities need unit tests (`tests/Unit/`) covering the core logic, and — for anything
reachable over the wire — inspector tests (`tests/Inspector/`) for end-to-end coverage. If you're
adding a documented pattern, consider adding or updating an example under `examples/`.
## Documentation
The guides under `docs/` are built with [Zensical](https://zensical.org/) in `--strict` mode,
which fails the build on a broken internal link:
```bash
make docs-guides
```
Links between guide pages must be relative paths that resolve within `docs/` (e.g.
`protocol-versions.md`, `../CLAUDE.md` will *not* resolve — Zensical only follows the `docs/` tree).
For anything at the repo root (`CLAUDE.md`, `ROADMAP.md`, `CHANGELOG.md`), link to it by its GitHub
URL instead, matching the pattern already used across `docs/`. The class-level API reference is
generated separately by phpDocumentor (`make docs-api`) and isn't hand-written.
## Versioning
The SDK follows [Semantic Versioning](https://semver.org/) — see
[SDK tier target](docs/sdk-tier.md#versioning) for what that means pre- and post-1.0, and how it
lines up with Symfony's backward compatibility promise.
## Licensing
New contributions are licensed under Apache License, Version 2.0. Existing code predating this
policy remains under the MIT License — see [LICENSE](LICENSE) for the details. By opening a pull
request, you agree your contribution is provided under those terms.
## Getting help
If something is unclear or you want early feedback on an approach before writing code, open an
issue or a draft pull request — that's the right place to ask, rather than guessing at scope.

View file

@ -1,15 +1,4 @@
.PHONY: deps-stable deps-low cs phpstan tests unit-tests integration-tests inspector-tests coverage ci ci-stable ci-lowest conformance-tests conformance-server conformance-client conformance-draft conformance-draft-server conformance-draft-client docs docs-guides docs-api docs-serve .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
# The 2026-07-28 scenarios ship on the `alpha` dist-tag; `latest` (0.1.x) has
# none of them. Pinned to the same version CI runs (see
# .github/workflows/pipeline.yaml), so a local pass means a green pipeline.
CONFORMANCE_VERSION ?= 0.2.0-alpha.11
CONFORMANCE = npx --yes @modelcontextprotocol/conformance@$(CONFORMANCE_VERSION)
# 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: deps-stable:
composer update --prefer-stable composer update --prefer-stable
@ -29,9 +18,6 @@ tests:
unit-tests: unit-tests:
vendor/bin/phpunit --testsuite=unit vendor/bin/phpunit --testsuite=unit
integration-tests:
vendor/bin/phpunit --testsuite=integration
inspector-tests: inspector-tests:
vendor/bin/phpunit --testsuite=inspector vendor/bin/phpunit --testsuite=inspector
@ -41,33 +27,11 @@ conformance-server:
docker compose -f tests/Conformance/Fixtures/docker-compose.yml up -d docker compose -f tests/Conformance/Fixtures/docker-compose.yml up -d
@echo "Waiting for server to start..." @echo "Waiting for server to start..."
@sleep 5 @sleep 5
rm -rf tests/Conformance/results cd tests/Conformance && npx @modelcontextprotocol/conformance server --url http://localhost:8000/ || true
cd tests/Conformance && $(CONFORMANCE) server --url http://localhost:8000/ --suite all --spec-version 2025-11-25 --expected-failures conformance-baseline-2025-11-25.yml --output-dir results || true
php tests/Conformance/score.php server 2025-11-25
docker compose -f tests/Conformance/Fixtures/docker-compose.yml down docker compose -f tests/Conformance/Fixtures/docker-compose.yml down
conformance-client: conformance-client:
rm -rf tests/Conformance/results cd tests/Conformance && npx @modelcontextprotocol/conformance client --command "php $(CURDIR)/tests/Conformance/client.php" --suite all --expected-failures conformance-baseline.yml || true
cd tests/Conformance && $(CONFORMANCE) client --command "php $(CURDIR)/tests/Conformance/client.php" --suite all --spec-version 2025-11-25 --expected-failures conformance-baseline-2025-11-25.yml --output-dir results || true
php tests/Conformance/score.php client 2025-11-25
# --- 2026-07-28 (SEP-2575 stateless lifecycle) ------------------------------
conformance-draft: conformance-draft-server conformance-draft-client
conformance-draft-server:
docker compose -f tests/Conformance/Fixtures/docker-compose.yml up -d
@echo "Waiting for server to start..."
@sleep 5
rm -rf tests/Conformance/results-2026-07-28
cd tests/Conformance && $(CONFORMANCE) server --url http://localhost:8000/ --suite all --spec-version 2026-07-28 --expected-failures conformance-baseline-2026-07-28.yml --output-dir results-2026-07-28 || true
php tests/Conformance/score.php server 2026-07-28 results-2026-07-28
docker compose -f tests/Conformance/Fixtures/docker-compose.yml down
conformance-draft-client:
rm -rf tests/Conformance/results-2026-07-28
cd tests/Conformance && $(CONFORMANCE) client --command "php $(CURDIR)/tests/Conformance/client.php" --suite all --spec-version 2026-07-28 --expected-failures conformance-baseline-2026-07-28.yml --output-dir results-2026-07-28 || true
php tests/Conformance/score.php client 2026-07-28 results-2026-07-28
coverage: coverage:
XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=unit --coverage-html=coverage XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=unit --coverage-html=coverage
@ -78,19 +42,7 @@ ci-stable: deps-stable cs phpstan tests
ci-lowest: deps-low cs phpstan tests ci-lowest: deps-low cs phpstan tests
# The published site is the guides (Zensical) with the phpDocumentor API docs:
# reference mounted at /api/. `zensical build` wipes site/, so it runs first. vendor/bin/phpdoc
docs: docs-guides docs-api @grep -q 'No errors have been found' .phpdoc/build/reports/errors.html || \
rm -rf site/api (echo "Documentation errors found. See build/docs/reports/errors.html" && exit 1)
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

275
README.md
View file

@ -1,13 +1,7 @@
# MCP PHP SDK # MCP PHP SDK
[![Latest Version](https://img.shields.io/packagist/v/mcp/sdk.svg)](https://packagist.org/packages/mcp/sdk) The official PHP SDK for Model Context Protocol (MCP). It provides a framework-agnostic API for implementing MCP servers
[![CI](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/pipeline.yaml/badge.svg)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/pipeline.yaml) and clients in PHP.
[![PHP Version](https://img.shields.io/packagist/php-v/mcp/sdk.svg)](https://packagist.org/packages/mcp/sdk)
[![License](https://img.shields.io/packagist/l/mcp/sdk.svg)](LICENSE)
The official PHP SDK for the Model Context Protocol (MCP). It provides a framework-agnostic API for implementing MCP
servers and clients in PHP — tools, resources, prompts, STDIO and HTTP transports, sessions, authorization, and both
protocol eras (the `initialize` handshake and the stateless `2026-07-28` revision).
This project represents a collaboration between [the PHP Foundation](https://thephp.foundation/) and the [Symfony project](https://symfony.com/). It adopts This project represents a collaboration between [the PHP Foundation](https://thephp.foundation/) and the [Symfony project](https://symfony.com/). It adopts
development practices and standards from the Symfony project, including [Coding Standards](https://symfony.com/doc/current/contributing/code/standards.html) and the development practices and standards from the Symfony project, including [Coding Standards](https://symfony.com/doc/current/contributing/code/standards.html) and the
@ -16,16 +10,18 @@ development practices and standards from the Symfony project, including [Coding
Until the first major release, this SDK is considered [experimental](https://symfony.com/doc/current/contributing/code/experimental.html), please see the [roadmap](./ROADMAP.md) for Until the first major release, this SDK is considered [experimental](https://symfony.com/doc/current/contributing/code/experimental.html), please see the [roadmap](./ROADMAP.md) for
planned next steps and features. planned next steps and features.
## Protocol Conformance ## Table of Contents
Measured weekly against the [MCP Conformance Test Framework](https://github.com/modelcontextprotocol/conformance), - [Installation](#installation)
as passing checks out of total. The `2026-07-28` scores run against the framework's `alpha` releases, so they move as - [Overview](#overview)
upstream publishes new scenarios. - [Server SDK](#server-sdk)
- [Client SDK](#client-sdk)
| Revision | Server | Client | - [Documentation](#documentation)
|:--------:|:------:|:------:| - [External Resources](#external-resources)
| `2026-07-28` | [![Server conformance 2026-07-28](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/server-conformance-2026-07-28.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) | [![Client conformance 2026-07-28](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/client-conformance-2026-07-28.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) | - [PHP Libraries Using the MCP SDK](#php-libraries-using-the-mcp-sdk)
| `2025-11-25` | [![Server conformance 2025-11-25](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/server-conformance-2025-11-25.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) | [![Client conformance 2025-11-25](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/modelcontextprotocol/php-sdk/badges/client-conformance-2025-11-25.json)](https://github.com/modelcontextprotocol/php-sdk/actions/workflows/conformance-weekly.yaml) | - [Contributing](#contributing)
- [Credits](#credits)
- [License](#license)
## Installation ## Installation
@ -33,21 +29,28 @@ upstream publishes new scenarios.
composer require mcp/sdk composer require mcp/sdk
``` ```
## Build a server ## Overview
A server is a plain PHP class plus three lines of wiring: The MCP PHP SDK provides both **server** and **client** implementations for the Model Context Protocol, enabling you to:
- **Build MCP Servers**: Expose your PHP application's functionality (tools, resources, prompts) to AI agents
- **Build MCP Clients**: Connect to and interact with MCP servers from your PHP applications
## Server SDK
Build MCP servers to expose your PHP application's capabilities to AI agents like Claude, Codex, and others.
### Quick Start
```php ```php
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Server; use Mcp\Server;
use Mcp\Server\Transport\StdioTransport; use Mcp\Server\Transport\StdioTransport;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Attribute\McpResource;
class Calculator // Define capabilities using PHP attributes
class CalculatorCapabilities
{ {
/**
* Adds two numbers.
*/
#[McpTool] #[McpTool]
public function add(int $a, int $b): int public function add(int $a, int $b): int
{ {
@ -55,56 +58,216 @@ class Calculator
} }
#[McpResource(uri: 'config://calculator/settings')] #[McpResource(uri: 'config://calculator/settings')]
public function settings(): array public function getSettings(): array
{ {
return ['precision' => 2]; return ['precision' => 2];
} }
} }
exit(Server::builder() // Build and run the server
->setServerInfo('Calculator', '1.0.0') $server = Server::builder()
->setDiscovery(__DIR__, ['.'], excludeDirs: ['vendor']) ->setServerInfo('Calculator Server', '1.0.0')
->build() ->setDiscovery(__DIR__, ['.']) // Auto-discover attributes
->run(new StdioTransport())); ->build();
$transport = new StdioTransport();
$server->run($transport);
``` ```
The walkthrough in [First server](docs/get-started/first-server.md) explains each piece, and ### Server Capabilities
[Try it with the Inspector](docs/get-started/inspector.md) shows it running.
## Build a client - **Tools**: Executable functions that AI agents can call
- **Resources**: Data sources that can be read (files, configs, databases)
- **Resource Templates**: Dynamic resources with URI parameters
- **Prompts**: Pre-defined templates for AI interactions
- **Server-Initiated Communication**: Elicitations, sampling, logging, progress notifications
### Registration Methods
There are multiple ways to register your MCP capabilities—choose the approach that best fits your application's architecture:
**1. Attribute-Based Discovery** — Define capabilities using PHP attributes for automatic discovery:
```php
#[McpTool]
public function generateReport(): string { /* ... */ }
#[McpResource(uri: 'config://app/settings')]
public function getConfig(): array { /* ... */ }
```
**2. Manual Registration** — Register capabilities programmatically without attributes:
```php
$server = Server::builder()
->addTool([Calculator::class, 'add'], 'add_numbers')
->addResource([Config::class, 'get'], 'config://app')
->build();
```
**3. Hybrid Approach** — Combine both methods for maximum flexibility:
```php
$server = Server::builder()
->setDiscovery(__DIR__, ['.'])
->addTool([ExternalService::class, 'process'], 'external')
->build();
```
### Transports
Choose the transport that matches your deployment environment:
**1. STDIO Transport** — For command-line integration and local processes:
```php
$transport = new StdioTransport();
$server->run($transport);
```
**2. HTTP Transport** — For web-based servers and distributed systems:
```php
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
$response = $server->run($transport);
```
### Session Management
Configure session storage to maintain state between requests. Choose the backend that fits your infrastructure:
**In-Memory** (default, suitable for STDIO):
```php
$server = Server::builder()
->setSession(ttl: 7200) // 2 hours
->build();
```
**File-Based** (suitable for single-server HTTP deployments):
```php
$server = Server::builder()
->setSession(new FileSessionStore(__DIR__ . '/sessions'))
->build();
```
**PSR-16 Cache** (for example with Redis for scaled deployments):
```php
$server = Server::builder()
->setSession(new Psr16SessionStore(
cache: new Psr16Cache($redisAdapter),
prefix: 'mcp-',
ttl: 3600
))
->build();
```
[→ Server Documentation](docs/server-builder.md)
## Client SDK
Connect to MCP servers from your PHP applications to access their tools, resources, and prompts.
### Quick Start
```php ```php
use Mcp\Client; use Mcp\Client;
use Mcp\Client\Transport\StdioTransport; use Mcp\Client\Transport\StdioTransport;
// Build the client
$client = Client::builder() $client = Client::builder()
->setClientInfo('My Application', '1.0.0') ->setClientInfo('My Application', '1.0.0')
->setInitTimeout(30)
->setRequestTimeout(120)
->build(); ->build();
$client->connect(new StdioTransport(command: 'php', args: ['/path/to/server.php'])); // Connect to a server
$transport = new StdioTransport(
command: 'php',
args: ['/path/to/server.php'],
);
$client->connect($transport);
// Discover and use capabilities
$tools = $client->listTools(); $tools = $client->listTools();
$result = $client->callTool('add', ['a' => 5, 'b' => 3]); $result = $client->callTool('add', ['a' => 5, 'b' => 3]);
$resources = $client->listResources();
$content = $client->readResource('config://calculator/settings');
$client->disconnect(); $client->disconnect();
``` ```
See [Connecting to a server](docs/client/connecting.md) for transports, timeouts, and the ### Client Capabilities
handlers that answer server-initiated requests.
- **Tool Calling**: List and execute tools from any MCP server
- **Resource Access**: Read static and dynamic resources
- **Prompt Management**: List and retrieve prompt templates
- **Completion Support**: Request argument completion suggestions
### Advanced Features
- **Progress Tracking**: Real-time progress during long operations
```php
$result = $client->callTool(
name: 'process_data',
arguments: ['dataset' => 'large_file.csv'],
onProgress: function (float $progress, ?float $total, ?string $message) {
echo "Progress: {$progress}/{$total} - {$message}\n";
}
);
```
- **Sampling Support**: Handle server LLM sampling requests
```php
$samplingHandler = new SamplingRequestHandler($myCallback);
$client = Client::builder()
->setCapabilities(new ClientCapabilities(sampling: true))
->addRequestHandler($samplingHandler)
->build();
```
- **Logging Notifications**: Receive server log messages
```php
$loggingHandler = new LoggingNotificationHandler($myCallback);
$client = Client::builder()
->addNotificationHandler($loggingHandler)
->build();
```
### Transports
Connect to MCP servers using the transport that matches your setup:
**1. STDIO Transport** — Connect to local server processes:
```php
$transport = new StdioTransport(
command: 'php',
args: ['/path/to/server.php'],
);
$client->connect($transport);
```
**2. HTTP Transport** — Connect to remote or web-based servers:
```php
$transport = new HttpTransport('http://localhost:8000');
$client->connect($transport);
```
[→ Client Documentation](docs/client.md)
## Documentation ## Documentation
The full documentation is published at **[php.sdk.modelcontextprotocol.io](https://php.sdk.modelcontextprotocol.io/)**. ### Core Concepts
- **[Get started](docs/get-started/index.md)** — Install the SDK and build your first server - **[Server Builder](docs/server-builder.md)** — Complete ServerBuilder reference and configuration
- **[Servers](docs/servers/index.md)** — Tools, resources, resource templates, prompts, and how to register them - **[Client](docs/client.md)** — Client SDK for connecting to and communicating with MCP servers
- **[Inside your handler](docs/handlers/index.md)** — Talking back to the client, logging, and asking for input - **[Transports](docs/transports.md)** — STDIO and HTTP transport setup and usage
- **[Running your server](docs/run/index.md)** — Server builder, STDIO and HTTP transports, framework integration, sessions, authorization - **[MCP Elements](docs/mcp-elements.md)** — Creating tools, resources, prompts, and templates
- **[Clients](docs/client/index.md)** — Client SDK for connecting to and communicating with MCP servers - **[Server-Client Communication](docs/server-client-communication.md)** — Sampling, logging, progress, and notifications
- **[Protocol versions](docs/protocol-versions.md)** — The two protocol eras, and what revision `2026-07-28` changed - **[Events](docs/events.md)** — Hooking into server lifecycle with events
- **[Advanced](docs/advanced/index.md)** — Events, protocol extensions (including MCP Apps), and custom message handlers
- **[Examples](docs/examples.md)** — Runnable server and client examples ### Learning & Examples
- **[API Reference](https://php.sdk.modelcontextprotocol.io/api/)** — Generated class reference
- **[Examples](docs/examples.md)** — Comprehensive example walkthroughs for servers and clients
- **[ROADMAP.md](ROADMAP.md)** — Planned features and development roadmap
## External Resources ## External Resources
@ -114,23 +277,15 @@ The full documentation is published at **[php.sdk.modelcontextprotocol.io](https
## PHP Libraries Using the MCP SDK ## PHP Libraries Using the MCP SDK
- [api-platform/mcp](https://github.com/api-platform/mcp) — MCP integration for API Platform - [pronskiy/mcp](https://github.com/pronskiy/mcp) — Additional developer experience layer
- [bnomei/kirby-mcp](https://github.com/bnomei/kirby-mcp) — MCP server for the Kirby CMS
- [drupal/mcp_server](https://www.drupal.org/project/mcp_server) — MCP server for Drupal exposing configuration and entities as MCP elements
- [josbeir/cakephp-synapse](https://github.com/josbeir/cakephp-synapse) — CakePHP plugin exposing application functionality over MCP
- [nette/mcp-inspector](https://github.com/nette/mcp-inspector) — MCP server for introspecting Nette applications
- [symfony/ai-mate](https://github.com/symfony/ai-mate) — AI development assistant MCP server for Symfony projects
- [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) — Symfony integration bundle - [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) — Symfony integration bundle
- [josbeir/cakephp-synapse](https://github.com/josbeir/cakephp-synapse) — CakePHP integration plugin
Building something on top of the SDK? Open a pull request to add it to this list.
## Contributing ## Contributing
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project.
the project. Start by [reporting issues](https://github.com/modelcontextprotocol/php-sdk/issues) or
[sending pull requests](https://github.com/modelcontextprotocol/php-sdk/pulls). See See the [Contributing Guide](CONTRIBUTING.md) to get started before you [report issues](https://github.com/modelcontextprotocol/php-sdk/issues) and [send pull requests](https://github.com/modelcontextprotocol/php-sdk/pulls).
[CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and what to run before
opening a PR.
## Credits ## Credits

View file

@ -5,17 +5,11 @@ This roadmap is a living document that outlines the planned features and improve
## Goals for the First Major Release ## Goals for the First Major Release
- **Server** - **Server**
- [x] Implement full support for elicitations - [ ] Implement full support for elicitations
- [ ] Implement OAuth2 authentication for server - [ ] Implement OAuth2 authentication for server
- **Client** - **Client**
- [x] Implement client-side support - [x] Implement client-side support
- [x] Implement client examples and documentation - [x] Implement client examples and documentation
- [ ] Implement OAuth2 authentication for client
- **Schema** - **Schema**
- [ ] Implement schema generation based on TS or JSON Schema - [ ] Implement schema generation based on TS or JSON Schema
- [ ] Achieve full spec conformance
- **Extensions**
- [ ] Implement the Tasks extension
- **Architecture**
- [ ] Split the SDK into multiple packages

View file

@ -1,97 +0,0 @@
# 0001 — The MCP server is an OAuth Resource Server, not an Authorization Server
- Status: Accepted
- Date: 2026-06-15
## Context
OAuth 2.1 defines three distinct roles:
| Role | What it does | Status in this SDK | Scope |
|------|--------------|--------------------|-------|
| Resource Server | Validates incoming bearer tokens, serves Protected Resource Metadata (RFC 9728), emits `WWW-Authenticate` | Shipped (`AuthorizationMiddleware`, `JwtTokenValidator`, `ProtectedResourceMetadata`) | **IN scope** |
| Delegation / proxy to an upstream AS | Forwards `/authorize` and `/token` to your existing IdP | Shipped (`OAuthProxyMiddleware`) | **IN scope — delegation ONLY** |
| Authorization Server / Identity Provider (IdP) | Mints its own tokens, registers clients, runs login and consent | Absent | **OUT of scope** |
The SDK repeatedly receives pull requests that move it toward becoming a full OAuth 2.1
**authorization server** — asking the MCP server to mint its own access and refresh tokens,
register clients, and run login and consent flows, that is, to become an Identity Provider.
The most explicit example is [#373](https://github.com/modelcontextprotocol/php-sdk/pull/373)
("[Server] Add native OAuth 2.1 authorization server", ~3,400 lines across 50+ files), but the
direction has crept in incrementally rather than in one PR.
A contributing factor is that the SDK already exposes the *authorization-server endpoints in
proxy form*, and that surface has grown:
[#221](https://github.com/modelcontextprotocol/php-sdk/pull/221) added the OAuth resource-server
middleware, and [#269](https://github.com/modelcontextprotocol/php-sdk/pull/269) added Dynamic
Client Registration (RFC 7591). Today `OAuthProxyMiddleware` answers `/authorize`, `/token`, and
`/.well-known/oauth-authorization-server`, and DCR endpoints exist. The shape of that surface
invites contributors to "finish the job" by backing those endpoints with a real token issuer.
It is not scaffolding to be completed — it is a delegating proxy, and that is the whole of its
intent.
## Decision
**The MCP server is an OAuth 2.1 Resource Server that MAY delegate to an upstream
authorization server. It will NOT issue tokens or act as an Identity Provider.**
Concretely:
- The SDK validates bearer tokens, serves RFC 9728 Protected Resource Metadata, and emits
`WWW-Authenticate` challenges. This is the Resource Server role and is fully supported.
- The SDK MAY delegate `/authorize` and `/token` to an upstream authorization server via
`OAuthProxyMiddleware`. This is delegation only: the middleware redirects the browser to
the upstream `/authorize` endpoint and proxies `/token` requests to the upstream token
endpoint. It never mints, signs, stores, or rotates tokens of its own.
- The SDK will NOT implement an authorization server: no token issuance, no token signing or
key management, no login UI, no consent UI, no authorization-code or refresh-token storage,
no first-party Dynamic Client Registration acting as an issuer.
Pull requests that add authorization-server / IdP behavior are declined by reference to this
ADR.
## Rationale
- **Security liability.** Issuing tokens means owning signing-key generation, storage, and
rotation; authorization-code and refresh-token persistence; refresh-token rotation and
replay detection; and consent. A defect in any of these is a credential-issuance
vulnerability affecting every consumer of the SDK. This is precisely the surface an MCP SDK
should not own.
- **RFC footprint.** A correct authorization server must implement and keep current with
RFC 6749 (OAuth 2.0), RFC 7591 (Dynamic Client Registration), RFC 8414 (Authorization
Server Metadata), PKCE (RFC 7636), and refresh-token rotation guidance, among others. That
is an open-ended maintenance and conformance burden far outside the SDK's purpose.
- **Mature implementations already exist.** Token issuance is a solved problem.
`league/oauth2-server` provides it as a PHP library, and every production IdP — Keycloak,
Auth0, Microsoft Entra ID, Okta — provides it as a service. Re-implementing it inside an
MCP SDK adds risk without adding value.
## Boundary statement
`OAuthProxyMiddleware` **delegates** to an upstream IdP. It is **not** authorization-server
scaffolding to be completed. Backing its `/authorize` and `/token` endpoints with a
first-party token issuer is out of scope and will be declined.
## Consequences
- The supported authorization architecture is: an external authorization server (your IdP or
`league/oauth2-server` running in your own application) issues tokens; the MCP server
validates them as a Resource Server and optionally proxies the OAuth flow to that upstream.
- Contributors get a single, citable ruling for why authorization-server PRs are declined,
reducing repeated large-PR churn.
- Resource Server and proxy/delegation features remain welcome and supported.
## Alternatives / what to do instead
If you need an authorization server (token issuance, client registration, login, consent), do
**not** add it to this SDK. Instead:
- **Front the MCP server with an existing IdP** — Keycloak, Auth0, Microsoft Entra ID, or
Okta. Point `JwtTokenValidator` and `ProtectedResourceMetadata` at that issuer, and
optionally use `OAuthProxyMiddleware` to delegate `/authorize` and `/token` to it.
- **Run `league/oauth2-server` in your own application**, behind the SDK's existing proxy and
validator seams. The MCP server validates the tokens it issues; it does not issue them
itself.
See [`../docs/run/authorization.md`](../docs/run/authorization.md) for the supported Resource Server
and delegation setup.

View file

@ -1,10 +0,0 @@
# Architecture Decision Records
This directory holds Architecture Decision Records (ADRs): short documents that capture a
significant architectural decision, the context that led to it, and its consequences. They
exist so a decision can be cited once and referenced thereafter — for example, when declining
a pull request that conflicts with an accepted decision.
## Records
- [0001 — The MCP server is an OAuth Resource Server, not an Authorization Server](0001-oauth-authorization-server-out-of-scope.md)

View file

@ -32,7 +32,6 @@
"psr/http-server-handler": "^1.0", "psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0", "psr/http-server-middleware": "^1.0",
"psr/log": "^1.0 || ^2.0 || ^3.0", "psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/deprecation-contracts": "^2.5 || ^3.0",
"symfony/uid": "^5.4 || ^6.4 || ^7.3 || ^8.0" "symfony/uid": "^5.4 || ^6.4 || ^7.3 || ^8.0"
}, },
"suggest": { "suggest": {
@ -77,15 +76,11 @@
"Mcp\\Example\\Server\\DiscoveryUserProfile\\": "examples/server/discovery-userprofile/", "Mcp\\Example\\Server\\DiscoveryUserProfile\\": "examples/server/discovery-userprofile/",
"Mcp\\Example\\Server\\EnvVariables\\": "examples/server/env-variables/", "Mcp\\Example\\Server\\EnvVariables\\": "examples/server/env-variables/",
"Mcp\\Example\\Server\\ExplicitRegistration\\": "examples/server/explicit-registration/", "Mcp\\Example\\Server\\ExplicitRegistration\\": "examples/server/explicit-registration/",
"Mcp\\Example\\Server\\McpApps\\": "examples/server/mcp-apps/",
"Mcp\\Example\\Server\\OAuthKeycloak\\": "examples/server/oauth-keycloak/", "Mcp\\Example\\Server\\OAuthKeycloak\\": "examples/server/oauth-keycloak/",
"Mcp\\Example\\Server\\OAuthMicrosoft\\": "examples/server/oauth-microsoft/", "Mcp\\Example\\Server\\OAuthMicrosoft\\": "examples/server/oauth-microsoft/",
"Mcp\\Example\\Server\\SchemaShowcase\\": "examples/server/schema-showcase/", "Mcp\\Example\\Server\\SchemaShowcase\\": "examples/server/schema-showcase/",
"Mcp\\Tests\\": "tests/" "Mcp\\Tests\\": "tests/"
}, }
"classmap": [
"tests/Unit/Capability/Discovery/Fixtures/AlternativeFileNameToolHandler.class.inc"
]
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="none" d="M23.5996 85.2532L86.2021 22.6507C94.8457 14.0071 108.86 14.0071 117.503 22.6507C126.147 31.2942 126.147 45.3083 117.503 53.9519L70.2254 101.23" stroke="currentColor" stroke-width="11.0667" stroke-linecap="round"/><path fill="none" d="M70.8789 100.578L117.504 53.952C126.148 45.3083 140.163 45.3083 148.806 53.952L149.132 54.278C157.776 62.9216 157.776 76.9357 149.132 85.5792L92.5139 142.198C89.6327 145.079 89.6327 149.75 92.5139 152.631L104.14 164.257" stroke="currentColor" stroke-width="11.0667" stroke-linecap="round"/><path fill="none" d="M101.853 38.3013L55.553 84.6011C46.9094 93.2447 46.9094 107.258 55.553 115.902C64.1966 124.546 78.2106 124.546 86.8543 115.902L133.154 69.6025" stroke="currentColor" stroke-width="11.0667" stroke-linecap="round"/></svg>

Before

Width:  |  Height:  |  Size: 848 B

View file

@ -1 +0,0 @@
php.sdk.modelcontextprotocol.io

View file

@ -1,98 +0,0 @@
# 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; handlers are collected in call order, and the whole custom list runs before the
built-in 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`](https://github.com/modelcontextprotocol/php-sdk/blob/main/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.

View file

@ -1,160 +0,0 @@
# Protocol Extensions
MCP protocol extensions advertise additional, optional capabilities alongside the regular ones —
during the `initialize` handshake, or on revision `2026-07-28` (which has no handshake) inside the
capabilities that travel with every request. A server opts in via `Builder::enableExtension()` and
the SDK places the advertisement correctly for whichever era the client speaks:
```php
use Mcp\Schema\Extension\Apps\McpApps;
use Mcp\Server;
$server = Server::builder()
->setServerInfo('My Server', '1.0.0')
->enableExtension(new McpApps())
->build();
```
Pass one or more `ExtensionInterface` instances; multiple extensions can
be enabled in a single call. Enabling the same extension twice throws a
`LogicException`.
Clients (hosts) advertise the extensions they support the same way, via
`Client\Builder::enableExtension()`; the payload lands under
`capabilities.extensions` in the initialize request — or, on a modern revision,
under the client capabilities carried in each request's `_meta` envelope.
> Note: extensions enabled via `enableExtension()` are merged into the
> `extensions` capability even when you supply your own `ServerCapabilities` /
> `ClientCapabilities` via `setCapabilities()`. An enabled extension overrides
> any entry under the same id already present in those capabilities.
## Checking what the client negotiated
An extension is only in effect when both sides advertise it. On the server, a
handler can ask the `ClientGateway` from the injected `RequestContext` whether the
connected client did:
```php
use Mcp\Schema\Extension\Apps\McpApps;
use Mcp\Server\RequestContext;
public function getWeather(string $city, RequestContext $context): string
{
if (!$context->getClientGateway()->supportsExtension(McpApps::EXTENSION_ID)) {
// text-only fallback for hosts without MCP Apps support
}
// ...
}
```
## MCP Apps (`io.modelcontextprotocol/ui`)
The [MCP Apps extension][ext-apps] lets servers expose interactive HTML UIs as
resources. Clients that support it render them in sandboxed iframes and bridge
tool calls between the iframe (the *View*) and the server via the host.
A UI consists of two pieces wired together by `_meta.ui`:
1. **A resource** with URI scheme `ui://` and MIME type
`text/html;profile=mcp-app`, returning the HTML body.
2. **A tool** linked to that resource via `UiToolMeta`, so the client knows to
open the UI when the tool is invoked.
```php
use Mcp\Schema\Content\TextResourceContents;
use Mcp\Schema\Extension\Apps\McpApps;
use Mcp\Schema\Extension\Apps\ToolVisibility;
use Mcp\Schema\Extension\Apps\UiResourceContentMeta;
use Mcp\Schema\Extension\Apps\UiResourceCsp;
use Mcp\Schema\Extension\Apps\UiResourcePermissions;
use Mcp\Schema\Extension\Apps\UiToolMeta;
$server = Server::builder()
->enableExtension(new McpApps())
->addResource(
fn () => new TextResourceContents(
uri: 'ui://my-app',
mimeType: McpApps::MIME_TYPE,
text: file_get_contents(__DIR__.'/app.html'),
meta: ['ui' => new UiResourceContentMeta(
csp: new UiResourceCsp(connectDomains: ['https://api.example.com']),
permissions: new UiResourcePermissions(geolocation: true),
prefersBorder: true,
)],
),
'ui://my-app',
mimeType: McpApps::MIME_TYPE,
meta: ['ui' => McpApps::resourceMarker()],
)
->addTool(
$myToolHandler,
'my_tool',
meta: ['ui' => new UiToolMeta(
resourceUri: 'ui://my-app',
visibility: [ToolVisibility::Model, ToolVisibility::App],
)],
)
->build();
```
Note the two distinct `_meta.ui` shapes: the resource *descriptor* (its
`resources/list` entry) carries only an empty marker — `McpApps::resourceMarker()` —
flagging it as an MCP App, while the resource *content* returned by `resources/read`
carries the structured `UiResourceContentMeta` with the actual CSP and permission
configuration.
### Attribute-based discovery
The same linkage works with `#[McpResource]` / `#[McpTool]`, since both accept a
`meta` array and PHP allows `new` in attribute arguments. The one difference is the
descriptor marker: `McpApps::resourceMarker()` is a method call and cannot appear
in an attribute, so spell it as `new \stdClass()` there.
```php
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Extension\Apps\McpApps;
use Mcp\Schema\Extension\Apps\ToolVisibility;
use Mcp\Schema\Extension\Apps\UiToolMeta;
final class WeatherApp
{
#[McpResource(uri: 'ui://my-app', mimeType: McpApps::MIME_TYPE, meta: ['ui' => new \stdClass()])]
public function view(): TextResourceContents { /* as above */ }
#[McpTool(name: 'my_tool', meta: ['ui' => new UiToolMeta(resourceUri: 'ui://my-app', visibility: [ToolVisibility::Model, ToolVisibility::App])])]
public function myTool(string $city): string { /* ... */ }
}
```
### Server-side DTOs
| Class | Purpose |
| --- | --- |
| `McpApps` | Extension marker; provides `EXTENSION_ID`, `MIME_TYPE`, `URI_SCHEME` constants. |
| `UiToolMeta` | Tool `_meta.ui` payload: `resourceUri` + `visibility`. |
| `ToolVisibility` | Enum: `Model`, `App`. |
| `UiResourceContentMeta` | Resource content `_meta.ui`: `csp`, `permissions`, `domain`, `prefersBorder`. |
| `UiResourceCsp` | CSP allow-lists: `connectDomains`, `resourceDomains`, `frameDomains`, `baseUriDomains`. |
| `UiResourcePermissions` | Sandbox permissions: `camera`, `microphone`, `geolocation`, `clipboardWrite`. |
### Writing the HTML view
The View and host exchange `JSONRPCMessage` **objects** (not JSON strings) via
`window.parent.postMessage`. Before the host forwards `tools/call`,
`tool-input`, or `tool-result`, the View must complete the spec-mandated
handshake:
1. View → Host: `ui/initialize` request
2. Host → View: response with `hostCapabilities`, `hostInfo`, `hostContext`
3. View → Host: `ui/notifications/initialized`
4. View → Host: `ui/notifications/size-changed` whenever the iframe wants to
resize
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`](https://github.com/modelcontextprotocol/php-sdk/blob/main/examples/server/mcp-apps/weather-app.html).
[ext-apps]: https://github.com/modelcontextprotocol/ext-apps

View file

@ -1,10 +0,0 @@
# 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.

Some files were not shown because too many files have changed in this diff Show more