Skip to content

Laravel MCP

簡介

Laravel MCP 提供了一種簡單且優雅的方式,讓 AI 用戶端能透過模型上下文協議 (Model Context Protocol) 與您的 Laravel 應用程式進行互動。它提供了一個具表現力且流暢的介面來定義伺服器、工具、資源與提示詞,從而實現由 AI 驅動的應用程式互動。

安裝

要開始使用,請使用 Composer 套件管理工具將 Laravel MCP 安裝到您的專案中:

shell
composer require laravel/mcp

發布路由

安裝 Laravel MCP 後,請執行 vendor:publish Artisan 命令來發布 routes/ai.php 檔案,您將在此檔案中定義您的 MCP 伺服器:

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

此命令會在您應用程式的 routes 目錄中建立 routes/ai.php 檔案,您將使用該檔案來註冊您的 MCP 伺服器。

建立伺服器

您可以使用 make:mcp-server Artisan 命令來建立 MCP 伺服器。伺服器扮演著中央通訊點的角色,將工具、資源和提示詞等 MCP 能力公開給 AI 用戶端:

shell
php artisan make:mcp-server WeatherServer

此命令將在 app/Mcp/Servers 目錄中建立一個新的伺服器類別。產生的伺服器類別繼承了 Laravel MCP 的基礎 Laravel\Mcp\Server 類別,並提供了用於配置伺服器以及註冊工具、資源與提示詞的屬性 (Attributes) 和屬性 (Properties):

php
<?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
{
    /**
     * The tools registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
     */
    protected array $tools = [
        // GetCurrentWeatherTool::class,
    ];

    /**
     * The resources registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
     */
    protected array $resources = [
        // WeatherGuidelinesResource::class,
    ];

    /**
     * The prompts registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
     */
    protected array $prompts = [
        // DescribeWeatherPrompt::class,
    ];
}

伺服器註冊

建立伺服器後,您必須在 routes/ai.php 檔案中註冊它才能使其可被存取。Laravel MCP 提供了兩種註冊伺服器的方法:web 用於可透過 HTTP 存取的伺服器,而 local 則用於命令列伺服器。

Web 伺服器

Web 伺服器是最常見的伺服器類型,可透過 HTTP POST 請求存取,因此非常適合遠端 AI 用戶端或基於 Web 的整合。請使用 web 方法來註冊 Web 伺服器:

php
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

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

就像一般的路由一樣,您可以套用中介層來保護您的 Web 伺服器:

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

本地伺服器

本地伺服器以 Artisan 命令的形式執行,非常適合建立本地 AI 助手整合,例如 Laravel Boost。請使用 local 方法來註冊本地伺服器:

php
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

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

註冊完成後,您通常不需要手動執行 mcp:start Artisan 命令。相反地,請配置您的 MCP 用戶端 (AI 代理) 來啟動伺服器,或使用 MCP Inspector

工具

工具讓您的伺服器能夠公開 AI 客戶端可以呼叫的功能。它們允許語言模型執行動作、執行程式碼或與外部系統互動:

php
<?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
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $location = $request->get('location');

        // Get weather...

        return Response::text('The weather is...');
    }

    /**
     * Get the tool's input schema.
     *
     * @return array<string, \Illuminate\JsonSchema\Types\Type>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'location' => $schema->string()
                ->description('The location to get the weather for.')
                ->required(),
        ];
    }
}

建立工具

要建立工具,請執行 make:mcp-tool Artisan 命令:

shell
php artisan make:mcp-tool CurrentWeatherTool

建立工具後,將其註冊到伺服器的 $tools 屬性中:

php
<?php

namespace App\Mcp\Servers;

use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    /**
     * The tools registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
     */
    protected array $tools = [
        CurrentWeatherTool::class,
    ];
}

工具名稱、標題與描述

預設情況下,工具的名稱 (name) 和標題 (title) 是根據類別名稱衍生而來的。例如,CurrentWeatherTool 的名稱將會是 current-weather,標題則是 Current Weather Tool。您可以使用 NameTitle 屬性來自訂這些值:

php
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 屬性提供具意義的描述:

php
use Laravel\Mcp\Server\Attributes\Description;

#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
    //
}

📌 備註

描述是工具元數據 (metadata) 的關鍵部分,因為它能幫助 AI 模型理解何時以及如何有效地使用該工具。

工具輸入結構 (Input Schemas)

工具可以定義輸入結構 (input schemas) 來指定它們接受來自 AI 客戶端的哪些引數。請使用 Laravel 的 Illuminate\Contracts\JsonSchema\JsonSchema 構建器來定義工具的輸入需求:

php
<?php

namespace App\Mcp\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Get the tool's input schema.
     *
     * @return array<string, \Illuminate\JsonSchema\Types\Type>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'location' => $schema->string()
                ->description('The location to get the weather for.')
                ->required(),

            'units' => $schema->string()
                ->enum(['celsius', 'fahrenheit'])
                ->description('The temperature units to use.')
                ->default('celsius'),
        ];
    }
}

工具輸出結構 (Output Schemas)

工具可以定義 輸出結構 (output schemas) 來指定回應的結構。這能讓需要可解析工具結果的 AI 客戶端獲得更好的整合體驗。使用 outputSchema 方法來定義工具的輸出結構:

php
<?php

namespace App\Mcp\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Get the tool's output schema.
     *
     * @return array<string, \Illuminate\JsonSchema\Types\Type>
     */
    public function outputSchema(JsonSchema $schema): array
    {
        return [
            'temperature' => $schema->number()
                ->description('Temperature in Celsius')
                ->required(),

            'conditions' => $schema->string()
                ->description('Weather conditions')
                ->required(),

            'humidity' => $schema->integer()
                ->description('Humidity percentage')
                ->required(),
        ];
    }
}

驗證工具引數

JSON Schema 定義為工具引數提供了基本結構,但您可能還希望強制執行更複雜的驗證規則。

Laravel MCP 與 Laravel 的 驗證功能 無縫整合。您可以在工具的 handle 方法中驗證傳入的工具引數:

php
<?php

namespace App\Mcp\Tools;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $validated = $request->validate([
            'location' => 'required|string|max:100',
            'units' => 'in:celsius,fahrenheit',
        ]);

        // Fetch weather data using the validated arguments...
    }
}

當驗證失敗時,AI 客戶端將根據您提供的錯誤訊息採取行動。因此,提供清晰且可操作的錯誤訊息至關重要:

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

工具依賴注入

Laravel 服務容器 被用來解析所有工具。因此,您可以在建構子中對工具可能需要的任何依賴進行型別提示 (type-hint)。宣告的依賴將會自動被解析並注入到工具實例中:

php
<?php

namespace App\Mcp\Tools;

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

class CurrentWeatherTool extends Tool
{
    /**
     * Create a new tool instance.
     */
    public function __construct(
        protected WeatherRepository $weather,
    ) {}

    // ...
}

除了建構子注入外,您也可以在工具的 handle() 方法中對依賴進行型別提示。當方法被呼叫時,服務容器會自動解析並注入這些依賴:

php
<?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
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request, WeatherRepository $weather): Response
    {
        $location = $request->get('location');

        $forecast = $weather->getForecastFor($location);

        // ...
    }
}

工具註解 (Annotations)

您可以使用 註解 (Annotations) 來強化您的工具,以為 AI 客戶端提供額外的元數據。這些註解能幫助 AI 模型理解工具的行為與能力。註解透過屬性 (attributes) 添加到工具中:

php
<?php

namespace App\Mcp\Tools;

use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tool;

#[IsIdempotent]
#[IsReadOnly]
class CurrentWeatherTool extends Tool
{
    //
}

可用的註解包括:

註解類型描述
#[IsReadOnly]boolean表示該工具不會修改其環境。
#[IsDestructive]boolean表示該工具可能會執行破壞性更新(僅在非唯讀時有意義)。
#[IsIdempotent]boolean表示使用相同引數的重複呼叫不會產生額外影響(僅在非唯讀時有意義)。
#[IsOpenWorld]boolean表示該工具可能會與外部實體互動。

註解值可以使用布林引數明確設定:

php
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
use Laravel\Mcp\Server\Tools\Annotations\IsOpenWorld;
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tool;

#[IsReadOnly(true)]
#[IsDestructive(false)]
#[IsOpenWorld(false)]
#[IsIdempotent(true)]
class CurrentWeatherTool extends Tool
{
    //
}

條件式工具註冊

您可以透過在工具類別中實作 shouldRegister 方法,在執行時條件式地註冊工具。此方法允許您根據應用程式狀態、配置或請求參數來決定工具是否應可用:

php
<?php

namespace App\Mcp\Tools;

use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Determine if the tool should be registered.
     */
    public function shouldRegister(Request $request): bool
    {
        return $request?->user()?->subscribed() ?? false;
    }
}

當工具的 shouldRegister 方法回傳 false 時,它不會出現在可用工具列表中,且無法由 AI 客戶端呼叫。

工具回應

工具必須回傳一個 Laravel\Mcp\Response 實例。Response 類別提供了幾個便捷方法來建立不同類型的回應:

對於簡單的文字回應,請使用 text 方法:

php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 */
public function handle(Request $request): Response
{
    // ...

    return Response::text('Weather Summary: Sunny, 72°F');
}

若要表示工具執行期間發生錯誤,請使用 error 方法:

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

若要回傳圖片或音訊內容,請使用 imageaudio 方法:

php
return Response::image(file_get_contents(storage_path('weather/radar.png')), 'image/png');

return Response::audio(file_get_contents(storage_path('weather/alert.mp3')), 'audio/mp3');

您也可以使用 fromStorage 方法直接從 Laravel 檔案系統磁碟載入圖片與音訊內容。MIME 類型將會從檔案中自動偵測:

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

如果需要,您可以指定特定的磁碟或覆寫 MIME 類型:

php
return Response::fromStorage('weather/radar.png', disk: 's3');

return Response::fromStorage('weather/radar.png', mimeType: 'image/webp');

多重內容回應

工具可以透過回傳 Response 實例的陣列來回傳多個內容片段:

php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 *
 * @return array<int, \Laravel\Mcp\Response>
 */
public function handle(Request $request): array
{
    // ...

    return [
        Response::text('Weather Summary: Sunny, 72°F'),
        Response::text("**Detailed Forecast**\n- Morning: 65°F\n- Afternoon: 78°F\n- Evening: 70°F")
    ];
}

結構化回應

工具可以使用 structured 方法回傳 結構化內容。這能為 AI 客戶端提供可解析的數據,同時保持與 JSON 編碼文字表示法的向下相容性:

php
return Response::structured([
    'temperature' => 22.5,
    'conditions' => 'Partly cloudy',
    'humidity' => 65,
]);

如果您需要在結構化內容旁提供自定義文字,請在回應工廠上使用 withStructuredContent 方法:

php
return Response::make(
    Response::text('Weather is 22.5°C and sunny')
)->withStructuredContent([
    'temperature' => 22.5,
    'conditions' => 'Sunny',
]);

串流回應

對於執行時間較長的操作或即時數據串流,工具可以在其 handle 方法中回傳一個 產生器 (generator)。這使得在最終回應之前,能向客戶端發送中間更新:

php
<?php

namespace App\Mcp\Tools;

use Generator;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Handle the tool request.
     *
     * @return \Generator<int, \Laravel\Mcp\Response>
     */
    public function handle(Request $request): Generator
    {
        $locations = $request->array('locations');

        foreach ($locations as $index => $location) {
            yield Response::notification('processing/progress', [
                'current' => $index + 1,
                'total' => count($locations),
                'location' => $location,
            ]);

            yield Response::text($this->forecastFor($location));
        }
    }
}

