跳转至内容

Laravel MCP

简介

Laravel MCP 提供了一种简单且优雅的方式,让 AI 客户端能够通过 模型上下文协议 (Model Context Protocol) 与您的 Laravel 应用程序进行交互。它提供了一个富有表现力且流畅的接口,用于定义服务器、工具、资源和提示词,从而实现 AI 驱动的应用程序交互。

安装

要开始使用,请使用 Composer 包管理器将 Laravel MCP 安装到您的项目中

1composer require laravel/mcp

发布路由

安装 Laravel MCP 后,执行 vendor:publish Artisan 命令来发布 routes/ai.php 文件,您将在该文件中定义您的 MCP 服务器

1php artisan vendor:publish --tag=ai-routes

此命令会在您应用程序的 routes 目录中创建 routes/ai.php 文件,您将使用它来注册您的 MCP 服务器。

创建服务器

您可以使用 make:mcp-server Artisan 命令创建 MCP 服务器。服务器充当中心通信点,向 AI 客户端公开工具、资源和提示词等 MCP 功能

1php artisan make:mcp-server WeatherServer

此命令将在 app/Mcp/Servers 目录中创建一个新的服务器类。生成的服务器类继承自 Laravel MCP 的基础 Laravel\Mcp\Server 类,并提供用于配置服务器及注册工具、资源和提示词的属性与方法

1<?php
2 
3namespace App\Mcp\Servers;
4 
5use Laravel\Mcp\Server\Attributes\Instructions;
6use Laravel\Mcp\Server\Attributes\Name;
7use Laravel\Mcp\Server\Attributes\Version;
8use Laravel\Mcp\Server;
9 
10#[Name('Weather Server')]
11#[Version('1.0.0')]
12#[Instructions('This server provides weather information and forecasts.')]
13class WeatherServer extends Server
14{
15 /**
16 * The tools registered with this MCP server.
17 *
18 * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
19 */
20 protected array $tools = [
21 // GetCurrentWeatherTool::class,
22 ];
23 
24 /**
25 * The resources registered with this MCP server.
26 *
27 * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
28 */
29 protected array $resources = [
30 // WeatherGuidelinesResource::class,
31 ];
32 
33 /**
34 * The prompts registered with this MCP server.
35 *
36 * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
37 */
38 protected array $prompts = [
39 // DescribeWeatherPrompt::class,
40 ];
41}

服务器注册

创建服务器后,必须在 routes/ai.php 文件中注册它才能使其可访问。Laravel MCP 提供了两种注册服务器的方法:用于 HTTP 可访问服务器的 web 方法,以及用于命令行服务器的 local 方法。

Web 服务器

Web 服务器是最常见的服务器类型,可以通过 HTTP POST 请求进行访问,非常适合远程 AI 客户端或基于 Web 的集成。使用 web 方法注册 Web 服务器

1use App\Mcp\Servers\WeatherServer;
2use Laravel\Mcp\Facades\Mcp;
3 
4Mcp::web('/mcp/weather', WeatherServer::class);

与普通路由一样,您可以应用中间件来保护您的 Web 服务器

1Mcp::web('/mcp/weather', WeatherServer::class)
2 ->middleware(['throttle:mcp']);

本地服务器

本地服务器作为 Artisan 命令运行,非常适合构建本地 AI 助手集成,例如 Laravel Boost。使用 local 方法注册本地服务器

1use App\Mcp\Servers\WeatherServer;
2use Laravel\Mcp\Facades\Mcp;
3 
4Mcp::local('weather', WeatherServer::class);

注册完成后,通常不需要手动运行 mcp:start Artisan 命令。相反,应配置您的 MCP 客户端(AI 智能体)来启动服务器,或者使用 MCP Inspector

工具

工具使您的服务器能够公开 AI 客户端可以调用的功能。它们允许语言模型执行操作、运行代码或与外部系统交互

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use Illuminate\Contracts\JsonSchema\JsonSchema;
6use Laravel\Mcp\Request;
7use Laravel\Mcp\Response;
8use Laravel\Mcp\Server\Attributes\Description;
9use Laravel\Mcp\Server\Tool;
10 
11#[Description('Fetches the current weather forecast for a specified location.')]
12class CurrentWeatherTool extends Tool
13{
14 /**
15 * Handle the tool request.
16 */
17 public function handle(Request $request): Response
18 {
19 $location = $request->get('location');
20 
21 // Get weather...
22 
23 return Response::text('The weather is...');
24 }
25 
26 /**
27 * Get the tool's input schema.
28 *
29 * @return array<string, \Illuminate\JsonSchema\Types\Type>
30 */
31 public function schema(JsonSchema $schema): array
32 {
33 return [
34 'location' => $schema->string()
35 ->description('The location to get the weather for.')
36 ->required(),
37 ];
38 }
39}

创建工具

要创建工具,请运行 make:mcp-tool Artisan 命令

1php artisan make:mcp-tool CurrentWeatherTool

创建工具后,将其注册到服务器的 $tools 属性中

1<?php
2 
3namespace App\Mcp\Servers;
4 
5use App\Mcp\Tools\CurrentWeatherTool;
6use Laravel\Mcp\Server;
7 
8class WeatherServer extends Server
9{
10 /**
11 * The tools registered with this MCP server.
12 *
13 * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
14 */
15 protected array $tools = [
16 CurrentWeatherTool::class,
17 ];
18}

工具名称、标题和描述

默认情况下,工具的名称和标题派生自类名。例如,CurrentWeatherTool 的名称将为 current-weather,标题为 Current Weather Tool。您可以使用 NameTitle 属性自定义这些值

1use Laravel\Mcp\Server\Attributes\Name;
2use Laravel\Mcp\Server\Attributes\Title;
3 
4#[Name('get-optimistic-weather')]
5#[Title('Get Optimistic Weather Forecast')]
6class CurrentWeatherTool extends Tool
7{
8 // ...
9}

工具描述不会自动生成。您应该始终使用 Description 属性提供有意义的描述

1use Laravel\Mcp\Server\Attributes\Description;
2 
3#[Description('Fetches the current weather forecast for a specified location.')]
4class CurrentWeatherTool extends Tool
5{
6 //
7}

描述是工具元数据的关键部分,因为它有助于 AI 模型理解何时以及如何有效地使用该工具。

工具输入架构

工具可以定义输入架构,以指定它们接受 AI 客户端的哪些参数。使用 Laravel 的 Illuminate\Contracts\JsonSchema\JsonSchema 构建器来定义工具的输入要求

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use Illuminate\Contracts\JsonSchema\JsonSchema;
6use Laravel\Mcp\Server\Tool;
7 
8class CurrentWeatherTool extends Tool
9{
10 /**
11 * Get the tool's input schema.
12 *
13 * @return array<string, \Illuminate\JsonSchema\Types\Type>
14 */
15 public function schema(JsonSchema $schema): array
16 {
17 return [
18 'location' => $schema->string()
19 ->description('The location to get the weather for.')
20 ->required(),
21 
22 'units' => $schema->string()
23 ->enum(['celsius', 'fahrenheit'])
24 ->description('The temperature units to use.')
25 ->default('celsius'),
26 ];
27 }
28}

工具输出架构

工具可以定义 输出架构 以指定其响应结构。这使得能够更好地与需要可解析工具结果的 AI 客户端集成。使用 outputSchema 方法定义工具的输出结构

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use Illuminate\Contracts\JsonSchema\JsonSchema;
6use Laravel\Mcp\Server\Tool;
7 
8class CurrentWeatherTool extends Tool
9{
10 /**
11 * Get the tool's output schema.
12 *
13 * @return array<string, \Illuminate\JsonSchema\Types\Type>
14 */
15 public function outputSchema(JsonSchema $schema): array
16 {
17 return [
18 'temperature' => $schema->number()
19 ->description('Temperature in Celsius')
20 ->required(),
21 
22 'conditions' => $schema->string()
23 ->description('Weather conditions')
24 ->required(),
25 
26 'humidity' => $schema->integer()
27 ->description('Humidity percentage')
28 ->required(),
29 ];
30 }
31}

验证工具参数

JSON Schema 定义为工具参数提供了基本结构,但您可能还希望强制执行更复杂的验证规则。

Laravel MCP 与 Laravel 的 验证功能 无缝集成。您可以在工具的 handle 方法中验证传入的工具参数

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Response;
7use Laravel\Mcp\Server\Tool;
8 
9class CurrentWeatherTool extends Tool
10{
11 /**
12 * Handle the tool request.
13 */
14 public function handle(Request $request): Response
15 {
16 $validated = $request->validate([
17 'location' => 'required|string|max:100',
18 'units' => 'in:celsius,fahrenheit',
19 ]);
20 
21 // Fetch weather data using the validated arguments...
22 }
23}

当验证失败时,AI 客户端将根据您提供的错误消息采取行动。因此,提供清晰且可操作的错误消息至关重要

1$validated = $request->validate([
2 'location' => ['required','string','max:100'],
3 'units' => 'in:celsius,fahrenheit',
4],[
5 'location.required' => 'You must specify a location to get the weather for. For example, "New York City" or "Tokyo".',
6 'units.in' => 'You must specify either "celsius" or "fahrenheit" for the units.',
7]);

工具依赖注入

Laravel 服务容器 用于解析所有工具。因此,您可以在工具的构造函数中对所需的任何依赖项进行类型提示。声明的依赖项将自动解析并注入到工具实例中

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use App\Repositories\WeatherRepository;
6use Laravel\Mcp\Server\Tool;
7 
8class CurrentWeatherTool extends Tool
9{
10 /**
11 * Create a new tool instance.
12 */
13 public function __construct(
14 protected WeatherRepository $weather,
15 ) {}
16 
17 // ...
18}

除了构造函数注入外,您还可以在工具的 handle() 方法中对依赖项进行类型提示。当调用该方法时,服务容器会自动解析并注入这些依赖项

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use App\Repositories\WeatherRepository;
6use Laravel\Mcp\Request;
7use Laravel\Mcp\Response;
8use Laravel\Mcp\Server\Tool;
9 
10class CurrentWeatherTool extends Tool
11{
12 /**
13 * Handle the tool request.
14 */
15 public function handle(Request $request, WeatherRepository $weather): Response
16 {
17 $location = $request->get('location');
18 
19 $forecast = $weather->getForecastFor($location);
20 
21 // ...
22 }
23}

工具注解

您可以使用 注解 来增强工具,向 AI 客户端提供额外的元数据。这些注解有助于 AI 模型理解工具的行为和功能。注解通过属性 (attributes) 添加到工具中

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
6use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
7use Laravel\Mcp\Server\Tool;
8 
9#[IsIdempotent]
10#[IsReadOnly]
11class CurrentWeatherTool extends Tool
12{
13 //
14}

可用的注解包括

注解 类型 描述
#[IsReadOnly] boolean 表示该工具不会修改其环境。
#[IsDestructive] boolean 表示该工具可能会执行破坏性更新(仅在非只读时有意义)。
#[IsIdempotent] boolean 表示使用相同参数的重复调用没有额外影响(在非只读时)。
#[IsOpenWorld] boolean 表示该工具可能与外部实体交互。

注解值可以使用布尔参数显式设置

1use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
2use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
3use Laravel\Mcp\Server\Tools\Annotations\IsOpenWorld;
4use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
5use Laravel\Mcp\Server\Tool;
6 
7#[IsReadOnly(true)]
8#[IsDestructive(false)]
9#[IsOpenWorld(false)]
10#[IsIdempotent(true)]
11class CurrentWeatherTool extends Tool
12{
13 //
14}

条件性工具注册

您可以通过在工具类中实现 shouldRegister 方法来在运行时有条件地注册工具。此方法允许您根据应用程序状态、配置或请求参数来确定工具是否可用

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Server\Tool;
7 
8class CurrentWeatherTool extends Tool
9{
10 /**
11 * Determine if the tool should be registered.
12 */
13 public function shouldRegister(Request $request): bool
14 {
15 return $request?->user()?->subscribed() ?? false;
16 }
17}

当工具的 shouldRegister 方法返回 false 时,它将不会出现在可用工具列表中,也无法被 AI 客户端调用。

工具响应

工具必须返回 Laravel\Mcp\Response 的实例。Response 类提供了多种便捷方法来创建不同类型的响应

对于简单的文本响应,请使用 text 方法

1use Laravel\Mcp\Request;
2use Laravel\Mcp\Response;
3 
4/**
5 * Handle the tool request.
6 */
7public function handle(Request $request): Response
8{
9 // ...
10 
11 return Response::text('Weather Summary: Sunny, 72°F');
12}

要指示工具执行期间发生错误,请使用 error 方法

1return Response::error('Unable to fetch weather data. Please try again.');

要返回图像或音频内容,请使用 imageaudio 方法

1return Response::image(file_get_contents(storage_path('weather/radar.png')), 'image/png');
2 
3return Response::audio(file_get_contents(storage_path('weather/alert.mp3')), 'audio/mp3');

您也可以使用 fromStorage 方法直接从 Laravel 文件系统磁盘加载图像和音频内容。MIME 类型将自动从文件中检测

1return Response::fromStorage('weather/radar.png');

如果需要,您可以指定特定的磁盘或覆盖 MIME 类型

1return Response::fromStorage('weather/radar.png', disk: 's3');
2 
3return Response::fromStorage('weather/radar.png', mimeType: 'image/webp');

多内容响应

工具可以通过返回 Response 实例数组来返回多个内容片段

1use Laravel\Mcp\Request;
2use Laravel\Mcp\Response;
3 
4/**
5 * Handle the tool request.
6 *
7 * @return array<int, \Laravel\Mcp\Response>
8 */
9public function handle(Request $request): array
10{
11 // ...
12 
13 return [
14 Response::text('Weather Summary: Sunny, 72°F'),
15 Response::text('**Detailed Forecast**\n- Morning: 65°F\n- Afternoon: 78°F\n- Evening: 70°F')
16 ];
17}

结构化响应

工具可以使用 structured 方法返回 结构化内容。这为 AI 客户端提供了可解析的数据,同时保持了与 JSON 编码文本表示的向后兼容性

1return Response::structured([
2 'temperature' => 22.5,
3 'conditions' => 'Partly cloudy',
4 'humidity' => 65,
5]);

如果您需要在结构化内容之外提供自定义文本,请在响应工厂上使用 withStructuredContent 方法

1return Response::make(
2 Response::text('Weather is 22.5°C and sunny')
3)->withStructuredContent([
4 'temperature' => 22.5,
5 'conditions' => 'Sunny',
6]);

流式响应

对于长时间运行的操作或实时数据流,工具可以从其 handle 方法返回一个 生成器 (generator)。这使得在最终响应之前能够向客户端发送中间更新

