Compare commits

..
642 changed files with 4721 additions and 56493 deletions

9
.gitattributes vendored
View file

@ -1,12 +1,7 @@
/.github export-ignore /.git* export-ignore
/.phpdoc export-ignore
/docs export-ignore
/examples export-ignore /examples export-ignore
/tests export-ignore /tests export-ignore
/.php-cs-fixer.dist.php 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 /phpstan* export-ignore
/phpunit.xml.dist export-ignore /phpunit.xml.dist export-ignore
/Makefile export-ignore

2
.github/CODEOWNERS vendored
View file

@ -1 +1 @@
* @chr-hertel @Nyholm @CodeWithKyrian @soyuka * @chr-hertel @Nyholm @CodeWithKyrian

View file

@ -1,11 +0,0 @@
version: 2
updates:
- package-ecosystem: composer
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

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,63 +0,0 @@
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.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: "none"
- name: Install Composer dependencies
uses: "ramsey/composer-install@v4"
- name: Install uv
uses: astral-sh/setup-uv@v10.1.0
with:
enable-cache: true
- name: Build documentation
run: make docs
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
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

View file

@ -5,81 +5,36 @@ 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
strategy: strategy:
matrix: matrix:
php: ['8.1', '8.2', '8.3', '8.4', '8.5'] php: ['8.1', '8.2', '8.3', '8.4']
dependencies: ['lowest', 'highest'] dependencies: ['lowest', 'highest']
symfony-version: ['']
include:
- symfony-version: '5.4.*'
dependencies: 'highest'
php: '8.2'
- symfony-version: '6.4.*'
dependencies: 'highest'
php: '8.2'
- symfony-version: '7.4.*'
dependencies: 'highest'
php: '8.4'
- symfony-version: '8.0.*'
dependencies: 'highest'
php: '8.4'
env:
SYMFONY_REQUIRE: ${{ matrix.symfony-version || '>=6.4' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v5
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
tools: flex
coverage: "none" coverage: "none"
- name: Install Composer - name: Install Composer
uses: "ramsey/composer-install@v4" uses: "ramsey/composer-install@v3"
with: with:
dependency-versions: "${{ matrix.dependencies }}" dependency-versions: "${{ matrix.dependencies }}"
- 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@v5
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -88,143 +43,21 @@ jobs:
coverage: "none" coverage: "none"
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v7 uses: actions/setup-node@v5
with: with:
node-version: '22' node-version: '22'
- name: Install Composer - name: Install Composer
uses: "ramsey/composer-install@v4" uses: "ramsey/composer-install@v3"
- name: Tests - name: Tests
run: vendor/bin/phpunit --testsuite=inspector run: vendor/bin/phpunit --testsuite=inspector
conformance-server:
name: conformance / server (${{ matrix.spec-version }})
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:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: '22'
- name: Install Composer
uses: "ramsey/composer-install@v4"
- 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 ${{ 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
if: failure()
run: |
echo "=== Docker Compose Logs ==="
docker compose -f tests/Conformance/Fixtures/docker-compose.yml logs
echo ""
echo "=== Conformance Log ==="
cat tests/Conformance/logs/conformance.log 2>/dev/null || echo "No conformance log found"
echo ""
echo "=== Test Results (first failed test) ==="
find tests/Conformance/results -name "checks.json" 2>/dev/null | head -3 | while read f; do
echo "--- $f ---"
cat "$f"
echo ""
done || echo "No results found"
echo ""
echo "=== Directory permissions ==="
ls -la tests/Conformance/
ls -la tests/Conformance/logs/ 2>/dev/null || echo "logs dir issue"
ls -la tests/Conformance/sessions/ 2>/dev/null || echo "sessions dir issue"
- name: Cleanup
if: always()
run: docker compose -f tests/Conformance/Fixtures/docker-compose.yml down
conformance-client:
name: conformance / client (${{ matrix.spec-version }})
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:
- name: Checkout
uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: "none"
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: '22'
- name: Install Composer
uses: "ramsey/composer-install@v4"
- name: Create log directory
run: mkdir -p tests/Conformance/logs
- name: Run client conformance tests
working-directory: ./tests/Conformance
run: >-
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
if: failure()
run: |
echo "=== Client Conformance Log ==="
cat tests/Conformance/logs/client-conformance.log 2>/dev/null || echo "No client conformance log found"
echo ""
echo "=== Test Results ==="
find tests/Conformance/results -name "checks.json" 2>/dev/null | head -3 | while read f; do
echo "--- $f ---"
cat "$f"
echo ""
done || echo "No results found"
qa: qa:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v5
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
@ -236,15 +69,10 @@ jobs:
run: composer validate --strict run: composer validate --strict
- name: Install Composer - name: Install Composer
uses: "ramsey/composer-install@v4" uses: "ramsey/composer-install@v3"
- name: Code Style PHP - name: Code Style PHP
run: vendor/bin/php-cs-fixer fix --dry-run run: vendor/bin/php-cs-fixer fix --dry-run
- 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
run: make docs-api

13
.gitignore vendored
View file

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

View file

@ -1,39 +1,45 @@
<?php <?php
/* /*
* This file is part of the official PHP MCP SDK. * This file is part of the Symfony package.
* *
* A collaboration between Symfony and the PHP Foundation. * (c) Fabien Potencier <fabien@symfony.com>
* *
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
if (!file_exists(__DIR__.'/src')) { if (!file_exists(__DIR__.'/src')) {
exit(0); exit(0);
} }
$fileHeader = <<<'EOF' $fileHeaderParts = [
This file is part of the official PHP MCP SDK. <<<'EOF'
This file is part of the official PHP MCP SDK.
A collaboration between Symfony and the PHP Foundation. A collaboration between Symfony and the PHP Foundation.
For the full copyright and license information, please view the LICENSE EOF,
file that was distributed with this source code. <<<'EOF'
EOF;
return (new Config()) For the full copyright and license information, please view the LICENSE
->setParallelConfig(ParallelConfigFactory::detect()) file that was distributed with this source code.
EOF,
];
return (new PhpCsFixer\Config())
// @see https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7777
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
->setRules([ ->setRules([
'@Symfony' => true, '@Symfony' => true,
'@Symfony:risky' => true, '@Symfony:risky' => true,
'header_comment' => ['header' => $fileHeader], 'protected_to_private' => false,
'declare_strict_types' => false,
'header_comment' => [
'header' => implode('', $fileHeaderParts),
],
'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'],
]) ])
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setFinder((new Finder())->in(__DIR__)) ->setFinder((new PhpCsFixer\Finder())->in(__DIR__))
; ;

View file

@ -1,13 +0,0 @@
{% extends 'layout.html.twig' %}
{% set topMenu = {
"menu": [
{ "name": "Guides", "url": "/"},
{ "name": "Specification", "url": "https://modelcontextprotocol.io/" }
],
"social": [
{ "iconClass": "fab fa-github", "url": "https://github.com/modelcontextprotocol/php-sdk"},
{ "iconClass": "fab fa-discord", "url": "https://discord.gg/6CSzBmMkjX"}
]
}
%}

View file

@ -1,10 +0,0 @@
<h1 class="phpdocumentor-title">
<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"/>
<path d="M109.853 46.9411L59.6482 97.1457C50.2757 106.518 50.2757 121.714 59.6482 131.087V131.087C69.0208 140.459 84.2168 140.459 93.5894 131.087L143.794 80.8822" stroke="black" stroke-width="12" stroke-linecap="round"/>
</svg>
<span style="margin-left: 0.25em;">{{ project.name }}</span>
</a>
</h1>

View file

@ -1,148 +1,3 @@
# Changelog # Changelog
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
-----
* Add built-in authentication middleware for HTTP transport using OAuth
* Add client component for building MCP clients
* Add `Builder::setReferenceHandler()` to allow custom `ReferenceHandlerInterface` implementations (e.g. authorization decorators)
* Add elicitation enum schema types per SEP-1330: `TitledEnumSchemaDefinition`, `MultiSelectEnumSchemaDefinition`, `TitledMultiSelectEnumSchemaDefinition`
* [BC break] Make Symfony Finder component optional. Users would need to install `symfony/finder` now themselves
* Add `LenientOidcDiscoveryMetadataPolicy` for identity providers that omit `code_challenge_methods_supported` (e.g. FusionAuth, Microsoft Entra ID)
* Add OAuth 2.0 Dynamic Client Registration middleware (RFC 7591)
* Add optional `title` field to `Prompt` and `McpPrompt` for MCP spec compliance
* [BC Break] `Builder::addPrompt()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments for `$description` must switch to named arguments.
* Add optional `title` field to `Tool` and `McpTool` for MCP spec compliance
* [BC Break] `Tool::__construct()` signature changed — `$title` parameter added between `$name` and `$inputSchema`. Callers using positional arguments must switch to named arguments or pass `null` for `$title`.
* [BC Break] `McpTool` attribute signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments for `$description` must switch to named arguments.
* [BC Break] `Builder::addTool()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments for `$description` must switch to named arguments.
0.4.0
-----
* Rename `Mcp\Server\Session\Psr16StoreSession` to `Mcp\Server\Session\Psr16SessionStore`
* Add missing handlers for resource subscribe/unsubscribe and persist subscriptions via session
* Introduce `SessionManager` to encapsulate session handling (replaces `SessionFactory`) and move garbage collection logic from `Protocol`.
0.3.0
-----
* Add output schema support to MCP tools
* Add validation of the input parameters given to a Tool.
* Rename `Mcp\Capability\Registry\ResourceReference::$schema` to `Mcp\Capability\Registry\ResourceReference::$resource`.
* Introduce `SchemaGeneratorInterface` and `DiscovererInterface` to allow custom schema generation and discovery implementations.
* Remove `DocBlockParser::getSummary()` method, use `DocBlockParser::getDescription()` instead.
0.2.2
-----
* Throw exception when trying to inject parameter with the unsupported names `$_session` or `$_request`.
* `Throwable` objects are passed to log context instead of the exception message.
0.2.1
-----
* Add `RunnerControl` for `StdioTransport` to allow break out from continuously listening for new input.
* Open range of supported Symfony versions to include v5.4
0.2.0
-----
* Make `Protocol` stateless by decouple if from `TransportInterface`. Removed `Protocol::getTransport()`.
* Change signature of `Builder::addLoaders(...$loaders)` to `Builder::addLoaders(iterable $loaders)`.
* Removed `ClientAwareInterface` in favor of injecting a `RequestContext` with argument injection.
* The `ClientGateway` cannot be injected with argument injection anymore. Use `RequestContext` instead.
* Removed `ClientAwareTrait`
* Removed `Protocol::getTransport()`
* Added parameter for `TransportInterface` to `Protocol::processInput()`
0.1.0
-----
* First tagged release of package
* Support for implementing MCP server

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.

205
LICENSE
View file

@ -1,200 +1,11 @@
The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License.
No rights beyond those granted by the applicable original license are conveyed for such contributions.
---
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright
owner or by an individual or Legal Entity authorized to submit on behalf
of the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
---
MIT License MIT License
Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is furnished
furnished to do so, subject to the following conditions: to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
@ -204,13 +15,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
SOFTWARE. THE SOFTWARE.
---
Creative Commons Attribution 4.0 International (CC-BY-4.0)
Documentation in this project (excluding specifications) is licensed under
CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
the full license text.

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 rector phpstan tests coverage run-examples ci ci-stable ci-lowest
# 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,46 +18,9 @@ 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
conformance-tests: conformance-server conformance-client
conformance-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
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
conformance-client:
rm -rf tests/Conformance/results
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
@ -77,20 +29,3 @@ ci: ci-stable
ci-stable: deps-stable cs phpstan tests 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
# 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

302
README.md
View file

@ -1,31 +1,27 @@
# 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 > [!IMPORTANT]
servers and clients in PHP — tools, resources, prompts, STDIO and HTTP transports, sessions, authorization, and both > This SDK is currently in active development with ongoing refinement of its architecture and features. While
protocol eras (the `initialize` handshake and the stateless `2026-07-28` revision). > functional, the API may experience changes as we work toward stabilization.
>
> If you want to help us stabilize the SDK, please see the [issue tracker](https://github.com/modelcontextprotocol/php-sdk/issues).
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
[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). [Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html).
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).
planned next steps and features.
## Protocol Conformance ## Roadmap
Measured weekly against the [MCP Conformance Test Framework](https://github.com/modelcontextprotocol/conformance), **Features**
as passing checks out of total. The `2026-07-28` scores run against the framework's `alpha` releases, so they move as - [ ] Stabilize server component with all needed handlers and functional tests
upstream publishes new scenarios. - [ ] Extend documentation, including integration guides for popular frameworks
- [ ] Implement Client component
| Revision | Server | Client | - [ ] Support multiple schema versions
|:--------:|:------:|:------:|
| `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) |
| `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) |
## Installation ## Installation
@ -33,20 +29,30 @@ upstream publishes new scenarios.
composer require mcp/sdk composer require mcp/sdk
``` ```
## Build a server ## Quick Start
A server is a plain PHP class plus three lines of wiring: This example demonstrates the most common usage pattern - a STDIO server using attribute discovery.
### 1. Define Your MCP Elements
Create a class with MCP capabilities using attributes:
```php ```php
use Mcp\Capability\Attribute\McpResource; <?php
use Mcp\Capability\Attribute\McpTool;
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
class Calculator namespace App;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Attribute\McpResource;
class CalculatorElements
{ {
/** /**
* Adds two numbers. * Adds two numbers together.
*
* @param int $a The first number
* @param int $b The second number
* @return int The sum of the two numbers
*/ */
#[McpTool] #[McpTool]
public function add(int $a, int $b): int public function add(int $a, int $b): int
@ -54,88 +60,226 @@ class Calculator
return $a + $b; return $a + $b;
} }
#[McpResource(uri: 'config://calculator/settings')] /**
public function settings(): array * Performs basic arithmetic operations.
*/
#[McpTool(name: 'calculate')]
public function calculate(float $a, float $b, string $operation): float|string
{ {
return ['precision' => 2]; return match($operation) {
'add' => $a + $b,
'subtract' => $a - $b,
'multiply' => $a * $b,
'divide' => $b != 0 ? $a / $b : 'Error: Division by zero',
default => 'Error: Unknown operation'
};
}
#[McpResource(
uri: 'config://calculator/settings',
name: 'calculator_config',
mimeType: 'application/json'
)]
public function getSettings(): array
{
return ['precision' => 2, 'allow_negative' => true];
} }
} }
exit(Server::builder()
->setServerInfo('Calculator', '1.0.0')
->setDiscovery(__DIR__, ['.'], excludeDirs: ['vendor'])
->build()
->run(new StdioTransport()));
``` ```
The walkthrough in [First server](docs/get-started/first-server.md) explains each piece, and ### 2. Create the Server Script
[Try it with the Inspector](docs/get-started/inspector.md) shows it running.
## Build a client Create your MCP server:
```php ```php
use Mcp\Client; #!/usr/bin/env php
use Mcp\Client\Transport\StdioTransport; <?php
$client = Client::builder() declare(strict_types=1);
->setClientInfo('My Application', '1.0.0')
require_once __DIR__ . '/vendor/autoload.php';
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
$server = Server::builder()
->setServerInfo('Calculator Server', '1.0.0')
->setDiscovery(__DIR__, ['.'])
->build(); ->build();
$client->connect(new StdioTransport(command: 'php', args: ['/path/to/server.php'])); $transport = new StdioTransport();
$tools = $client->listTools(); $server->run($transport);
$result = $client->callTool('add', ['a' => 5, 'b' => 3]);
$client->disconnect();
``` ```
See [Connecting to a server](docs/client/connecting.md) for transports, timeouts, and the ### 3. Configure Your MCP Client
handlers that answer server-initiated requests.
Add to your client configuration (e.g., Claude Desktop's `mcp.json`):
```json
{
"mcpServers": {
"php-calculator": {
"command": "php",
"args": ["/absolute/path/to/your/server.php"]
}
}
}
```
### 4. Test Your Server
```bash
# Test with MCP Inspector
npx @modelcontextprotocol/inspector php /path/to/server.php
# Your AI assistant can now call:
# - add: Add two integers
# - calculate: Perform arithmetic operations
# - Read config://calculator/settings resource
```
## Key Features
### Attribute-Based Discovery
Define MCP elements using PHP attributes with automatic discovery:
```php
// Tool with automatic name and description from method
#[McpTool]
public function generateReport(): string { /* ... */ }
// Tool with custom name
#[McpTool(name: 'custom_name')]
public function myMethod(): string { /* ... */ }
// Resource with URI and metadata
#[McpResource(uri: 'config://app/settings', mimeType: 'application/json')]
public function getConfig(): array { /* ... */ }
```
### Manual Registration
Register capabilities programmatically:
```php
$server = Server::builder()
->addTool([MyClass::class, 'myMethod'], 'tool_name')
->addResource([MyClass::class, 'getData'], 'data://config')
->build();
```
### Multiple Transport Options
**STDIO Transport** (Command-line integration):
```php
$transport = new StdioTransport();
$server->run($transport);
```
**HTTP Transport** (Web-based communication):
```php
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
$response = $server->run($transport);
// Handle $response in your web application
```
### Session Management
By default, the SDK uses in-memory sessions. You can configure different session stores:
```php
use Mcp\Server\Session\FileSessionStore;
use Mcp\Server\Session\InMemorySessionStore;
use Mcp\Server\Session\Psr16StoreSession;
use Symfony\Component\Cache\Psr16Cache;
use Symfony\Component\Cache\Adapter\RedisAdapter;
// Use default in-memory sessions with custom TTL
$server = Server::builder()
->setSession(ttl: 7200) // 2 hours
->build();
// Override with file-based storage
$server = Server::builder()
->setSession(new FileSessionStore(__DIR__ . '/sessions'))
->build();
// Override with in-memory storage and custom TTL
$server = Server::builder()
->setSession(new InMemorySessionStore(3600))
->build();
// Override with PSR-16 cache-based storage
// Requires psr/simple-cache and symfony/cache (or any other PSR-16 implementation)
// composer require psr/simple-cache symfony/cache
$redisAdapter = new RedisAdapter(
RedisAdapter::createConnection('redis://localhost:6379'),
'mcp_sessions'
);
$server = Server::builder()
->setSession(new Psr16StoreSession(
cache: new Psr16Cache($redisAdapter),
prefix: 'mcp-',
ttl: 3600
))
->build();
```
### Discovery Caching
Use any PSR-16 cache implementation to cache discovery results and avoid running discovery on every server start:
```php
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;
$cache = new Psr16Cache(new FilesystemAdapter('mcp-discovery'));
$server = Server::builder()
->setDiscovery(
basePath: __DIR__,
scanDirs: ['.', 'src'], // Default: ['.', 'src']
excludeDirs: ['vendor'], // Default: ['vendor', 'node_modules']
cache: $cache
)
->build();
```
## Documentation ## 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
- [Transports](docs/transports.md) - STDIO and HTTP transport setup and usage
- [MCP Elements](docs/mcp-elements.md) - Creating tools, resources, and prompts
- [Client Communiocation](docs/client-communication.md) - Communicating back to the client from server-side
- **[Get started](docs/get-started/index.md)** — Install the SDK and build your first server **Learning:**
- **[Servers](docs/servers/index.md)** — Tools, resources, resource templates, prompts, and how to register them - [Examples](docs/examples.md) - Comprehensive example walkthroughs
- **[Inside your handler](docs/handlers/index.md)** — Talking back to the client, logging, and asking for input
- **[Running your server](docs/run/index.md)** — Server builder, STDIO and HTTP transports, framework integration, sessions, authorization
- **[Clients](docs/client/index.md)** — Client SDK for connecting to and communicating with MCP servers
- **[Protocol versions](docs/protocol-versions.md)** — The two protocol eras, and what revision `2026-07-28` changed
- **[Advanced](docs/advanced/index.md)** — Events, protocol extensions (including MCP Apps), and custom message handlers
- **[Examples](docs/examples.md)** — Runnable server and client examples
- **[API Reference](https://php.sdk.modelcontextprotocol.io/api/)** — Generated class reference
## External Resources **External Resources:**
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- **[Model Context Protocol Documentation](https://modelcontextprotocol.io)** — Official MCP documentation - [Model Context Protocol specification](https://spec.modelcontextprotocol.io)
- **[Model Context Protocol Specification](https://spec.modelcontextprotocol.io)** — Protocol specification - [Officially supported servers](https://github.com/modelcontextprotocol/servers)
- **[Officially Supported Servers](https://github.com/modelcontextprotocol/servers)** — Reference server implementations
## 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 DX layer
- [bnomei/kirby-mcp](https://github.com/bnomei/kirby-mcp) — MCP server for the Kirby CMS * [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) - Symfony integration bundle
- [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
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. Start by [reporting issues](https://github.com/modelcontextprotocol/php-sdk/issues) or the project. 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).
[sending pull requests](https://github.com/modelcontextprotocol/php-sdk/pulls). See
[CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and what to run before
opening a PR.
## Credits ## Credits
The starting point for this SDK was the [PHP-MCP](https://github.com/php-mcp/server) project, initiated by [Kyrian Obikwelu](https://github.com/CodeWithKyrian), and the [Symfony AI initiative](https://github.com/symfony/ai). We are grateful for the work done by both projects and their contributors, which created a solid foundation for this SDK. The starting point for this SDK was the [PHP-MCP](https://github.com/php-mcp/server) project, initiated by
[Kyrian Obikwelu](https://github.com/CodeWithKyrian), and the [Symfony AI initiative](https://github.com/symfony/ai). We are grateful for the work
done by both projects and their contributors, which created a solid foundation for this SDK.
## License ## License
This project is licensed under the Apache License, Version 2.0 for new contributions, with existing code under the MIT License — see the [LICENSE](LICENSE) file for details. This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

View file

@ -1,21 +0,0 @@
# Roadmap
This roadmap is a living document that outlines the planned features and improvements for our project.
## Goals for the First Major Release
- **Server**
- [x] Implement full support for elicitations
- [ ] Implement OAuth2 authentication for server
- **Client**
- [x] Implement client-side support
- [x] Implement client examples and documentation
- [ ] Implement OAuth2 authentication for client
- **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,21 +0,0 @@
# Security Policy
Thank you for helping keep the Model Context Protocol and its ecosystem secure.
## Reporting Security Issues
If you discover a security vulnerability in this repository, please report it through
the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
for this repository.
Please **do not** report security vulnerabilities through public GitHub issues, discussions,
or pull requests.
## What to Include
To help us triage and respond quickly, please include:
- A description of the vulnerability
- Steps to reproduce the issue
- The potential impact
- Any suggested fixes (optional)

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

@ -1,8 +1,8 @@
{ {
"name": "mcp/sdk", "name": "mcp/sdk",
"description": "Model Context Protocol SDK for Client and Server applications in PHP",
"license": "Apache-2.0",
"type": "library", "type": "library",
"description": "Model Context Protocol SDK for Client and Server applications in PHP",
"license": "MIT",
"authors": [ "authors": [
{ {
"name": "Christopher Hertel", "name": "Christopher Hertel",
@ -22,40 +22,28 @@
"ext-fileinfo": "*", "ext-fileinfo": "*",
"opis/json-schema": "^2.4", "opis/json-schema": "^2.4",
"php-http/discovery": "^1.20", "php-http/discovery": "^1.20",
"phpdocumentor/reflection-docblock": "^5.6 || ^6.0", "phpdocumentor/reflection-docblock": "^5.6",
"psr/clock": "^1.0", "psr/clock": "^1.0",
"psr/container": "^1.0 || ^2.0", "psr/container": "^2.0",
"psr/event-dispatcher": "^1.0", "psr/event-dispatcher": "^1.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.1", "psr/http-factory": "^1.1",
"psr/http-message": "^1.1 || ^2.0", "psr/http-message": "^2.0",
"psr/http-server-handler": "^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/finder": "^6.4 || ^7.3 || ^8.0",
"symfony/uid": "^5.4 || ^6.4 || ^7.3 || ^8.0" "symfony/uid": "^6.4 || ^7.3 || ^8.0"
},
"suggest": {
"symfony/finder": "Required for file-based discovery."
}, },
"require-dev": { "require-dev": {
"ext-openssl": "*", "php-cs-fixer/shim": "^3.84",
"composer/semver": "^3.0",
"firebase/php-jwt": "^6.10 || ^7.0",
"laminas/laminas-httphandlerrunner": "^2.12",
"nyholm/psr7": "^1.8",
"nyholm/psr7-server": "^1.1",
"phar-io/composer-distributor": "^1.0.2",
"php-cs-fixer/shim": "^3.91",
"phpdocumentor/shim": "^3",
"phpstan/phpstan": "^2.1", "phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^10.5", "phpunit/phpunit": "^10.5",
"psr/simple-cache": "^2.0 || ^3.0", "psr/cache": "^3.0",
"symfony/cache": "^5.4 || ^6.4 || ^7.3 || ^8.0", "psr/simple-cache": "^3.0",
"symfony/console": "^5.4 || ^6.4 || ^7.3 || ^8.0", "symfony/cache": "^6.4 || ^7.3 || ^8.0",
"symfony/finder": "^5.4 || ^6.4 || ^7.3 || ^8.0", "symfony/console": "^6.4 || ^7.3 || ^8.0",
"symfony/http-client": "^5.4 || ^6.4 || ^7.3 || ^8.0", "symfony/process": "^6.4 || ^7.3 || ^8.0",
"symfony/process": "^5.4 || ^6.4 || ^7.3 || ^8.0" "nyholm/psr7": "^1.8",
"nyholm/psr7-server": "^1.1",
"laminas/laminas-httphandlerrunner": "^2.12"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -64,34 +52,24 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"Mcp\\Example\\Server\\CachedDiscovery\\": "examples/server/cached-discovery/", "Mcp\\Example\\CachedDiscovery\\": "examples/cached-discovery/",
"Mcp\\Example\\Server\\ClientCommunication\\": "examples/server/client-communication/", "Mcp\\Example\\ClientCommunication\\": "examples/client-communication/",
"Mcp\\Example\\Server\\ClientLogging\\": "examples/server/client-logging/", "Mcp\\Example\\CombinedRegistration\\": "examples/combined-registration/",
"Mcp\\Example\\Server\\CombinedRegistration\\": "examples/server/combined-registration/", "Mcp\\Example\\ComplexToolSchema\\": "examples/complex-tool-schema/",
"Mcp\\Example\\Server\\Elicitation\\": "examples/server/elicitation/", "Mcp\\Example\\CustomDependencies\\": "examples/custom-dependencies/",
"Mcp\\Example\\Server\\ComplexToolSchema\\": "examples/server/complex-tool-schema/", "Mcp\\Example\\CustomMethodHandlers\\": "examples/custom-method-handlers/",
"Mcp\\Example\\Server\\Conformance\\": "examples/server/conformance/", "Mcp\\Example\\DiscoveryCalculator\\": "examples/discovery-calculator/",
"Mcp\\Example\\Server\\CustomDependencies\\": "examples/server/custom-dependencies/", "Mcp\\Example\\DiscoveryUserProfile\\": "examples/discovery-userprofile/",
"Mcp\\Example\\Server\\CustomMethodHandlers\\": "examples/server/custom-method-handlers/", "Mcp\\Example\\EnvVariables\\": "examples/env-variables/",
"Mcp\\Example\\Server\\DiscoveryCalculator\\": "examples/server/discovery-calculator/", "Mcp\\Example\\ExplicitRegistration\\": "examples/explicit-registration/",
"Mcp\\Example\\Server\\DiscoveryUserProfile\\": "examples/server/discovery-userprofile/", "Mcp\\Example\\SchemaShowcase\\": "examples/schema-showcase/",
"Mcp\\Example\\Server\\EnvVariables\\": "examples/server/env-variables/",
"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\\OAuthMicrosoft\\": "examples/server/oauth-microsoft/",
"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": {
"sort-packages": true,
"allow-plugins": { "allow-plugins": {
"php-http/discovery": false, "php-http/discovery": false
"phpdocumentor/shim": true }
},
"sort-packages": true
} }
} }

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