使用说明
Laravel MCP 配备 Passport OAuth 的设置指南
本指南详细记录了在使用Laravel Passport的Laravel MCP(模型上下文协议)服务器上实施OAuth认证的完整设置过程。
✅ 工作设置(或“运行环境”)
此项目已全面配置并经过测试,使用了:
- 支持PKCE的OAuth 2.0认证
- 用户使用的UUID主键
- 基于会话的网页认证
- 受保护的和公开的MCP端点
- MCP Inspector 集成
快速入门
如果这已经设置好了,只需运行:
# Start the server
php artisan serve
# In another terminal, test with MCP Inspector
php artisan mcp:inspector mcp/admin
OAuth 凭据:
- 在你的数据库中查找客户端ID和密钥
oauth_clients桌子 - 测试用户:
test@example.com/password
先决条件
- Laravel 11.x
- PHP 8.1及以上版本
- MySQL 8.0及以上版本
- Composer(注:Composer在编程领域通常指一个依赖管理工具,用于PHP等语言的项目中,此处直接保留原词,不作具体翻译)
步骤1:安装Laravel Passport
php artisan install:api --passport
这个命令将:
- 安装 Laravel Passport 包
- 发布并运行Passport迁移
- 为安全访问令牌生成加密密钥
步骤2:为UUID配置用户模型
更新 app/Models/User.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, Notifiable, HasUuids;
// ... rest of your model
}
重要的这个 HasUuids 如果您的用户表使用UUID作为主键,则需要此特性。
步骤3:配置身份验证守护程序
更新 config/auth.php:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport', // Changed from 'token' to 'passport'
'provider' => 'users',
],
],
步骤4:修复UUID的护照迁移问题
关键的;严重的如果使用UUID,请更新Passport迁移以使用 foreignUuid 而不是 foreignId:
编辑这些迁移文件:
database/migrations/*_create_oauth_auth_codes_table.phpdatabase/migrations/*_create_oauth_access_tokens_table.phpdatabase/migrations/*_create_oauth_device_codes_table.php
变化:
$table->foreignId('user_id')->index();
收件人:
$table->foreignUuid('user_id')->index();
同时更新 database/migrations/*_create_users_table.php 会话表:
$table->foreignUuid('user_id')->nullable()->index();
然后运行迁移:
php artisan migrate:fresh
步骤5:配置CORS
跨域资源共享(CORS)对于OAuth流程至关重要,尤其是当MCP检查器或AI代理运行在不同的源时。
更新 config/cors.php:
<?php
return [
'paths' => ['api/*', 'mcp/*', 'oauth/*', '.well-known/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'], // For production, specify exact origins
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true, // Required for OAuth with cookies
];
CORS 配置详解
- 路径包含
oauth/*对于OAuth端点以及.well-known/*用于OAuth发现 - **允许的来源(或:允许的起源)**设置为
['*']用于开发。在生产环境中,请指定确切的来源,如['https://your-domain.com'] - 支持凭据必须是
true在跨域请求中允许cookies和认证头信息 - 允许的方法:
['*']允许所有HTTP方法(GET、POST、OPTIONS等) - 允许的头部(信息):
['*']允许所有头部信息,包括Authorization以及自定义头部
生产环境CORS配置
对于生产环境,请收紧CORS设置:
'allowed_origins' => [
'https://your-production-domain.com',
'https://mcp-inspector.example.com',
],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
'supports_credentials' => true,
步骤5:配置护照授权视图
更新 app/Providers/AppServiceProvider.php:
use Laravel\Passport\Passport;
public function boot(): void
{
Passport::authorizationView('mcp.authorize');
}
步骤6:发布MCP授权视图
php artisan vendor:publish --tag=mcp-views
这产生了 resources/views/mcp/authorize.blade.php。
步骤7:修复授权视图CSS加载问题
编辑 resources/views/mcp/authorize.blade.php 并替换为 @vite 使用内联样式指令以避免加载问题:
<style>
body { font-family: sans-serif; margin: 0; padding: 0; }
.bg-background { background: #f5f5f5; }
.text-foreground { color: #333; }
.bg-card { background: white; }
.text-card-foreground { color: #333; }
.border { border: 1px solid #e5e7eb; }
.rounded-lg { border-radius: 0.5rem; }
.shadow-sm { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
.text-primary { color: #4f46e5; }
.bg-primary { background: #4f46e5; }
.text-primary-foreground { color: white; }
.bg-muted\/50 { background: rgba(243, 244, 246, 0.5); }
.text-muted-foreground { color: #6b7280; }
button:hover { opacity: 0.9; }
</style>
步骤8:修复授权视图状态参数
编辑 resources/views/mcp/authorize.blade.php 并更新隐藏状态输入:
改变:
<input type="hidden" name="state" value="">
收件人:
<input type="hidden" name="state" value="{{ $request->state ?? '' }}">
这适用于批准和拒绝两种表单。
步骤9:配置MCP OAuth路由
更新 routes/ai.php:
<?php
use App\Mcp\Servers\AdminServer;
use App\Mcp\Servers\WarriorServer;
use Laravel\Mcp\Facades\Mcp;
// OAuth discovery and client registration routes
Mcp::oauthRoutes();
// Public MCP server (no auth)
Mcp::web('/mcp/warrior', WarriorServer::class);
// Admin MCP server (OAuth protected)
Mcp::web('/mcp/admin', AdminServer::class)
->middleware('auth:api');
步骤10:设置网页认证
在授权OAuth客户端之前,您需要一个网页认证系统供用户登录。
选项A:简单登录(用于测试)
创建 routes/auth.php:
<?php
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use Illuminate\Support\Facades\Route;
Route::middleware('guest')->group(function () {
Route::get('login', [AuthenticatedSessionController::class, 'create'])->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
});
Route::middleware('auth')->group(function () {
Route::get('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
});
创造 app/Http/Controllers/Auth/AuthenticatedSessionController.php:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class AuthenticatedSessionController extends Controller
{
public function create(Request $request): View
{
return view('auth.login');
}
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(route('dashboard'));
}
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
创建 app/Http/Requests/Auth/LoginRequest.php:
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
}
}
创建 resources/views/auth/login.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Login - {{ config('app.name') }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:sans-serif;background:#f5f5f5;margin:0;">
<div style="background:white;padding:40px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1);max-width:400px;width:100%;">
<h2 style="margin:0 0 10px;text-align:center;">MCP OAuth Login</h2>
<p style="color:#666;text-align:center;margin:0 0 30px;">Login to authorize the application</p>
@if($errors->any())
<div style="background:#fee;color:#c33;padding:10px;border-radius:4px;margin-bottom:20px;">
{{ $errors->first() }}
</div>
@endif
<form method="POST" action="/login">
@csrf
<div style="margin-bottom:20px;">
<label style="display:block;margin-bottom:5px;font-weight:500;">Email</label>
<input type="email" name="email" value="{{ old('email') }}" required autofocus
style="width:100%;padding:10px;border:1px solid #ddd;border-radius:4px;font-size:14px;">
</div>
<div style="margin-bottom:20px;">
<label style="display:block;margin-bottom:5px;font-weight:500;">Password</label>
<input type="password" name="password" required
style="width:100%;padding:10px;border:1px solid #ddd;border-radius:4px;font-size:14px;">
</div>
<button type="submit" style="width:100%;padding:12px;font-size:16px;cursor:pointer;background:#4f46e5;color:white;border:none;border-radius:6px;font-weight:500;">
Login
</button>
</form>
</div>
</body>
</html>
选项B:使用Laravel Breeze(推荐用于生产环境)
composer require laravel/breeze --dev
php artisan breeze:install blade
php artisan migrate
npm install && npm run build
步骤10:更新网页路由
更新 routes/web.php:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', function () {
return 'Dashboard - You are logged in as ' . auth()->user()->email;
})->name('dashboard');
});
require __DIR__.'/auth.php';
注没有从...重定向 /authorize 到;向;朝;对于;为了 /oauth/authorize 是需要的。该 Mcp::oauthRoutes('oauth') 调用时会自动注册Passport路由于 /oauth/authorize。
步骤11:创建OAuth客户端
php artisan passport:client
当被提示时:
- 输入客户端名称(例如,“MCP 管理员检查员”)
- 将重定向URI留空(按回车键)
保存生成的客户端ID和客户端密钥。
步骤12:更新客户端重定向URI
重定向URI需要存储为JSON数组。请手动更新它:
mysql -u root your_database_name
UPDATE oauth_clients
SET redirect_uris = '["http://localhost:6274/oauth/callback"]'
WHERE id = 'your-client-id';
或者使用tinker:
php artisan tinker
$client = \Laravel\Passport\Client::find('your-client-id');
$client->redirect = ['http://localhost:6274/oauth/callback'];
$client->save();
步骤13:创建测试用户
php artisan tinker
\App\Models\User::create([
'name' => 'Test User',
'email' => 'test@example.com',
'password' => bcrypt('password')
]);
步骤14:测试OAuth流程
- 启动MCP检查器:
php artisan mcp:inspector mcp/admin
-
在检查员仪表盘中:
- 请输入您的客户端ID
- 输入您的客户端密钥
- 点击“连接”
-
浏览器将打开到授权页面:
- 使用您的测试用户凭据登录
- 点击“授权”
- 浏览器重定向回检查器
- 检查员现在可以访问受保护的MCP服务器
OAuth流程图
┌─────────────┐ ┌──────────────┐
│ │ 1. Request /mcp/admin │ │
│ MCP │───────────────────────────────────>│ Laravel │
│ Inspector │ │ MCP App │
│ │ 2. 401 Unauthenticated │ │
│ │<───────────────────────────────────│ │
└─────────────┘ └──────────────┘
│ │
│ 3. Redirect to /oauth/authorize │
│──────────────────────────────────────────────────>
│ │
│ 4. Not logged in → redirect to /login │
│<──────────────────────────────────────────────────
│ │
│ 5. User enters credentials │
│──────────────────────────────────────────────────>
│ │
│ 6. Login successful → redirect to /oauth/authorize
│<──────────────────────────────────────────────────
│ │
│ 7. Show authorization screen │
│<──────────────────────────────────────────────────
│ │
│ 8. User clicks "Authorize" │
│──────────────────────────────────────────────────>
│ │
│ 9. Redirect with authorization code │
│<──────────────────────────────────────────────────
│ │
│ 10. Exchange code for access token │
│──────────────────────────────────────────────────>
│ │
│ 11. Return access token │
│<──────────────────────────────────────────────────
│ │
│ 12. Access /mcp/admin with token │
│──────────────────────────────────────────────────>
│ │
│ 13. Return MCP server response │
│<──────────────────────────────────────────────────
故障排除
问题:“invalid_client”错误
解决方案检查以下内容:
- 客户端ID存在于
oauth_clients桌子 redirect_uris以JSON数组形式存储:["http://localhost:6274/oauth/callback"]- 客户端未被撤销
- 你在使用 纯文本 客户端密钥(非数据库中的哈希版本)
问题:“invalid_request”(无效请求)- 请检查 client_secret 参数
解决方案:
- 确保您使用的是创建客户端时显示的原始客户端密钥
- 数据库中存储的是哈希版本——你需要原始的明文秘密
- 如果丢失,请创建一个新的客户端:
php artisan passport:client --no-interaction
问题:登录循环 - 成功认证后重定向回登录页面
解决方案:
- 最常见的会话表
user_id列类型不匹配- 如果使用UUID:更改
foreignId('user_id')toforeignUuid('user_id')在会话表迁移过程中 - 跑
php artisan migrate:fresh
- 如果使用UUID:更改
- 清除会话:
mysql -u root your_db -e "TRUNCATE TABLE sessions;" - 清除缓存:
php artisan optimize:clear - 确保
SESSION_DRIVER=database在里面.env
问题:“在OAuth授权过程中,请求缺少必需的参数”
解决方案:
- 检查一下是否
state参数正在通过授权表单传递 - 更新
resources/views/mcp/authorize.blade.php:<input type="hidden" name="state" value="{{ $request->state ?? '' }}">
问题:UUID 兼容性错误 / “无效的用户ID”
解决方案:
- 关键的;严重的;危急的所有引用(的外键)
users.id必须使用foreignUuid()如果用户表使用UUIDs - 更新这些迁移:
*_create_oauth_auth_codes_table.php*_create_oauth_access_tokens_table.php*_create_oauth_device_codes_table.php*_create_users_table.php(会话表)*_create_transactions_table.php(如果存在)
- 添加
HasUuids“trait to User model” 可以翻译为“应用于用户模型的特性”或“用户模型的特性/特质”。这里,“trait”指的是某个特定的特征或属性,“User model”指的是用户模型。根据上下文,具体翻译可能略有不同,但基本意思是指将某个特性或属性应用到或描述用户模型 - 跑
php artisan migrate:fresh
问题:授权时出现“无法获取”错误
解决方案:
- 移除
@vite来自授权视图的指令 - 使用内联CSS代替
- 如有需要,请在授权视图中禁用JavaScript
问题:浏览器控制台中的CORS错误
解决方案:
- 验证
config/cors.php包括oauth/*在路径中 - 确保
supports_credentials被设定为true - 检查一下
allowed_origins包括请求的来源 - 清除配置缓存:
php artisan config:clear
问题:预检OPTIONS请求失败
解决方案:
- 确保CORS中间件已注册在
bootstrap/app.php - 验证
allowed_methods包括OPTIONS - 检查Web服务器(nginx/Apache)是否阻止了OPTIONS请求
安全考虑事项
-
永远不要提交OAuth密钥添加到
.gitignore:storage/oauth-*.key -
在生产环境中使用HTTPS更新
.env:APP_URL=https://your-domain.com -
在生产环境中限制CORS来源更新
config/cors.php:'allowed_origins' => ['https://your-domain.com'], -
定期更换密钥定期生成新的OAuth客户端
-
实现作用域为不同的访问级别定义特定权限
-
监控OAuth使用情况记录授权尝试和令牌使用情况
-
限制OAuth端点的速率防止暴力破解攻击
额外资源
摘要
现在,您已经拥有了一个功能齐全的Laravel MCP服务器,配置了OAuth认证和CORS:
- ✅ 公共MCP服务器位于
/mcp/warrior(无需认证) - ✅ 已保护MCP服务器于
/mcp/admin(需要OAuth) - ✅ 带有用户登录的OAuth授权流程
- ✅ 已为跨域请求配置了CORS(跨域资源共享)
- ✅ 基于令牌的安全API访问
现在,AI代理可以从任何来源使用标准的OAuth 2.0流程对您的受保护MCP服务器进行身份验证和访问。