1<?php
2 
3namespace App\Mcp\Tools;
4 
5use Generator;
6use Laravel\Mcp\Request;
7use Laravel\Mcp\Response;
8use Laravel\Mcp\Server\Tool;
9 
10class CurrentWeatherTool extends Tool
11{
12 /**
13 * Handle the tool request.
14 *
15 * @return \Generator<int, \Laravel\Mcp\Response>
16 */
17 public function handle(Request $request): Generator
18 {
19 $locations = $request->array('locations');
20 
21 foreach ($locations as $index => $location) {
22 yield Response::notification('processing/progress', [
23 'current' => $index + 1,
24 'total' => count($locations),
25 'location' => $location,
26 ]);
27 
28 yield Response::text($this->forecastFor($location));
29 }
30 }
31}

使用基于 Web 的服务器时,流式响应会自动打开 SSE (服务器发送事件) 流,将每个产生的消息作为事件发送给客户端。

Prompts

提示词 (Prompts) 使您的服务器能够共享可重用的提示词模板,AI 客户端可以使用这些模板与语言模型进行交互。它们提供了一种标准化方式来组织常见的查询和交互。

创建提示词 (Prompts)

要创建提示词,请运行 make:mcp-prompt Artisan 命令

1php artisan make:mcp-prompt DescribeWeatherPrompt

创建提示词后,将其注册到服务器的 $prompts 属性中

1<?php
2 
3namespace App\Mcp\Servers;
4 
5use App\Mcp\Prompts\DescribeWeatherPrompt;
6use Laravel\Mcp\Server;
7 
8class WeatherServer extends Server
9{
10 /**
11 * The prompts registered with this MCP server.
12 *
13 * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
14 */
15 protected array $prompts = [
16 DescribeWeatherPrompt::class,
17 ];
18}

提示词名称、标题和描述

默认情况下,提示词的名称和标题派生自类名。例如,DescribeWeatherPrompt 的名称将为 describe-weather,标题为 Describe Weather Prompt。您可以使用 NameTitle 属性自定义这些值

1use Laravel\Mcp\Server\Attributes\Name;
2use Laravel\Mcp\Server\Attributes\Title;
3 
4#[Name('weather-assistant')]
5#[Title('Weather Assistant Prompt')]
6class DescribeWeatherPrompt extends Prompt
7{
8 // ...
9}

提示词描述不会自动生成。您应该始终使用 Description 属性提供有意义的描述

1use Laravel\Mcp\Server\Attributes\Description;
2 
3#[Description('Generates a natural-language explanation of the weather for a given location.')]
4class DescribeWeatherPrompt extends Prompt
5{
6 //
7}

描述是提示词元数据的关键部分,因为它有助于 AI 模型理解何时以及如何最好地使用该提示词。

提示词参数

提示词可以定义参数,允许 AI 客户端使用特定值自定义提示词模板。使用 arguments 方法定义您的提示词接受哪些参数

1<?php
2 
3namespace App\Mcp\Prompts;
4 
5use Laravel\Mcp\Server\Prompt;
6use Laravel\Mcp\Server\Prompts\Argument;
7 
8class DescribeWeatherPrompt extends Prompt
9{
10 /**
11 * Get the prompt's arguments.
12 *
13 * @return array<int, \Laravel\Mcp\Server\Prompts\Argument>
14 */
15 public function arguments(): array
16 {
17 return [
18 new Argument(
19 name: 'tone',
20 description: 'The tone to use in the weather description (e.g., formal, casual, humorous).',
21 required: true,
22 ),
23 ];
24 }
25}

验证提示词参数

提示词参数会根据定义自动验证,但您可能还希望强制执行更复杂的验证规则。

Laravel MCP 与 Laravel 的 验证功能 无缝集成。您可以在提示词的 handle 方法中验证传入的提示词参数

1<?php
2 
3namespace App\Mcp\Prompts;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Response;
7use Laravel\Mcp\Server\Prompt;
8 
9class DescribeWeatherPrompt extends Prompt
10{
11 /**
12 * Handle the prompt request.
13 */
14 public function handle(Request $request): Response
15 {
16 $validated = $request->validate([
17 'tone' => 'required|string|max:50',
18 ]);
19 
20 $tone = $validated['tone'];
21 
22 // Generate the prompt response using the given tone...
23 }
24}

当验证失败时,AI 客户端将根据您提供的错误消息采取行动。因此,提供清晰且可操作的错误消息至关重要

1$validated = $request->validate([
2 'tone' => ['required','string','max:50'],
3],[
4 'tone.*' => 'You must specify a tone for the weather description. Examples include "formal", "casual", or "humorous".',
5]);

提示词依赖注入

Laravel 服务容器 用于解析所有提示词。因此,您可以在提示词的构造函数中对所需的任何依赖项进行类型提示。声明的依赖项将自动解析并注入到提示词实例中

1<?php
2 
3namespace App\Mcp\Prompts;
4 
5use App\Repositories\WeatherRepository;
6use Laravel\Mcp\Server\Prompt;
7 
8class DescribeWeatherPrompt extends Prompt
9{
10 /**
11 * Create a new prompt instance.
12 */
13 public function __construct(
14 protected WeatherRepository $weather,
15 ) {}
16 
17 //
18}

除了构造函数注入外,您还可以在提示词的 handle 方法中对依赖项进行类型提示。当调用该方法时,服务容器会自动解析并注入这些依赖项

