跳转到主要内容

什么是 MCP

Model Context Protocol(MCP) 是一个规范,让 AI 客户端(Claude、Cursor、GitHub Copilot 等)与应用之间以标准化协议通信。通过实现 MCP 服务器,AI 代理就可以访问你 Laravel 应用中的数据,或者执行相应的操作。
Laravel MCP 是在 Laravel 13 中新增的官方扩展包。它以 laravel/mcp 形式提供,覆盖构建 MCP 服务器所需的一整套能力。
MCP 服务器主要提供三类能力:
能力说明
工具(Tools)AI 客户端可调用的函数。用于搜索、更新、对接外部 API 等
资源(Resources)AI 客户端可读取的数据或上下文信息
提示(Prompts)可复用的提示模板

安装

通过 Composer 安装扩展包。
composer require laravel/mcp
安装后运行 vendor:publish Artisan 命令来生成 routes/ai.php 文件。
php artisan vendor:publish --tag=ai-routes
该命令会创建 routes/ai.php,你可以在里面注册 MCP 服务器。

创建服务器

make:mcp-server Artisan 命令生成服务器类。
php artisan make:mcp-server WeatherServer
服务器类会生成到 app/Mcp/Servers 目录下。
<?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
{
    protected array $tools = [
        // GetCurrentWeatherTool::class,
    ];

    protected array $resources = [
        // WeatherGuidelinesResource::class,
    ];

    protected array $prompts = [
        // DescribeWeatherPrompt::class,
    ];
}

注册服务器

创建服务器后,在 routes/ai.php 中注册。有 Web 服务器本地服务器 两种注册方式。

Web 服务器

Web 服务器通过 HTTP POST 请求访问,适合远程 AI 客户端或基于 Web 的集成。
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/weather', WeatherServer::class);
可以像普通路由一样应用中间件。
Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware(['throttle:mcp']);

本地服务器

本地服务器以 Artisan 命令的形式运行,用于与 Claude Desktop 等本地 AI 客户端对接。
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::local('weather', WeatherServer::class);
本地服务器通常由 MCP 客户端自动启动。无需手动执行 mcp:start Artisan 命令。

工具

工具是 AI 客户端可以调用的函数,用来获取数据、对接外部 API、操作数据库等。

创建工具

make:mcp-tool Artisan 命令生成工具类。
php artisan make:mcp-tool CurrentWeatherTool
将工具注册到服务器的 $tools 属性中。
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    protected array $tools = [
        CurrentWeatherTool::class,
    ];
}
一个基础的工具类示例:
<?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 sunny, 22°C.');
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'location' => $schema->string()
                ->description('The location to get the weather for.')
                ->required(),
        ];
    }
}

工具的名称与说明

默认的名称与标题会根据类名自动推断。CurrentWeatherTool 的名称是 current-weather,标题是 Current Weather Tool。可通过 NameTitle 属性自定义。
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)不会自动生成。它对 AI 模型理解如何调用工具至关重要,请务必写清晰、有意义的说明。

输入 Schema

schema 方法用来定义输入参数的 schema。可以使用 Laravel 提供的 JSON schema builder 指定类型与约束。
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'),
    ];
}

输出 Schema

outputSchema 方法可以定义响应结构,帮助 AI 客户端更好地解析响应。
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(),
    ];
}

校验

handle 方法内可以使用 Laravel 的标准校验能力。
public function handle(Request $request): Response
{
    $validated = $request->validate([
        'location' => 'required|string|max:100',
        'units' => 'in:celsius,fahrenheit',
    ], [
        'location.required' => 'You must specify a location. For example, "New York City" or "Tokyo".',
        'units.in' => 'You must specify either "celsius" or "fahrenheit" for the units.',
    ]);

    // 使用校验后的数据继续处理...
}
校验失败时,AI 客户端会根据错误信息重试。请提供具体、可执行的错误消息。

依赖注入

工具通过 Laravel 服务容器解析,因此可以在构造函数或 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 __construct(
        protected WeatherRepository $weather,
    ) {}

    public function handle(Request $request, WeatherRepository $weather): Response
    {
        $location = $request->get('location');
        $forecast = $weather->getForecastFor($location);

        return Response::text("Forecast: {$forecast}");
    }
}

