All notable changes to this project are documented here.
The format is based on Keep a Changelog, and
this project adheres to Semantic Versioning.
Patch releases (v1.7.X) carry non-breaking bug fixes and minor features;
minor releases (v1.X) carry breaking changes, major features, and expanded
MCP protocol version support; a major release (v2) will align with a
corresponding v2 in the wider MCP SDK ecosystem. See
CONTRIBUTING.md for the full versioning policy.
This file was introduced during the v1.7.x series. Structured entries below cover v1.6.0 and later; earlier releases can be reviewed via the Git tag history.
The v2 pre-release of the SDK, now in beta, adding day-one support for the
MCP 2026-07-28 "stateless core" spec revision alongside the existing
2024-11-05 … 2025-11-25 support. Contains breaking API changes relative to v1; see
docs/api-audit-v2.md for the full v1 → v2 API audit
that will feed the migration guide.
- MCP
2026-07-28protocol support (SEP-2575 stateless core):server/discoveranswered on stdio and HTTP without any prior handshake, fully sessionless on HTTP (noMcp-Session-Idminted or echoed, responses are plain JSON); client-side probing viaClientSession::discover(). The newMetaKeysclass holds the keys of the per-request_metaenvelope (protocol version, client info, client capabilities).- Dual-era negotiation. Server side: the era is detected per request — a
modern
_metaenvelope (or modernMCP-Protocol-Versionheader) selects stateless2026-07-28semantics on a fresh ephemeral context, while a legacyinitializeis served unchanged. Methods removed by the stateless revision (initialize,ping,logging/setLevel,resources/subscribe/unsubscribe) answer-32601on the modern path, and the RC-windowDRAFT-2026-v1identifier is accepted as an alias for2026-07-28on the per-request path only. Client side:Client::connect()probesserver/discoverfirst and falls back to the legacy handshake per the spec's rules; it gainsprotocolMode(auto/legacy/modern),protocolVersion, andprobeTimeoutoptions, and modern sessions stamp the_metaenvelope on every request with theMCP-Protocol-Versionheader mirrored from it. - Caching hints (SEP-2549):
ttlMs/cacheScopeon the six cacheable result types via the newCacheableResultinterface/trait, plus theresultTypediscriminator on every result — stamped with conservative defaults for2026-07-28clients and stripped for legacy clients. - JSON Schema 2020-12 in tool schemas (SEP-2106): composition,
conditionals, and
$refare passed through without dereferencing, andstructuredContentmay be any JSON value — including an explicitnull— when anoutputSchemais declared, with adaptation for legacy clients. - W3C Trace Context pass-through (SEP-414): the reserved
traceparent/tracestate/baggagekeys in_metawith a newTraceContextaccessor class; no OpenTelemetry dependency. - Request-metadata headers (SEP-2243): servers validate
Mcp-Method,Mcp-Name, and a consistentMCP-Protocol-Versionon modern HTTP requests (mismatches rejected with-32020 HeaderMismatch). Designated tool parameters (x-mcp-headerschema annotations on string/integer/boolean properties) are validated server-side against the request'sMcp-Param-*headers and mirrored client-side from cachedtools/listschemas, with strict exact-lowercase=?base64?…?=sentinel encoding for unsafe values. Shared rules live inMcp\Shared\McpHeaders; stdio is exempt (headers are HTTP-only). - Subscription streams (SEP-2575):
subscriptions/listenimplemented on HTTP and stdio — the acknowledgement is the stream's first message, every frame carries theio.modelcontextprotocol/subscriptionId_metakey (preserving the listen id's original wire type), theSubscriptionFilteris strictly contained, and the newSubscriptionsListenResultis sent as a closing frame when the server ends a subscription on its own initiative. Event fan-out crosses processes through the newSubscriptionBusInterface(FileSubscriptionBusfor typical PHP hosting,InMemorySubscriptionBusfor tests and long-running runtimes) withMcpServerpublish helpers (publishToolsListChanged()etc.). - Multi-round-trip input (SEP-2322): on the modern path,
tools/callandprompts/getanswerInputRequiredResult(resultType: "input_required",inputRequests, signedrequestState) instead of server-initiated sampling/elicitation/roots requests.ElicitationContext/SamplingContextresolve from the round'sinputResponsesor suspend, and the newInputContextbatches mixed elicitation + sampling + roots requests into a single round. The newRequestStateCodecHMAC-signsrequestStateand binds it to the authenticated principal, so tampering, expiry, and cross-user replay are rejected. Clients serviceinputRequeststhroughonElicit, the newonSampling, and roots handlers, retrying with key-matchedinputResponses, verbatimrequestStateecho, and a 16-round cap.
- Tasks extension (SEP-2663), declared via the new SEP-2133
extensionscapability. A clean, breaking redesign of the pre-release experimental Tasks surface to the2026-07-28stateless model (see Removed):- Methods are
tasks/get/tasks/update/tasks/cancel. Atools/callthe server augments as a task returns a flatCreateTaskResult(discriminated byresultType: "task");tasks/getreturns a flatDetailedTaskthat inlinesresult(completed),error(failed), orinputRequests(input_required) by status;tasks/updateandtasks/cancelreturn empty acks. Task fields are namedttlMs(always emitted,null= unlimited) andpollIntervalMs. - Server:
McpServer::enableTasks()registers the task methods and declares the extension; a tool opts into task augmentation viatool(..., taskSupport: …)with the newMcp\Server\TaskSupport(FORBIDDENdefault /OPTIONAL/REQUIRED). The file-basedTaskManagerimplements the SEP-2663 state machine (working ⇄ input_required → terminal,ttlMsexpiry, idempotent cancel). Everytasks/*method requires the client to have declared the extension (rejected-32021otherwise). Execution is synchronous-capture for shared-hosting compatibility; genuine async tasks are driven by the application viaMcpServer::getTaskManager(). In-task input reuses the SEP-2322 machinery: a task tool that elicits parks asinput_required, surfaces itsinputRequestsviatasks/get, and resumes ontasks/update. - Client:
ClientSession::getTask()/updateTask()/cancelTask()(andClientwrappers);ClientSession::declareExtension()declares extensions in the per-request capability envelope. - The SEP-2133 framework itself: a new
extensionsfield onServerCapabilities/ClientCapabilitiesand theMcp\Types\ExtensionIdsconstants. A malformed (non-object) extension value is ignored, so it can never unlock a feature.
- Methods are
- Application-driven task deferral (
TaskContext::defer()). A task-augmented tool can now hand its work to an out-of-band worker (a queue consumer, cron job, or another process) instead of finishing synchronously — the in-band entry point to the application-driven model the Tasks guide describes. The tool type-hints the new injectableMcp\Server\Tasks\TaskContext(always injected non-null and stripped from the tool's input schema; inert on plain synchronous calls, withisTask()/taskId()for branching), hands the taskId to its worker, and callsdefer(?statusMessage): the task staysworking, the client receives the flatCreateTaskResulthandle and pollstasks/get, and the worker settles the record through the existingMcpServer::getTaskManager()API. Deferral is race-safe against fast workers — anything the worker writes beforedefer()unwinds wins (an earlier progress message is kept, and a task the worker already settled is returned settled on the create response). Callingdefer()outside a task round is a-32603programming error.TaskManagergains theworking → workingself-transition so workers can refresh progress (the documented worker call previously threw), and a transition toworkingsheds staleinputRequests/requestStateso a resumed tool that defers surfaces handle-only. Documented in the Tasks guide ("Deferring to a background worker");examples/tasks_server.phpdemonstrates the full flow with aqueue-batchtool and a--workermode that settles queued tasks. - MCP Apps extension (SEP-1865, ext-apps revision
2026-01-26). Server-side support for host-rendered tool UIs — capability declaration plus_metaplumbing; the extension adds no new RPC method:- The new
McpServer::ui(tool, uri, name, html, …)helper attaches aui://template resource to a registered tool in one call: it registers the resource with MIMEtext/html;profile=mcp-app(McpServer::UI_MIME_TYPE), links the tool through_meta.ui.resourceUri(dual-writing the deprecated flat key for host back-compat), and declares the extension. Optional host hints:visibility,csp,permissions,domain, andprefersBorder; the HTML may be a string or a callback invoked lazily at read time. - Declared through the new generic
Server::declareExtension(), advertised in bothinitializeandserver/discover. Graceful degradation is automatic: a host that cannot render the UI ignores_meta.uiand the tool still returns its ordinarycontent.
- The new
- Added a MCP Apps for PHP skill for AI coding agents.
- Ahead-of-time validation for MCP Apps host hints. The new public
static
McpServer::validateUiHints(?visibility, ?csp, ?permissions)applies exactly the checksui()applies at registration (both delegate to the same internal validators), so deployments that author hints into stored configuration can reject an invalid hint at config-write time instead of throwing on every request during per-request server construction. The closed sets themselves are now public constants (McpServer::UI_VISIBILITY,UI_CSP_KEYS,UI_PERMISSIONS) for configuration UIs to enumerate. Registration behavior is unchanged. - Tool annotations on
McpServer::tool(). New trailingannotations:parameter (an array or aMcp\Types\ToolAnnotations, normalized via the newToolAnnotations::parse()) emitting the spec'sToolAnnotationsbehavioral hints (readOnlyHint/destructiveHint/idempotentHint/openWorldHint/title, spec revision2025-03-26) on listed tools; stripped automatically for clients that negotiated2024-11-05. - Feature-lifecycle deprecations (SEP-2596/SEP-2577). The spec's
deprecated-features registry is mirrored as
Mcp\Shared\FeatureLifecycle(Roots, Sampling, Logging, and Dynamic Client Registration deprecated at2026-07-28; theincludeContextsampling values at2025-11-25). There is no wire-level deprecation signal and wire behavior is unchanged — deprecated features keep working. The SDK emits one PSR-3 warning per feature per session when a deprecated feature is exercised on a session whose negotiated revision deprecates it, and the affectedTypes/classes, capability slots, and feature APIs carry@deprecateddocblocks. - Client authorization hardening. SEP-2468
issvalidation per RFC 9207 (error params never acted on whenissfails; newAuthorizationCallbackResult, backward compatible with string-returning handlers); SEP-837application_typeon dynamic registration; SEP-2352 authorization-server migration — the PRM is re-fetched on 401 and on 403insufficient_scope, tokens and credentials are never reused across issuers, and pre-registered credentials are issuer-bound via the newClientCredentials::$issuer, required by default (the explicitOAuthConfiguration::$allowUnboundClientCredentialsflag restores the published-spec behavior of pinning to the first validated issuer for the process lifetime); SEP-2207offline_accessgating onscopes_supported; theclient_credentialsgrant (private_key_jwt with ES256/RS256 assertions and client_secret_basic); and the SEP-990 cross-app-access flow (RFC 8693 token exchange + RFC 7523 jwt-bearer). TaskManager::sweep()— a bounded, resumable task-store sweep.cleanup()examines every task record in one call, which cannot fit a fixed cron budget once a file store has grown large (and task filenames are hashed, so callers cannot bound the work externally). The newsweep(?int $maxFiles = null, ?string $cursor = null)examines at mostmaxFilesrecords per call in stable filename order and returns{examined, deleted, cursor}— persist the cursor and pass it back to resume the pass on the next cron run (null= pass completed; orphaned lock sidecars are swept only on a completing call). The bound covers the per-record work (locked reads, expiry checks, deletions) — the filename enumeration itself still spans the store each call, cheap by comparison but not constant.cleanup()now delegates to an unboundedsweep(), with behavior unchanged.Mcp\Server\Tasks\TaskTransitionRejectedException— the dedicated typeTaskManagernow throws when an illegal task state transition is rejected (typically the documented settlement race: the task went terminal first because atasks/cancelor a concurrent settlement won), carrying the observed store status ($fromStatus) and the rejected target ($toStatus). Out-of-band workers can catch this precise type instead of the overbroad\InvalidArgumentExceptionthe rejection previously threw bare — which could silently misclassify a genuine programming error as a benign lost race. Fully backward compatible: the new type extends\InvalidArgumentExceptionwith a byte-identical message, so existing catches keep working. The Tasks guide's "Cancellation races" advice now names the precise type.- New typed exceptions
Mcp\Shared\UnknownMethodException,Mcp\Client\Transport\ReadTimeoutException, andMcp\Server\Transport\TransportClosedException, replacing exception-message string matching (messages unchanged for backward compatibility). Client::onSampling()— a pre-connect registration wrapper mirroringonElicit()/onListRoots(), so a sampling handler can be registered through the publicClient::connect()flow (session-levelClientSession::onSampling()must run before initialization, whichconnect()performs; without the wrapper the capability could not be advertised). Applied on bothconnect()andresumeHttpSession().- Pagination cursors on the client list methods, and the new
ClientSession::listResourceTemplates().listTools(),listPrompts(),listResources(), andlistResourceTemplates()accept an optional opaque cursor (a previous page'snextCursor), so paginated catalogs can be walked entirely through the convenience API — matching the Python and TypeScript SDKs — where pagination previously required dropping tosendRequest()with the typedList…Requestclasses. Calls without a cursor are wire-identical to before. On the modern HTTP path, a continuation page merges into the SEP-2243 annotation caches instead of resetting them, so paginating never drops an earlier page'sMcp-Param-*hints or rejection guards; a fresh (cursor-free) listing still resets both caches as before. - New examples, one per major v2 feature —
stateless_server.php,client_negotiation.php,tasks_server.php/tasks_client.php,elicitation_server.php/elicitation_client.php, andexamples/apps_server/— plus a newexamples/README.mdindex. Every runnable example was executed end-to-end over both stdio and HTTP. - Dual-track conformance testing for the
2026-07-28RC window: the stable conformance tool pin remains the legacy regression gate (composer conformance— its baseline is now empty, 100% of stable scenarios pass), and a second pin on the upstream RC-validation line runs the draft-spec suite (composer conformance-draft) againstconformance/conformance-draft-baseline.yml, with a CI job for each track. See conformance/README.md. - docs/v2-development-plan.md — the working plan for v2 development — and docs/api-audit-v2.md — the v1 → v2 API audit feeding the migration guide. v2 pre-release notice added to the README.
- The v2 documentation set: a v1 → v2 migration guide
(docs/migration-v2.md) covering every breaking
and behavioral change with executed before/after code and the
deprecated-features registry; extension guides for Tasks
(docs/tasks.md) and MCP Apps
(docs/apps.md); a documentation index
(docs/README.md) labeling every document's audience;
and a README rewritten as the v2 front door. The
server and client
development guides were overhauled to teach the
2026-07-28model as the default with legacy-only behavior explicitly marked — covering the stateless lifecycle,server/discover, dual-era negotiation, caching hints,subscriptions/listenpublishing, SEP-2243 designated parameters, multi-round-trip input, the Tasks client API, and the OAuth additions. Every code snippet in the new and overhauled documents was extracted verbatim and executed (or, for fragments, syntax-checked) against the SDK. AGENTS.md, CONTRIBUTING.md, docs/testing.md (now the canonical test-stack reference), docs/compatibility.md, tests/README.md, ROADMAP.md, and SUPPORT.md were swept for v1-era drift in the same change set.
- Spec PR #3002 absorbed (final
2026-07-28wire revision, merged upstream 2026-07-16):io.modelcontextprotocol/clientInfoin the modern request_metaenvelope is now a SHOULD — identity-less requests are served (a present-but-malformed value is still rejected-32602), andServerSession::getClientParams()->clientInfois nullable on the modern path. Server identity moved into result_meta["io.modelcontextprotocol/serverInfo"]: every modern response is stamped (handler-authored values win; legacy responses are never stamped), the top-levelDiscoverResult::$serverInfofield is removed (read identity viaDiscoverResult::getServerInfo()), and the client reads identity from_metaonly — newClientSession::getServerInfo(): ?Implementationreturns null for servers that do not identify themselves (InitializeResult::$serverInfois now nullable; the legacy handshake still requires it on the wire, and the forced-modernunknown@0.0.0placeholder is gone). The value is self-reported and display-only per the spec. - The
initializehandshake now caps at the newVersion::LATEST_LEGACY_PROTOCOL_VERSION(2025-11-25) — the handshake itself is removed in2026-07-28(SEP-2575), so the stateless revision is only selectable via the per-request_metaenvelope. The client'sinitialize()now requests the latest legacy revision. - The new
2026-07-28error codes follow the draft allocation policy:HeaderMismatch-32020,MissingRequiredClientCapability-32021, andUnsupportedProtocolVersion-32022(constants onMcp\Shared\McpError). Legacy (2025-11-25and earlier) behavior is unchanged. - SEP-2164: a missing resource is reported as
-32602(Invalid params) to2026-07-28clients while legacy revisions keep-32002; both shapes now carry the requesteduriinerror.data, and a missing resource is always an error, never an emptycontentsarray. - HTTP client error handling brought to parity with stdio: JSON-RPC error
responses now surface to callers as typed
Mcp\Shared\McpErrorwith code and data intact, where the HTTP transport previously threw an opaqueRuntimeException("Critical MCP error: …"). A configured clientreadTimeoutis now also enforced against a peer that sends nothing at all. Both are wire-compatible behavior fixes; theMcpErrorchange is API-visible to v1 code that caughtRuntimeExceptionfrom HTTP calls. McpServer's tools/call wrapper now propagates SDK-raisedMcpErrors as JSON-RPC protocol errors (matching the existingMcpServerExceptionhandling) instead of converting them intoisErrortool results — an API-visible change for tool handlers that deliberately throwMcpError.ClientSession::callTool()now returnsCallToolResult|CreateTaskResult— it surfaces a task handle when the server augments the call as a task.Server::getCapabilities()derives theresources.subscribecapability from actualresources/subscribehandler registration instead of hardcodingfalse, so the advertisement matches what the server really serves.examples/server_auth/modernized onto the fluentMcpServerAPI withwithAuth()(same filename and endpoints), and itstest-client.htmlnow exercises both protocol eras.- ROADMAP.md updated:
v2confirmed as the release vehicle for2026-07-28day-one support, and the MCP Apps extension (SEP-1865) promoted to a committed v2 release feature. - ROADMAP.md: two items added to the post-v2 embedding batteries, drawn
from production embedding feedback on the v2 beta — a handler
call-interceptor seam on
McpServer(generic decoration of tool/prompt handlers — cross-cutting error translation, metering, audit — without disturbing reflection-driven parameter matching) and a client-side HTTP exchange seam (the mirror of the server'sHttpIoInterface, enabling in-process client+server testing and non-cURL environments; to be designed together with the already-planned outbound endpoint/redirect policy seam). - ROADMAP.md rewritten now that v2 is feature-complete and in testing:
day-one support for each MCP spec release is codified as the project's
standing top priority, the immediate items narrow to what remains for
the
2026-07-28release gates, and a post-v2 direction is laid out — dependency-free embedding and web-integration batteries, with framework bridge packages documented as a possible future. See ROADMAP.md for the plan.
- The pre-release experimental Tasks API, replaced by the SEP-2663
extension without deprecation shims: the
taskscapability slot andTaskCapabilitytype; thetasks/listandtasks/resultmethods (now answered-32601; the completed result is inlined in thetasks/getresponse) with theirTaskListRequest/TaskListResult,TaskResultRequest, andTaskStatusNotificationtypes;ClientSession::listTasks()andgetTaskResult(); theio.modelcontextprotocol/related-task_metakey; and the previously stubbed$taskparameter onElicitationContext::form()/url()/requiresForm().
- Tool-annotation stripping for pre-
2025-03-26clients now removes onlyannotationsand preserves every other tool field —title,icons,outputSchema,execution, and extra fields such as the Apps_meta.uilink (the strip path previously rebuilt the tool with only name/inputSchema/description) — andtools/listresults are now walked per-tool (the adaptation previously matched only a bareToolresult and never fired on the list path). - An empty
ToolAnnotationsnow serializes as the spec's object shape ({}) instead of PHP's default[]for an empty array, matching the SDK's established empty-object handling (Meta,ExperimentalCapabilities, …) — previously a parsed"annotations": {}re-emitted as a JSON array on round-trip. - The stdio server now detects EOF on stdin and shuts down cleanly instead
of busy-waiting forever
(#61):
StdioServerTransport::readMessage()throws the newTransportClosedExceptionat EOF, whichServerSessiontreats as a clean shutdown signal, and handler-wrapping catch blocks rethrow it so a tool blocked on a stdio elicitation/sampling round-trip cannot swallow the shutdown into an error response aimed at a dead stream. ClientSession::negotiate()performs the spec's select-and-continue corrective retry after-32022 UnsupportedProtocolVersionexactly once, with the advertised version permitted to equal the one just rejected; a second-32022propagates instead of looping.- HTTP session detach/resume now restores the modern (
2026-07-28) era:ClientSession::createRestored()andClient::resumeHttpSession()accept a new optional$modernWireVersionparameter and auto-detect modern mode when the persisted negotiated version is a modern revision, so resumed sessions stamp the per-request_metaenvelope again and skip the legacy-only resume steps. Legacy resumes are byte-identical to before. The webclient reference persists and passes through the wire version. HttpServerRunnerno longer leaks the ephemeral modern session into a later legacy request on a reused runner instance (long-running runtimes, embedding, tests): the modern session is installed only for the duration of its own dispatch and the previous session is restored on every exit path.- The SEP-2352 issuer-migration guard is durable across PHP processes
(enforced through issuer-bound credentials rather than mutable migration
state — see the authorization-hardening entry under Added) and also runs
on the 403
insufficient_scopepath, so a step-up flow cannot carry credentials to a new issuer unchecked. The webclient reference collects and persists the issuer alongside pre-registered credentials and gates the legacy unbound mode behind an explicit checkbox. RequestStateCodec::withFileSecret()hardened for multi-tenant hosts: the per-installation signing secret initializes race-safely under an exclusive lock (reclaiming stubs left by crashed writers), locks its permissions to 0600 before any secret byte is written, and refuses symlinks and foreign-owned or other-readable files at the predictable default shared-temp path (POSIX).- Resource-content
_metanow round-trips:ResourceContentsno longer leaks the trait's internal extra-field storage as a literal wire key on serialize, andTextResourceContents/BlobResourceContentspreserve extra fields on parse — so a client retains the SEP-1865_meta.uihints onresources/readcontent.ExtraFieldsTraitgainssetExtraField()/getExtraField()accessors. HttpServerSession::toArray()deep-normalizesclientParams, so declared client capabilities (e.g. a bareelicitation: {}) survive cross-request restoration on session stores that keep PHP values in memory.ServerSession::adaptResponseForClient()adapts a shallow clone instead of mutating the handler'sResultin place — a handler-cached result served to a legacy client no longer loses itsttlMs/cacheScopefor subsequent modern clients, and modern stamping no longer leaks SDK defaults into handler state.initializerequests carrying_meta(e.g. SEP-414 trace context) no longer crash typed request construction with aTypeError.TaskManager's file store is now safe for concurrent cross-process mutation — load-bearing for the deferred-worker flow, where a worker,tasks/get, andtasks/cancelrace across processes. Every state transition runs its complete read-validate-write under a per-record advisory lock (a.locksidecar file,flockLOCK_EX, removed with the record and swept bycleanup()when orphaned), closing the lost-update interleaving where a stale read could overwrite a newer transition and resurrect a cancelled or completed task back toworking. Reads takeLOCK_SH, and writes open non-truncating ('c') and truncate only after the exclusive lock is held —file_put_contents(..., LOCK_EX)truncates before acquiring the lock, so a reader could observe an empty record and treat a live task as missing.cleanup(), which previously read without a lock and deleted any record that failed to decode, reads underLOCK_SHtoo, so it can no longer delete a live task caught mid-write. The locks are advisory and assume a local filesystem, like the file store itself.ToolInputSchema::jsonSerialize()no longer relies on a dead guard aroundproperties: the serializer used!empty()on theToolInputPropertiesobject intending to omit the key when no properties are declared, butempty()on an object is always false in PHP, so the omit branch could never fire and"properties": {}was always emitted. Always emitting the explicit empty object is now the deliberate, test-pinned behavior — the spec markspropertiesoptional on every supported revision and an empty object is semantically equivalent to omitting it, and the reference TypeScript and Python SDKs both emit"properties": {}— so the wire output is byte-identical to before; only the misleading dead code and its comment were removed.- Injected context parameters no longer leak into advertised metadata:
buildSchemaFromCallback()now strips anInputContextparameter from the reflection-built toolinputSchemalike the other four injectable contexts (anInputContext-hinted tool previously advertised a bogus required string property — per the2026-07-28spec,inputSchemadescribes only the client-suppliedtools/callarguments, and SEP-2322 input gathering is out-of-band), and prompt registration applies the same stripping so a context-hinted prompt callback no longer advertises a phantom required argument inprompts/list. Dispatch was already correct; only the advertised schemas change.
- URI template matching engine that decides whether a concrete URI matches a registered URI template and extracts the variable values.
- Implements resources templates as an API that aligns with the rest of the SDK.
- Server-side completions API
- Server and client doc additions
- Old examples that used low-level functions instead of the modern MCP API wrapper
- Published a day-one support roadmap for the MCP
2026-07-28"stateless core" spec revision (RC locked 2026-05-21). Adoption is additive and version-negotiated — the new stateless paths run only for clients that negotiate2026-07-28, leaving2024-11-05…2025-11-25core flows untouched. The experimental Tasks primitive will be replaced cleanly with the SEP-2663 extension. See ROADMAP.md. No protocol changes ship in this entry; this records the planned direction only. - Client side documentation guide
ClientSession::onListRoots()andClient::onListRoots()register aroots/listhandler and advertise therootscapability in the initialization handshake (listChanged: trueby default; passlistChanged: falsefor a static root set). Call beforeconnect()/initialize().ClientSession::onElicit()andClient::onElicit()accept a newsupportsUrlModeparameter (defaultfalse) that opts the client into advertising theurlsub-capability for elicitation, alongsideform. Without this flag spec-compliant servers will only send form-mode elicitation/create requests; the dispatch path for URL mode requests was already in place but unreachable in practice.
- Updated CI GitHub action for Node.js 24
- ElicitationCompleteNotification moved to the correct union type
- Expand server side documentation guide
- CancelledNotification now serializes requestId and optional reason under params, matching the MCP spec.
- Notifications carrying a
_metaobject no longer throw aTypeErrorwhile parsing._metais a typed?Metaproperty onNotificationParams, but the factory methods forwarded leftover wire fields with a direct assignment that bypassed the extra-field path and fataled against the typed slot. A spec-validnotifications/cancelledsuch as{requestId, _meta: {...}}now parses, as donotifications/progress,notifications/message,notifications/tasks/status, andnotifications/resources/updated, which shared the same defect._metais now normalized into aMetaobject via the newNotificationParams::applyWireFields()helper. - The client now declares the
rootscapability during initialization when a roots handler is registered viaonListRoots(), satisfying the MCP spec MUST for clients that support roots. Previously there was no high-level way to advertiseroots, so a spec-compliant server never calledroots/listand anynotifications/roots/list_changedthe client sent used an un-negotiated capability. ClientSessionnow responds to server-initiatedpingrequests with an empty result, satisfying the MCP spec MUST. Previously the request was dispatched into the session but no handler replied, so a server probing client liveness would see the ping time out and could consider the connection stale. A new publicRequestResponder::hasResponded()accessor lets user-registeredonRequesthandlers coexist with the built-in responder.
- Repository-level governance and process documentation: CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, SUPPORT, GOVERNANCE, ROADMAP, CHANGELOG.
- Issue and pull request templates under
.github/. - Expanded documentation under
docs/(compatibility, dependency policy, labels, testing) andconformance/README.md. - GitHub action for official MCP conformance tests
- GitHub action for unit tests and PHPStan
- PHP 8.1 compatibility fix for SSE emitter test
- Removed Chinese language README, it was a community contribution and we cannot currently verify an updated translation would be accurate.
Targets MCP spec revision 2025-11-25. Expands client-side auth back-compat,
adds server-initiated sampling, and broadens SSE streaming support on both sides
of the wire. Conformance: 100% of applicable required tests pass against suite
v0.1.16; three known failures remain in optional MCP Extensions (see
conformance/conformance-baseline.yml).
- Full MCP conformance test suite integration (
composer conformance,composer conformance-server,composer conformance-client) with a pinned tool version inpackage.json. - Server-initiated sampling.
- Server-side SSE elicitation and progress notifications.
- Client-side elicitation handler groundwork and elicitation defaults.
- Client-side SSE retry and improved cURL failure tolerance.
- Client-side back-compat for the
2025-03-26auth flow. - Optional
inputSchemaparameter for tool registration. - DNS rebinding protection on the HTTP server transport.
- Alignment with SEP-1699 on the client.
- Preservation of tool-call metadata to support future progress-notification plumbing.
- Additional client-side auth conformance coverage.
- HTTP server transport streaming and protocol handling refined.
- Webclient example updated to align with the current SDK client, and the automatic GET SSE stream is now disabled by default inside the webclient wrapper for broader shared-hosting compatibility.
- HTTP client SSE behavior on PHP 8.1.
- Cross-feature result carry-over across HTTP suspend/resume transitions.
- Unit test output noise.
- Correct sample field name for the
elicitation-sep1330-enumsscenario. - SSE streaming parser bug.
- HTTP origin allowlist normalization.
ElicitationCreateResultreturn type corrected.
- README and
AGENTS.mdrefreshed.
- Server developer documentation updated to cover elicitation.
- Removed the unnecessary
enableElicitationgate method.
- Experimental task features no longer interfere with elicitation requests.
_elicitationResultsis now correctly forwarded on HTTP resume.
- Dead code cleanup.
Release notes for v1.0.x through v1.5.x were not captured in this file.
Those tags remain in the repository and their commit history is authoritative.
See https://github.com/logiscape/mcp-sdk-php/tags.