1<?php
2 
3namespace App\Mcp\Prompts;
4 
5use App\Repositories\WeatherRepository;
6use Laravel\Mcp\Request;
7use Laravel\Mcp\Response;
8use Laravel\Mcp\Server\Prompt;
9 
10class DescribeWeatherPrompt extends Prompt
11{
12 /**
13 * Handle the prompt request.
14 */
15 public function handle(Request $request, WeatherRepository $weather): Response
16 {
17 $isAvailable = $weather->isServiceAvailable();
18 
19 // ...
20 }
21}

条件性提示词注册

您可以通过在提示词类中实现 shouldRegister 方法来在运行时有条件地注册提示词。此方法允许您根据应用程序状态、配置或请求参数来确定提示词是否可用

1<?php
2 
3namespace App\Mcp\Prompts;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Server\Prompt;
7 
8class CurrentWeatherPrompt extends Prompt
9{
10 /**
11 * Determine if the prompt should be registered.
12 */
13 public function shouldRegister(Request $request): bool
14 {
15 return $request?->user()?->subscribed() ?? false;
16 }
17}

当提示词的 shouldRegister 方法返回 false 时,它将不会出现在可用提示词列表中,也无法被 AI 客户端调用。

提示词响应

提示词可以返回单个 Laravel\Mcp\ResponseLaravel\Mcp\Response 实例的可迭代对象。这些响应封装了将发送给 AI 客户端的内容

1<?php
2 
3namespace App\Mcp\Prompts;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Response;
7use Laravel\Mcp\Server\Prompt;
8 
9class DescribeWeatherPrompt extends Prompt
10{
11 /**
12 * Handle the prompt request.
13 *
14 * @return array<int, \Laravel\Mcp\Response>
15 */
16 public function handle(Request $request): array
17 {
18 $tone = $request->string('tone');
19 
20 $systemMessage = "You are a helpful weather assistant. Please provide a weather description in a {$tone} tone.";
21 
22 $userMessage = "What is the current weather like in New York City?";
23 
24 return [
25 Response::text($systemMessage)->asAssistant(),
26 Response::text($userMessage),
27 ];
28 }
29}

您可以使用 asAssistant() 方法来指示响应消息应被视为来自 AI 助手,而普通消息则被视为用户输入。

资源

资源 (Resources) 使您的服务器能够公开数据和内容,AI 客户端在与语言模型交互时可以读取和使用这些内容作为上下文。它们提供了一种共享静态或动态信息的方式,例如文档、配置或任何有助于为 AI 响应提供信息的其他数据。

创建资源

要创建资源,请运行 make:mcp-resource Artisan 命令

1php artisan make:mcp-resource WeatherGuidelinesResource

创建资源后,将其注册到服务器的 $resources 属性中

1<?php
2 
3namespace App\Mcp\Servers;
4 
5use App\Mcp\Resources\WeatherGuidelinesResource;
6use Laravel\Mcp\Server;
7 
8class WeatherServer extends Server
9{
10 /**
11 * The resources registered with this MCP server.
12 *
13 * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
14 */
15 protected array $resources = [
16 WeatherGuidelinesResource::class,
17 ];
18}

资源名称、标题和描述

默认情况下,资源的名称和标题派生自类名。例如,WeatherGuidelinesResource 的名称将为 weather-guidelines,标题为 Weather Guidelines Resource。您可以使用 NameTitle 属性自定义这些值

1use Laravel\Mcp\Server\Attributes\Name;
2use Laravel\Mcp\Server\Attributes\Title;
3 
4#[Name('weather-api-docs')]
5#[Title('Weather API Documentation')]
6class WeatherGuidelinesResource extends Resource
7{
8 // ...
9}

资源描述不会自动生成。您应该始终使用 Description 属性提供有意义的描述

1use Laravel\Mcp\Server\Attributes\Description;
2 
3#[Description('Comprehensive guidelines for using the Weather API.')]
4class WeatherGuidelinesResource extends Resource
5{
6 //
7}

描述是资源元数据的关键部分,因为它有助于 AI 模型理解何时以及如何有效地使用该资源。

资源模板

资源模板 使您的服务器能够公开与带有变量的 URI 模式匹配的动态资源。您可以创建单个资源来处理基于模板模式的多个 URI,而不是为每个资源定义静态 URI。

创建资源模板

要创建资源模板,请在资源类上实现 HasUriTemplate 接口,并定义一个返回 UriTemplate 实例的 uriTemplate 方法

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Response;
7use Laravel\Mcp\Server\Attributes\Description;
8use Laravel\Mcp\Server\Attributes\MimeType;
9use Laravel\Mcp\Server\Contracts\HasUriTemplate;
10use Laravel\Mcp\Server\Resource;
11use Laravel\Mcp\Support\UriTemplate;
12 
13#[Description('Access user files by ID')]
14#[MimeType('text/plain')]
15class UserFileResource extends Resource implements HasUriTemplate
16{
17 /**
18 * Get the URI template for this resource.
19 */
20 public function uriTemplate(): UriTemplate
21 {
22 return new UriTemplate('file://users/{userId}/files/{fileId}');
23 }
24 
25 /**
26 * Handle the resource request.
27 */
28 public function handle(Request $request): Response
29 {
30 $userId = $request->get('userId');
31 $fileId = $request->get('fileId');
32 
33 // Fetch and return the file content...
34 
35 return Response::text($content);
36 }
37}