當使用 Web 伺服器時,串流回應會自動開啟一個 SSE (Server-Sent Events) 串流,將每個產生的訊息作為事件發送到客戶端。

提示詞

提示詞 讓您的伺服器能夠分享可重複使用的提示詞模板,AI 用戶端可以使用這些模板來與語言模型進行互動。它們提供了一種標準化的方式來建構常見的查詢與互動。

建立提示詞

要建立提示詞,請執行 make:mcp-prompt Artisan 命令:

shell
php artisan make:mcp-prompt DescribeWeatherPrompt

建立提示詞後,將其註冊到伺服器的 $prompts 屬性中:

php
<?php

namespace App\Mcp\Servers;

use App\Mcp\Prompts\DescribeWeatherPrompt;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    /**
     * The prompts registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
     */
    protected array $prompts = [
        DescribeWeatherPrompt::class,
    ];
}

提示詞名稱、標題與描述

預設情況下,提示詞的名稱與標題是根據類別名稱而來的。例如,DescribeWeatherPrompt 的名稱將會是 describe-weather,標題則是 Describe Weather Prompt。您可以使用 NameTitle 屬性來自定義這些值:

php
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;

#[Name('weather-assistant')]
#[Title('Weather Assistant Prompt')]
class DescribeWeatherPrompt extends Prompt
{
    // ...
}

提示詞描述不會自動生成。您應該始終使用 Description 屬性提供具備意義的描述:

php
use Laravel\Mcp\Server\Attributes\Description;

#[Description('Generates a natural-language explanation of the weather for a given location.')]
class DescribeWeatherPrompt extends Prompt
{
    //
}

📌 備註

描述是提示詞元數據 (metadata) 的關鍵部分,因為它能幫助 AI 模型理解何時以及如何最有效地使用該提示詞。

提示詞引數

提示詞可以定義引數,讓 AI 用戶端能夠使用特定值來自定義提示詞模板。使用 arguments 方法來定義您的提示詞接受哪些引數:

php
<?php

namespace App\Mcp\Prompts;

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

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Get the prompt's arguments.
     *
     * @return array<int, \Laravel\Mcp\Server\Prompts\Argument>
     */
    public function arguments(): array
    {
        return [
            new Argument(
                name: 'tone',
                description: 'The tone to use in the weather description (e.g., formal, casual, humorous).',
                required: true,
            ),
        ];
    }
}

驗證提示詞引數

提示詞引數會根據其定義自動進行驗證,但您可能還想強制執行更複雜的驗證規則。

Laravel MCP 與 Laravel 的 驗證功能 無縫整合。您可以在提示詞的 handle 方法中驗證傳入的提示詞引數:

php
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Handle the prompt request.
     */
    public function handle(Request $request): Response
    {
        $validated = $request->validate([
            'tone' => 'required|string|max:50',
        ]);

        $tone = $validated['tone'];

        // Generate the prompt response using the given tone...
    }
}

當驗證失敗時,AI 用戶端將根據您提供的錯誤訊息採取行動。因此,提供清晰且可操作的錯誤訊息至關重要:

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

提示詞依賴注入

Laravel 服務容器 用於解析所有提示詞。因此,您可以在建構子中對提示詞可能需要的任何依賴進行類型提示 (type-hint)。宣告的依賴將會被自動解析並注入到提示詞實例中:

php
<?php

namespace App\Mcp\Prompts;

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

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Create a new prompt instance.
     */
    public function __construct(
        protected WeatherRepository $weather,
    ) {}

    //
}

除了建構子注入外,您也可以在提示詞的 handle 方法中對依賴進行類型提示。當方法被呼叫時,服務容器會自動解析並注入這些依賴:

php
<?php

namespace App\Mcp\Prompts;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Handle the prompt request.
     */
    public function handle(Request $request, WeatherRepository $weather): Response
    {
        $isAvailable = $weather->isServiceAvailable();

        // ...
    }
}

條件式提示詞註冊

