> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel MCP

> 了解如何在 Laravel 应用中嵌入 Model Context Protocol (MCP) 服务器，为 AI 编码代理定义工具、资源与提示，让它们与你的应用对话。

## 什么是 MCP

**Model Context Protocol（MCP）** 是一个规范，让 AI 客户端（Claude、Cursor、GitHub Copilot 等）与应用之间以标准化协议通信。通过实现 MCP 服务器，AI 代理就可以访问你 Laravel 应用中的数据，或者执行相应的操作。

<Info>
  Laravel MCP 是在 Laravel 13 中新增的官方扩展包。它以 `laravel/mcp` 形式提供，覆盖构建 MCP 服务器所需的一整套能力。
</Info>

MCP 服务器主要提供三类能力：

| 能力                | 说明                              |
| ----------------- | ------------------------------- |
| **工具（Tools）**     | AI 客户端可调用的函数。用于搜索、更新、对接外部 API 等 |
| **资源（Resources）** | AI 客户端可读取的数据或上下文信息              |
| **提示（Prompts）**   | 可复用的提示模板                        |

## 安装

通过 Composer 安装扩展包。

```shell theme={null}
composer require laravel/mcp
```

安装后运行 `vendor:publish` Artisan 命令来生成 `routes/ai.php` 文件。

```shell theme={null}
php artisan vendor:publish --tag=ai-routes
```

该命令会创建 `routes/ai.php`，你可以在里面注册 MCP 服务器。

## 创建服务器

用 `make:mcp-server` Artisan 命令生成服务器类。

```shell theme={null}
php artisan make:mcp-server WeatherServer
```

服务器类会生成到 `app/Mcp/Servers` 目录下。

```php theme={null}
<?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 的集成。

```php theme={null}
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/weather', WeatherServer::class);
```

可以像普通路由一样应用中间件。

```php theme={null}
Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware(['throttle:mcp']);
```

#### 本地服务器

本地服务器以 Artisan 命令的形式运行，用于与 Claude Desktop 等本地 AI 客户端对接。

```php theme={null}
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::local('weather', WeatherServer::class);
```

<Tip>
  本地服务器通常由 MCP 客户端自动启动。无需手动执行 `mcp:start` Artisan 命令。
</Tip>

## 工具

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

### 创建工具

用 `make:mcp-tool` Artisan 命令生成工具类。

```shell theme={null}
php artisan make:mcp-tool CurrentWeatherTool
```

将工具注册到服务器的 `$tools` 属性中。

```php theme={null}
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    protected array $tools = [
        CurrentWeatherTool::class,
    ];
}
```

一个基础的工具类示例：

```php theme={null}
<?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`。可通过 `Name` 与 `Title` 属性自定义。

```php theme={null}
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
{
    // ...
}
```

<Warning>
  工具的说明（`Description`）不会自动生成。它对 AI 模型理解如何调用工具至关重要，请务必写清晰、有意义的说明。
</Warning>

### 输入 Schema

`schema` 方法用来定义输入参数的 schema。可以使用 Laravel 提供的 JSON schema builder 指定类型与约束。

```php theme={null}
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 客户端更好地解析响应。

```php theme={null}
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 的标准校验能力。

```php theme={null}
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.',
    ]);

    // 使用校验后的数据继续处理...
}
```

<Tip>
  校验失败时，AI 客户端会根据错误信息重试。请提供具体、可执行的错误消息。
</Tip>

### 依赖注入

工具通过 Laravel 服务容器解析，因此可以在构造函数或 `handle` 方法中通过类型提示注入依赖。

```php theme={null}
<?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 客户端提供关于工具行为的额外提示。

```php theme={null}
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` 方法可以在运行时按条件注册工具。

```php theme={null}
public function shouldRegister(Request $request): bool
{
    return $request?->user()?->subscribed() ?? false;
}
```

返回 `false` 时该工具对 AI 客户端不可见。

### 响应

工具需要返回一个 `Laravel\Mcp\Response` 实例。