当资源实现了 HasUriTemplate 接口时,它将被注册为资源模板而不是静态资源。AI 客户端随后可以使用与模板模式匹配的 URI 请求资源,来自 URI 的变量将自动提取并可在资源的 handle 方法中使用。

URI 模板语法

URI 模板使用花括号括起来的占位符来定义 URI 中的变量段

1new UriTemplate('file://users/{userId}');
2new UriTemplate('file://users/{userId}/files/{fileId}');
3new UriTemplate('https://api.example.com/{version}/{resource}/{id}');

访问模板变量

当 URI 与您的资源模板匹配时,提取的变量将自动合并到请求中,可以使用 get 方法进行访问

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Response;
7use Laravel\Mcp\Server\Contracts\HasUriTemplate;
8use Laravel\Mcp\Server\Resource;
9use Laravel\Mcp\Support\UriTemplate;
10 
11class UserProfileResource extends Resource implements HasUriTemplate
12{
13 public function uriTemplate(): UriTemplate
14 {
15 return new UriTemplate('file://users/{userId}/profile');
16 }
17 
18 public function handle(Request $request): Response
19 {
20 // Access the extracted variable
21 $userId = $request->get('userId');
22 
23 // Access the full URI if needed
24 $uri = $request->uri();
25 
26 // Fetch user profile...
27 
28 return Response::text("Profile for user {$userId}");
29 }
30}

Request 对象提供了提取的变量和请求的原始 URI,为您处理资源请求提供了完整的上下文。

资源 URI 和 MIME 类型

每个资源由唯一的 URI 标识,并具有关联的 MIME 类型,帮助 AI 客户端理解资源的格式。

默认情况下,资源的 URI 是根据资源名称生成的,因此 WeatherGuidelinesResource 的 URI 为 weather://resources/weather-guidelines。默认 MIME 类型为 text/plain

您可以使用 UriMimeType 属性自定义这些值

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use Laravel\Mcp\Server\Attributes\MimeType;
6use Laravel\Mcp\Server\Attributes\Uri;
7use Laravel\Mcp\Server\Resource;
8 
9#[Uri('weather://resources/guidelines')]
10#[MimeType('application/pdf')]
11class WeatherGuidelinesResource extends Resource
12{
13}

URI 和 MIME 类型有助于 AI 客户端确定如何适当处理和解释资源内容。

资源请求

与工具和提示词不同,资源不能定义输入架构或参数。但是,您仍然可以在资源的 handle 方法中与请求对象进行交互

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Response;
7use Laravel\Mcp\Server\Resource;
8 
9class WeatherGuidelinesResource extends Resource
10{
11 /**
12 * Handle the resource request.
13 */
14 public function handle(Request $request): Response
15 {
16 // ...
17 }
18}

资源依赖注入

Laravel 服务容器 用于解析所有资源。因此,您可以在资源的构造函数中对所需的任何依赖项进行类型提示。声明的依赖项将自动解析并注入到资源实例中

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use App\Repositories\WeatherRepository;
6use Laravel\Mcp\Server\Resource;
7 
8class WeatherGuidelinesResource extends Resource
9{
10 /**
11 * Create a new resource instance.
12 */
13 public function __construct(
14 protected WeatherRepository $weather,
15 ) {}
16 
17 // ...
18}

除了构造函数注入外,您还可以在资源的 handle 方法中对依赖项进行类型提示。当调用该方法时,服务容器会自动解析并注入这些依赖项

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use App\Repositories\WeatherRepository;
6use Laravel\Mcp\Request;
7use Laravel\Mcp\Response;
8use Laravel\Mcp\Server\Resource;
9 
10class WeatherGuidelinesResource extends Resource
11{
12 /**
13 * Handle the resource request.
14 */
15 public function handle(WeatherRepository $weather): Response
16 {
17 $guidelines = $weather->guidelines();
18 
19 return Response::text($guidelines);
20 }
21}

资源注解

您可以使用 注解 来增强资源,向 AI 客户端提供额外的元数据。注解通过属性添加到资源中

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use Laravel\Mcp\Enums\Role;
6use Laravel\Mcp\Server\Annotations\Audience;
7use Laravel\Mcp\Server\Annotations\LastModified;
8use Laravel\Mcp\Server\Annotations\Priority;
9use Laravel\Mcp\Server\Resource;
10 
11#[Audience(Role::User)]
12#[LastModified('2025-01-12T15:00:58Z')]
13#[Priority(0.9)]
14class UserDashboardResource extends Resource
15{
16 //
17}

可用的注解包括

注解 类型 描述
#[Audience] 角色或数组 指定预期的受众(Role::UserRole::Assistant 或两者兼有)。
#[Priority] 浮点数 0.0 到 1.0 之间的数值分数,表示资源的重要性。
#[LastModified] string 一个 ISO 8601 时间戳,显示资源上次更新的时间。

条件性资源注册