注解

给工具加上注解,可以向 AI 客户端提供关于工具行为的额外提示。
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]表示工具不会修改环境
#[IsDestructive]表示工具可能执行破坏性更新
#[IsIdempotent]表示以相同参数重复调用不会产生副作用
#[IsOpenWorld]表示工具可能会与外部实体交互

条件注册

通过实现 shouldRegister 方法可以在运行时按条件注册工具。
public function shouldRegister(Request $request): bool
{
    return $request?->user()?->subscribed() ?? false;
}
返回 false 时该工具对 AI 客户端不可见。

响应

工具需要返回一个 Laravel\Mcp\Response 实例。
return Response::text('Weather Summary: Sunny, 22°C');
return Response::error('Unable to fetch weather data. Please try again.');
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');

// 直接从 storage 读取(MIME 类型自动识别)
return Response::fromStorage('weather/radar.png');
public function handle(Request $request): array
{
    return [
        Response::text('Weather Summary: Sunny, 22°C'),
        Response::text("**Detailed Forecast**\n- Morning: 18°C\n- Afternoon: 25°C"),
    ];
}
返回便于 AI 客户端解析的结构化数据。
return Response::structured([
    'temperature' => 22.5,
    'conditions' => 'Partly cloudy',
    'humidity' => 65,
]);
长耗时处理中实时推送进度。
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));
    }
}

提示(Prompts)

提示是可复用的提示模板,可以为 AI 客户端与语言模型的交互提供标准化的常用查询。

创建提示

php artisan make:mcp-prompt DescribeWeatherPrompt
将其注册到服务器的 $prompts 属性中。
use App\Mcp\Prompts\DescribeWeatherPrompt;

class WeatherServer extends Server
{
    protected array $prompts = [
        DescribeWeatherPrompt::class,
    ];
}

提示参数

arguments 方法用来定义提示的参数。
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Prompts\Argument;

class DescribeWeatherPrompt extends Prompt
{
    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'];

        // 使用指定的 tone 生成提示...
    }
}
校验失败时 AI 客户端会参考错误消息重试。请提供具体、可执行的消息。
$validated = $request->validate([
    'tone' => ['required', 'string', 'max:50'],
], [
    'tone.*' => '请指定 tone,例如 "formal"、"casual"、"humorous"。',
]);

依赖注入

提示通过 Laravel 服务容器解析,构造函数与 handle 方法都可以类型提示注入依赖。
<?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 方法同样支持依赖注入。
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;
    }
}
返回 false 时提示对 AI 客户端不可见,也无法调用。

提示响应

提示的 handle 方法可以返回用户消息与助手消息,通过 asAssistant() 标记为助手侧消息。
<?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): 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 Tokyo?';

        return [
            Response::text($systemMessage)->asAssistant(),
            Response::text($userMessage),
        ];
    }
}

资源

资源是 AI 客户端可以作为上下文读取的数据或信息。可以提供文档、配置、动态数据等,帮助提升 AI 回答的质量。

创建资源

php artisan make:mcp-resource WeatherGuidelinesResource
注册到服务器的 $resources 属性中。
use App\Mcp\Resources\WeatherGuidelinesResource;

class WeatherServer extends Server
{
    protected array $resources = [
        WeatherGuidelinesResource::class,
    ];
}
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Resource;

#[Description('Comprehensive guidelines for using the Weather API.')]
class WeatherGuidelinesResource extends Resource
{
    public function handle(Request $request): Response
    {
        $guidelines = "# Weather API Guidelines\n\n- Always specify a location...";

        return Response::text($guidelines);
    }
}

URI 与 MIME 类型

默认会根据类名生成 URI(例如 weather://resources/weather-guidelines)。可通过 UriMimeType 属性自定义。
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 变量的动态资源,可实现 HasUriTemplate 接口。
<?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
{
    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("File {$fileId} for user {$userId}");
    }
}
URI 中的变量会自动写入请求中,可以通过 get 方法取出。

资源请求

