Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sdk Laravel Package

mcp/sdk

The official PHP SDK for the Model Context Protocol: a framework-agnostic API for building MCP servers and clients in PHP, with tools, resources, prompts, STDIO and HTTP transports, sessions, authorization, and support for both protocol eras.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

  • Protocol Alignment: MCP SDK remains the reference PHP implementation for exposing tools, resources, and prompts to AI agents. v0.9.0 strengthens the server role specifically — it narrows authorization to the resource server role (ADR 0002) and adds opt-in per-session concurrency control, both of which are the concerns a Laravel API layer actually owns.
  • Framework Agnosticism: Still attribute-, DI-, and PSR-driven. Note a new PSR dependency direction: JwtTokenValidator::fromIssuer() caches keys in a PSR-6 pool, while sessions use PSR-16. Laravel ships PSR-16 natively (Cache) but not PSR-6 — a symfony/cache adapter bridged to a Laravel store becomes required infrastructure, not an optional detail.
  • Modularity: Hybrid registration and multiple transports unchanged; PassthroughMiddleware is a small but useful escape hatch for teams that want to own the HTTP middleware stack entirely rather than fight the SDK's defaults.
  • Concurrency Model Maturity: The v0.8.1 registry-change fix has been extended into a broader correctness story — per-request response routing, per-fiber elicitation/progress isolation, UUID request ids, and narrowed listChanged announcements. This is the release where the SDK stops being "single-request-at-a-time only" and becomes defensible behind a real web server.
  • Multi-Era Support: StdioTransport and the client now speak both protocol eras (2026-07-28 alongside the handshake era), with -32022 naming served revisions when a request can't be negotiated. This is architecturally significant: it lets a Laravel deployment serve heterogeneous AI clients without a parallel deployment.

Integration Feasibility

  • Laravel Compatibility:
    • Service Providers: Unchanged — wrap Mcp\Server/Mcp\Client in a provider. The upgrade touches the internals of that provider, not the pattern.
    • HTTP Transport: StreamableHttpTransport now behaves materially differently on POST: a POST is answered with its own responses, queued server-initiated messages are sent as SSE (no longer bundled into a JSON array, which 2025-06-18+ clients reject), and a POST containing only responses/notifications returns 202 with no body. Laravel routes and any Nginx/Octane layer must be configured for streaming and must not buffer.
    • Session Locking: Builder::setSessionLock() is opt-in and is the single most Laravel-relevant new feature. FileSessionLock (flock) is fine for single-node; SymfonyLockSessionLock maps cleanly onto symfony/lock's Redis/PDO/Postgres stores, which aligns with an existing Laravel Redis or database backend.
    • Authorization: OAuthProxyMiddleware, ClientRegistrationMiddleware, ClientRegistrarInterface, JwksProvider*, OidcDiscoveryInterface and the OIDC metadata policies are removed. Laravel Passport/Sanctum integration can no longer lean on the SDK to act as an OAuth proxy or dynamic client registration endpoint — the app must be a pure resource server validating bearer tokens and enforcing scopes.
  • Database/ORM: Unchanged — Eloquent-backed resources and tools remain straightforward. Concurrency fixes make read-modify-write tools safer, but session state is not a substitute for transactional DB writes.
  • Scope Enforcement: ScopePolicy replaces requireScopes() and answers 403 insufficient_scope per method and per tool, with scope hierarchies. This is a natural fit for Laravel Gates/Policies and is a net improvement over string comparisons scattered in tool code.
  • Headers and Mirrored Parameters: Mcp-Param-* headers whose mirrored argument is absent from the body are now rejected with -32020 (SEP-2243). Any proxy, CDN, or Laravel middleware that strips unknown Mcp-* headers will turn valid calls into errors.
  • Custom Transports: TransportInterface::send() now receives every response of a non-suspending handler (a batch may yield several), and Protocol::sendRequest()/checkResponse()/handleFiberYield() exchange Uuid instead of int. handleFiberYield() returns the id of the request it sent. Any bespoke Laravel transport (broadcast/queue/websocket bridge) is a rewrite, not a patch.

Technical Risk

  • Severity Change vs. v0.8.1: The prior assessment correctly called v0.8.1 a low-risk patch. v0.9.0 is the opposite — a minor version carrying a large, explicitly labelled set of [BC Break] items across transports, protocol ids, authorization, and middleware. This is not a drop-in bump.
  • Composer Constraint Reality: ^0.8.1 resolves to >=0.8.1 <0.9.0, so the upgrade is explicitly opt-in. That is protective, but it also means the team will silently sit on 0.8.x until someone deliberately bumps — schedule the decision rather than waiting for a security-driven scramble.
  • Authorization Rework — Highest Effort Item:
    • Removal of the OAuth proxy/registration stack invalidates any pilot that used the SDK as an authorization server facade.
    • JwtTokenValidator is now final, takes a single issuer (not an alias list), a $keys set (e.g. CachedKeySet), an alg allowlist, $tokenType, and $leeway. Multi-tenant Laravel apps validating several issuers must construct multiple validators and dispatch between them.
    • AuthorizationResult::allow() takes an AccessToken; OAuthRequestMetaMiddleware is replaced by RequestContext::getAccessToken().
    • ProtectedResourceMetadata now requires $resource, serves at the RFC 9728 §3.1 derived path, and requires https except loopback — a misconfigured APP_URL breaks discovery in staging.
  • Middleware Security Posture Tightened:
    • DnsRebindingProtectionMiddleware always validates Host (missing Host is rejected) and checks Origin against a new allowedOrigins list (scheme + port, empty by default; same-origin always allowed). Behind a load balancer that rewrites or drops Host, or for browser-based clients, this is a production-breaking default if unconfigured.
    • CorsMiddleware now lets OPTIONS through without a token (fixing a real preflight 401), allows Mcp-Method, Mcp-Name, Mcp-Param-* and wildcard-prefixed headers, and exposes WWW-Authenticate. Laravel's own HandleCors must not shadow or contradict this.
  • Concurrency Semantics Are Opt-In: without setSessionLock(), the session-overwrite fix (Session::save() writes only changed keys) mitigates but does not eliminate interleaving. Per-session serialization trades throughput on hot sessions for correctness — a design decision, not a config toggle.
  • Failure Signals Changed: an unreachable Psr16SessionStore now returns 503 instead of 404 (previously clients would silently re-initialize), and lock failures/timeouts return 503 with JSON-RPC errors (SessionLockTimeoutException, SessionLockException). Monitoring, alerting, and client retry logic that keyed off 404 must be updated.
  • Release Notes Appear Truncated: the provided changelog ends mid-sentence at the listChanged item ("…neve…"). Before committing, obtain the complete release body plus any UPGRADING.md and ADR 0002 text — an incomplete BC list is itself a risk.
  • Cadence Track Record: 0.6 → 0.8.1 → 0.9.0 in a short window, with BC breaks in the latest minor. Treat pre-1.0 MCP surfaces as an experimental boundary behind an internal adapter layer so SDK churn does not leak into domain code.

Key Questions

  1. Authorization Ownership: With the OAuth proxy and client registration removed, which system is the authorization server now (Passport? External IdP?)? Who owns the resource metadata document and its https-only $resource value?
  2. Multi-Issuer Support: Does any Laravel surface need to validate tokens from more than one issuer, given JwtTokenValidator is final with a single issuer?
  3. Session Lock Backend: Single-node FileSessionLock, or SymfonyLockSessionLock over Redis/PDO? What is the acceptable p95 added latency from serializing one session's concurrent requests?
  4. Custom Transports: Do we own any TransportInterface implementation? If so, the Uuid id and send()-receives-all-responses changes are a scheduled rewrite.
  5. Era Matrix: Which protocol revisions must we serve (2026-07-28 vs. handshake era)? Does any endpoint intentionally serve a single era, and is -32022 handled by clients?
  6. Streaming Path: Can Nginx/Cloudflare/Octane deliver SSE responses unbuffered for the new POST behavior, and does the Laravel route return 202 correctly for notification-only POSTs?
  7. Proxy Header Fidelity: Do any hops strip Host, Origin, or Mcp-Param-*? Both DNS-rebinding protection and SEP-2243 depend on those surviving intact.
  8. Scope Model: How do MCP scopes and hierarchies map onto existing Laravel Gates/Policies — one source of truth or duplicated?
  9. Upgrade Window: What is the tolerance for a BC-breaking minor every ~quarter, and who re-runs tool contract tests each time?

Integration Approach

Stack Fit

  • Laravel Synergy:
    • Service Container: McpServerServiceProvider now also binds SessionLockInterface, the PSR-6 key-set pool, and ScopePolicy implementations — not just the server/client.
    • Middleware: Use PassthroughMiddleware if the team prefers to own the stack; otherwise configure SDK CorsMiddleware ($allowedHeaders, allowedOrigins) deliberately and ensure Laravel's HandleCors does not conflict.
    • Routing: Map MCP HTTP endpoints to Laravel routes; ensure the POST handler can emit SSE and return 202 with no body for responses/notifications-only POSTs.
    • Gates/Policies: Implement ScopePolicy on top of Laravel's authorization layer so MCP scope checks and app-level checks share one definition.
  • Database:
    • Eloquent-as-resource patterns unchanged. Session writes are now key-level (no whole-session overwrite), but treat sessions as transport state, never as a data store.
  • Cache / Lock Backends:
    • PSR-16 for sessions → Laravel Redis store via adapter.
    • PSR-6 for JWKS caching → symfony/cache over a Laravel-backed store; document the two-pool split or the team will conflate them.
    • Lock → SymfonyLockSessionLock over symfony/lock Redis/PDO for any multi-node deployment.
  • Queue Jobs: Unchanged for heavy tools; note the new -32022/-32020/503 responses create retryable vs. non-retryable classes that job wrappers should distinguish.

Migration Path

  1. Freeze & Branch: Keep production on ^0.8.1. Create an upgrade branch pinned to ^0.9.0; do not widen a caret on 0.x expecting a smooth ride.
  2. Read First: Full v0.9.0 release body, UPGRADING.md, and ADR 0002. The provided notes are truncated — treat the authoritative list as a prerequisite gate.
  3. Authorization First (largest blast radius): Remove dependence on OAuthProxyMiddleware/ClientRegistrationMiddleware/JwksProvider*; move token validation to JwtTokenValidator::fromIssuer() with CachedKeySet; migrate requireScopes() calls to ScopePolicy; fix ProtectedResourceMetadata $resource and https.
  4. Transport & Protocol Ids Second: Convert custom transports to Uuid ids and the new send() contract; verify SSE response path and 202 handling; re-test any Mcp-Param-* mirroring.
  5. Session Lock Third (opt-in): Enable setSessionLock() with SymfonyLockSessionLock; load-test one hot session to quantify serialization cost.
  6. Security Middleware Last: Configure allowedOrigins (empty by default!) and confirm Host survives every proxy hop before enabling in staging.

Compatibility

  • Laravel Versions: PHP 8.1+ baseline holds; validate on Laravel 10/11 and specifically Octane, where long-lived workers interact with the new streaming and fiber-resumption behavior.
  • SDK Version: Target ^0.9.0 deliberately. ^0.8.1 will not pull 0.9.0 — that constraint is now a deliberate hold, not a safety net to forget about.
  • Dependencies: symfony/lock (new, for SymfonyLockSessionLock) and a PSR-6 pool (new, for JWKS caching) enter the dependency graph. Confirm they don't collide with existing symfony/* pins.
  • Removed APIs: Any reference to OAuthRequestMetaMiddleware, ClientRegistrarInterface, ClientRegistrationException, JwksProvider(Interface), OidcDiscoveryInterface, or AuthorizationResult::allow() with request attributes will fatal — grep the codebase before upgrading.
  • Tool/Resource Naming: Unchanged guidance; keep MCP paths off reserved Laravel route prefixes.
  • Tool Signatures: Variadic handling from 0.8.1 is retained; re-assert list-shaped tool arguments and add coverage for concurrent invocations in the same session.

Sequencing

  1. Evaluate (no code): confirm ADR 0002 implications, choose session lock backend, choose PSR-6 pool, inventory custom transports and removed-API usage.
  2. Upgrade & Boot: bump to ^0.9.0, publish config, fix service provider bindings until the app boots.
  3. Authorization Refactor: JwtTokenValidator, ScopePolicy, ProtectedResourceMetadata, RequestContext::getAccessToken().
  4. Transport Adaptation: Uuid ids, send() semantics, SSE and 202 routes, middleware ordering (PassthroughMiddleware or explicit config).
  5. Concurrency Enablement: setSessionLock(), session store exists() exception handling, retry-on-503 client policy.
  6. Test Matrix: single-request, concurrent-same-session, dual-era, CORS preflight, Host/Origin validation, lock timeout, session store outage, Mcp-Param-* rejection.
  7. Canary: internal tools only, then public endpoints; keep the 0.8.x branch deployable until two clean weeks of production traffic.

Operational Impact

Maintenance

  • Dependency Cadence: Pre-1.0 with BC breaks per minor. Budget a recurring quarterly upgrade review with a named owner; this is now a standing cost, not a one-off evaluation.
  • Constraint Hygiene: An ^0.8 constraint is an invisible deferral. Track the pinned MCP version in a visible place (README/ADR) and alert on new minor releases.
  • Dual Cache Abstractions: PSR-16 (sessions) and PSR-6 (JWKS) will be confused by new team members. Document which Laravel store backs which pool and their TTLs/failure behavior.
  • Logging Signals Changed: 404 no longer means "session gone" in the cache-outage case; 503 now covers store outages and lock failures. Update log-based dashboards, alerts, and runbooks accordingly.
  • Correlation Ids: Client-request ids are UUIDs. Any log parsing, trace correlation, or dashboard keyed on integer ids breaks silently.
  • Schema/Protocol Evolution: Multi-era support reduces upgrade pressure on clients but doubles the test matrix. Track the spec and the served-revision list explicitly.

Support

  • Debugging: SSE-not-buffered is the first thing to check on "responses never arrive"; Host/Origin rejections are the first thing to check on "works locally, 4xx in staging."
  • Troubleshooting Guide:
    • 503 + lock error: lock backend unhealthy or contention — check Redis/PDO lock store, then session fan-out.
    • 503 + session store error: PSR-16 store unreachable — previously a silent 404 re-init; clients should now retry, not re-initialize.
    • 403 insufficient_scope: ScopePolicy mapping or hierarchy misconfigured per method/tool.
    • -32020: Mcp-Param-* header present but mirrored argument missing — usually a client/proxy contract mismatch.
    • -32022: client speaks a revision this endpoint doesn't serve — check the endpoint's served revisions.
    • 401 on preflight: should no longer occur behind AuthorizationMiddleware; if it does, a conflicting CORS layer is in front.
  • Community: Active release cadence with new external contributors remains a positive signal; the volume of BC breaks also means upstream discussion is the fastest path on ambiguous migration items.

Scaling

  • Per-Session Serialization: setSessionLock() converts intra-session concurrency into a queue. Design for many sessions, low per-session concurrency; a single hot session (one agent doing heavy parallel tool calls) will see p95 rise and eventually 503. Measure before enabling broadly.
  • Lock Backend Scale: flock is single-node only. Any autoscaled or multi-AZ Laravel deployment needs SymfonyLockSessionLock over a shared store; the lock store inherits the availability profile of Redis/DB.
  • Stateless HTTP: unchanged requirement — no session affinity; sessions in shared PSR-16, locks in shared store.
  • Streaming Cost: SSE responses hold a PHP worker (or an Octane task) for the duration of the stream. The keep-alive/abort fix removes the worst case (a disconnected client pinning a worker until timeout), but concurrency budgets must still account for open streams.
  • Rate Limiting: keep Laravel throttle on MCP endpoints; also rate-limit per session, since locking makes one session's throughput a shared resource.

Failure Modes

Component Failure Scenario Mitigation
Session Lock Lock timeout or failing lock backend → 503 Choose a redundant shared lock store; client retry with backoff; alert on SessionLockTimeoutException rate.
Session Store PSR-16 unreachable → 503 (was 404) Health-check the store; ensure clients retry rather than re-initialize; handle SessionStoreException from exists().
HTTP Streaming Proxy/Octane buffers SSE; queued messages never delivered Disable buffering end-to-end; verify POST returns SSE for queued messages and 202 for notification-only POSTs.
DNS Rebinding Protection Missing/rewritten Host rejected; cross-origin browser client blocked by empty allowedOrigins Preserve Host through every hop; explicitly configure allowedOrigins (scheme + port) or ['*'] where appropriate.
CORS Mcp-* headers or WWW-Authenticate not allowed/exposed Configure SDK CorsMiddleware (allowedHeaders, wildcard prefixes) and remove conflicting Laravel HandleCors rules.
Authorization Removed OAuth proxy/registration classes referenced; multi-issuer token rejected Inventory removed APIs pre-upgrade; one JwtTokenValidator per issuer with explicit dispatch.
Resource Metadata $resource missing or non-https → discovery fails Set https APP_URL/resource value; use loopback allowance only in local dev.
Scope Enforcement ScopePolicy misconfigured → blanket 403 or over-permissive tool access Map scopes per method/tool; test hierarchies explicitly; add negative tests.
Custom Transport Uuid ids or new send() semantics unhandled → dropped/misrouted responses Rewrite against the new contract; assert multi-response batches.
Mirrored Params Mcp-Param-* header stripped by proxy → -32020 Allowlist Mcp-* headers at CDN/LB; validate end-to-end.
Protocol Era Client revision unsupported → -32022 Publish served revisions; test both eras; handle negotiation errors client-side.
Variadic Tools Array/spread args coerced incorrectly Pin ≥0.8.1 (satisfied by 0.9.0); assert element typing in tool tests.
Registry/List Changes Spurious capability-change announcements Satisfied by 0.8.1 + narrowed listChanged in 0.9.0; verify composed registries.

Ramp-Up

  • Onboarding:
    • Rewrite the internal Laravel MCP guide around resource-server authorization (ADR 0002), dual PSR pools, and session locking. The 0.8.x guide is now misleading on auth.
    • Publish a starter template pinned to ^0.9.0 with SymfonyLockSessionLock and a PSR-6 JWKS pool preconfigured.
  • Team Training:
    • Developers: ScopePolicy + Laravel Gates as one authorization model; attribute-based tool discovery unchanged.
    • Ops/SRE: SSE and buffering, Host/Origin validation through proxies, lock-store health, and the new 503 semantics.
  • Adoption Phases:
    1. Upgrade internals only — no new capabilities; prove boot, auth, and transport parity.
    2. Enable session locking on internal tools; measure per-session latency.
    3. Expand to public/AI-facing endpoints with the full era and CORS matrix.
    4. Client integration for internal AI workflows, now on dual-era support.
  • Effort Signal for Planning: This is a multi-sprint upgrade driven by the authorization rework, not an afternoon dependency bump — materially different from v0.8.1, and the assessment's effort estimate should be revised upward accordingly.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc