MCP
Laravel MCP
简介
Laravel MCP 为 AI 客户端提供了一种简单、优雅的方式,使其能够通过模型上下文协议与你的 Laravel 应用交互。它提供富有表现力的流畅接口,用于定义服务器、工具、资源和提示词,让 AI 能够与你的应用交互。
安装
首先,使用 Composer 包管理器将 Laravel MCP 安装到你的项目中:
composer require laravel/mcp
发布路由
安装 Laravel MCP 后,执行 vendor:publish Artisan 命令,发布用于定义 MCP 服务器的 routes/ai.php 文件:
php artisan vendor:publish --tag=ai-routes
此命令会在应用的 routes 目录中创建 routes/ai.php 文件,你将在其中注册 MCP 服务器。
创建服务器
你可以使用 make:mcp-server Artisan 命令创建 MCP 服务器。服务器作为统一的通信入口,向 AI 客户端开放工具、资源和提示词等 MCP 功能:
php artisan make:mcp-server WeatherServer
此命令将在 app/Mcp/Servers 目录中创建一个新的服务器类。生成的服务器类继承 Laravel MCP 的基类 Laravel\Mcp\Server,并提供特性和属性,用于配置服务器以及注册工具、资源和提示词:
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
use Laravel\Mcp\Server;
#[Name('Weather Server')]
#[Version('1.0.0')]
#[Instructions('This server provides weather information and forecasts.')]
class WeatherServer extends Server
{
/**
* 在此 MCP 服务器中注册的工具。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
// GetCurrentWeatherTool::class,
];
/**
* 在此 MCP 服务器中注册的资源。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
*/
protected array $resources = [
// WeatherGuidelinesResource::class,
];
/**
* 在此 MCP 服务器中注册的提示词。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
*/
protected array $prompts = [
// DescribeWeatherPrompt::class,
];
}
安装
首先,使用 Composer 包管理器将 Laravel MCP 安装到你的项目中:
composer require laravel/mcp
发布路由
安装 Laravel MCP 后,执行 vendor:publish Artisan 命令发布 routes/ai.php 文件,你将在其中定义 MCP 服务器:
php artisan vendor:publish --tag=ai-routes
此命令在应用的 routes 目录中创建 routes/ai.php 文件,你将使用它来注册 MCP 服务器。
创建服务器
你可以使用 make:mcp-server Artisan 命令创建 MCP 服务器。服务器充当中央通信点,向 AI 客户端公开工具、资源和提示等 MCP 功能:
php artisan make:mcp-server WeatherServer
此命令将在 app/Mcp/Servers 目录中创建一个新的服务器类。生成的服务器类扩展了 Laravel MCP 的基础 Laravel\Mcp\Server 类,并提供了用于配置服务器和注册工具、资源和提示的属性:
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
use Laravel\Mcp\Server;
#[Name('Weather Server')]
#[Version('1.0.0')]
#[Instructions('This server provides weather information and forecasts.')]
class WeatherServer extends Server
{
/**
* 注册到该MCP服务器的工具。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
// GetCurrentWeatherTool::class,
];
/**
* 已注册至该MCP服务器的资源。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
*/
protected array $resources = [
// WeatherGuidelinesResource::class,
];
/**
* 在该MCP服务器上注册的提示词。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
*/
protected array $prompts = [
// DescribeWeatherPrompt::class,
];
}
注册服务器
创建服务器后,你必须在 routes/ai.php 文件中注册它,才能使其可被访问。Laravel MCP 提供两种注册服务器的方法:web 用于可通过 HTTP 访问的服务器,local 用于命令行服务器。
Web 服务器
Web 服务器是最常见的服务器类型,可通过 HTTP POST 请求访问,非常适合远程 AI 客户端或基于 Web 的集成。使用 web 方法注册 Web 服务器:
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/weather', WeatherServer::class);
与普通路由一样,你可以为 Web 服务器应用中间件来保护它们:
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware(['throttle:mcp']);
本地服务器
本地服务器以 Artisan 命令的形式运行,非常适合构建 Laravel Boost 等本地 AI 助手集成。使用 local 方法注册本地服务器:
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::local('weather', WeatherServer::class);
注册后,你通常无需手动运行 mcp:start Artisan 命令。你可以配置 MCP 客户端(AI 智能体)来启动服务器,或使用 MCP Inspector。
缓存提示
对于服务器发现、基础组件列表和资源读取等可缓存的响应,Laravel MCP 会附带缓存提示。默认情况下,这些响应被标记为私有,存活时间为零毫秒。
你可以使用 Cacheable 特性自定义服务器的默认缓存提示:
use Laravel\Mcp\Enums\CacheScope;
use Laravel\Mcp\Server\Attributes\Cacheable;
#[Cacheable(ttlMs: 60_000, scope: CacheScope::Public)]
class WeatherServer extends Server
{
/**
* 获取各个 MCP 方法的缓存提示。
*
* @return array<string, \Laravel\Mcp\Server\Attributes\Cacheable>
*/
protected function cacheHints(): array
{
return [
'tools/list' => new Cacheable(ttlMs: 30_000, scope: CacheScope::Public),
];
}
}
CacheScope::Private 作用域将缓存响应限制在相同的授权上下文内,而 CacheScope::Public 允许在用户之间共享响应。缓存提示仅供参考,响应是否实际缓存由 MCP 客户端或宿主决定。cacheHints 返回的特定方法缓存提示优先于服务器的 Cacheable 特性。
你可以在资源类上应用 Cacheable 特性,为单个资源覆盖服务器的缓存提示:
#[Cacheable(ttlMs: 300_000, scope: CacheScope::Public)]
class WeatherGuidelinesResource extends Resource
{
// ...
}
资源的 Cacheable 特性优先于特定方法的缓存提示和服务器的默认缓存提示。
工具
工具使服务器能够向 AI 客户端开放可调用的功能,让语言模型执行操作、运行代码或与外部系统交互:
<?php
namespace App\Mcp\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
/**
* 处理工具请求。
*/
public function handle(Request $request): Response
{
$location = $request->get('location');
// 获取天气……
return Response::text('The weather is...');
}
/**
* 获取工具的输入模式。
*
* @return array<string, \Illuminate\JsonSchema\Types\Type>
*/
public function schema(JsonSchema $schema): array
{
return [
'location' => $schema->string()
->description('The location to get the weather for.')
->required(),
];
}
}
服务注册
创建服务后,您必须在 routes/ai.php 文件中注册它以使其可访问。Laravel MCP 提供两种注册服务的方法:web 用于 HTTP 可访问的服务,local 用于命令行服务。
Web 服务
Web 服务是最常见的服务类型,可通过 HTTP POST 请求访问,非常适合远程 AI 客户端或基于 Web 的集成。使用 web 方法注册 Web 服务:
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/weather', WeatherServer::class);
就像普通路由一样,您可以将中间件应用到 Web 服务以保护它们:
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware(['throttle:mcp']);
本地服务
本地服务作为 Artisan 命令运行,非常适合构建本地 AI 助手集成,如 Laravel Boost。使用 local 方法注册本地服务:
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::local('weather', WeatherServer::class);
注册后,您通常不需要手动运行 mcp:start Artisan 命令。相反,配置您的 MCP 客户端(AI 代理)来启动服务器或使用 MCP 检查器。
工具
工具使您的服务器能够向 AI 客户端公开功能。它们允许语言模型执行操作、运行代码或与外部系统交互:
<?php
namespace App\Mcp\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
/**
* 处理工具请求
*/
public function handle(Request $request): Response
{
$location = $request->get('location');
// 获取天气...
return Response::text('The weather is...');
}
/**
* 获取工具的输入结构。
*
* @return array<string, \Illuminate\JsonSchema\Types\Type>
*/
public function schema(JsonSchema $schema): array
{
return [
'location' => $schema->string()
->description('The location to get the weather for.')
->required(),
];
}
}
创建工具
要创建工具,请运行 make:mcp-tool Artisan 命令:
php artisan make:mcp-tool CurrentWeatherTool
创建工具后,在服务器的 $tools 属性中注册它:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 在此 MCP 服务器中注册的工具。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
CurrentWeatherTool::class,
];
}
可搜索的工具目录
当服务器具有大量工具时,可以将其中一些工具放入可搜索的目录,而无需向 AI 客户端公布每个工具。可搜索的目录会开放两个工具:search_tools 按工具名称、描述和输入模式搜索目录;execute_tools 则调用搜索结果中的一个或多个工具。
要创建可搜索的目录,请在服务器的 $tools 属性中使用 ToolSearch 类作为数组键:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Tools\CurrentWeatherTool;
use App\Mcp\Tools\HistoricalWeatherTool;
use App\Mcp\Tools\WeatherAlertsTool;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Tools\ToolSearch;
class WeatherServer extends Server
{
/**
* 在此 MCP 服务器中注册的工具。
*
* @var array<int|string, \Laravel\Mcp\Server\Tool|class-string<\Laravel\Mcp\Server\Tool>|array<int, \Laravel\Mcp\Server\Tool|class-string<\Laravel\Mcp\Server\Tool>>>
*/
protected array $tools = [
CurrentWeatherTool::class,
ToolSearch::class => [
HistoricalWeatherTool::class,
WeatherAlertsTool::class,
],
];
}
在此示例中,CurrentWeatherTool 会直接公布,而历史天气和天气警报工具则通过可搜索的目录提供。搜索或执行目录中的工具时,仍会遵守有条件注册工具的规则。
单次 execute_tools 调用可执行的工具数量上限和响应大小上限,分别由 mcp.tool_search.max_tool_calls 和 mcp.tool_search.max_output_bytes 配置项控制。
工具名称、标题和描述
默认情况下,工具的名称和标题由类名派生而来。例如,CurrentWeatherTool 的名称为 current-weather,标题为 Current Weather Tool。你可以使用 Name 和 Title 特性自定义这些值:
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('get-optimistic-weather')]
#[Title('Get Optimistic Weather Forecast')]
class CurrentWeatherTool extends Tool
{
// ...
}
工具描述不会自动生成。你应始终使用 Description 特性提供有意义的描述:
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
//
}
[!NOTE]
描述是工具元数据的关键部分,它能帮助 AI 模型理解何时以及如何有效地使用工具。
工具输入模式
工具可以定义输入模式,指定它们接受 AI 客户端传入哪些参数。使用 Laravel 的 Illuminate\Contracts\JsonSchema\JsonSchema 构建器定义工具的输入要求:
<?php
namespace App\Mcp\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 获取工具的输入模式。
*
* @return array<string, \Illuminate\JsonSchema\Types\Type>
*/
public function schema(JsonSchema $schema): array
{
return [
'location' => $schema->string()
->description('The location to get the weather for.')
->required(),
'units' => $schema->string()
->enum(['celsius', 'fahrenheit'])
->description('The temperature units to use.')
->default('celsius'),
];
}
}
创建工具
要创建工具,运行 make:mcp-tool Artisan 命令:
php artisan make:mcp-tool CurrentWeatherTool
创建工具后,在服务器的 $tools 属性中注册它:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 在此MCP服务器中注册的工具。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
CurrentWeatherTool::class,
];
}
工具名称、标题和描述
默认情况下,工具的名称和标题派生自类名。例如,CurrentWeatherTool 的名称为 current-weather,标题为 Current Weather Tool。您可以通过定义工具的 $name 和 $title 属性来自定义这些值:
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('get-optimistic-weather')]
#[Title('Get Optimistic Weather Forecast')]
class CurrentWeatherTool extends Tool
{
// ...
}
工具描述不会自动生成。你应该始终使用 Description 属性提供有意义的描述:
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
//
}
[!注意]
描述是工具元数据的关键部分,因为它帮助 AI 模型理解何时以及如何有效地使用该工具。
工具输入模式
工具可以定义输入模式来指定它们从 AI 客户端接受的参数。使用 Laravel 的 Illuminate\Contracts\JsonSchema\JsonSchema 构建器来定义工具的输入要求:
<?php
namespace App\Mcp\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 获取工具的输入模式。
*
* @return array<string, \Illuminate\JsonSchema\Types\Type>
*/
public function schema(JsonSchema $schema): array
{
return [
'location' => $schema->string()
->description('The location to get the weather for.')
->required(),
'units' => $schema->string()
->enum(['celsius', 'fahrenheit'])
->description('The temperature units to use.')
->default('celsius'),
];
}
}
工具输出模式
工具可以定义输出模式,以指定响应的结构。这有助于与需要可解析工具结果的 AI 客户端集成。使用 outputSchema 方法定义工具的输出结构:
<?php
namespace App\Mcp\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 获取工具的输出模式。
*
* @return array<string, \Illuminate\JsonSchema\Types\Type>
*/
public function outputSchema(JsonSchema $schema): array
{
return [
'temperature' => $schema->number()
->description('Temperature in Celsius')
->required(),
'conditions' => $schema->string()
->description('Weather conditions')
->required(),
'humidity' => $schema->integer()
->description('Humidity percentage')
->required(),
];
}
}
验证工具参数
JSON Schema 定义提供了工具参数的基本结构,但你可能还需要执行更复杂的验证规则。
Laravel MCP 与 Laravel 的验证功能无缝集成。你可以在工具的 handle 方法中验证传入的工具参数:
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 处理工具请求。
*/
public function handle(Request $request): Response
{
$validated = $request->validate([
'location' => 'required|string|max:100',
'units' => 'in:celsius,fahrenheit',
]);
// 使用验证后的参数获取天气数据……
}
}
验证失败时,AI 客户端会根据你提供的错误消息采取行动。因此,提供清晰且能指导下一步操作的错误消息至关重要:
$validated = $request->validate([
'location' => ['required','string','max:100'],
'units' => 'in:celsius,fahrenheit',
],[
'location.required' => 'You must specify a location to get the weather for. For example, "New York City" or "Tokyo".',
'units.in' => 'You must specify either "celsius" or "fahrenheit" for the units.',
]);
工具依赖注入
Laravel 服务容器负责解析所有工具。因此,你可以在工具的构造函数中通过类型提示声明所需的依赖。这些依赖会自动解析并注入工具实例:
<?php
namespace App\Mcp\Tools;
use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 创建一个新的工具实例。
*/
public function __construct(
protected WeatherRepository $weather,
) {}
// ...
}
除了构造函数注入,你还可以在工具的 handle() 方法中通过类型提示声明依赖。调用该方法时,服务容器会自动解析并注入这些依赖:
<?php
namespace App\Mcp\Tools;
use App\Repositories\WeatherRepository;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 处理工具请求。
*/
public function handle(Request $request, WeatherRepository $weather): Response
{
$location = $request->get('location');
$forecast = $weather->getForecastFor($location);
// ...
}
}
工具注解
你可以为工具添加注解,向 AI 客户端提供额外的元数据。这些注解有助于 AI 模型理解工具的行为和能力。注解通过特性添加到工具上:
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tool;
#[IsIdempotent]
#[IsReadOnly]
class CurrentWeatherTool extends Tool
{
//
}
可用的注解包括:
| 注解 | 类型 | 描述 |
|---|---|---|
#[IsReadOnly] |
boolean | 表示工具不会修改其运行环境。 |
#[IsDestructive] |
boolean | 表示工具可能执行破坏性更新(仅在非只读时有意义)。 |
#[IsIdempotent] |
boolean | 表示使用相同参数重复调用不会产生额外影响(非只读时)。 |
#[IsOpenWorld] |
boolean | 表示工具可能与外部实体交互。 |
你可以使用布尔参数显式设置注解值:
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
use Laravel\Mcp\Server\Tools\Annotations\IsOpenWorld;
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tool;
#[IsReadOnly(true)]
#[IsDestructive(false)]
#[IsOpenWorld(false)]
#[IsIdempotent(true)]
class CurrentWeatherTool extends Tool
{
//
}
有条件地注册工具
你可以在工具类中实现 shouldRegister 方法,在运行时有条件地注册工具。此方法允许你根据应用状态、配置或请求参数,确定是否应提供某个工具:
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 确定是否应注册此工具。
*/
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}
当工具的 shouldRegister 方法返回 false 时,它不会出现在可用工具列表中,AI 客户端也无法调用它。
工具响应
工具必须返回 Laravel\Mcp\Response 实例。Response 类提供了多种便捷方法,用于创建不同类型的响应:
对于简单的文本响应,使用 text 方法:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 处理工具请求。
*/
public function handle(Request $request): Response
{
// ...
return Response::text('Weather Summary: Sunny, 72°F');
}
要表示工具执行期间发生错误,使用 error 方法:
return Response::error('Unable to fetch weather data. Please try again.');
要返回图像或音频内容,使用 image 和 audio 方法:
return Response::image(file_get_contents(storage_path('weather/radar.png')), 'image/png');
return Response::audio(file_get_contents(storage_path('weather/alert.mp3')), 'audio/mp3');
你也可以使用 fromStorage 方法,直接从 Laravel 文件系统磁盘加载图像和音频内容。MIME 类型会根据文件自动检测:
return Response::fromStorage('weather/radar.png');
如果需要,你可以指定磁盘或覆盖 MIME 类型:
return Response::fromStorage('weather/radar.png', disk: 's3');
return Response::fromStorage('weather/radar.png', mimeType: 'image/webp');
多内容响应
工具可以通过返回 Response 实例数组来返回多项内容:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 处理工具请求。
*
* @return array<int, \Laravel\Mcp\Response>
*/
public function handle(Request $request): array
{
// ...
return [
Response::text('Weather Summary: Sunny, 72°F'),
Response::text("**Detailed Forecast**\n- Morning: 65°F\n- Afternoon: 78°F\n- Evening: 70°F")
];
}
结构化响应
工具可以使用 structured 方法返回结构化内容。这既为 AI 客户端提供可解析的数据,又通过 JSON 编码的文本表示保持向后兼容:
return Response::structured([
'temperature' => 22.5,
'conditions' => 'Partly cloudy',
'humidity' => 65,
]);
如果需要在结构化内容之外提供自定义文本,请在响应工厂上使用 withStructuredContent 方法:
return Response::make(
Response::text('Weather is 22.5°C and sunny')
)->withStructuredContent([
'temperature' => 22.5,
'conditions' => 'Sunny',
]);
流式响应
对于耗时较长的操作或实时数据流,工具可以从 handle 方法返回生成器,以便在最终响应之前向客户端发送中间状态更新:
<?php
namespace App\Mcp\Tools;
use Generator;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 处理工具请求。
*
* @return \Generator<int, \Laravel\Mcp\Response>
*/
public function handle(Request $request): Generator
{
$locations = $request->array('locations');
foreach ($locations as $index => $location) {
yield Response::notification('processing/progress', [
'current' => $index + 1,
'total' => count($locations),
'location' => $location,
]);
yield Response::text($this->forecastFor($location));
}
}
}
使用 Web 服务器时,流式响应会自动打开 SSE(服务器发送事件)流,将每条通过 yield 产生的消息作为事件发送给客户端。
提示词
提示词使服务器能够共享可复用的提示词模板,供 AI 客户端与语言模型交互。它们为组织常见查询和交互提供了标准化方式。
创建提示词
要创建提示词,请运行 make:mcp-prompt Artisan 命令:
php artisan make:mcp-prompt DescribeWeatherPrompt
创建提示词后,在服务器的 $prompts 属性中注册它:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Prompts\DescribeWeatherPrompt;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 在此 MCP 服务器中注册的提示词。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
*/
protected array $prompts = [
DescribeWeatherPrompt::class,
];
}
提示词名称、标题和描述
默认情况下,提示词的名称和标题由类名派生而来。例如,DescribeWeatherPrompt 的名称为 describe-weather,标题为 Describe Weather Prompt。你可以使用 Name 和 Title 特性自定义这些值:
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('weather-assistant')]
#[Title('Weather Assistant Prompt')]
class DescribeWeatherPrompt extends Prompt
{
// ...
}
提示词描述不会自动生成。你应始终使用 Description 特性提供有意义的描述:
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Generates a natural-language explanation of the weather for a given location.')]
class DescribeWeatherPrompt extends Prompt
{
//
}
[!NOTE]
描述是提示词元数据的关键部分,它能帮助 AI 模型理解何时以及如何充分利用提示词。
提示词参数
提示词可以定义参数,允许 AI 客户端使用具体的值自定义提示词模板。使用 arguments 方法定义提示词接受的参数:
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Prompts\Argument;
class DescribeWeatherPrompt extends Prompt
{
/**
* 获取提示词的参数。
*
* @return array<int, \Laravel\Mcp\Server\Prompts\Argument>
*/
public function arguments(): array
{
return [
new Argument(
name: 'tone',
description: 'The tone to use in the weather description (e.g., formal, casual, humorous).',
required: true,
),
];
}
}
验证提示词参数
提示词参数会根据其定义自动验证,但你可能还需要执行更复杂的验证规则。
Laravel MCP 与 Laravel 的验证功能无缝集成。你可以在提示词的 handle 方法中验证传入的提示词参数:
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;
class DescribeWeatherPrompt extends Prompt
{
/**
* 处理提示词请求。
*/
public function handle(Request $request): Response
{
$validated = $request->validate([
'tone' => 'required|string|max:50',
]);
$tone = $validated['tone'];
// 使用给定的语气生成提示词响应……
}
}
验证失败时,AI 客户端会根据你提供的错误消息采取行动。因此,提供清晰且能指导下一步操作的错误消息至关重要:
$validated = $request->validate([
'tone' => ['required','string','max:50'],
],[
'tone.*' => 'You must specify a tone for the weather description. Examples include "formal", "casual", or "humorous".',
]);
提示词依赖注入
Laravel 服务容器负责解析所有提示词。因此,你可以在提示词的构造函数中通过类型提示声明所需的依赖。这些依赖会自动解析并注入提示词实例:
<?php
namespace App\Mcp\Prompts;
use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Prompt;
class DescribeWeatherPrompt extends Prompt
{
/**
* 创建一个新的提示词实例。
*/
public function __construct(
protected WeatherRepository $weather,
) {}
//
}
除了构造函数注入,你还可以在提示词的 handle 方法中通过类型提示声明依赖。调用该方法时,服务容器会自动解析并注入这些依赖:
<?php
namespace App\Mcp\Prompts;
use App\Repositories\WeatherRepository;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;
class DescribeWeatherPrompt extends Prompt
{
/**
* 处理提示词请求。
*/
public function handle(Request $request, WeatherRepository $weather): Response
{
$isAvailable = $weather->isServiceAvailable();
// ...
}
}
有条件地注册提示词
你可以在提示词类中实现 shouldRegister 方法,在运行时有条件地注册提示词。此方法允许你根据应用状态、配置或请求参数,确定是否应提供某个提示词:
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Prompt;
class CurrentWeatherPrompt extends Prompt
{
/**
* 确定是否应注册此提示词。
*/
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}
当提示词的 shouldRegister 方法返回 false 时,它不会出现在可用提示词列表中,AI 客户端也无法调用它。
提示词响应
提示词可以返回单个 Laravel\Mcp\Response,也可以返回包含多个 Laravel\Mcp\Response 实例的可迭代对象。这些响应封装了将发送给 AI 客户端的内容:
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;
class DescribeWeatherPrompt extends Prompt
{
/**
* 处理提示词请求。
*
* @return array<int, \Laravel\Mcp\Response>
*/
public function handle(Request $request): array
{
$tone = $request->string('tone');
$systemMessage = "You are a helpful weather assistant. Please provide a weather description in a {$tone} tone.";
$userMessage = "What is the current weather like in New York City?";
return [
Response::text($systemMessage)->asAssistant(),
Response::text($userMessage),
];
}
}
你可以使用 asAssistant() 方法,将响应消息标记为来自 AI 助手的消息;普通消息则被视为用户输入。
资源
资源使服务器能够开放数据和内容,供 AI 客户端在与语言模型交互时读取并用作上下文。它们可以共享文档、配置等静态或动态信息,以及任何有助于 AI 生成回答的数据。
创建资源
要创建资源,请运行 make:mcp-resource Artisan 命令:
php artisan make:mcp-resource WeatherGuidelinesResource
创建资源后,在服务器的 $resources 属性中注册它:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Resources\WeatherGuidelinesResource;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 在此 MCP 服务器中注册的资源。
*
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
*/
protected array $resources = [
WeatherGuidelinesResource::class,
];
}
资源名称、标题和描述
默认情况下,资源的名称和标题由类名派生而来。例如,WeatherGuidelinesResource 的名称为 weather-guidelines,标题为 Weather Guidelines Resource。你可以使用 Name 和 Title 特性自定义这些值:
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('weather-api-docs')]
#[Title('Weather API Documentation')]
class WeatherGuidelinesResource extends Resource
{
// ...
}
资源描述不会自动生成。你应始终使用 Description 特性提供有意义的描述:
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Comprehensive guidelines for using the Weather API.')]
class WeatherGuidelinesResource extends Resource
{
//
}
[!NOTE]
描述是资源元数据的关键部分,它能帮助 AI 模型理解何时以及如何有效地使用资源。
资源模板
资源模板使服务器能够开放与含变量的 URI 模式相匹配的动态资源。你无需为每个资源定义静态 URI,而是可以创建一个资源,根据模板模式处理多个 URI。
创建资源模板
要创建资源模板,请在资源类中实现 HasUriTemplate 接口,并定义一个返回 UriTemplate 实例的 uriTemplate 方法:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Contracts\HasUriTemplate;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Support\UriTemplate;
#[Description('Access user files by ID')]
#[MimeType('text/plain')]
class UserFileResource extends Resource implements HasUriTemplate
{
/**
* 获取此资源的 URI 模板。
*/
public function uriTemplate(): UriTemplate
{
return new UriTemplate('file://users/{userId}/files/{fileId}');
}
/**
* 处理资源请求。
*/
public function handle(Request $request): Response
{
$userId = $request->get('userId');
$fileId = $request->get('fileId');
// 获取并返回文件内容……
return Response::text($content);
}
}
当资源实现 HasUriTemplate 接口时,它将注册为资源模板,而非静态资源。随后,AI 客户端可以使用匹配模板模式的 URI 请求资源;URI 中的变量会被自动提取,供资源的 handle 方法使用。
URI 模板语法
URI 模板使用花括号包裹的占位符来定义 URI 中的可变部分:
new UriTemplate('file://users/{userId}');
new UriTemplate('file://users/{userId}/files/{fileId}');
new UriTemplate('https://api.example.com/{version}/{resource}/{id}');
访问模板变量
当 URI 与资源模板匹配时,提取出的变量会自动合并到请求中,你可以使用 get 方法访问它们:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Contracts\HasUriTemplate;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Support\UriTemplate;
class UserProfileResource extends Resource implements HasUriTemplate
{
public function uriTemplate(): UriTemplate
{
return new UriTemplate('file://users/{userId}/profile');
}
public function handle(Request $request): Response
{
// 访问提取出的变量
$userId = $request->get('userId');
// 按需访问完整 URI
$uri = $request->uri();
// 获取用户资料……
return Response::text("Profile for user {$userId}");
}
}
Request 对象同时提供提取出的变量和原始请求 URI,为处理资源请求提供完整的上下文。
资源 URI 和 MIME 类型
每个资源都由唯一的 URI 标识,并具有相应的 MIME 类型,帮助 AI 客户端理解资源的格式。
默认情况下,资源的 URI 根据资源名称生成,因此 WeatherGuidelinesResource 的 URI 为 weather://resources/weather-guidelines。默认的 MIME 类型为 text/plain。
你可以使用 Uri 和 MimeType 特性自定义这些值:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Attributes\Uri;
use Laravel\Mcp\Server\Resource;
#[Uri('weather://resources/guidelines')]
#[MimeType('application/pdf')]
class WeatherGuidelinesResource extends Resource
{
}
URI 和 MIME 类型有助于 AI 客户端确定如何恰当地处理和解读资源内容。
资源请求
与工具和提示词不同,资源不能定义输入模式或参数。不过,你仍然可以在资源的 handle 方法中使用请求对象:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Resource;
class WeatherGuidelinesResource extends Resource
{
/**
* 处理资源请求。
*/
public function handle(Request $request): Response
{
// ...
}
}
资源依赖注入
Laravel 服务容器负责解析所有资源。因此,你可以在资源的构造函数中通过类型提示声明所需的依赖。这些依赖会自动解析并注入资源实例:
<?php
namespace App\Mcp\Resources;
use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Resource;
class WeatherGuidelinesResource extends Resource
{
/**
* 创建一个新的资源实例。
*/
public function __construct(
protected WeatherRepository $weather,
) {}
// ...
}
除了构造函数注入,你还可以在资源的 handle 方法中通过类型提示声明依赖。调用该方法时,服务容器会自动解析并注入这些依赖:
<?php
namespace App\Mcp\Resources;
use App\Repositories\WeatherRepository;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Resource;
class WeatherGuidelinesResource extends Resource
{
/**
* 处理资源请求。
*/
public function handle(WeatherRepository $weather): Response
{
$guidelines = $weather->guidelines();
return Response::text($guidelines);
}
}
资源注解
你可以为资源添加注解,向 AI 客户端提供额外的元数据。注解通过特性添加到资源上:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Enums\Role;
use Laravel\Mcp\Server\Annotations\Audience;
use Laravel\Mcp\Server\Annotations\LastModified;
use Laravel\Mcp\Server\Annotations\Priority;
use Laravel\Mcp\Server\Resource;
#[Audience(Role::User)]
#[LastModified('2025-01-12T15:00:58Z')]
#[Priority(0.9)]
class UserDashboardResource extends Resource
{
//
}
可用的注解包括:
| 注解 | 类型 | 描述 |
|---|---|---|
#[Audience] |
Role 或数组 | 指定目标受众(Role::User、Role::Assistant 或两者)。 |
#[Priority] |
float | 介于 0.0 和 1.0 之间的数值,表示资源的重要程度。 |
#[LastModified] |
string | 表示资源最后更新时间的 ISO 8601 时间戳。 |
有条件地注册资源
你可以在资源类中实现 shouldRegister 方法,在运行时有条件地注册资源。此方法允许你根据应用状态、配置或请求参数,确定是否应提供某个资源:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Resource;
class WeatherGuidelinesResource extends Resource
{
/**
* 确定是否应注册此资源。
*/
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}
当资源的 shouldRegister 方法返回 false 时,它不会出现在可用资源列表中,AI 客户端也无法访问它。
资源响应
资源必须返回 Laravel\Mcp\Response 实例。Response 类提供了多种便捷方法,用于创建不同类型的响应:
对于简单的文本内容,使用 text 方法:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 处理资源请求。
*/
public function handle(Request $request): Response
{
// ...
return Response::text($weatherData);
}
资源链接响应
要返回资源链接,使用 resourceLink 方法,并提供 URI 和名称。与内嵌资源不同,资源链接返回一个 URI 指针,由 AI 客户端独立获取对应内容:
return Response::resourceLink(
uri: 'file:///data/report.json',
name: 'monthly-report',
mimeType: 'application/json',
);
你也可以传入已注册的资源类或实例,这样会自动继承资源的 URI、名称、标题、描述和 MIME 类型:
return Response::resourceLink(new WeatherForecastResource);
Blob 响应
要返回 Blob 内容,使用 blob 方法并传入 Blob 内容:
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
返回 Blob 内容时,MIME 类型由资源所配置的 MIME 类型决定:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Resource;
#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
//
}
错误响应
要表示获取资源期间发生错误,使用 error() 方法:
return Response::error('Unable to fetch weather data for the specified location.');
应用
Laravel MCP 支持 MCP Apps。这是模型上下文协议的一项扩展,允许工具在受支持宿主的沙箱 iframe 中渲染交互式 HTML 应用。你可以借此构建仪表盘、表单、可视化界面等超越纯文本响应的丰富体验。
MCP 应用由相互配合的两部分组成:
- 应用资源,返回应用所需的完整、自包含 HTML。
- 工具,通过
#[RendersApp]特性关联到应用资源。调用工具时,宿主会获取并渲染关联的资源。
创建应用资源
你可以使用 make:mcp-app-resource Artisan 命令创建应用资源:
php artisan make:mcp-app-resource WeatherDashboardApp
此命令会创建两个文件:app/Mcp/Resources 中的 PHP 类和 resources/views/mcp 中的 Blade 视图。视图名称根据类名自动推导。例如,WeatherDashboardApp 对应 mcp.weather-dashboard-app:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\AppMeta;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\AppResource;
#[Description('An interactive weather dashboard.')]
#[AppMeta]
class WeatherDashboardApp extends AppResource
{
/**
* 处理应用资源请求。
*/
public function handle(Request $request): Response
{
return Response::view('mcp.weather-dashboard-app', [
'title' => $this->title(),
]);
}
}
AppResource 继承基类 Resource,并自动配置 MCP Apps 规范要求的 ui:// URI 方案和 text/html;profile=mcp-app MIME 类型。与其他资源一样,你必须将其注册到服务器的 $resources 数组中。
生成的 Blade 视图使用 <x-mcp::app> 组件,该组件渲染完整的 HTML 文档,并内置可直接使用的客户端 MCP SDK:
<x-mcp::app :title="$title">
<x-slot:head>
<script type="module">
createMcpApp(async (app) => {
document.getElementById('run-btn').addEventListener('click', async () => {
const result = await app.callServerTool('get-weather-data', {});
document.getElementById('output').textContent = result.content[0]?.text ?? '';
});
});
</script>
</x-slot:head>
<div id="app">
<button id="run-btn">Refresh</button>
<p id="output"></p>
</div>
</x-mcp::app>
全局函数 createMcpApp 由内置 SDK 提供,负责将 iframe 连接到服务器、应用宿主主题,并提供 callServerTool、sendMessage、openLink 等辅助方法和事件回调。完整的客户端 API 请参阅 MCP Apps 规范。
通过工具渲染应用
要显示应用资源,请使用 #[RendersApp] 特性将工具关联到该资源。调用工具时,Laravel MCP 会在工具元数据中包含资源的 URI,以便宿主在沙箱 iframe 中渲染应用:
<?php
namespace App\Mcp\Tools;
use App\Mcp\Resources\WeatherDashboardApp;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Tool;
#[RendersApp(resource: WeatherDashboardApp::class)]
class ShowWeatherDashboard extends Tool
{
/**
* 处理工具请求。
*/
public function handle(Request $request): Response
{
return Response::text('Weather dashboard loaded.');
}
}
只要注册了任意 AppResource,Laravel MCP 就会在服务器的 extensions 能力中自动公布 io.modelcontextprotocol/ui 扩展,因此无需额外配置服务器。
应用工具的可见性
每个带有 #[RendersApp] 特性的工具都可以通过 visibility 参数限制调用方。这适用于提供仅供应用使用的私有工具,让 UI 调用它们来加载或刷新数据,同时不向模型暴露这些工具:
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Ui\Enums\Visibility;
#[RendersApp(resource: WeatherDashboardApp::class, visibility: [Visibility::App])]
class GetWeatherData extends Tool
{
// ...
}
Visibility 枚举包含 Model 和 App 两个枚举项,默认同时包含两者。对于 UI 直接调用的后端操作,使用 [Visibility::App];若要禁止 UI 调用某个工具,则使用 [Visibility::Model]。
应用配置
应用资源上的 #[AppMeta] 特性用于配置 iframe 的内容安全策略、浏览器权限,以及应包含在视图 <head> 中的库脚本:
use Laravel\Mcp\Server\Attributes\AppMeta;
use Laravel\Mcp\Server\Ui\Enums\Library;
use Laravel\Mcp\Server\Ui\Enums\Permission;
#[AppMeta(
connectDomains: ['https://api.weather.com'],
permissions: [Permission::Geolocation],
libraries: [Library::Tailwind, Library::Alpine],
)]
class WeatherDashboardApp extends AppResource
{
// ...
}
Library 枚举包含常用前端库的预配置 CDN 脚本,例如 Library::Tailwind 和 Library::Alpine,其 CDN 来源会自动合并到 CSP 中。Permission 枚举涵盖 Camera、Microphone、Geolocation 和 ClipboardWrite 等浏览器权限。
对于需要计算或动态生成的配置,请使用 Laravel\Mcp\Server\Ui 命名空间中的 AppMeta、Csp 和 Permissions 流畅构建器,重写资源的 appMeta 方法。
使用 Boost 构建应用
Laravel MCP 包含专门用于构建 MCP Apps 的 Boost 技能参考。如果你安装了 Laravel Boost,可以让 AI 编码智能体调用 mcp-development 技能,为你生成应用资源、Blade 视图和关联工具的基础代码。
完整的协议参考,包括全部客户端 API 和模式详情,请参阅官方 MCP Apps 文档。
元数据
Laravel MCP 还支持 MCP 规范定义的 _meta 字段,某些 MCP 客户端或集成需要此字段。元数据可以应用于所有 MCP 基础组件,包括工具、资源、提示词及其响应。
你可以使用 withMeta 方法为单个响应内容附加元数据:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 处理工具请求。
*/
public function handle(Request $request): Response
{
return Response::text('The weather is sunny.')
->withMeta(['source' => 'weather-api', 'cached' => true]);
}
对于应用于整个响应封装的结果级元数据,请使用 Response::make 包装响应,并在返回的响应工厂实例上调用 withMeta:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
/**
* 处理工具请求。
*/
public function handle(Request $request): ResponseFactory
{
return Response::make(
Response::text('The weather is sunny.')
)->withMeta(['request_id' => '12345']);
}
要为工具、资源或提示词本身附加元数据,请在类中定义 $meta 属性:
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
#[Description('Fetches the current weather forecast.')]
class CurrentWeatherTool extends Tool
{
protected ?array $meta = [
'version' => '2.0',
'author' => 'Weather Team',
];
// ...
}
图标
MCP 客户端可以显示服务器及其基础组件的图标。你可以使用 Icon 特性,在服务器、工具、资源或提示词上声明图标:
use Laravel\Mcp\Enums\IconTheme;
use Laravel\Mcp\Server\Attributes\Icon;
#[Icon('mcp/server.png', mimeType: 'image/png', sizes: ['48x48'])]
#[Icon('mcp/server-dark.svg', theme: IconTheme::Dark)]
class WeatherServer extends Server
{
// ...
}
Icon 特性可以重复使用,因此你可以声明多个图标,以提供不同尺寸或浅色、深色主题的变体。
你也可以重写 icons 方法,以编程方式定义图标。当图标取决于运行时条件时,这种方式非常实用:
use Laravel\Mcp\Schema\Icon;
class CurrentWeatherTool extends Tool
{
/**
* 获取工具的图标。
*
* @return array<int, Icon>
*/
public function icons(): array
{
return [
Icon::from('mcp/tool.png', mimeType: 'image/png'),
];
}
}
通过特性和 icons 方法定义的图标会自动合并。图标路径按以下规则解析:
- 带有
https:或data:等 URI 方案的路径按原样使用。 - 相对路径使用 Laravel 的
asset辅助函数解析为 URL。
身份认证
与路由一样,你可以使用中间件对 Web MCP 服务器进行身份认证。为 MCP 服务器添加身份认证后,用户必须先通过认证才能使用服务器的任何功能。
本节介绍外部 AI 客户端访问你的应用的 MCP 服务器时的认证。如果要使用用户凭据将你的应用连接到其他服务的 MCP 服务器,请参阅客户端身份认证。
你可以通过两种方式对 MCP 服务器的访问进行身份认证:使用 Laravel Sanctum,或通过 Authorization HTTP 请求头传入任意令牌,进行简单的令牌认证;也可以使用 Laravel Passport 进行 OAuth 认证。
OAuth 2.1
保护 Web MCP 服务器最完善的方式,是通过 Laravel Passport 使用 OAuth 认证。
通过 OAuth 对 MCP 服务器进行身份认证时,请在 routes/ai.php 文件中调用 Mcp::oauthRoutes 方法,注册所需的 OAuth2 发现路由和客户端注册路由。然后,在同一文件中为 Mcp::web 路由应用 Passport 的 auth:api 中间件:
use App\Mcp\Servers\WeatherExample;
use Laravel\Mcp\Facades\Mcp;
Mcp::oauthRoutes();
Mcp::web('/mcp/weather', WeatherExample::class)
->middleware('auth:api');
全新安装 Passport
如果你的应用尚未使用 Laravel Passport,请按照 Passport 的安装和部署指南将其添加到应用中。继续之前,你应准备好一个 OAuthenticatable 模型、新的认证看守器以及 Passport 密钥。
接下来,发布 Laravel MCP 提供的 Passport 授权视图:
php artisan vendor:publish --tag=mcp-views
然后,使用 Passport::authorizationView 方法让 Passport 使用该视图。通常应在应用的 AppServiceProvider 的 boot 方法中调用此方法:
use Laravel\Passport\Passport;
/**
* 引导应用服务。
*/
public function boot(): void
{
Passport::authorizationView(function ($parameters) {
return view('mcp.authorize', $parameters);
});
}
在身份认证期间,此视图会显示给最终用户,让他们拒绝或批准 AI 智能体的认证请求。
[!NOTE]
在此场景中,我们只是将 OAuth 用作与底层可认证模型之间的转换层,并未处理 OAuth 的许多方面,例如作用域。
使用现有的 Passport 安装
如果你的应用已使用 Laravel Passport,Laravel MCP 应能与现有的 Passport 安装无缝配合。不过,由于 OAuth 主要被用作与底层可认证模型之间的转换层,目前不支持自定义作用域。
Laravel MCP 通过上述 Mcp::oauthRoutes 方法,添加、公布并使用单一的 mcp:use 作用域。
Passport 与 Sanctum
OAuth2.1 是模型上下文协议规范中规定的身份认证机制,也是 MCP 客户端支持最广泛的机制。因此,我们建议尽可能使用 Passport。
如果你的应用已使用 Sanctum,再添加 Passport 可能较为繁琐。在这种情况下,我们建议继续使用 Sanctum,直到明确需要使用仅支持 OAuth 的 MCP 客户端时,再添加 Passport。
限制 OAuth 路由的请求频率
根据 MCP 规范和 RFC 7591 的要求,Mcp::oauthRoutes 注册的动态客户端注册路由可公开访问,允许任意 MCP 客户端无需身份认证即可注册。由于每次注册都会在数据库中创建新的 OAuth 客户端,你应根据应用的流量对这些路由进行频率限制。为此,可以在应用 Laravel throttle 中间件的路由组内注册这些路由:
use Illuminate\Support\Facades\Route;
use Laravel\Mcp\Facades\Mcp;
Route::middleware('throttle:10,1')->group(function () {
Mcp::oauthRoutes();
});
如果 MCP 客户端无需动态注册自身,你可以将 registration 参数设为 false,完全禁用客户端注册路由:
Mcp::oauthRoutes(registration: false);
Sanctum
如果你想使用 Sanctum 保护 MCP 服务器,只需在 routes/ai.php 文件中为服务器添加 Sanctum 的身份认证中间件。然后,确保 MCP 客户端提供 Authorization: Bearer <token> 请求头,以顺利通过身份认证:
use App\Mcp\Servers\WeatherExample;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/demo', WeatherExample::class)
->middleware('auth:sanctum');
自定义 MCP 身份认证
如果你的应用签发自定义 API 令牌,你可以为 Mcp::web 路由分配任意中间件,对 MCP 服务器进行身份认证。自定义中间件可以手动检查 Authorization 请求头,以认证传入的 MCP 请求。
授权
你可以通过 $request->user() 方法访问当前已认证用户,从而在 MCP 工具和资源中执行授权检查:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 处理工具请求。
*/
public function handle(Request $request): Response
{
if (! $request->user()->can('read-weather')) {
return Response::error('Permission denied.');
}
// ...
}
MCP 客户端
除了构建服务器,Laravel MCP 还包含一个客户端,可连接第一方或第三方的其他 MCP 服务器。客户端让应用能够发现并调用 MCP 服务器开放的工具,尤其适合让 AI 智能体使用外部 MCP 服务器提供的能力。
例如,聊天应用可以使用每个用户的 GitHub 令牌连接 GitHub 的 MCP 服务器,让智能体回答有关该用户仓库的问题。在此场景中,你的应用是 MCP 客户端,无需创建 MCP 服务器或注册 Mcp::web 路由。
连接服务器
你可以使用 Client::web 方法连接可通过 HTTP 访问的 MCP 服务器,并传入服务器的 URL:
use Laravel\Mcp\Client;
$client = Client::web('https://api.githubcopilot.com/mcp/');
要连接以命令形式运行的本地 MCP 服务器,请使用 Client::local 方法,提供启动服务器所需的命令和参数:
use Laravel\Mcp\Client;
$client = Client::local('php', ['artisan', 'mcp:start']);
客户端采用延迟连接,首次列出或调用工具时会自动建立连接。如果需要手动管理连接,可以使用 connect、connected 和 disconnect 方法:
$client->connect();
if ($client->connected()) {
$capabilities = $client->capabilities();
$server = $client->serverInfo();
}
$client->disconnect();
你可以使用 withTimeout 方法自定义请求超时时间:
$client = Client::web('https://api.githubcopilot.com/mcp/')->withTimeout(30);
命名客户端
你可以选择注册可复用的命名客户端,而无需在每次使用时构建客户端。registerClient 方法为出站连接命名;它不会开放 MCP 服务器,也不会为用户授予权限。通常使用 Mcp 门面,在服务提供者的 boot 方法中完成注册:
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;
Mcp::registerClient('github', fn () => Client::web('https://api.githubcopilot.com/mcp/'));
注册后,你可以在应用中的任意位置按名称解析客户端:
use Laravel\Mcp\Facades\Mcp;
$client = Mcp::client('github');
命名客户端在每个请求中只解析一次,并在请求生命周期结束时自动断开连接。
客户端身份认证
Bearer 令牌
要连接受 Bearer 令牌保护的 Web MCP 服务器,请使用 withToken 方法。你可以传入令牌字符串,也可以传入延迟解析令牌的闭包:
use Illuminate\Support\Facades\Auth;
use Laravel\Mcp\Client;
$client = Client::web('https://api.githubcopilot.com/mcp/')->withToken(
config('services.github_mcp.token'),
);
$client = Client::web('https://api.githubcopilot.com/mcp/')->withToken(
fn () => Auth::user()->github_mcp_token,
);
如果你已通过 Socialite 或其他账户关联流程获得合适的令牌,将它传入 withToken 即可,无需注册 MCP OAuth 路由。
使用 OAuth 关联用户账户
设想一个聊天应用:用户关联项目管理服务后,向智能体提问「我有哪些任务已逾期?」外部项目管理服务开放了一个受 OAuth 保护的 MCP 服务器。你的应用必须将每个用户重定向到该服务的授权同意页面,用其批准结果换取令牌,并保存令牌以供未来调用工具时使用。
Laravel MCP 使用 withOAuth 和 Mcp::oAuthRoutesFor 处理此账户关联流程。首先,在服务提供者中注册命名客户端。OAuth 客户端凭据用于向服务标识你的应用,而 withToken 则在调用工具时提供已关联用户所保存的访问令牌:
use Illuminate\Support\Facades\Auth;
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;
Mcp::registerClient('projects', fn () => Client::web('https://projects.example.com/mcp')
->withOAuth(
clientId: config('services.projects.client_id'),
clientSecret: config('services.projects.client_secret'),
)
->withToken(fn () => Auth::user()->projects_token));
[!NOTE]
clientId和clientSecret参数可以省略。如果授权服务器支持,Laravel 会使用客户端 ID 元数据文档;对于旧版服务器,则回退到动态客户端注册。
授权服务器必须在其授权服务器元数据中公布对 S256 PKCE 代码质询方法的支持。如果未公布 PKCE 支持,Laravel 将拒绝授权尝试。
接下来,在 routes/ai.php 文件中注册账户关联路由。回调会接收客户端名称和生成的 TokenSet,以便为已登录用户保存令牌:
use Illuminate\Support\Facades\Auth;
use Laravel\Mcp\Client\OAuth\TokenSet;
use Laravel\Mcp\Facades\Mcp;
Mcp::oAuthRoutesFor('projects', function (string $client, TokenSet $token) {
Auth::user()->update([
'projects_token' => $token->accessToken,
]);
return redirect('/chat');
}, middleware: ['web', 'auth']);
oAuthRoutesFor 方法会注册三个命名路由:连接路由(mcp.oauth.{client}.connect)将用户重定向到授权服务器;回调路由(mcp.oauth.{client}.callback)交换授权码并调用你的处理程序;公开的客户端 ID 元数据文档路由为 mcp.oauth.{client}.client-metadata。
连接路由和回调路由默认使用 web 中间件组;上面的账户关联示例还添加了 auth,要求用户先登录。元数据路由不使用此中间件,因为授权服务器必须能够获取该文档。
「连接项目服务」按钮应将用户导向连接路由:
return redirect()->route('mcp.oauth.projects.connect');
Laravel 会将用户重定向到服务以批准访问,在用户返回后交换授权码,并调用你的回调。在后续聊天请求中,命名客户端会使用保存的令牌。例如,AI SDK 智能体可以开放此服务的工具:
public function tools(): iterable
{
return [
...Mcp::client('projects')->tools(),
];
}
使用加密类型转换保存令牌,并在模型序列化时隐藏它们。如果服务签发的令牌会过期,还应保存 TokenSet 中的刷新令牌和过期信息,并在调用工具之前处理续期。对于队列中的智能体,请从显式提供的用户解析凭据,而非使用 Auth::user()。
[!NOTE]
Mcp::oAuthRoutesFor将你的用户关联到外部服务。相比之下,Mcp::oauthRoutes允许外部 AI 客户端使用 Passport 认证并访问你自己的 MCP 服务器。上述账户关联流程无需使用 Passport 或Mcp::oauthRoutes。
客户端元数据
元数据文档将你的应用描述为公共 OAuth 客户端,并使用应用的 APP_URL 生成客户端 ID 和回调 URL。因此,你应确保生产环境中的 APP_URL 环境变量设置正确。你可以使用 clientMetadataUri 和 clientMetadata 参数自定义元数据路由,并提供额外的元数据:
use Laravel\Mcp\Client\OAuth\TokenSet;
use Laravel\Mcp\Facades\Mcp;
Mcp::oAuthRoutesFor(
'projects',
function (string $client, TokenSet $token) {
// 保存令牌……
return redirect('/dashboard');
},
clientMetadataUri: 'oauth/projects/client.json',
clientMetadata: [
'client_name' => 'Acme Dashboard',
'logo_uri' => 'https://acme.com/logo.png',
],
);
工具
你可以使用 tools 方法获取 MCP 服务器开放的工具,该方法返回以名称为键的工具集合:
use Laravel\Mcp\Facades\Mcp;
$tools = Mcp::client('github')->tools();
foreach ($tools as $tool) {
$tool->name;
$tool->title;
$tool->description;
$tool->inputSchema;
}
客户端会自动分页获取所有可用工具。你可以使用 limit 参数限制返回的工具数量:
$tools = Mcp::client('github')->tools(limit: 10);
要调用工具,请使用 callTool 方法,传入工具名称和参数数组。返回的 ToolResult 实例提供对工具响应的访问:
use Laravel\Mcp\Facades\Mcp;
$result = Mcp::client('github')->callTool('list_issues', [
'owner' => 'laravel',
'repo' => 'framework',
]);
$result->text(); // 响应的文本内容……
(string) $result; // 等同于调用 text()……
$result->isError; // 工具是否报告了错误……
$result->structuredContent; // 结构化内容(如果有)……
你也可以直接从已列出的工具实例调用工具:
$tools = Mcp::client('github')->tools();
$result = $tools['list_issues']->call([
'owner' => 'laravel',
'repo' => 'framework',
]);
如果你使用 Laravel AI SDK 构建智能体,也可以将 MCP 客户端中的工具直接提供给智能体,让模型在响应提示词时调用它们。更多信息请参阅 AI SDK 文档的 MCP 工具一节。
提示词
你可以使用 prompts 方法获取 MCP 服务器开放的提示词,该方法返回以名称为键的提示词集合:
use Laravel\Mcp\Facades\Mcp;
$prompts = Mcp::client('github')->prompts();
foreach ($prompts as $prompt) {
$prompt->name;
$prompt->title;
$prompt->description;
$prompt->arguments;
}
客户端会自动分页获取所有可用提示词。你可以使用 limit 参数限制返回的提示词数量:
$prompts = Mcp::client('github')->prompts(limit: 10);
要获取提示词,请使用 getPrompt 方法,传入提示词名称和参数数组。返回的 PromptResult 实例提供对生成消息的访问:
use Laravel\Mcp\Facades\Mcp;
$result = Mcp::client('github')->getPrompt('issue_to_fix_workflow', [
'owner' => 'laravel',
'repo' => 'framework',
'title' => 'Fix typo in README',
'description' => 'The installation section has a typo.',
]);
$result->text(); // 消息的文本内容……
(string) $result; // 等同于调用 text()……
$result->messages; // 提示词返回的原始消息……
$result->description; // 提示词描述(如果有)……
资源
你可以使用 resources 方法获取 MCP 服务器开放的资源,该方法返回以 URI 为键的资源集合:
use Laravel\Mcp\Facades\Mcp;
$resources = Mcp::client('github')->resources();
foreach ($resources as $resource) {
$resource->uri;
$resource->name;
$resource->title;
$resource->description;
$resource->mimeType;
$resource->size;
}
客户端会自动分页获取所有可用资源。你可以使用 limit 参数限制返回的资源数量:
$resources = Mcp::client('github')->resources(limit: 10);
要读取资源,请使用 readResource 方法,并传入资源 URI。返回的 ResourceReadResult 实例提供对资源内容的访问:
use Laravel\Mcp\Facades\Mcp;
$result = Mcp::client('github')->readResource('repo://laravel/framework/contents/README.md');
$result->content(); // 资源内容,按需解码 base64 Blob……
(string) $result; // 等同于调用 content()……
$result->mimeType(); // 资源的 MIME 类型(如果有)……
$result->contents; // 资源返回的原始内容……
测试服务器
你可以使用内置的 MCP Inspector 或编写单元测试来测试 MCP 服务器。
MCP Inspector
交互式工具 MCP Inspector 用于测试和调试 MCP 服务器。你可以用它连接服务器、验证身份认证,并试用工具、资源和提示词。
你可以为任意已注册的服务器运行 Inspector:
# Web 服务器……
php artisan mcp:inspector mcp/weather
# 名为「weather」的本地服务器……
php artisan mcp:inspector weather
此命令会启动 MCP Inspector,并提供可复制到 MCP 客户端的客户端设置,以确保配置正确。如果 Web 服务器受身份认证中间件保护,请确保连接时包含所需的请求头,例如 Authorization Bearer 令牌。
单元测试
你可以为 MCP 服务器、工具、资源和提示词编写单元测试。
首先,创建一个新的测试用例,并在注册了目标基础组件的服务器上调用它。例如,要测试 WeatherServer 上的工具:
test('tool', function () {
$response = WeatherServer::tool(CurrentWeatherTool::class, [
'location' => 'New York City',
'units' => 'fahrenheit',
]);
$response
->assertOk()
->assertSee('The current weather in New York City is 72°F and sunny.');
});
/**
* 测试工具。
*/
public function test_tool(): void
{
$response = WeatherServer::tool(CurrentWeatherTool::class, [
'location' => 'New York City',
'units' => 'fahrenheit',
]);
$response
->assertOk()
->assertSee('The current weather in New York City is 72°F and sunny.');
}
同样,你也可以测试提示词和资源:
$response = WeatherServer::prompt(...);
$response = WeatherServer::resource(...);
你还可以在调用基础组件之前链式调用 actingAs 方法,以已认证用户的身份进行操作:
$response = WeatherServer::actingAs($user)->tool(...);
收到响应后,你可以使用各种断言方法验证响应的内容和状态。
你可以使用 assertOk 方法断言响应成功。此方法会检查响应是否没有任何错误:
$response->assertOk();
你可以使用 assertSee 方法断言响应包含指定文本:
$response->assertSee('The current weather in New York City is 72°F and sunny.');
你可以使用 assertHasErrors 方法断言响应包含错误:
$response->assertHasErrors();
$response->assertHasErrors([
'Something went wrong.',
]);
你可以使用 assertHasNoErrors 方法断言响应不包含错误:
$response->assertHasNoErrors();
你可以使用 assertName()、assertTitle() 和 assertDescription() 方法断言响应包含指定的元数据:
$response->assertName('current-weather');
$response->assertTitle('Current Weather Tool');
$response->assertDescription('Fetches the current weather forecast for a specified location.');
你可以使用 assertSentNotification 和 assertNotificationCount 方法断言通知已发送:
$response->assertSentNotification('processing/progress', [
'step' => 1,
'total' => 5,
]);
$response->assertSentNotification('processing/progress', [
'step' => 2,
'total' => 5,
]);
$response->assertNotificationCount(5);
最后,如果你希望检查原始响应内容,可以使用 dd 或 dump 方法输出响应以进行调试:
$response->dd();
$response->dump();
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
Laravel 13 中文文档
关于 LearnKu