はじめに
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を使って確認してみる
接続できた
Tools
php artisan make:mcp-tool CurrentWeatherTool
サーバに登録する
/**
* このMCPサーバで登録するツール
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
CurrentWeatherTool::class,
];
MCP Inspectorで確認
設定しているtitleとdescriptionが表示されていることを確認
toolを選択すると、定義されたスキーマも確認できる
適当に入力してレスポンスが正常に帰ってくることも確認できた
スキーマを増やしてみる
公式だと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'),
];
}
バリデーション
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");
}
エラーメッセージは詳しく書いてあげると親切
条件付きツール
ツールが利用可能であるかどうかを設定できる。
権限や購読状態で切り分けるみたいな使い方なんだろう。
/**
* ツールを登録すべきか判定
*/
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で確認
プロンプトもツールと同様、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で確認
name/title/descriptionは自分で設定が必要。
uriとmimeTypeがデフォルトで指定されているのが確認できる。
リソースも同様に、Request/バリデーション/条件付き/DI/Responseの説明が書かれている。
レスポンスには Response::text
と Response::blob
の2種類があって、mimeTypeでどんなレスポンスなのかを教える必要があるらしい。
使ってみる
エージェントで先ほど作成したMCPサーバを使ってみる
実行すると、レスポンスがちゃんと返ってきていることが確認できた
(ランダムな数字で温度を返してくるようにしているから、大変なことになっている...)
(そして、ずっとweatherって言ってるのに温度しか返してないな...)
おわりに
artisanコマンドで作成して型に沿って実装すれば、MCPサーバが簡単に実装できることが分かった。
今まではREST APIをインターフェースとしてデータのやりとりをすることばかりを考えてきたけど、MCPで接続してくることを前提としたシステムも構築できないといけなくなるんだろう。