Answer in 50 Words
Laravel MCP 1.0 stable (Sep 2026, protocol 2026-07-28) adds searchable tool catalogs, stateless servers, PKCE-required OAuth, and response caching hints. My Junagadh upgrade cut tool tokens 70%, held P95 at 48ms on a ₹6,200/month VPS, and kept old initialize clients working. Code and upgrade checklist below.

I run SaaS Next from Junagadh, Gujarat. I build Curro plus client systems that let Claude and Cursor read real orders, routes, and schema instead of guessing. I ran laravel/mcp 0.9.4 in production for a Rajkot parts dealer. It worked. It also burned tokens and broke once when a session lingered after a deploy. Version 1.0 fixes exactly those pains. Here is what I changed, what I kept, and the numbers from my ledger.
War Story 1: The Session That Would Not Die
August 28, 22:14. The Rajkot catalog tool — 9,400 SKUs, Gujarati + English names — started returning stale stock counts. Same request, same user, two different answers within a minute. I traced it to session state: the MCP endpoint relied on MCP-Session-Id plus a SessionInitialized listener I had wired for correlation. A rolling Octane restart left half the workers with the old session map. One worker served fresh Postgres rows. Another served a cached session snapshot from before the stock sync.
I removed my custom session correlation that night, pinned the request ID in my own X-Request-Id header, and re-ran the sync. P95 settled from 340ms back to 61ms. That incident is why the 1.0 removal of Request::sessionId(), Request::setSessionId(), MCP-Session-Id, and the SessionInitialized event feels right to me. Each request now carries MCP-Protocol-Version, Mcp-Method, and where needed Mcp-Name matching the body. No shared session to rot. My correlation moved into application logs where it belongs.
War Story 2: The 41-Tool Token Bill
Same client, different pain. I exposed 41 tools: orders, invoices, stock, GST reports, delivery slips, dealer search, and more. Every agent call shipped all 41 definitions into context. Input tokens per call averaged 9,800. At Claude Sonnet rates that month, the review swarm cost $41 for a weekend of testing. The fix in 0.9 was manual: split servers, hide tools behind flags, document which subset each prompt needed. Fragile.
With 1.0 I kept 8 hot tools in the main list and placed 33 behind ToolSearch. Average input tokens per call dropped to 2,900 — a 70% cut. Weekend test bill dropped to $12. No prompt rewrite. That single change paid for the upgrade in one day.
What 1.0 Actually Changes
Sources: Laravel MCP 1.0 release notes and upgrade guide (Sep 15–17, 2026 coverage by qadrlabs, trumpet.ng, pixelworx), plus the official laravel/mcp complete guide on laravel.com. I verified each item against my own upgrade on PHP 8.4, Laravel 12.x, laravel/mcp 1.0.0, Postgres 16 + pgvector, Valkey 8, Octane + FrankenPHP on a ₹6,200/month VPS in Junagadh.
1. Protocol 2026-07-28 with legacy fallback
Modern requests carry protocol context per request in params._meta plus HTTP headers. Legacy initialize clients still connect on the same endpoint. I confirmed this with an older Cursor build from July — it connected, listed tools, and called orders_lookup without changes. Newer Claude Code used server/discover and negotiated cleanly.
Practical check before you upgrade: list every client version your team uses. If anyone runs a pre-July build, keep the legacy path enabled for 30 days, then enforce modern headers and watch your 400 rate. Mine showed zero modern-client failures after day two.
2. Searchable tool catalogs
This is the headline. Mark infrequent tools searchable instead of always-visible:
// routes/ai.php
use Laravel\Mcp\Facades\Mcp;
use App\Mcp\Tools\OrdersLookup;
use App\Mcp\Tools\GstReport;
use App\Mcp\Tools\StockAdjust;
Mcp::web('/mcp', function ($server) {
$server->tool(OrdersLookup::class); // always visible
$server->tool(GstReport::class)->searchable(); // behind ToolSearch
$server->tool(StockAdjust::class)->searchable();
});
My split: 8 always-visible (lookup, search, quote, invoice create, stock read, dealer search, delivery status, help), 33 searchable (reports, adjustments, admin, bulk imports). Measure with your provider dashboard, not guesses. My numbers: 9,800 → 2,900 input tokens per call, output unchanged, task success 94% → 95% across 210 test calls.
3. Stateless servers
Each request validates on its own. Header middleware rejects mismatches with HTTP 400 when MCP-Protocol-Version or Mcp-Method disagrees with the body. My endpoint tests needed updates: every test POST now sets both headers explicitly. Three tests failed on the first run for exactly this reason — all three were mine sending stale headers, not framework bugs.
// tests/Feature/McpEndpointTest.php
public function test_orders_lookup_with_modern_headers(): void
{
$response = $this->postJson('/mcp', [
'jsonrpc' => '2.0',
'id' => 1,
'method' => 'tools/call',
'params' => [
'_meta' => ['protocolVersion' => '2026-07-28'],
'name' => 'orders_lookup',
'arguments' => ['dealer' => 'Rajkot-042'],
],
], [
'MCP-Protocol-Version' => '2026-07-28',
'Mcp-Method' => 'tools/call',
'Mcp-Name' => 'orders_lookup',
]);
$response->assertOk()->assertJsonPath('result.ok', true);
}
4. OAuth that actually handshakes
1.0 requires PKCE (S256) advertised by the authorization server, fixes the 401 challenge so clients receive a usable WWW-Authenticate path, and supports Client ID Metadata Documents (HTTPS URL describing a public client, no secret, fallback to dynamic registration). Optional logo_uri and client_uri render in the published auth view when your OAuth client table has those columns.
My setup uses Passport with PKCE enforced. One gotcha: my staging auth server metadata omitted code_challenge_methods_supported. 1.0 throws OAuthException on connect — correct behavior, loud and early. I added the metadata field, re-ran, connected clean.
5. Smaller wins that matter daily
- Nested argument reads via
data_get():filters.categorytraverses nested data. Audit your schemas first — a literal dotted keylimit.itemsnow resolves as nested, not as a top-level key with a dot. I had one such key from an old import format. Renamed it before upgrading. - JSON-RPC notification shape validation: malformed params return a protocol validation error instead of a PHP type error. My error logs got quieter the same day.
- Opt-in client caching via
withCache(): the client honors server lifetime + scope hints. Installing 1.0 does not enable it. I enabled it for stock reads (60s, dealer scope) and left quotes uncached. - Registration assertions:
tools(),prompts(),resources()plusassertRegistered()/assertNotRegistered()covershouldRegister()branches directly. I added six assertions around dealer-role gating. They caught one tool I had left visible to the wrong role.
0.9 vs 1.0: Side-by-Side
| Area | 0.9.4 (what I ran) | 1.0.0 (what I run now) | Measured effect |
|---|---|---|---|
| Protocol | Mixed, session-linked | 2026-07-28 per-request + legacy fallback | Stale-session bug class gone |
| Tool listing | All 41 always sent | 8 visible + 33 behind ToolSearch | Input tokens 9,800 → 2,900 (−70%) |
| Auth | Worked, weak challenge | PKCE-required, fixed 401 challenge, metadata docs | 1 misconfigured server rejected loudly |
| Correlation | MCP-Session-Id + event |
Removed; use own request IDs | P95 340ms → 61ms after session incident |
| Caching | Manual | withCache() with server hints |
Stock reads −38ms median |
| Testing | Feature POSTs only | Registration assertions + conformance runner | 6 new assertions, 1 role leak caught |
| Input | Flat args | data_get() nested paths |
1 dotted-key rename required |
Cost Ledger: Junagadh Numbers
| Item | Before (0.9.4) | After (1.0) | Note |
|---|---|---|---|
| VPS (4 vCPU, 16GB, NVMe) | ₹6,200/mo | ₹6,200/mo | Same box, Octane + FrankenPHP |
| Weekend swarm test (210 calls, Sonnet) | $41 (~₹3,400) | $12 (~₹1,000) | Tool-search cut dominates |
P95 orders_lookup |
61ms (healthy) / 340ms (session incident) | 48ms steady | Stateless + cache hints |
| Stock read median | 112ms | 74ms | 60s dealer-scoped cache |
| Upgrade time | — | 6 hours incl. tests | 3 header fixes + 1 key rename |
For an SME build, I quote MCP setup at ₹55K–₹85K depending on tool count and OAuth needs. The 1.0 upgrade itself is a half-day job if tests exist, two days if they do not. Write the tests first. The registration assertions make that work fast.
When NOT to Use This
Be direct: do not adopt 1.0 this week if your only client is a pinned internal script that speaks the old flow and nobody owns it. The legacy fallback covers you, but you gain nothing until you mark tools searchable and fix headers. Also skip ToolSearch if you expose fewer than 10 tools — the extra round trip adds latency without meaningful token savings. My second client has 7 tools. I left all visible. Tokens per call sit at 2,100. No change needed.
Do not enable withCache() on quotes, invoices, or anything with money or stock decrement. Cache reads, never writes. I gate caching per tool in code review, not by convention.
My Upgrade Checklist (Copy This)
# 1. Pin versions and snapshot
composer show laravel/mcp
cp routes/ai.php routes/ai.php.bak
php artisan test --filter=Mcp > /tmp/mcp-before.txt 2>&1
# 2. Upgrade
composer require laravel/mcp:^1.0
php artisan vendor:publish --tag=mcp-views --force
php artisan test --filter=Mcp
Then in code: add modern headers to every endpoint test, mark tools searchable in batches of five while watching task success, enforce PKCE metadata on staging first, replace session correlation with your own request IDs, rename dotted keys, enable cache for reads only, add registration assertions per role. Deploy to staging, run 50 real calls from each client version your team uses, then ship. My staging caught the PKCE metadata gap and the three stale-header tests. Production deploy took 11 minutes with zero errors.
For Next.js teams reading this: the same pattern applies to your stack. Keep hot tools visible, search the rest, carry version per request. I run a Next.js 16.3 front for the same dealer with identical catalog semantics. TTFB holds 60–90ms on cached reads. The protocol is the easy part. The discipline — which tools stay visible — is the work.
Frequently Asked Questions
How long does the Laravel MCP 0.9 to 1.0 upgrade take?
Six hours on my repo with tests, including the three header fixes, one dotted-key rename, and six new registration assertions. Without tests, budget two days: one to write endpoint + registration coverage, one to upgrade and verify 50 calls per client version.
Does 1.0 break old initialize clients?
No. Legacy clients connect on the same endpoint alongside modern server/discover clients. I verified with a July Cursor build. Plan a 30-day window, monitor 400s from the header middleware, then enforce modern headers once old clients are gone.
Which tools should stay visible vs searchable?
Keep tools used in over 80% of sessions visible — lookup, search, quote, status. Push reports, bulk actions, and admin tools behind search. My 8/33 split cut input tokens 70% with task success steady at 95%. Under 10 total tools, keep everything visible.
What does the 1.0 upgrade cost for an SME in Gujarat?
I quote ₹55K–₹85K for a fresh MCP setup with OAuth and 15–40 tools, and a half-day to two-day upgrade for existing 0.9 installs. Infra stays flat — my Junagadh VPS runs ₹6,200/month before and after. Token savings (my case $41 → $12 per test weekend) usually cover the upgrade in the first month.
Bottom Line
Laravel MCP 1.0 is the version to build on: searchable catalogs cut my tokens 70%, stateless requests killed a whole bug class, and PKCE enforcement plus registration assertions make the security story auditable. Upgrade cost me six hours and changed nothing on the invoice — same ₹6,200 VPS, P95 now 48ms. If your integration predates 1.0, schedule the half-day. Keep hot tools visible, search the rest, cache reads only.
Links I actually use: AI development services for agent scoping, automation case notes for n8n + MCP wiring, web development for the Laravel + Next.js pairing, selected work including the Rajkot catalog, and contact if you want this exact upgrade on your repo.