1. 为什么要在 Laravel 里手搓一个 MCP 服务
MCP 服务说白了就是给 AI 客户端准备的一套“工具插座”。你写好的 PHP 函数,通过 MCP 协议暴露出去,Cursor、Claude Desktop 这类客户端就能像调用本地函数一样调用它。对 PHP 开发者来说,Laravel 本身就是干这个的天然好手:路由、中间件、服务容器、配置管理全都现成,不用再自己搭一套 HTTP 框架。
我这次要做的场景很具体:用 Laravel 12 从零搭一个 MCP 服务,里面放一个加法工具laravel_adder,然后把这个服务的模型调用通道统一走 TaoToken 的 Key 和 API 地址。这样做的价值在于,你本地调试 MCP 工具时,不用在代码里散落一堆不同厂商的 Key,一个统一入口就能切换模型、看调用量、排查问题。
适合谁看?如果你会一点 PHP,用过 Composer,知道 Laravel 的artisan命令怎么跑,那这篇就能直接跟做。如果你完全没碰过 Laravel,建议先花半小时把官方文档的“路由”和“中间件”两节过一遍,不然配置片段里的路径你会对不上。
先说清楚 MCP 服务在 Laravel 里的定位。它不是一个普通的 Web 页面,而是一个遵循 MCP 协议的端点。客户端通过 STDIO 或 SSE/Streamable HTTP 两种传输方式跟它通信。STDIO 适合本地进程直连,SSE 适合走 HTTP 端口。我在 Windows 环境下试过 STDIO,一直连不上,换成 SSE 就通了,所以后面配置我会以 SSE 为主,STDIO 作为备选写出来。
整个链路是这样的:AI 客户端 → MCP 端点(Laravel 路由)→ 工具方法(CalculatorService::add)→ 如果需要模型能力,再通过 TaoToken 的统一 API 通道去请求模型。工具本身是纯 PHP 计算,不依赖模型;但一旦你的工具要调用大模型做总结、翻译、代码生成,统一 Key 的价值就出来了。
所以这篇的结构是:先把 Laravel MCP 服务跑起来,能返回结果;再把 TaoToken 的 Key 和 Base URL 接进去;最后用 curl 验证端点,把常见报错一个个排掉。每一步都有可复制的代码,你照着敲就行。
2. 用 TaoToken 统一 Key 接入前的环境准备与依赖安装
在写任何业务代码之前,先把地基打好。Laravel 12 对 PHP 版本有要求,MCP 的 SDK 也有自己的依赖。我实测下来,PHP 8.1 是底线,8.2/8.3 更稳。你需要确认这几个扩展开着:json、mbstring、pcre。这三个基本是 Laravel 的标配,但有些精简版 PHP 镜像会缺pcre,跑composer时会报错。
先建项目。如果你已经有 Laravel 12 项目,跳过这步:
composer create-project laravel/laravel laravel-mcp-demo "12.*" cd laravel-mcp-demo然后装 MCP 的 Laravel SDK。这里用的是php-mcp/laravel,版本锁^3.0:
composer require php-mcp/laravel:^3.0 -W-W参数是允许更新依赖树里的其他包,避免版本冲突卡住。装完之后发布配置文件和迁移文件:
php artisan vendor:publish --provider="PhpMcp\Laravel\McpServiceProvider" --tag="mcp-config" php artisan vendor:publish --provider="PhpMcp\Laravel\McpServiceProvider" --tag="mcp-migrations" php artisan migrate第一条命令会在config/mcp.php生成配置文件,第二条生成数据库迁移,第三条执行迁移。MCP 服务需要存一些工具注册信息和调用记录,所以有迁移这一步。
接下来是 TaoToken 的接入准备。TaoToken 提供统一的 API 通道,你只需要一个 Key 和一个 Base URL,就能在代码里请求不同模型。先去控制台拿 Key:
打开 https://taotoken.net/console 创建 API Key,然后在 https://taotoken.net/api-keys 管理你的 Key 列表。Base URL 统一用 https://taotoken.net/api。
拿到 Key 之后,不要硬编码在代码里,写进.env:
TAOTOKEN_API_KEY=sk-你的实际Key TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_MODEL=claude-sonnet-4-5然后在config/services.php里加一段映射,让 Laravel 能读到:
'taotoken' => [ 'key' => env('TAOTOKEN_API_KEY'), 'base_url' => env('TAOTOKEN_BASE_URL', 'https://taotoken.net/api'), 'model' => env('TAOTOKEN_MODEL', 'claude-sonnet-4-5'), ],这样你在任何地方用config('services.taotoken.key')就能取到 Key,不用满项目找。环境变量改完记得清缓存:
php artisan config:clear php artisan cache:clear到这里,Laravel 项目、MCP SDK、TaoToken 的 Key 三样都齐了。下一步开始写路由和工具逻辑。
3. 可复制的 Laravel MCP 路由、控制器与 TaoToken 配置片段
这一节是核心,所有代码都能直接复制。先建路由文件。在routes目录下新建mcp.php:
<?php use App\Services\CalculatorService; use PhpMcp\Laravel\Facades\Mcp; Mcp::tool([CalculatorService::class, 'add']) ->name('laravel_adder') ->description('Add two numbers together');这段代码把CalculatorService的add方法注册成一个 MCP 工具,名字叫laravel_adder。客户端看到的就是这个名字和描述。
然后写工具逻辑。新建app/Services/CalculatorService.php:
<?php namespace App\Services; use PhpMcp\Server\Attributes\McpTool; class CalculatorService { /** * Adds two numbers using a tool. * * @param int $a The first number. * @param int $b The second number. * @return int The sum. */ #[McpTool(name: 'laravel_adder')] public function add(int $a, int $b): int { $sum = $a + $b + 10000; return $sum; } }注意这里我故意加了+ 10000,方便你验证调用确实走到了这个方法,而不是被缓存或别的东西拦截。真实项目里你换成自己的业务逻辑就行。
MCP 支持四种元素类型,这里列个对照表,你按需选:
| 类型 | 用途 | 示例 |
|---|---|---|
| Tools | 执行函数调用 | 计算、发邮件、查数据库 |
| Resources | 通过 URI 访问的静态内容 | config://settings、file://readme.txt |
| Resource Templates | 带 URI 模式的动态资源 | user://{id}/profile |
| Prompts | 对话开场白或模板 | summarize、translate |
服务发现默认是开的,config/mcp.php里auto_discover为true。你也可以手动跑:
php artisan mcp:discover php artisan mcp:discover --force php artisan mcp:discover --no-cache第一条是发现并缓存,第二条强制重新发现忽略缓存,第三条只发现不写缓存。改完工具代码后,跑一次--force最保险。
接下来把 TaoToken 的配置接进 MCP 服务。如果你希望工具内部调用模型,可以在CalculatorService里注入一个 HTTP 客户端,走 TaoToken 的 Base URL。这里给一个可复制的配置片段,放在config/mcp.php的server段附近:
'server' => [ 'name' => 'laravel-mcp-demo', 'version' => '1.0.0', 'transport' => env('MCP_TRANSPORT', 'sse'), 'taotoken' => [ 'base_url' => env('TAOTOKEN_BASE_URL', 'https://taotoken.net/api'), 'key' => env('TAOTOKEN_API_KEY'), 'model' => env('TAOTOKEN_MODEL', 'claude-sonnet-4-5'), ], ],然后在.env里补上传输方式:
MCP_TRANSPORT=sseSSE 模式下,MCP 端点默认挂在/mcp。你可以在routes/mcp.php里确认路由注册,或者用php artisan route:list看:
php artisan route:list | grep mcp应该能看到mcp:serve相关的路由。如果没看到,检查bootstrap/app.php里有没有加载routes/mcp.php。Laravel 12 默认只加载web.php和console.php,你需要手动加:
->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', then: function () { Route::middleware('api') ->group(base_path('routes/mcp.php')); }, )这段加在bootstrap/app.php的withRouting里。加完之后再跑route:list,就能看到 MCP 路由了。
4. 启动服务并用 curl 验证 MCP 端点返回结果
配置写完,启动 Laravel 开发服务器:
php artisan serve --host=127.0.0.1 --port=8000服务起来后,MCP 端点在http://127.0.0.1:8000/mcp。先用 curl 探一下 SSE 握手:
curl -N -H "Accept: text/event-stream" http://127.0.0.1:8000/mcp-N是禁用缓冲,让你实时看到 SSE 流。正常的话你会看到类似event: endpoint和data: /mcp/message的输出。如果卡住不动,说明路由没通或者中间件拦了。
接下来发一个真正的工具调用请求。MCP 的 JSON-RPC 格式长这样:
curl -X POST http://127.0.0.1:8000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "laravel_adder", "arguments": { "a": 3, "b": 4 } } }'预期返回里result.content会包含10007,因为3 + 4 + 10000 = 10007。如果你看到这个数字,说明整条链路通了:路由 → 工具注册 → 方法执行 → 结果返回。
再验证一下工具列表:
curl -X POST http://127.0.0.1:8000/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'返回里应该能看到laravel_adder的名字和描述。这一步能帮你确认服务发现有没有生效。
如果你要在 Cursor 或 Claude Desktop 里接,SSE 方式的配置是:
{ "mcpServers": { "laravel_adder": { "url": "http://127.0.0.1:8000/mcp" } } }STDIO 方式我也写出来,但 Windows 下我实测连不上,你可以试试:
{ "mcpServers": { "laravel_adder": { "command": "php", "args": [ "artisan所在的绝对路径", "mcp:serve", "--transport=stdio" ] } } }注意artisan所在的绝对路径要换成你项目里artisan文件的完整路径,比如D:/code/laravel-mcp-demo/artisan。Windows 路径用正斜杠或双反斜杠都行。
验证模型通道是否走 TaoToken,可以加一个调用模型的工具,然后在日志里看请求地址。简单做法是在CalculatorService里加一个方法,用 Laravel 的 HTTP 客户端请求 TaoToken:
use Illuminate\Support\Facades\Http; public function askModel(string $prompt): string { $response = Http::withToken(config('services.taotoken.key')) ->post(config('services.taotoken.base_url') . '/v1/messages', [ 'model' => config('services.taotoken.model'), 'max_tokens' => 256, 'messages' => [ ['role' => 'user', 'content' => $prompt], ], ]); return $response->json('content.0.text', ''); }这段代码把请求打到 TaoToken 的 Base URL,Key 从配置读。你可以在storage/logs/laravel.log里看到实际请求,确认没有走错地址。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
这一节把我踩过的坑列出来,你对照报错直接定位。
401 Unauthorized。最常见的原因是 Key 没读到或者格式不对。先确认.env里TAOTOKEN_API_KEY没有多余空格,然后跑php artisan config:clear。如果还报 401,检查config/services.php里的键名是不是taotoken,代码里取的是config('services.taotoken.key')。另外确认请求头是Authorization: Bearer sk-xxx,不是x-api-key。
local proxy failed。这个报错通常出现在客户端连 MCP 端点时。原因一般是端点地址写错,或者 Laravel 服务没起来。先curl http://127.0.0.1:8000/mcp看有没有响应。如果 curl 通但客户端不通,检查客户端配置里的 URL 是不是http://127.0.0.1:8000/mcp,不要写成localhost,有些客户端解析localhost会走 IPv6 导致连不上。
reading choices 相关报错。这个一般出现在你调用模型接口时,返回体不是预期的 JSON 结构。比如你请求 TaoToken 的/v1/messages,但返回的是错误页。先看storage/logs/laravel.log里的原始响应,确认base_url拼对了。https://taotoken.net/api后面接/v1/messages,不要重复加/api。
OAuth 报错。MCP 客户端有些版本会尝试 OAuth 流程,如果你的服务没配 OAuth,就会报错。解决办法是在客户端配置里显式指定不需要认证,或者用 SSE 方式直连。如果你在 Cursor 里看到 OAuth 相关提示,检查 MCP 配置里有没有多余的auth字段,删掉再试。
再补一个 Windows 下 STDIO 连不上的排查。我试过把artisan路径写成相对路径,客户端找不到;写成绝对路径后还是连不上,换 SSE 就通了。如果你非要用 STDIO,确认php命令在系统 PATH 里,用where php能查到。查不到就把command换成php的绝对路径。
还有一个容易忽略的点:MCP 服务发现缓存。你改了工具代码但客户端看到的还是旧描述,跑php artisan mcp:discover --force强制刷新。如果还不行,删掉bootstrap/cache下的缓存文件再试。
最后,如果你在工具里调 TaoToken 的模型接口,报model not found,检查TAOTOKEN_MODEL的值是不是 TaoToken 支持的模型 ID。去 https://taotoken.net/doc 看模型列表,别自己编名字。
6. 把 MCP 服务接进日常开发流的几个实用动作
服务跑通之后,别让它停在 demo 状态。我平时会做三件事,让它真正进开发流。
第一件,把 MCP 端点加到项目的 README 或者团队文档里,写清楚启动命令和客户端配置。这样换台机器或者同事接手,不用重新踩一遍坑。配置片段直接贴 SSE 那段 JSON,改个端口就能用。
第二件,给工具加日志。MCP 调用出问题时,光看客户端报错很难定位。在CalculatorService的方法里加一行Log::info('laravel_adder called', ['a' => $a, 'b' => $b]);,然后tail -f storage/logs/laravel.log,调用有没有进来一目了然。
第三件,把模型调用统一收口到 TaoToken。你可以在config/services.php里只留一份 Key,所有需要模型的地方都走config('services.taotoken')。这样换模型、查用量、排故障都只在一个地方操作。需要看调用记录就去 https://taotoken.net/console,需要管 Key 就去 https://taotoken.net/api-keys。
如果你要长期跑编码类 Agent,或者 MCP 工具里频繁调模型,可以看看 Coding Plan:https://taotoken.net/coding-plan 。它适合那种每天都要跟模型打交道的场景,比按次调用省心。
模型对话调试用这个入口:https://taotoken.net/models ,可以直接在页面上试模型返回,确认 Key 和模型 ID 没问题再写进代码。
接入文档在 https://taotoken.net/doc ,里面有完整的 API 说明和示例。遇到不确定的参数,先翻文档再改代码,比瞎试快。
最后留一个我常用的验证顺序:先curl通 MCP 端点,再在客户端里tools/list看到工具,最后调一次tools/call拿到结果。三步都过,说明服务没问题。哪一步卡住,就回到对应小节查报错。这套流程我跑过十几遍,基本能覆盖九成以上的连接问题。