您可以通过在资源类中实现 shouldRegister 方法来在运行时有条件地注册资源。此方法允许您根据应用程序状态、配置或请求参数来确定资源是否可用

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use Laravel\Mcp\Request;
6use Laravel\Mcp\Server\Resource;
7 
8class WeatherGuidelinesResource extends Resource
9{
10 /**
11 * Determine if the resource should be registered.
12 */
13 public function shouldRegister(Request $request): bool
14 {
15 return $request?->user()?->subscribed() ?? false;
16 }
17}

当资源的 shouldRegister 方法返回 false 时,它将不会出现在可用资源列表中,也无法被 AI 客户端访问。

资源响应

资源必须返回 Laravel\Mcp\Response 的实例。Response 类提供了多种便捷方法来创建不同类型的响应

对于简单的文本内容,请使用 text 方法

1use Laravel\Mcp\Request;
2use Laravel\Mcp\Response;
3 
4/**
5 * Handle the resource request.
6 */
7public function handle(Request $request): Response
8{
9 // ...
10 
11 return Response::text($weatherData);
12}

Blob 响应

要返回二进制大对象 (blob) 内容,请使用 blob 方法,并提供 blob 内容

1return Response::blob(file_get_contents(storage_path('weather/radar.png')));

返回 blob 内容时,MIME 类型将由您资源配置的 MIME 类型决定

1<?php
2 
3namespace App\Mcp\Resources;
4 
5use Laravel\Mcp\Server\Attributes\MimeType;
6use Laravel\Mcp\Server\Resource;
7 
8#[MimeType('image/png')]
9class WeatherGuidelinesResource extends Resource
10{
11 //
12}

错误响应

要指示资源检索期间发生错误,请使用 error() 方法

1return Response::error('Unable to fetch weather data for the specified location.');

元数据 (Metadata)

Laravel MCP 还支持 MCP 规范 中定义的 _meta 字段,这是某些 MCP 客户端或集成所必需的。元数据可以应用于所有 MCP 原语,包括工具、资源和提示词,以及它们的响应。

您可以使用 withMeta 方法将元数据附加到单独的响应内容

1use Laravel\Mcp\Request;
2use Laravel\Mcp\Response;
3 
4/**
5 * Handle the tool request.
6 */
7public function handle(Request $request): Response
8{
9 return Response::text('The weather is sunny.')
10 ->withMeta(['source' => 'weather-api', 'cached' => true]);
11}

对于适用于整个响应信封的结果级元数据,请使用 Response::make 包装您的响应,并在返回的响应工厂实例上调用 withMeta

1use Laravel\Mcp\Request;
2use Laravel\Mcp\Response;
3use Laravel\Mcp\ResponseFactory;
4 
5/**
6 * Handle the tool request.
7 */
8public function handle(Request $request): ResponseFactory
9{
10 return Response::make(
11 Response::text('The weather is sunny.')
12 )->withMeta(['request_id' => '12345']);
13}

要将元数据附加到工具、资源或提示词本身,请在类上定义一个 $meta 属性

1use Laravel\Mcp\Server\Attributes\Description;
2use Laravel\Mcp\Server\Tool;
3 
4#[Description('Fetches the current weather forecast.')]
5class CurrentWeatherTool extends Tool
6{
7 protected ?array $meta = [
8 'version' => '2.0',
9 'author' => 'Weather Team',
10 ];
11 
12 // ...
13}

认证

就像路由一样,您可以使用中间件验证 Web 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 发现和客户端注册路由。然后,将 Passport 的 auth:api 中间件应用到 routes/ai.php 文件中的 Mcp::web 路由

1use App\Mcp\Servers\WeatherExample;
2use Laravel\Mcp\Facades\Mcp;
3 
4Mcp::oauthRoutes();
5 
6Mcp::web('/mcp/weather', WeatherExample::class)
7 ->middleware('auth:api');

全新安装 Passport

如果您的应用程序尚未使用 Laravel Passport,请遵循 Passport 的 安装和部署指南 将其添加到您的应用程序中。在继续之前,您应该拥有一个 OAuthenticatable 模型、新的身份验证守卫 (guard) 和 Passport 密钥。

接下来,您应该发布 Laravel MCP 提供的 Passport 授权视图

1php artisan vendor:publish --tag=mcp-views

然后,使用 Passport::authorizationView 方法指示 Passport 使用此视图。通常,此方法应在应用程序 AppServiceProviderboot 方法中调用

1use Laravel\Passport\Passport;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Passport::authorizationView(function ($parameters) {
9 return view('mcp.authorize', $parameters);
10 });
11}

此视图将在身份验证期间显示给最终用户,以拒绝或批准 AI 智能体的身份验证尝试。

Authorization screen example

在这种情况下,我们只是将 OAuth 用作到底层可验证模型的转换层。我们忽略了 OAuth 的许多方面,例如作用域 (scopes)。

使用现有的 Passport 安装

如果您的应用程序已经在使用 Laravel Passport,Laravel MCP 应该可以在您现有的 Passport 安装中无缝运行,但目前不支持自定义作用域,因为 OAuth 主要用作到底层可验证模型的转换层。

