Skip to content

[Client][Server] Add Roots support - #395

Open
wachterjohannes wants to merge 2 commits into
modelcontextprotocol:mainfrom
wachterjohannes:eve/roots
Open

[Client][Server] Add Roots support#395
wachterjohannes wants to merge 2 commits into
modelcontextprotocol:mainfrom
wachterjohannes:eve/roots

Conversation

@wachterjohannes

@wachterjohannes wachterjohannes commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Adds support for the MCP Roots capability, letting a client expose file:// "workspace folders" that the server is allowed to operate on.

Client side

  • ListRootsRequestHandler + RootsCallbackInterface — answer server roots/list requests from a user-provided callback (mirrors the existing SamplingRequestHandler / SamplingCallbackInterface pattern).
  • Client::sendRootsListChanged() — send notifications/roots/list_changed. Guarded: it throws unless the client advertised the roots.listChanged capability, so a misconfigured client can't emit an undeclared notification.
  • Mcp\Exception\RootsException — thrown from a roots callback, its message is forwarded to the server (same contract as SamplingException); any other throwable is logged and returned as a generic internal error.

Server side

  • ClientGateway::listRoots() — request the client's roots (returns ListRootsResult, throws ClientException on an error response).
  • ClientGateway::supportsRoots() — capability-gating helper alongside supportsElicitation().

Schema

  • ListRootsResult::fromArray() — parse the client's response; Root continues to enforce the file:// URI scheme required by the spec.

Docs / examples / tests

  • README + docs/client.md sections and a runnable examples/client/stdio_roots.php.
  • Unit tests covering the handler, result parsing (incl. non-file:// rejection), capability serialization/round-trip, sendRootsListChanged() gating, and the ClientGateway roots methods.
  • CHANGELOG entry under 0.7.0.

Conformance notes

  • Root URIs are restricted to file:// per the current spec.
  • roots capability presence gates roots/list; roots.listChanged gates the list_changed notification.

@chr-hertel chr-hertel added Client Issues & PRs related to the Client component Server Issues & PRs related to the Server component improves spec compliance Improves consistency with other SDKs such as TyepScript labels Jul 14, 2026
Comment thread src/Schema/Result/ListRootsResult.php Outdated
@chr-hertel chr-hertel added the needs more work Not ready to be merged yet, needs additional follow-up from the author(s). label Jul 14, 2026
@chr-hertel chr-hertel added this to the 0.8.0 milestone Jul 27, 2026
@chr-hertel chr-hertel removed the needs more work Not ready to be merged yet, needs additional follow-up from the author(s). label Jul 27, 2026
Comment thread CHANGELOG.md Outdated
Comment thread src/Client.php
Comment thread examples/client/stdio_roots.php
@chr-hertel chr-hertel added the needs more work Not ready to be merged yet, needs additional follow-up from the author(s). label Jul 27, 2026
@wachterjohannes
wachterjohannes force-pushed the eve/roots branch 2 times, most recently from 7c4b520 to bdba67f Compare August 14, 2026 07:29
Wire up the MCP "roots" capability on both sides:

Client (php-sdk as client):
- RootsCallbackInterface + ListRootsRequestHandler answer server roots/list
  requests, mirroring the Sampling handler.
- Client::sendRootsListChanged() emits notifications/roots/list_changed.

Server (php-sdk as server):
- ClientGateway::listRoots() requests the client's roots, and
  supportsRoots() reports whether the client advertised the capability,
  normalizing the stored capabilities to an array so an empty
  capabilities object (\stdClass) doesn't cause a TypeError.
- Add ListRootsResult::fromArray() (used by listRoots()).
- ListRootsRequestHandler forwards RootsException messages to the server
  (new Mcp\Exception\RootsException), mirroring SamplingRequestHandler.
- Client::sendRootsListChanged() throws when the client did not advertise
  the roots.listChanged capability.

Examples, docs and tests:
- examples/README/docs advertise
  `new ClientCapabilities(roots: true, rootsListChanged: true)` before
  sending notifications/roots/list_changed.
- CHANGELOG entry for Roots (unreleased 0.7.0 section at the time).
- Unit tests for capability serialization/round-trip, sendRootsListChanged
  gating, and RootsException message forwarding.

phpunit, php-cs-fixer and phpstan green.
…, connection guard, working example

Responds to chr-hertel's review of the Roots support:

- ListRootsResult::fromArray() rejects non-array root elements instead of
  accepting malformed entries silently.
- CHANGELOG entry moved to the next unreleased section as the release
  train advanced past it (0.7.0 -> 0.8.0).
- Client::sendRootsListChanged() went straight to
  Protocol::sendNotification(), which sends via
  `$this->transport?->send()` -- on a client that is not connected the
  notification was silently dropped. It now throws a ConnectionException
  like sendRequest() does.
- The stdio example only advertised the roots capability, so the
  roots/list handler never actually ran. The client-communication demo
  server gets an `inspect_workspace_roots` tool that calls
  ClientGateway::supportsRoots()/listRoots(); the example calls it, so
  the server actually issues roots/list and the client answers it.
  Dropped the try/catch wrapper from the example.
- ClientTest's connected-client mock now goes through setState() so
  isConnected() reflects a real handshake instead of a stubbed flag.

phpunit, php-cs-fixer and phpstan green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Client Issues & PRs related to the Client component improves spec compliance Improves consistency with other SDKs such as TyepScript needs more work Not ready to be merged yet, needs additional follow-up from the author(s). Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants