Skip to content

v3.0.0 — Laravel MCP 1.0

Choose a tag to compare

@sylvesterdamgaard sylvesterdamgaard released this 16 Sep 07:39
· 56 commits to main since this release

Requires laravel/mcp ^1.0. Legacy MCP clients still connect: a client sending
initialize with protocol 2025-06-18 or 2025-11-25 negotiates as before.

Breaking

laravel/mcp is now ^1.0 only. 1.0 replaces the initialize handshake with
server/discover, removes session tracking, and validates headers on every POST.
Supporting 0.x alongside it would mean carrying two protocol behaviours indefinitely.
Nothing in this package needed rewriting — PKCE with S256 and Client ID Metadata
Documents, the two things 1.0 makes mandatory, were already implemented here.

The tool catalog is partial by default. Only statamic-entries,
statamic-blueprints and statamic-system-discover are listed when a client connects;
the other eight are exposed through search_tools and execute_tools.

tools/list was 31,077 bytes — roughly 7,800 tokens of JSON schema that every client
loaded on every connection, before it had asked anything. It is now 12,614 bytes, a 59%
cut.

Nothing is restricted by this. ToolSearch honours shouldRegister(), and
execute_tools invokes the tool's own handle(), so token scopes, resource policy,
Statamic permissions and the confirmation gate all apply exactly as on a direct call.
A searchable tool is hidden, never ungated.

This changes what a client sees, so it can regress a client that handles search_tools
badly. Set STATAMIC_MCP_SEARCHABLE_CATALOG=false to list every tool directly.

Added

  • Cache hints. server/discover, tools/list and prompts/list are reusable for
    5 minutes; the blueprint resources for 1. Scope is private throughout — these
    responses are filtered per caller. Tool calls are never cacheable.
  • WebEndpointProtocolTest — the web endpoint driven over HTTP the way a real
    client drives it, through CORS, auth, throttling, the permission gate and 1.0's
    header validation. That layer had no coverage at all.

Fixed

  • CORS preflights no longer 404. Mcp::web() registers no OPTIONS route, so
    preflights never reached the CORS middleware and its preflight branch was dead code.
    Since Authorization alone triggers a preflight, no cross-origin browser client could
    connect at all — a pre-existing bug. The addon now registers its own OPTIONS route and
    allows the MCP-* headers 1.0 requires, with CORS moved outermost so header
    rejections still carry Access-Control-Allow-Origin.
  • execute_tools keeps the configured response budget. ToolSearch capped output
    at its own unrelated limit and counted the envelope roughly twice, so responses that
    worked on a direct call failed once the tool moved behind the catalog.
  • The CP dashboard no longer breaks when a non-domain key is added under tools.
  • LargeDatasetTest no longer fails at random, and now catches what it claims to.
    It compared wall-clock, which measures the machine rather than the algorithm. It now
    measures CPU time — and the index-write invariant is counted rather than timed, because
    timing could not see it: reintroducing the quadratic bug moved the measured ratio only
    from 1.35x to 2.12x, but moves the counted writes from 1 to 50.

Full detail in CHANGELOG.md.