网页
综合
更新时间 2026-09-11 12:01:00
A better way to build MCP servers with Laravel | Laravel - The clean stack for Artisans and agents
自动蜘蛛
laravel.com
正文约 6000 字
Every MCP tool loads on every request. Laravel MCP 1.0 fixes that with searchable tool catalogs: the same payload for 10 or 100 tools.
We ship an MCP server for Laravel Nightwatch. It does what we built it for. Users like it, use it, and immediately ask for the thing it does not do yet. Every ask is reasonable! But every ask is also another tool that the agent has to sift through.
Now think about a full Laravel Cloud MCP server: the API has a couple hundred endpoints. Deployments, environments, databases, workers, domains, logs. Handing that whole surface to an agent makes logical sense, because that is already how a lot of people use laravel cloud via agents.
The problem starts when an MCP server grows. Every tool you add brings its name, description, and full input schema into the agent's context before it has done any work. With enough tools, those definitions start competing for attention. The agent has more context to sift through, more similar choices to confuse, and less room to focus on the user's actual request. A separate tool for every endpoint makes sense at the API layer, but loading every one of them on every turn does not.
Cloudflare's Code Mode had come out about a year earlier, and it felt like the answer. But what I ended up shipping in Laravel MCP 1.0 is smaller and stranger: searchable tool catalogs, where the size of the tools/list payload stays the same whether the catalog holds 10 tools or 100.
This is the story of the four versions I went through, the three I threw away, and the mistake I made twice before I noticed it.
Tool calling looks like an API call, but it isn’t quite the same. It is text generation wearing a costume, which is the whole reason any of this matters.
A model generates a stream of tokens. Most map to words or fragments of words. But models are fine-tuned to emit a few special tokens that have no textual meaning at all, and two of them mean "a tool call starts here" and "a tool call ends here."
Between them, the model writes JSON:
The harness watches for that closing token, parses the JSON, and turns it into an MCP request against your Laravel app. Your tool runs, the result comes back through another pair of special tokens, and generation resumes as if the model had simply read it.
Look at where that note sits. Before the question is even read, the name, description, and full JSON Schema of every tool you expose is already in the context window. Not the ones being used. All of them, on every turn!
The numbers are worse than I expected. Anthropic's advanced tool use docs put a 50-plus tool MCP setup at around 72,000 tokens upfront. About a thousand tokens per tool. Several times bigger than my own guess. The token bill is the obvious cost, but the accuracy is the one that bothered me more.
On Anthropic's MCP evaluations with large tool libraries, deferring definitions and searching instead moved Opus 4 from 49% to 74%, and Opus 4.5 from 79.5% to 88.1%. Forty tool descriptions really is 40 chances to pick the wrong one.
Code Mode's argument is sharp, and I think it is correct: "LLMs are better at writing code to call MCP, than at calling MCP directly." Models have seen millions of real code examples and almost no real tool calls, because tool call syntax exists mostly in synthetic training data.
Cloudflare's line for it is better than anything I would write: "Making an LLM perform tasks with tool calling is like putting Shakespeare through a month-long class in Mandarin and then asking him to write a play in it."
They convert MCP tools into a TypeScript API and run the generated code in a V8 isolate. I did not need to convert anything, because the model already writes PHP, and so does the app. So version one generated a PHP function for each registered tool, straight from its name, description, and input schema:
The agent got those signatures instead of 40 tool definitions, wrote PHP against them, and I ran what came back through eval(). Essentially Tinker, with a language model at the keyboard.
I want to make this part clear, because it changes how you should read everything in this post: I did not type most of this code. The implementation was agent-written across all four versions.
My job was specifying, reading, and deciding what to keep. That is why the versions moved as fast as they did. It’s also how I managed to fool myself… twice.
This is the kind of thing the agent would write:
Two chained calls gave us twelve months of readings filtered down before any of it reached the model. Code Mode worked exactly as advertised, in PHP, in about a day. I was thrilled!
Every example I threw at it came back correct, which is exactly why I kept going as long as I did. A run that succeeds tells you nothing about what a run was allowed to do, and my tests only ever exercised the snippets I expected.
Then I looked at what else that snippet could have been:
Nothing in my design prevented any of it. The generated functions were a suggestion, not a boundary, and there was no other boundary anywhere. The code ran inside the application, in the same process, with the same container, the same environment, and the same database connection my own code uses.
I knew it was eval().
I had specified it and approved the diff, but what took me a day and a half to sit with is that eval() means something different when the author is a language model.
With a human, you are one careless line away from a bad afternoon, and there is a person who can be asked what they were thinking. Here, the full capability surface of PHP was reachable from any line the model chose to write, at whatever rate the agent chose to write them, and none of it would even need to be malicious. Even if it was just slightly wrong or overeager, we’d be in big trouble.
That was when I understood why Code Mode is primarily a method related to sandboxing and not a method strictly about code generation. Getting the model to write good code is the easy half, and I had already done it. All the expensive and
本页为82搜索生成的网页快照,内容来自站内索引;演示数据的原文链接仅作示例。