<AccordionGroup>
  <Accordion title="文本响应">
    ```php theme={null}
    return Response::text('Weather Summary: Sunny, 22°C');
    ```
  </Accordion>

  <Accordion title="错误响应">
    ```php theme={null}
    return Response::error('Unable to fetch weather data. Please try again.');
    ```
  </Accordion>

  <Accordion title="图片 / 音频响应">
    ```php theme={null}
    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');
    ```
  </Accordion>

  <Accordion title="多内容响应">
    ```php theme={null}
    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"),
        ];
    }
    ```
  </Accordion>

  <Accordion title="结构化响应">
    返回便于 AI 客户端解析的结构化数据。

    ```php theme={null}
    return Response::structured([
        'temperature' => 22.5,
        'conditions' => 'Partly cloudy',
        'humidity' => 65,
    ]);
    ```
  </Accordion>

  <Accordion title="流式响应">
    长耗时处理中实时推送进度。

    ```php theme={null}
    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));
        }
    }
    ```
  </Accordion>
</AccordionGroup>

## 提示（Prompts）

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

### 创建提示

```shell theme={null}
php artisan make:mcp-prompt DescribeWeatherPrompt
```

将其注册到服务器的 `$prompts` 属性中。

```php theme={null}
use App\Mcp\Prompts\DescribeWeatherPrompt;

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

### 提示参数

`arguments` 方法用来定义提示的参数。

```php theme={null}
<?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 的[校验能力](/zh/validation)无缝衔接，可以在提示的 `handle` 方法内进行校验。

```php theme={null}
<?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 客户端会参考错误消息重试。请提供具体、可执行的消息。

```php theme={null}
$validated = $request->validate([
    'tone' => ['required', 'string', 'max:50'],
], [
    'tone.*' => '请指定 tone，例如 "formal"、"casual"、"humorous"。',
]);
```

### 依赖注入

提示通过 Laravel 服务容器解析，构造函数与 `handle` 方法都可以类型提示注入依赖。

```php theme={null}
<?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 theme={null}
public function handle(Request $request, WeatherRepository $weather): Response
{
    $isAvailable = $weather->isServiceAvailable();

    // ...
}
```

### 条件注册

通过实现 `shouldRegister` 方法可以在运行时按条件注册提示。

```php theme={null}
<?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 theme={null}
<?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 回答的质量。

### 创建资源

```shell theme={null}
php artisan make:mcp-resource WeatherGuidelinesResource
```

注册到服务器的 `$resources` 属性中。

```php theme={null}
use App\Mcp\Resources\WeatherGuidelinesResource;

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

```php theme={null}
<?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`）。可通过 `Uri` 与 `MimeType` 属性自定义。

```php theme={null}
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 theme={null}
<?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 theme={null}
<?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 theme={null}
<?php

namespace App\Mcp\Resources;

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

class WeatherGuidelinesResource extends Resource
{
    public function __construct(
        protected WeatherRepository $weather,
    ) {}
}
```

```php theme={null}
public function handle(WeatherRepository $weather): Response
{
    return Response::text($weather->guidelines());
}
```

### 资源注解

资源可以带上受众、优先级、最后修改时间等注解。

```php theme={null}
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 theme={null}
<?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` 方法。

```php theme={null}
return Response::text($weatherData);
```

#### 资源链接响应

`resourceLink` 方法可以返回一个资源链接。与嵌入式资源不同，它返回的是 AI 客户端可独立获取的 URI 指针。

```php theme={null}
return Response::resourceLink(
    uri: 'file:///data/report.json',
    name: 'monthly-report',
    mimeType: 'application/json',
);
```

也可以传入已注册的资源类或实例，会自动继承其 URI、name、title、description、MIME 类型。

```php theme={null}
return Response::resourceLink(new WeatherForecastResource);
```

#### Blob 响应

返回二进制内容用 `blob` 方法。MIME 类型通过资源的 `#[MimeType]` 属性设置。

```php theme={null}
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
```

```php theme={null}
#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
    // ...
}
```

#### 错误响应

要返回错误，使用 `error` 方法。

```php theme={null}
return Response::error('无法获取指定地点的天气数据。');
```

## 应用（Apps）

Laravel MCP 支持 [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview)。这是 Model Context Protocol 的一个扩展，允许工具在受支持的宿主中，通过沙箱 iframe 渲染交互式的 HTML 应用。你可以基于此构建仪表盘、表单、可视化和其他富交互体验，而不局限于纯文本响应。

MCP 应用由两个协作的部分组成：

* **App 资源** — 返回应用的独立 HTML。
* **工具** — 通过 `#[RendersApp]` 属性关联到 App 资源。工具被调用时，宿主会拉取并渲染关联的资源。

### 创建 App 资源

用 `make:mcp-app-resource` Artisan 命令生成 App 资源。

```shell theme={null}
php artisan make:mcp-app-resource WeatherDashboardApp
```

命令会创建两个文件：`app/Mcp/Resources` 下的 PHP 类，以及 `resources/views/mcp` 下的 Blade 视图。视图名会根据类名自动推断。例如 `WeatherDashboardApp` 对应 `mcp.weather-dashboard-app`。

```php theme={null}
<?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 文档。

```blade theme={null}
<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 规范](https://modelcontextprotocol.io/extensions/apps/overview)。

### 通过工具渲染应用

要展示 App 资源，需要在工具上使用 `#[RendersApp]` 属性把它们关联起来。工具被调用时，Laravel MCP 会把资源 URI 放到工具的元数据中，让宿主在沙箱 iframe 内渲染应用。

```php theme={null}
<?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.');
    }
}
```

<Info>
  `AppResource` 注册后，Laravel MCP 会自动通告 `io.modelcontextprotocol/ui` 能力，无需额外的服务器配置。
</Info>

### 应用工具的可见性

每个 `#[RendersApp]` 工具都可以通过 `visibility` 参数限制调用方。当你有专供 UI 使用的、模型不需要看到的私有工具时非常有用。

```php theme={null}
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 直接调用的后端 action 用 `[Visibility::App]`；如果不希望 UI 调用工具，就用 `[Visibility::Model]`。

### 应用的配置

App 资源上的 `#[AppMeta]` 属性用于设置 iframe 的内容安全策略、浏览器权限，以及要注入到视图 `<head>` 中的库脚本。

```php theme={null}
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::Tailwind`、`Library::Alpine` 等常用前端库的 CDN 脚本，其 CDN 源会自动合并到 CSP 中。`Permission` 枚举覆盖 `Camera`、`Microphone`、`Geolocation`、`ClipboardWrite` 等浏览器权限。

<Tip>
  如果需要动态配置，可以重写资源的 `appMeta` 方法，使用 `Laravel\Mcp\Server\Ui` 命名空间下的 `AppMeta`、`Csp`、`Permissions` 流式构建器。
</Tip>

### 使用 Boost 开发应用

Laravel MCP 提供了专门用于构建 MCP Apps 的 [Boost](/zh/boost) skill 参考。安装 Laravel Boost 后，AI 编码代理可以调用 `mcp-development` skill，自动生成 App 资源、Blade 视图以及关联的工具。

关于协议的完整参考（包括客户端 API 与 schema 细节），请查看官方的 [MCP Apps 文档](https://modelcontextprotocol.io/extensions/apps/overview)。

## 元数据

可以给工具、资源、提示的响应附加 MCP 规范中的 `_meta` 字段。

```php theme={null}
// 给响应内容附加元数据
return Response::text('The weather is sunny.')
    ->withMeta(['source' => 'weather-api', 'cached' => true]);
```

要为整个响应外壳附加元数据，使用 `Response::make`。

```php theme={null}
return Response::make(
    Response::text('The weather is sunny.')
)->withMeta(['request_id' => '12345']);
```

若要给工具、资源、提示的类本身附加元数据，定义 `$meta` 属性即可。

```php theme={null}
class CurrentWeatherTool extends Tool
{
    protected ?array $meta = [
        'version' => '2.0',
        'author' => 'Weather Team',
    ];
}
```

## 图标

MCP 客户端可以显示服务器及其原语的图标。可以通过 `Icon` 属性为 Server、Tool、Resource、Prompt 声明图标。

```php theme={null}
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` 方法，通过编程方式定义图标，适合图标依赖运行时条件的情况。

```php theme={null}
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](https://laravel.com/docs/sanctum) 的 token 认证。MCP 客户端会发送 `Authorization: Bearer <token>` 头。

```php theme={null}
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

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

### OAuth 2.1

使用 [Laravel Passport](https://laravel.com/docs/passport) 的 OAuth 认证，适合需要更强安全性的场景。

```php theme={null}
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

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

使用 OAuth 时，需要发布 Passport 的授权视图并在服务提供者中配置。

```shell theme={null}
php artisan vendor:publish --tag=mcp-views
```

```php theme={null}
// AppServiceProvider::boot()
use Laravel\Passport\Passport;

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

## 授权

通过 `$request->user()` 获取当前已认证用户，就可以在工具或资源中进行授权检查。

```php theme={null}
public function handle(Request $request): Response
{
    if (! $request->user()->can('read-weather')) {
        return Response::error('Permission denied.');
    }

    // 继续处理...
}
```

## MCP 客户端

Laravel MCP 不只能构建服务器，也提供了连接其他 MCP 服务器的客户端。借助客户端，你可以发现并调用外部 MCP 服务器暴露的工具，特别适合把外部 MCP 服务器的能力提供给 [AI 代理](/zh/ai-sdk#mcp-tools)。

### 连接到服务器

对于可以通过 HTTP 访问的 MCP 服务器，使用 `Client::web` 方法并传入服务器 URL。

```php theme={null}
use Laravel\Mcp\Client;

$client = Client::web('https://mcp.example.com');
```

对于作为命令启动的本地 MCP 服务器，使用 `Client::local` 方法并传入命令和参数。

```php theme={null}
use Laravel\Mcp\Client;

$client = Client::local('php', ['artisan', 'mcp:start']);
```

客户端采用延迟连接（lazy connect），在首次获取或调用工具时自动建立连接。若要手动管理连接，可以使用 `connect`、`connected`、`ping`、`disconnect` 方法。

```php theme={null}
$client->connect();

$client->ping();

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

$client->disconnect();
```

`withTimeout` 方法可以自定义请求超时。

```php theme={null}
$client = Client::web('https://mcp.example.com')->withTimeout(30);
```

### 命名客户端

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

```php theme={null}
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;

Mcp::registerClient('github', fn () => Client::web('https://mcp.example.com'));
```

注册后可以按名字解析客户端。

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$client = Mcp::client('github');
```

命名客户端在每个请求内只会被解析一次，请求生命周期结束时自动断开。

### 客户端认证

访问受 Bearer token 保护的 Web MCP 服务器，可以使用 `withToken`。它接收 token 字符串或延迟解析的闭包。

```php theme={null}
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](#oauth) 保护的服务器，使用 `withOAuth`。

```php theme={null}
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'),
));
```

<Info>
  如果 MCP 服务器支持[动态客户端注册](https://datatracker.ietf.org/doc/html/rfc7591)，可以省略 `clientId` 和 `clientSecret`，客户端会自动注册。
</Info>

然后在 `routes/ai.php` 中使用 `oAuthRoutesFor` 方法为命名客户端注册 OAuth 路由。传入的闭包会在授权码交换完 access token 后接收客户端名与 `TokenSet`。

```php theme={null}
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 路由即可。

```php theme={null}
return redirect()->route('mcp.oauth.github.connect');
```

### 工具

`tools` 方法用来获取 MCP 服务器暴露的工具，以名字为键返回一个集合。

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$tools = Mcp::client('github')->tools();

foreach ($tools as $tool) {
    $tool->name;
    $tool->title;
    $tool->description;
    $tool->inputSchema;
}
```

客户端会自动处理分页取回所有工具。通过 `limit` 参数可以限制条数。

```php theme={null}
$tools = Mcp::client('github')->tools(limit: 10);
```

调用工具时使用 `callTool` 方法，传入工具名和参数数组。返回的 `ToolResult` 实例包含响应。

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$result = Mcp::client('github')->callTool('current-weather', [
    'location' => 'New York',
]);

$result->text();             // 响应的文本内容
(string) $result;            // 等同于 text()
$result->isError;            // 工具是否报告了错误
$result->structuredContent;  // 结构化内容（如果有）
```

也可以直接从列表中获取的工具实例上调用。

```php theme={null}
$tools = Mcp::client('github')->tools();

$result = $tools['current-weather']->call([
    'location' => 'New York',
]);
```

如果你在使用 [Laravel AI SDK](/zh/ai-sdk) 构建代理，可以直接把 MCP 客户端的工具传给代理，让模型在回答提示时调用它们。详情请参考 AI SDK 的 [MCP 工具](/zh/ai-sdk#mcp-tools) 章节。

### 提示

`prompts` 方法用来获取 MCP 服务器暴露的提示，以名字为键返回一个集合。

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$prompts = Mcp::client('github')->prompts();

foreach ($prompts as $prompt) {
    $prompt->name;
    $prompt->title;
    $prompt->description;
    $prompt->arguments;
}
```

客户端会自动分页，`limit` 参数可用来限制条数。

```php theme={null}
$prompts = Mcp::client('github')->prompts(limit: 10);
```

获取一个提示的结果使用 `getPrompt` 方法，传入提示名与参数数组。返回的 `PromptResult` 实例包含生成的消息。

```php theme={null}
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 为键返回一个集合。

```php theme={null}
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` 参数用于限制条数。

```php theme={null}
$resources = Mcp::client('github')->resources(limit: 10);
```

读取资源使用 `readResource` 方法，传入 URI。返回的 `ResourceReadResult` 实例包含资源内容。

```php theme={null}
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 服务器的行为。

```shell theme={null}
# Web 服务器
php artisan mcp:inspector mcp/weather

# 本地服务器（name 为 "weather" 时）
php artisan mcp:inspector weather
```

执行命令会启动 MCP Inspector，并允许复制客户端配置。若配置了认证中间件，请连接时带上 Authorization 头。

### 单元测试

你可以为工具、资源、提示编写单元测试。

<CodeGroup>
  ```php Pest theme={null}
  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.');
  });
  ```

  ```php PHPUnit theme={null}
  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.');
  }
  ```
</CodeGroup>

提示和资源同样可以测试。

```php theme={null}
$response = WeatherServer::prompt(DescribeWeatherPrompt::class, ['tone' => 'casual']);
$response = WeatherServer::resource(WeatherGuidelinesResource::class);
```

要以已认证用户的身份执行，使用 `actingAs`。

```php theme={null}
$response = WeatherServer::actingAs($user)->tool(CurrentWeatherTool::class, [...]);
```

主要断言方法如下：

```php theme={null}
$response->assertOk();           // 断言没有错误
$response->assertSee('...');     // 断言包含特定文本
```

判断是否有错误，使用 `assertHasErrors` / `assertHasNoErrors`。

```php theme={null}
$response->assertHasErrors();

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

$response->assertHasNoErrors();
```

也可以校验工具 / 资源 / 提示的名称、标题、说明。

```php theme={null}
$response->assertName('current-weather');
$response->assertTitle('Current Weather Tool');
$response->assertDescription('Fetches the current weather forecast for a specified location.');
```

校验流式响应的通知，使用 `assertSentNotification` 与 `assertNotificationCount`。

```php theme={null}
$response->assertSentNotification('processing/progress', [
    'step' => 1,
    'total' => 5,
]);

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

$response->assertNotificationCount(5);
```

调试响应内容时使用 `dd` 或 `dump`。

```php theme={null}
$response->dd();
$response->dump();
```


## Related topics

- [用 Laravel 构建 MCP 服务器](/zh/advanced/mcp-server.md)
- [Laravel AI SDK](/zh/ai-sdk.md)
- [Laravel Console Starter](/zh/packages/laravel-console-starter/index.md)
- [MCP](/zh/packages/laravel-copilot-sdk/mcp.md)
- [Laravel Boost](/zh/boost.md)