您可以透過在提示詞類別中實作 shouldRegister 方法,在執行時條件式地註冊提示詞。此方法允許您根據應用程式狀態、設定或請求參數來決定提示詞是否應該可用:

php
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Prompt;

class CurrentWeatherPrompt extends Prompt
{
    /**
     * Determine if the prompt should be registered.
     */
    public function shouldRegister(Request $request): bool
    {
        return $request?->user()?->subscribed() ?? false;
    }
}

當提示詞的 shouldRegister 方法返回 false 時,它將不會出現在可用提示詞列表中,且無法被 AI 用戶端呼叫。

提示詞回應

提示詞可以返回單個 Laravel\Mcp\Response 或一個 Laravel\Mcp\Response 實例的可迭代對象。這些回應封裝了將發送給 AI 用戶端的內容:

php
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Handle the prompt request.
     *
     * @return array<int, \Laravel\Mcp\Response>
     */
    public function handle(Request $request): array
    {
        $tone = $request->string('tone');

        $systemMessage = "You are a helpful weather assistant. Please provide a weather description in a {$tone} tone.";

        $userMessage = "What is the current weather like in New York City?";

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

您可以使用 asAssistant() 方法來表示該回應訊息應被視為來自 AI 助手,而一般訊息則被視為使用者輸入。

資源

資源 讓您的伺服器能夠公開 AI 用戶端在與語言模型互動時可以讀取並將其作為上下文 (context) 使用的資料與內容。它們提供了一種分享靜態或動態資訊的方式,例如文件、設定或任何有助於提供 AI 回應的資料。

建立資源

要建立資源,請執行 make:mcp-resource Artisan 命令:

shell
php artisan make:mcp-resource WeatherGuidelinesResource

建立資源後,將其註冊在伺服器的 $resources 屬性中:

php
<?php

namespace App\Mcp\Servers;

use App\Mcp\Resources\WeatherGuidelinesResource;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    /**
     * The resources registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
     */
    protected array $resources = [
        WeatherGuidelinesResource::class,
    ];
}

資源名稱、標題與描述

預設情況下,資源的名稱和標題是根據類別名稱衍生而來的。例如,WeatherGuidelinesResource 將具有 weather-guidelines 的名稱和 Weather Guidelines Resource 的標題。您可以使用 NameTitle 屬性來自定義這些值:

php
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;

#[Name('weather-api-docs')]
#[Title('Weather API Documentation')]
class WeatherGuidelinesResource extends Resource
{
    // ...
}

資源描述不會自動生成。您應該始終使用 Description 屬性提供有意義的描述:

php
use Laravel\Mcp\Server\Attributes\Description;

#[Description('Comprehensive guidelines for using the Weather API.')]
class WeatherGuidelinesResource extends Resource
{
    //
}

📌 備註

描述是資源元數據的關鍵部分,因為它能幫助 AI 模型理解何時以及如何有效地使用該資源。

資源模板

資源模板 使您的伺服器能夠公開與帶有變數的 URI 模式相匹配的動態資源。您不需要為每個資源定義靜態 URI,而是可以建立一個單一資源,根據模板模式處理多個 URI。

建立資源模板

要建立資源模板,請在您的資源類別中實作 HasUriTemplate 介面,並定義一個返回 UriTemplate 實例的 uriTemplate 方法:

php
<?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
{
    /**
     * Get the URI template for this resource.
     */
    public function uriTemplate(): UriTemplate
    {
        return new UriTemplate('file://users/{userId}/files/{fileId}');
    }

    /**
     * Handle the resource request.
     */
    public function handle(Request $request): Response
    {
        $userId = $request->get('userId');
        $fileId = $request->get('fileId');

        // Fetch and return the file content...

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

當資源實作 HasUriTemplate 介面時,它將被註冊為資源模板而非靜態資源。AI 客戶端隨後可以使用與模板模式相匹配的 URI 來請求資源,且 URI 中的變數將被自動提取,並可在資源的 handle 方法中使用。

URI 模板語法

URI 模板使用包含在花括號中的佔位符來定義 URI 中的變數片段:

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

存取模板變數

當 URI 與您的資源模板匹配時,提取的變數會自動合併到請求中,並可以使用 get 方法來存取:

php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Contracts\HasUriTemplate;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Support\UriTemplate;

class UserProfileResource extends Resource implements HasUriTemplate
{
    public function uriTemplate(): UriTemplate
    {
        return new UriTemplate('file://users/{userId}/profile');
    }

    public function handle(Request $request): Response
    {
        // Access the extracted variable
        $userId = $request->get('userId');

        // Access the full URI if needed
        $uri = $request->uri();

        // Fetch user profile...

        return Response::text("Profile for user {$userId}");
    }
}

Request 物件同時提供了提取的變數和被請求的原始 URI,讓您在處理資源請求時擁有完整的上下文。

資源 URI 與 MIME 類型

每個資源都由唯一的 URI 識別,並具有相關聯的 MIME 類型,以幫助 AI 客戶端理解資源的格式。

預設情況下,資源的 URI 是根據資源名稱生成的,因此 WeatherGuidelinesResource 將具有 weather://resources/weather-guidelines 的 URI。預設的 MIME 類型為 text/plain

您可以使用 UriMimeType 屬性來自定義這些值:

php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Attributes\Uri;
use Laravel\Mcp\Server\Resource;

#[Uri('weather://resources/guidelines')]
#[MimeType('application/pdf')]
class WeatherGuidelinesResource extends Resource
{
}

URI 和 MIME 類型能幫助 AI 客戶端決定如何適當地處理和解釋資源內容。

資源請求

與工具和提示詞不同,資源無法定義輸入結構或引數。然而,您仍然可以在資源的 handle 方法中與請求物件進行互動:

php
<?php

namespace App\Mcp\Resources;

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

class WeatherGuidelinesResource extends Resource
{
    /**
     * Handle the resource request.
     */
    public function handle(Request $request): Response
    {
        // ...
    }
}

資源依賴注入

Laravel 服務容器 被用於解析所有資源。因此,您可以在建構子中對資源可能需要的任何依賴項進行型別提示。宣告的依賴項將被自動解析並注入到資源實例中:

php
<?php

namespace App\Mcp\Resources;

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

class WeatherGuidelinesResource extends Resource
{
    /**
     * Create a new resource instance.
     */
    public function __construct(
        protected WeatherRepository $weather,
    ) {}

    // ...
}

除了建構子注入外,您也可以在資源的 handle 方法中對依賴項進行型別提示。當該方法被呼叫時,服務容器將自動解析並注入依賴項:

php
<?php

namespace App\Mcp\Resources;

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

class WeatherGuidelinesResource extends Resource
{
    /**
     * Handle the resource request.
     */
    public function handle(WeatherRepository $weather): Response
    {
        $guidelines = $weather->guidelines();

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

資源註解 (Annotations)

您可以使用 註解 (annotations) 來增強您的資源,以向 AI 用戶端提供額外的元數據。註解透過屬性 (attributes) 新增至資源中:

php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Enums\Role;
use Laravel\Mcp\Server\Annotations\Audience;
use Laravel\Mcp\Server\Annotations\LastModified;
use Laravel\Mcp\Server\Annotations\Priority;
use Laravel\Mcp\Server\Resource;

#[Audience(Role::User)]
#[LastModified('2025-01-12T15:00:58Z')]
#[Priority(0.9)]
class UserDashboardResource extends Resource
{
    //
}

可用的註解包括:

註解類型描述
#[Audience]Role 或陣列指定目標受眾 (Role::UserRole::Assistant 或兩者皆是)。
#[Priority]float介於 0.0 到 1.0 之間的數值分數,用以表示資源的重要性。
#[LastModified]string顯示資源上次更新時間的 ISO 8601 時間戳記。

條件式資源註冊

您可以透過在資源類別中實作 shouldRegister 方法,在執行時期條件式地註冊資源。此方法允許您根據應用程式狀態、設定或請求參數來決定資源是否應該可用:

php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Resource;

class WeatherGuidelinesResource extends Resource
{
    /**
     * Determine if the resource should be registered.
     */
    public function shouldRegister(Request $request): bool
    {
        return $request?->user()?->subscribed() ?? false;
    }
}

當資源的 shouldRegister 方法回傳 false 時,它將不會出現在可用資源列表中,且 AI 用戶端無法存取該資源。

資源回應

資源必須回傳一個 Laravel\Mcp\Response 的實例。Response 類別提供了幾個便捷方法來建立不同類型的回應:

對於簡單的文字內容,請使用 text 方法:

php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the resource request.
 */
public function handle(Request $request): Response
{
    // ...

    return Response::text($weatherData);
}

Blob 回應

若要回傳 Blob 內容,請使用 blob 方法並提供 Blob 內容:

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

回傳 Blob 內容時,MIME 類型將由您的資源所設定的 MIME 類型決定:

php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Resource;

#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
    //
}

錯誤回應

若要表示在資源擷取過程中發生錯誤,請使用 error() 方法:

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

元數據 (Metadata)

Laravel MCP 同樣支援 MCP 規範 中定義的 _meta 欄位,這是一些 MCP 用戶端或整合功能所要求的。元數據可以應用於所有 MCP 基礎元件 (primitives),包括工具、資源和提示詞,以及它們的回應。

您可以使用 withMeta 方法將元數據附加到單個回應內容中:

php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 */
public function handle(Request $request): Response
{
    return Response::text('The weather is sunny.')
        ->withMeta(['source' => 'weather-api', 'cached' => true]);
}

對於適用於整個回應封包 (response envelope) 的結果級元數據,請使用 Response::make 包裝您的回應,並在回傳的回應工廠實例上呼叫 withMeta

php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;

/**
 * Handle the tool request.
 */
public function handle(Request $request): ResponseFactory
{
    return Response::make(
        Response::text('The weather is sunny.')
    )->withMeta(['request_id' => '12345']);
}

若要將元數據附加到工具、資源或提示詞本身,請在類別中定義 $meta 屬性:

php
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;

#[Description('Fetches the current weather forecast.')]
class CurrentWeatherTool extends Tool
{
    protected ?array $meta = [
        'version' => '2.0',
        'author' => 'Weather Team',
    ];

    // ...
}

認證

就像路由一樣,您可以使用中介層來為 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 路由:

php
use App\Mcp\Servers\WeatherExample;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

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

新安裝 Passport

如果您的應用程式尚未使用 Laravel Passport,請參考 Passport 的 安裝與部署指南 將 Passport 加入您的應用程式。在繼續之前,您應該已經擁有 OAuthenticatable 模型、新的認證守衛以及 Passport 金鑰。

接下來,您應該發布 Laravel MCP 提供的 Passport 授權視圖:

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

然後,使用 Passport::authorizationView 方法指示 Passport 使用此視圖。通常,此方法應該在應用程式 AppServiceProviderboot 方法中呼叫:

php
use Laravel\Passport\Passport;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::authorizationView(function ($parameters) {
        return view('mcp.authorize', $parameters);
    });
}

此視圖將在認證期間顯示給最終使用者,以拒絕或批准 AI 代理的認證嘗試。

授權畫面範例

📌 備註

在此情境中,我們僅將 OAuth 作為底層可認證模型的轉換層。我們忽略了 OAuth 的許多面向,例如範圍 (scopes)。

使用現有的 Passport 安裝

如果您的應用程式已經在使用 Laravel Passport,Laravel MCP 應該能與您現有的 Passport 安裝無縫協作,但目前不支援自定義範圍 (custom scopes),因為 OAuth 主要被用作底層可認證模型的轉換層。

Laravel MCP 透過上述的 Mcp::oauthRoutes 方法,新增、宣告並使用單一的 mcp:use 範圍。

Passport vs. Sanctum

OAuth2.1 是 Model Context Protocol 規範中定義的認證機制,也是 MCP 用戶端中支援最廣泛的機制。因此,我們建議盡可能使用 Passport。

如果您的應用程式已經在使用 Sanctum,那麼加入 Passport 可能會比較繁瑣。在這種情況下,我們建議在您有明確且必要的需求必須使用僅支援 OAuth 的 MCP 用戶端之前,先在不使用 Passport 的情況下使用 Sanctum。

Sanctum

如果您想使用 Sanctum 來保護您的 MCP 伺服器,只需在 routes/ai.php 檔案中將 Sanctum 的認證中介層添加到您的伺服器即可。然後,請確保您的 MCP 用戶端提供 Authorization: Bearer <token> 標頭以確保認證成功:

php
use App\Mcp\Servers\WeatherExample;
use Laravel\Mcp\Facades\Mcp;

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

Custom MCP Authentication

如果您的應用程式發行自定義的 API 令牌,您可以透過將任何您想要的中介層分配給 Mcp::web 路由來對您的 MCP 伺服器進行認證。您的自定義中介層可以手動檢查 Authorization 標頭,以認證傳入的 MCP 請求。

授權

您可以透過 $request->user() 方法存取目前已認證的使用者,讓您能在 MCP 工具和資源中執行 授權檢查

php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 */
public function handle(Request $request): Response
{
    if (! $request->user()->can('read-weather')) {
        return Response::error('Permission denied.');
    }

    // ...
}

測試伺服器

您可以使用內建的 MCP Inspector 或撰寫單元測試來測試您的 MCP 伺服器。

MCP Inspector

MCP Inspector 是一個用於測試和除錯 MCP 伺服器的互動式工具。您可以使用它來連接伺服器、驗證認證,並嘗試使用工具、資源和提示詞。

您可以針對任何已註冊的伺服器執行檢查器:

shell
# Web server...
php artisan mcp:inspector mcp/weather

# Local server named "weather"...
php artisan mcp:inspector weather

此命令會啟動 MCP Inspector 並提供客戶端設定,您可以將其複製到您的 MCP 客戶端中,以確保所有設定正確無誤。如果您的 Web 伺服器受到認證中介層保護,請確保在連接時包含必要的標頭,例如 Authorization 承載令牌 (bearer token)。

單元測試

您可以為您的 MCP 伺服器、工具、資源和提示詞撰寫單元測試。

首先,建立一個新的測試案例,並在註冊該基礎元件的伺服器上調用它。例如,要測試 WeatherServer 上的工具:

php
test('tool', function () {
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'New York City',
        'units' => 'fahrenheit',
    ]);

    $response
        ->assertOk()
        ->assertSee('The current weather in New York City is 72°F and sunny.');
});
php
/**
 * Test a tool.
 */
public function test_tool(): void
{
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'New York City',
        'units' => 'fahrenheit',
    ]);

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

同樣地,您也可以測試提示詞和資源:

php
$response = WeatherServer::prompt(...);
$response = WeatherServer::resource(...);

您也可以在調用基礎元件之前串接 actingAs 方法,以模擬已認證的使用者:

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

收到回應後,您可以使用各種斷言方法來驗證回應的內容和狀態。

您可以使用 assertOk 方法斷言回應是否成功。這會檢查回應是否沒有任何錯誤:

php
$response->assertOk();

您可以使用 assertSee 方法斷言回應是否包含特定文字:

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

您可以使用 assertHasErrors 方法斷言回應是否包含錯誤:

php
$response->assertHasErrors();

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

您可以使用 assertHasNoErrors 方法斷言回應不包含任何錯誤:

php
$response->assertHasNoErrors();

您可以使用 assertName()assertTitle()assertDescription() 方法斷言回應是否包含特定的元數據 (metadata):

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

您可以使用 assertSentNotificationassertNotificationCount 方法斷言通知是否已發送:

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

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

$response->assertNotificationCount(5);

最後,如果您想檢查原始回應內容,可以使用 dddump 方法輸出回應以進行除錯:

php
$response->dd();
$response->dump();