> ## 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 伺服器可提供的功能主要分 3 類：

| 功能                | 說明                            |
| ----------------- | ----------------------------- |
| **工具（Tools）**     | AI 用戶端可呼叫的函式，如搜尋、更新或外部 API 整合 |
| **資源（Resources）** | AI 用戶端可讀取的資料或脈絡資訊             |
| **提示（Prompts）**   | 可重複使用的提示樣板                    |

## 安裝

以 Composer 安裝套件。

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

安裝後執行 `vendor:publish` 產生 `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`。
</Tip>

## 工具

工具是 AI 用戶端可呼叫的函式，可實作資料取得、外部 API 整合、資料庫操作等。

### 建立工具

以 `make:mcp-tool` 產生工具類別。

```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(),
        ];
    }
}
```

### 工具的名稱與說明

Laravel 會從類別名稱自動產生名稱與標題。例如 `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');

    // 直接從儲存讀取（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-TW/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),
        ];
    }
}
```

## 資源（Resources）

資源是 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 變數會自動放入 request，可用 `get` 取得。

### 資源的請求

與工具、提示不同，資源不能定義輸入 schema 或引數。但在 `handle` 中仍可透過 request 存取請求資訊。

```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,
    ) {}
}
```

`handle` 中亦可注入。

```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` 回傳資源連結。與內嵌資源不同，回傳的是 URI pointer，AI 用戶端會另行取得。

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

也可傳入已註冊的資源類別或實例，會自動繼承 URI、名稱、標題、說明、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('無法取得指定地點的天氣資料。');
```

## App

Laravel MCP 支援 [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview)。它是 Model Context Protocol 的延伸功能，可在支援的主機沙盒 iframe 中，讓工具渲染出互動式 HTML 應用程式。因此可打造超越純文字回應的儀表板、表單、視覺化等豐富體驗。

MCP app 由下列 2 部分協同運作：

* **App 資源** — 回傳應用程式獨立的 HTML。
* **工具** — 透過 `#[RendersApp]` 屬性連結到 App 資源。工具被呼叫時，主機會取得連結的資源並渲染。

### 建立 App 資源

以 `make:mcp-app-resource` Artisan 指令建立 App 資源。

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

此指令會建立 2 個檔案：位於 `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

要顯示 App 資源，透過 `#[RendersApp]` 屬性將工具連結到資源。當工具被呼叫時，Laravel MCP 會將資源的 URI 附加到工具中繼資料，讓主機能於沙盒 iframe 內渲染此 app。

```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>

### App 工具的可見性

每個 `#[RendersApp]` 工具可用 `visibility` 引數限制呼叫者。這對於 UI 用來載入 / 更新資料、但不希望被模型看見的私有 app 專用工具很有用。

```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` enum 有 `Model` 與 `App` 兩個值，預設兩者皆有。若工具只供 UI 直接呼叫請用 `[Visibility::App]`；若要讓 UI 無法使用該工具則用 `[Visibility::Model]`。

### App 設定

在 App 資源的 `#[AppMeta]` 屬性中，可設定 iframe 的 Content Security Policy、瀏覽器權限，以及要放入視圖 `<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` enum 預先設定了 `Library::Tailwind`、`Library::Alpine` 等常見前端函式庫 CDN 腳本，其 CDN 來源會自動加入 CSP。`Permission` enum 涵蓋 `Camera`、`Microphone`、`Geolocation`、`ClipboardWrite` 等瀏覽器權限。

<Tip>
  若需要動態設定，可透過 `Laravel\Mcp\Server\Ui` 命名空間的 `AppMeta`、`Csp`、`Permissions` 流暢建構器覆寫資源的 `appMeta` 方法。
</Tip>

### 用 Boost 開發 App

Laravel MCP 附有專供構建 MCP Apps 的[Boost](/zh-TW/boost) 技能參考。若已安裝 Laravel Boost，AI 編碼代理可呼叫 `mcp-development` 技能，自動產生 App 資源、Blade 視圖與連結的工具。

完整協定參考（含用戶端 API 與 schema 細節）請參閱官方 [MCP Apps 文件](https://modelcontextprotocol.io/extensions/apps/overview)。

## Meta 資料

可將 MCP 規格的 `_meta` 欄位附加到工具、資源、提示的回應上。

```php theme={null}
// 加到回應內容的 meta
return Response::text('The weather is sunny.')
    ->withMeta(['source' => 'weather-api', 'cached' => true]);
```

若要為整個回應信封加 meta，使用 `Response::make`：

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

要為工具、資源、提示類別本身加 meta，定義 `$meta` 屬性：

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

## 圖示

MCP 用戶端可為伺服器與其原語顯示圖示。以 `Icon` 屬性可為伺服器、工具、資源、提示宣告圖示。

```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` 輔助函式解析為 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 伺服器公開的工具，這對於在 [AI 代理](/zh-TW/ai-sdk#mcp-工具) 中提供外部 MCP 伺服器功能特別有用。

### 連線至伺服器

對於可透過 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);
```

### 命名用戶端

不必每次都重新建構，可註冊可重用的命名用戶端。通常在 service provider 的 `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-2-1) 保護的伺服器使用 `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');
});
```

如此會註冊 2 個命名路由：將使用者導向授權伺服器的 connect 路由（`mcp.oauth.{client}.connect`），以及交換授權碼並呼叫 handler 的 callback 路由（`mcp.oauth.{client}.callback`）。兩者皆使用 `web` 中介軟體群組（可透過 `middleware` 引數覆寫）。

開始授權流程時，將使用者導向 connect 路由：

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

### 工具

透過 `tools` 取得 MCP 伺服器公開的工具，回傳以名稱為 key 的集合。

```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-TW/ai-sdk) 構建代理，可將 MCP 用戶端的工具直接傳給代理，讓模型在回應提示時能呼叫。詳情見 AI SDK 的 [MCP 工具](/zh-TW/ai-sdk#mcp-工具) 章節。

### 提示

透過 `prompts` 取得 MCP 伺服器公開的提示，回傳以名稱為 key 的集合。

```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 為 key 的集合。

```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

# 本機伺服器（名稱為 "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-TW/advanced/mcp-server.md)
- [Laravel AI Agent 支援 MCP 伺服器](/zh-TW/blog/ai-sdk-mcp-client.md)
- [Laravel AI SDK](/zh-TW/ai-sdk.md)
- [進階主題](/zh-TW/advanced/index.md)
- [进阶主题](/zh-CN/advanced/index.md)