与工具或提示不同,资源不能定义输入 schema 或参数。但在 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 服务容器解析,构造函数或 handle 方法都可以类型提示。
<?php

namespace App\Mcp\Resources;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Resource;

class WeatherGuidelinesResource extends Resource
{
    public function __construct(
        protected WeatherRepository $weather,
    ) {}
}
public function handle(WeatherRepository $weather): Response
{
    return Response::text($weather->guidelines());
}

资源注解

资源可以带上受众、优先级、最后修改时间等注解。
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::UserRole::Assistant 或两者)
#[Priority]float重要度评分(0.0~1.0)
#[LastModified]stringISO 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;
    }
}
返回 false 时资源对 AI 客户端不可见,也无法访问。

资源响应

资源同样需要返回 Laravel\Mcp\Response 实例。 文本内容用 text 方法。
return Response::text($weatherData);

资源链接响应

resourceLink 方法可以返回一个资源链接。与嵌入式资源不同,它返回的是 AI 客户端可独立获取的 URI 指针。
return Response::resourceLink(
    uri: 'file:///data/report.json',
    name: 'monthly-report',
    mimeType: 'application/json',
);
也可以传入已注册的资源类或实例,会自动继承其 URI、name、title、description、MIME 类型。
return Response::resourceLink(new WeatherForecastResource);

Blob 响应

返回二进制内容用 blob 方法。MIME 类型通过资源的 #[MimeType] 属性设置。
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
    // ...
}

错误响应

要返回错误,使用 error 方法。
return Response::error('无法获取指定地点的天气数据。');

应用(Apps)

Laravel MCP 支持 MCP Apps。这是 Model Context Protocol 的一个扩展,允许工具在受支持的宿主中,通过沙箱 iframe 渲染交互式的 HTML 应用。你可以基于此构建仪表盘、表单、可视化和其他富交互体验,而不局限于纯文本响应。 MCP 应用由两个协作的部分组成:
  • App 资源 — 返回应用的独立 HTML。
  • 工具 — 通过 #[RendersApp] 属性关联到 App 资源。工具被调用时,宿主会拉取并渲染关联的资源。

创建 App 资源

make:mcp-app-resource Artisan 命令生成 App 资源。
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
{
    /**
     * Handle the app resource request.
     */
    public function handle(Request $request): Response
    {
        return Response::view('mcp.weather-dashboard-app', [
            'title' => $this->title(),
        ]);
    }
}
AppResource 继承自基础的 Resource 类,并会自动设置 MCP Apps 规范要求的 ui:// URI scheme 与 text/html;profile=mcp-app MIME 类型。与其他资源一样,也需要注册到服务器的 $resources 数组中。 生成的 Blade 视图使用 <x-mcp::app> 组件。它会渲染一个包含客户端 MCP SDK 的完整 HTML 文档。
<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 与服务器的连接、宿主主题应用,以及 callServerToolsendMessageopenLink 等辅助函数与事件回调。完整的客户端 API 请参考 MCP Apps 规范

通过工具渲染应用

要展示 App 资源,需要在工具上使用 #[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
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        return Response::text('Weather dashboard loaded.');
    }
}
AppResource 注册后,Laravel MCP 会自动通告 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 枚举有 ModelApp 两个成员,默认二者都启用。给 UI 直接调用的后端 action 用 [Visibility::App];如果不希望 UI 调用工具,就用 [Visibility::Model]

应用的配置

App 资源上的 #[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 枚举预置了 Library::TailwindLibrary::Alpine 等常用前端库的 CDN 脚本,其 CDN 源会自动合并到 CSP 中。Permission 枚举覆盖 CameraMicrophoneGeolocationClipboardWrite 等浏览器权限。
如果需要动态配置,可以重写资源的 appMeta 方法,使用 Laravel\Mcp\Server\Ui 命名空间下的 AppMetaCspPermissions 流式构建器。

使用 Boost 开发应用

Laravel MCP 提供了专门用于构建 MCP Apps 的 Boost skill 参考。安装 Laravel Boost 后,AI 编码代理可以调用 mcp-development skill,自动生成 App 资源、Blade 视图以及关联的工具。 关于协议的完整参考(包括客户端 API 与 schema 细节),请查看官方的 MCP Apps 文档

元数据

可以给工具、资源、提示的响应附加 MCP 规范中的 _meta 字段。
// 给响应内容附加元数据
return Response::text('The weather is sunny.')
    ->withMeta(['source' => 'weather-api', 'cached' => true]);
要为整个响应外壳附加元数据,使用 Response::make
return Response::make(
    Response::text('The weather is sunny.')
)->withMeta(['request_id' => '12345']);
若要给工具、资源、提示的类本身附加元数据,定义 $meta 属性即可。
class CurrentWeatherTool extends Tool
{
    protected ?array $meta = [
        'version' => '2.0',
        'author' => 'Weather Team',
    ];
}

图标

MCP 客户端可以显示服务器及其原语的图标。可以通过 Icon 属性为 Server、Tool、Resource、Prompt 声明图标。
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 scheme 的路径原样使用。
  • 相对路径通过 Laravel 的 asset helper 解析成 URL。

认证

Web 服务器可以使用 Laravel 标准的中间件来认证。

Sanctum

使用 Laravel Sanctum 的 token 认证。MCP 客户端会发送 Authorization: Bearer <token> 头。
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware('auth:sanctum');

OAuth 2.1

使用 Laravel Passport 的 OAuth 认证,适合需要更强安全性的场景。
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware('auth:api');
使用 OAuth 时,需要发布 Passport 的授权视图并在服务提供者中配置。
php artisan vendor:publish --tag=mcp-views
// AppServiceProvider::boot()
use Laravel\Passport\Passport;

Passport::authorizationView(function ($parameters) {
    return view('mcp.authorize', $parameters);
});

授权

通过 $request->user() 获取当前已认证用户,就可以在工具或资源中进行授权检查。
public function handle(Request $request): Response
{
    if (! $request->user()->can('read-weather')) {
        return Response::error('Permission denied.');
    }

    // 继续处理...
}

MCP 客户端

Laravel MCP 不只能构建服务器,也提供了连接其他 MCP 服务器的客户端。借助客户端,你可以发现并调用外部 MCP 服务器暴露的工具,特别适合把外部 MCP 服务器的能力提供给 AI 代理

连接到服务器

对于可以通过 HTTP 访问的 MCP 服务器,使用 Client::web 方法并传入服务器 URL。
use Laravel\Mcp\Client;

$client = Client::web('https://mcp.example.com');
对于作为命令启动的本地 MCP 服务器,使用 Client::local 方法并传入命令和参数。
use Laravel\Mcp\Client;

$client = Client::local('php', ['artisan', 'mcp:start']);
客户端采用延迟连接(lazy connect),在首次获取或调用工具时自动建立连接。若要手动管理连接,可以使用 connectconnectedpingdisconnect 方法。
$client->connect();

$client->ping();

if ($client->connected()) {
    // ...
}

$client->disconnect();
withTimeout 方法可以自定义请求超时。
$client = Client::web('https://mcp.example.com')->withTimeout(30);

命名客户端

除了每次都构建客户端,也可以注册可复用的命名客户端。通常在服务提供者的 boot 方法中通过 Mcp Facade 完成。
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;

Mcp::registerClient('github', fn () => Client::web('https://mcp.example.com'));
注册后可以按名字解析客户端。
use Laravel\Mcp\Facades\Mcp;

$client = Mcp::client('github');
命名客户端在每个请求内只会被解析一次,请求生命周期结束时自动断开。

客户端认证

访问受 Bearer token 保护的 Web MCP 服务器,可以使用 withToken。它接收 token 字符串或延迟解析的闭包。
use Illuminate\Support\Facades\Auth;
use Laravel\Mcp\Client;

$client = Client::web('https://mcp.example.com')->withToken($token);

$client = Client::web('https://mcp.example.com')->withToken(
    fn () => Auth::user()->mcpToken(),
);
访问受 OAuth 2.1 保护的服务器,使用 withOAuth
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;