Laravel MCP 通过上述讨论的 Mcp::oauthRoutes 方法,添加、声明并使用单个 mcp:use 作用域。

Passport 与 Sanctum

OAuth2.1 是模型上下文协议规范中记录的身份验证机制,也是 MCP 客户端中支持最广泛的机制。因此,我们建议尽可能使用 Passport。

如果您的应用程序已经在使用 Sanctum,那么添加 Passport 可能会很繁琐。在这种情况下,我们建议在您有明确、必要的需求去使用仅支持 OAuth 的 MCP 客户端之前,先使用 Sanctum 而不使用 Passport。

Sanctum

如果您想使用 Sanctum 保护您的 MCP 服务器,只需在 routes/ai.php 文件中将 Sanctum 的身份验证中间件添加到您的服务器即可。然后,确保您的 MCP 客户端提供 Authorization: Bearer <token> 头以确保身份验证成功

1use App\Mcp\Servers\WeatherExample;
2use Laravel\Mcp\Facades\Mcp;
3 
4Mcp::web('/mcp/demo', WeatherExample::class)
5 ->middleware('auth:sanctum');

自定义 MCP 身份验证

如果您的应用程序发行了自己的自定义 API 令牌,您可以通过为 Mcp::web 路由分配您想要的任何中间件来验证您的 MCP 服务器。您的自定义中间件可以手动检查 Authorization 头,以验证传入的 MCP 请求。

授权

您可以通过 $request->user() 方法访问当前经过身份验证的用户,从而允许您在 MCP 工具和资源中执行 授权检查

1use Laravel\Mcp\Request;
2use Laravel\Mcp\Response;
3 
4/**
5 * Handle the tool request.
6 */
7public function handle(Request $request): Response
8{
9 if (! $request->user()->can('read-weather')) {
10 return Response::error('Permission denied.');
11 }
12 
13 // ...
14}

测试服务器

您可以使用内置的 MCP Inspector 或通过编写单元测试来测试您的 MCP 服务器。

MCP Inspector

MCP Inspector 是用于测试和调试 MCP 服务器的交互式工具。使用它连接到您的服务器、验证身份验证并试用工具、资源和提示词。

您可以为任何已注册的服务器运行 Inspector

1# Web server...
2php artisan mcp:inspector mcp/weather
3 
4# Local server named "weather"...
5php artisan mcp:inspector weather

此命令会启动 MCP Inspector 并提供您可以复制到 MCP 客户端的客户端设置,以确保一切配置正确。如果您的 Web 服务器受身份验证中间件保护,请确保在连接时包含必要的头,例如 Authorization 持有者令牌。

单元测试

您可以为您的 MCP 服务器、工具、资源和提示词编写单元测试。

要开始使用,请创建一个新的测试用例,并在注册它的服务器上调用所需的基元。例如,要测试 WeatherServer 上的一个工具

1test('tool', function () {
2 $response = WeatherServer::tool(CurrentWeatherTool::class, [
3 'location' => 'New York City',
4 'units' => 'fahrenheit',
5 ]);
6 
7 $response
8 ->assertOk()
9 ->assertSee('The current weather in New York City is 72°F and sunny.');
10});
1/**
2 * Test a tool.
3 */
4public function test_tool(): void
5{
6 $response = WeatherServer::tool(CurrentWeatherTool::class, [
7 'location' => 'New York City',
8 'units' => 'fahrenheit',
9 ]);
10 
11 $response
12 ->assertOk()
13 ->assertSee('The current weather in New York City is 72°F and sunny.');
14}

同样,您可以测试提示词和资源

1$response = WeatherServer::prompt(...);
2$response = WeatherServer::resource(...);

您还可以通过在调用基元之前链接 actingAs 方法,以经过身份验证的用户身份进行操作

1$response = WeatherServer::actingAs($user)->tool(...);

收到响应后,您可以使用各种断言方法来验证响应的内容和状态。

您可以使用 assertOk 方法断言响应成功。这会检查响应是否没有任何错误

1$response->assertOk();

您可以使用 assertSee 方法断言响应包含特定文本

1$response->assertSee('The current weather in New York City is 72°F and sunny.');

您可以使用 assertHasErrors 方法断言响应包含错误

1$response->assertHasErrors();
2 
3$response->assertHasErrors([
4 'Something went wrong.',
5]);

您可以使用 assertHasNoErrors 方法断言响应不包含错误

1$response->assertHasNoErrors();

您可以使用 assertName()assertTitle()assertDescription() 方法断言响应包含特定元数据

1$response->assertName('current-weather');
2$response->assertTitle('Current Weather Tool');
3$response->assertDescription('Fetches the current weather forecast for a specified location.');

您可以使用 assertSentNotificationassertNotificationCount 方法断言已发送通知

1$response->assertSentNotification('processing/progress', [
2 'step' => 1,
3 'total' => 5,
4]);
5 
6$response->assertSentNotification('processing/progress', [
7 'step' => 2,
8 'total' => 5,
9]);
10 
11$response->assertNotificationCount(5);

最后,如果您想检查原始响应内容,可以使用 dddump 方法输出响应以进行调试

1$response->dd();
2$response->dump();