0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LaravelでMCPサーバを作って、Difyで使用するまで

Last updated at Posted at 2025-09-30

はじめに

Laravel MCP を使ってみるテスト

https://laravel.com/docs/12.x/mcp
https://readouble.com/laravel/12.x/ja/mcp.html

MCPサーバを構成する要素であるPrompts/Resources/Toolsをそれぞれ作る方法を試した。

準備

dockerでnginx/laravel/mysqlの構成のコンテナ一式を作成

http://localhost:8086で接続できる状態からスタートした(portは適当)

上から順に進めてみる

インストール

composer require laravel/mcp

routeの作成

routes/ai.php が生成された

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

サーバの作成

app/Mcp/Servers/WeatherServer.php が生成された

php artisan make:mcp-server WeatherServer

routes/ai.php にルートを登録

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

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

webサーバとローカルサーバの2種類があるらしいが、今回は未検証。

接続を試してみる

MCP Inspectorを使って確認してみる

image.png

接続できた

image.png

Tools

php artisan make:mcp-tool CurrentWeatherTool

サーバに登録する

    /**
     * このMCPサーバで登録するツール
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
     */
    protected array $tools = [
        CurrentWeatherTool::class,
    ];

MCP Inspectorで確認

image.png

設定しているtitleとdescriptionが表示されていることを確認

image.png

toolを選択すると、定義されたスキーマも確認できる

image.png

適当に入力してレスポンスが正常に帰ってくることも確認できた

image.png

スキーマを増やしてみる

公式だとarrayだけど、stringでないとエラーが出た(´・ω・`)

    /**
     * ツールの入力スキーマの取得
     *
     * @return array<string, \Illuminate\JsonSchema\JsonSchema>
     */
    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'),
        ];
    }

image.png

バリデーション

laravelでよくみるバリデーションの書き方が使える

    /**
     * ツールリクエストの処理
     */
    public function handle(Request $request): Response
    {
        $validated = $request->validate([
            'location' => ['required','string','max:100'],
            'units' => 'in:celsius,fahrenheit',
        ],[
            'location.required' => 'You must specify a location to get the weather for. For example, "New York City" or "Tokyo".',
            'units.in' => 'You must specify either "celsius" or "fahrenheit" for the units.',
        ]);

        $location = $validated['location'];
        $units = $validated['units'] ?? 'celsius';
        $unit = $units === 'fahrenheit' ? '°F' : '°C';

        // 天気を取得...

        return Response::text("The $location's weather is ... $unit");
    }

エラーメッセージは詳しく書いてあげると親切

image.png

条件付きツール

ツールが利用可能であるかどうかを設定できる。

権限や購読状態で切り分けるみたいな使い方なんだろう。

    /**
     * ツールを登録すべきか判定
     */
    public function shouldRegister(Request $request): bool
    {
        return false;
    }

レスポンス

複数のテキストを返すこともできるし

    /**
     * ツールリクエストの処理
     */
    public function handle(Request $request, WeatherRepository $weatherRepository): array
    {
        $forecast = $weatherRepository->getCurrentWeather($request);

        return [
            Response::text($forecast),
            Response::text('hogehoge')
        ];
    }

ストリーミングレスポンスも返せることを確認

    /**
     * ツールリクエストの処理
     *
     * @return \Generator<int, \Laravel\Mcp\Response>
     */
    public function handle(Request $request, WeatherRepository $weatherRepository): Generator
    {
        $forecast = $weatherRepository->getCurrentWeather($request);

        foreach (range(1, 3) as $num) {
            yield Response::notification('processing/progress', [
                'num' => $num,
            ]);

            sleep(1);
        }

        yield Response::text($forecast);
    }

10回回すようにしたらInspectorタイムアウトになった...どこで管理できるんだろう(要確認)

依存性の注入

上の例でちゃっかり出てきている WeatherRepository は、処理を外に切り出したもの。

<?php

namespace App\Repositories;

use Laravel\Mcp\Request;

class WeatherRepository
{
    public function getCurrentWeather(Request $request): string
    {
        $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.',
        ]);

        $location = $validated['location'];
        $units = $validated['units'] ?? 'celsius';
        $unitSymbol = $units === 'fahrenheit' ? '°F' : '°C';

        $value = mt_rand(1, 100);

        return "The {$location}'s weather is $value ({$unitSymbol})";
    }
}

Prompts

app/Mcp/Prompts/DescribeWeatherPrompt.php が生成された

php artisan make:mcp-prompt DescribeWeatherPrompt

サーバに登録する

    /**
     * このMCPサーバで登録するプロンプト
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
     */
    protected array $prompts = [
        DescribeWeatherPrompt::class,
    ];

Inspectorで確認

image.png

プロンプトもツールと同様、Request/バリデーション/条件付き/DI/Responseの説明が書かれているだけだったので割愛。

Resources

app/Mcp/Resources/WeatherGuidelinesResource.php が生成された

php artisan make:mcp-resource WeatherGuidelinesResource

サーバに登録

    /**
     * このMCPサーバで登録するリソース
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
     */
    protected array $resources = [
        WeatherGuidelinesResource::class,
    ];

Inspectorで確認

image.png

name/title/descriptionは自分で設定が必要。

uriとmimeTypeがデフォルトで指定されているのが確認できる。

リソースも同様に、Request/バリデーション/条件付き/DI/Responseの説明が書かれている。

レスポンスには Response::textResponse::blob の2種類があって、mimeTypeでどんなレスポンスなのかを教える必要があるらしい。

使ってみる

エージェントで先ほど作成したMCPサーバを使ってみる

image.png

image.png

実行すると、レスポンスがちゃんと返ってきていることが確認できた

image.png

(ランダムな数字で温度を返してくるようにしているから、大変なことになっている...)

(そして、ずっとweatherって言ってるのに温度しか返してないな...)

おわりに

artisanコマンドで作成して型に沿って実装すれば、MCPサーバが簡単に実装できることが分かった。

今まではREST APIをインターフェースとしてデータのやりとりをすることばかりを考えてきたけど、MCPで接続してくることを前提としたシステムも構築できないといけなくなるんだろう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?