Mcp::registerClient('github', fn () => Client::web('https://mcp.example.com')->withOAuth(
    clientId: config('services.github_mcp.client_id'),
    clientSecret: config('services.github_mcp.client_secret'),
));
如果 MCP 服务器支持动态客户端注册,可以省略 clientIdclientSecret,客户端会自动注册。
然后在 routes/ai.php 中使用 oAuthRoutesFor 方法为命名客户端注册 OAuth 路由。传入的闭包会在授权码交换完 access token 后接收客户端名与 TokenSet
use Illuminate\Support\Facades\Auth;
use Laravel\Mcp\Client\OAuth\TokenSet;
use Laravel\Mcp\Facades\Mcp;

Mcp::oAuthRoutesFor('github', function (string $client, TokenSet $token) {
    Auth::user()->update([
        'github_mcp_token' => $token->accessToken,
    ]);

    return redirect('/dashboard');
});
这样会注册两条命名路由:将用户重定向到授权服务器的 connect 路由(mcp.oauth.{client}.connect),以及交换授权码并调用处理器的 callback 路由(mcp.oauth.{client}.callback)。二者默认使用 web 中间件组(可通过 middleware 参数覆盖)。 开始授权流程时,把用户重定向到 connect 路由即可。
return redirect()->route('mcp.oauth.github.connect');

工具

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('current-weather', [
    'location' => 'New York',
]);

$result->text();             // 响应的文本内容
(string) $result;            // 等同于 text()
$result->isError;            // 工具是否报告了错误
$result->structuredContent;  // 结构化内容(如果有)
也可以直接从列表中获取的工具实例上调用。
$tools = Mcp::client('github')->tools();

$result = $tools['current-weather']->call([
    'location' => 'New York',
]);
如果你在使用 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('describe-weather', [
    'location' => 'New York',
]);

$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('weather://guidelines');

$result->content();   // 资源内容(base64 的 blob 会自动解码)
(string) $result;     // 等同于 content()
$result->mimeType();  // 资源的 MIME 类型(如有)
$result->contents;    // 资源返回的原始内容

测试

MCP Inspector

用交互式调试工具 “MCP Inspector” 可以验证 MCP 服务器的行为。
# Web 服务器
php artisan mcp:inspector mcp/weather

# 本地服务器(name 为 "weather" 时)
php artisan mcp:inspector weather
执行命令会启动 MCP Inspector,并允许复制客户端配置。若配置了认证中间件,请连接时带上 Authorization 头。

单元测试

你可以为工具、资源、提示编写单元测试。
test('tool', function () {
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'Tokyo',
        'units' => 'celsius',
    ]);

    $response
        ->assertOk()
        ->assertSee('The current weather in Tokyo is 22°C and sunny.');
});
public function test_tool(): void
{
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'Tokyo',
        'units' => 'celsius',
    ]);

    $response
        ->assertOk()
        ->assertSee('The current weather in Tokyo is 22°C and sunny.');
}
提示和资源同样可以测试。
$response = WeatherServer::prompt(DescribeWeatherPrompt::class, ['tone' => 'casual']);
$response = WeatherServer::resource(WeatherGuidelinesResource::class);
要以已认证用户的身份执行,使用 actingAs
$response = WeatherServer::actingAs($user)->tool(CurrentWeatherTool::class, [...]);
主要断言方法如下:
$response->assertOk();           // 断言没有错误
$response->assertSee('...');     // 断言包含特定文本
判断是否有错误,使用 assertHasErrors / assertHasNoErrors
$response->assertHasErrors();

$response->assertHasErrors([
    'Something went wrong.',
]);

$response->assertHasNoErrors();
也可以校验工具 / 资源 / 提示的名称、标题、说明。
$response->assertName('current-weather');
$response->assertTitle('Current Weather Tool');
$response->assertDescription('Fetches the current weather forecast for a specified location.');
校验流式响应的通知,使用 assertSentNotificationassertNotificationCount
$response->assertSentNotification('processing/progress', [
    'step' => 1,
    'total' => 5,
]);

$response->assertSentNotification('processing/progress', [
    'step' => 2,
    'total' => 5,
]);

$response->assertNotificationCount(5);
调试响应内容时使用 dddump
$response->dd();
$response->dump();
最后修改于 2026年7月13日