Laravel MCP
번역일: 2026년 6월 20일
Laravel MCP
소개
Laravel MCP는 AI 클라이언트가 Model Context Protocol을 통해 Laravel 애플리케이션과 상호작용할 수 있도록 간결하고 우아한 방법을 제공합니다. 서버, 도구, 리소스, 프롬프트를 표현력 있는 플루언트 인터페이스로 정의하여 AI 기반의 상호작용을 쉽게 구성할 수 있습니다.
설치
Composer를 사용해 Laravel MCP를 프로젝트에 설치합니다:
composer require laravel/mcp라우트 파일 게시
설치 후, vendor:publish Artisan 명령어를 실행하여 MCP 서버를 정의할 routes/ai.php 파일을 게시합니다:
php artisan vendor:publish --tag=ai-routes이 명령어를 실행하면 애플리케이션의 routes 디렉터리에 routes/ai.php 파일이 생성됩니다. 이 파일에서 MCP 서버를 등록하게 됩니다.
서버 생성
make:mcp-server Artisan 명령어로 MCP 서버를 생성할 수 있습니다. 서버는 AI 클라이언트에 도구, 리소스, 프롬프트 등의 MCP 기능을 노출하는 중심 통신 지점 역할을 합니다:
php artisan make:mcp-server WeatherServer이 명령어를 실행하면 app/Mcp/Servers 디렉터리에 새 서버 클래스가 생성됩니다. 생성된 클래스는 Laravel MCP의 기본 클래스인 Laravel\Mcp\Server를 상속하며, 도구, 리소스, 프롬프트를 등록하는 속성을 제공합니다:
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 이 MCP 서버에 등록된 도구 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
// ExampleTool::class,
];
/**
* 이 MCP 서버에 등록된 리소스 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
*/
protected array $resources = [
// ExampleResource::class,
];
/**
* 이 MCP 서버에 등록된 프롬프트 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
*/
protected array $prompts = [
// ExamplePrompt::class,
];
}서버 등록
서버를 생성한 뒤에는 routes/ai.php 파일에 등록해야 외부에서 접근할 수 있습니다. Laravel MCP는 두 가지 등록 방식을 제공합니다. web은 HTTP로 접근 가능한 서버, local은 커맨드라인 서버용입니다.
웹 서버
웹 서버는 가장 일반적인 형태로, HTTP POST 요청을 통해 접근할 수 있습니다. 원격 AI 클라이언트나 웹 기반 통합에 적합합니다. web 메서드로 등록합니다:
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/weather', WeatherServer::class);일반 라우트와 마찬가지로 미들웨어를 적용하여 서버를 보호할 수 있습니다:
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware(['throttle:mcp']);로컬 서버
로컬 서버는 Artisan 명령어로 실행되며, 개발, 테스트, 또는 로컬 AI 어시스턴트 연동에 적합합니다. local 메서드로 등록합니다:
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::local('weather', WeatherServer::class);등록 후, 일반적으로 mcp:start 명령어를 직접 실행할 필요는 없습니다. MCP 클라이언트(AI 에이전트)가 필요할 때 서버를 자동으로 시작하고 종료하도록 설정하면 됩니다:
php artisan mcp:start weather도구 (Tools)
도구는 AI 클라이언트가 호출할 수 있는 기능을 서버에 노출합니다. 언어 모델이 특정 동작을 수행하거나, 코드를 실행하거나, 외부 시스템과 상호작용할 수 있도록 합니다.
도구 생성
make:mcp-tool Artisan 명령어로 도구를 생성합니다:
php artisan make:mcp-tool CurrentWeatherTool도구를 생성한 뒤에는 서버의 $tools 속성에 등록합니다:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 이 MCP 서버에 등록된 도구 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
CurrentWeatherTool::class,
];
}도구 이름, 제목, 설명
기본적으로 도구의 이름과 제목은 클래스 이름에서 자동으로 생성됩니다. 예를 들어 CurrentWeatherTool은 이름이 current-weather, 제목이 Current Weather Tool이 됩니다. $name과 $title 속성을 정의하여 이 값을 직접 지정할 수 있습니다:
class CurrentWeatherTool extends Tool
{
/**
* 도구 이름.
*/
protected string $name = 'get-optimistic-weather';
/**
* 도구 제목.
*/
protected string $title = 'Get Optimistic Weather Forecast';
// ...
}도구 설명은 자동으로 생성되지 않습니다. $description 속성을 정의하여 항상 의미 있는 설명을 제공하세요:
class CurrentWeatherTool extends Tool
{
/**
* 도구 설명.
*/
protected string $description = '지정한 위치의 현재 날씨 예보를 가져옵니다.';
//
}NOTE
설명은 도구 메타데이터의 핵심 요소입니다. AI 모델이 이 도구를 언제, 어떻게 사용해야 하는지 이해하는 데 직접적으로 활용됩니다.
도구 입력 스키마
도구는 입력 스키마를 정의하여 AI 클라이언트로부터 어떤 인수를 받을지 명시할 수 있습니다. Illuminate\JsonSchema\JsonSchema 빌더를 사용하여 입력 요구사항을 정의합니다:
<?php
namespace App\Mcp\Tools;
use Illuminate\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 도구 입력 스키마 반환.
*
* @return array<string, JsonSchema>
*/
public function schema(JsonSchema $schema): array
{
return [
'location' => $schema->string()
->description('날씨를 조회할 위치.')
->required(),
'units' => $schema->enum(['celsius', 'fahrenheit'])
->description('온도 단위.')
->default('celsius'),
];
}
}도구 인수 유효성 검사
JSON 스키마 정의는 인수의 기본 구조를 제공하지만, 더 복잡한 유효성 검사 규칙이 필요할 수도 있습니다.
Laravel MCP는 Laravel의 유효성 검사 기능과 자연스럽게 통합됩니다. 도구의 handle 메서드 안에서 인수를 검사할 수 있습니다:
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 도구 요청 처리.
*/
public function handle(Request $request): Response
{
$validated = $request->validate([
'location' => 'required|string|max:100',
'units' => 'in:celsius,fahrenheit',
]);
// 검사된 인수로 날씨 데이터 조회...
}
}유효성 검사 실패 시 AI 클라이언트는 제공된 오류 메시지를 바탕으로 동작합니다. 따라서 명확하고 조치 가능한 오류 메시지를 작성하는 것이 매우 중요합니다:
$validated = $request->validate([
'location' => ['required', 'string', 'max:100'],
'units' => 'in:celsius,fahrenheit',
], [
'location.required' => '날씨를 조회할 위치를 지정해야 합니다. 예: "서울" 또는 "부산".',
'units.in' => '온도 단위는 "celsius" 또는 "fahrenheit" 중 하나여야 합니다.',
]);도구 의존성 주입
모든 도구는 Laravel 서비스 컨테이너를 통해 resolve됩니다. 생성자에 타입 힌트를 선언하면 의존성이 자동으로 주입됩니다:
<?php
namespace App\Mcp\Tools;
use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 새 도구 인스턴스 생성.
*/
public function __construct(
protected WeatherRepository $weather,
) {}
// ...
}생성자 주입 외에도, handle() 메서드에 타입 힌트를 추가하면 서비스 컨테이너가 해당 의존성을 자동으로 주입합니다:
<?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 handle(Request $request, WeatherRepository $weather): Response
{
$location = $request->get('location');
$forecast = $weather->getForecastFor($location);
// ...
}
}도구 어노테이션
PHP 어트리뷰트(attribute)를 사용하여 도구에 어노테이션을 추가하면 AI 클라이언트에 도구의 동작과 특성에 관한 추가 메타데이터를 전달할 수 있습니다:
<?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 | 도구가 외부 엔티티와 상호작용할 수 있음을 나타냅니다. |
조건부 도구 등록
도구 클래스에 shouldRegister 메서드를 구현하면 런타임에 도구를 조건부로 등록할 수 있습니다. 애플리케이션 상태, 설정, 또는 요청 정보에 따라 도구 노출 여부를 동적으로 결정할 수 있습니다:
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 도구를 등록할지 여부 결정.
*/
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}shouldRegister 메서드가 false를 반환하면 해당 도구는 사용 가능한 도구 목록에 표시되지 않으며, AI 클라이언트에서 호출할 수 없습니다.
도구 응답
도구는 Laravel\Mcp\Response 인스턴스를 반환해야 합니다. Response 클래스는 다양한 유형의 응답을 생성하는 편리한 메서드를 제공합니다.
단순 텍스트 응답은 text 메서드를 사용합니다:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 도구 요청 처리.
*/
public function handle(Request $request): Response
{
// ...
return Response::text('날씨 요약: 맑음, 22°C');
}도구 실행 중 오류가 발생했음을 나타내려면 error 메서드를 사용합니다:
return Response::error('날씨 데이터를 가져올 수 없습니다. 잠시 후 다시 시도하세요.');복수 콘텐츠 응답
도구는 Response 인스턴스 배열을 반환하여 여러 콘텐츠를 함께 전달할 수 있습니다:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 도구 요청 처리.
*
* @return array<int, \Laravel\Mcp\Response>
*/
public function handle(Request $request): array
{
// ...
return [
Response::text('날씨 요약: 맑음, 22°C'),
Response::text("**상세 예보**\n- 오전: 18°C\n- 오후: 25°C\n- 저녁: 21°C"),
];
}스트리밍 응답
장시간 실행되는 작업이나 실시간 데이터 스트리밍의 경우, handle 메서드에서 제너레이터(generator)를 반환하면 최종 응답 전에 중간 업데이트를 클라이언트에 전송할 수 있습니다:
<?php
namespace App\Mcp\Tools;
use Generator;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 도구 요청 처리.
*
* @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));
}
}
}웹 기반 서버에서 스트리밍 응답을 사용하면 SSE(Server-Sent Events) 스트림이 자동으로 열리고, yield된 각 메시지가 이벤트로 클라이언트에 전송됩니다.
프롬프트 (Prompts)
프롬프트는 AI 클라이언트가 언어 모델과 상호작용할 때 사용할 수 있는 재사용 가능한 프롬프트 템플릿을 서버에서 공유하는 기능입니다. 일반적인 질의와 상호작용을 표준화된 방식으로 구성할 수 있습니다.
프롬프트 생성
make:mcp-prompt Artisan 명령어로 프롬프트를 생성합니다:
php artisan make:mcp-prompt DescribeWeatherPrompt생성 후에는 서버의 $prompts 속성에 등록합니다:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Prompts\DescribeWeatherPrompt;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 이 MCP 서버에 등록된 프롬프트 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
*/
protected array $prompts = [
DescribeWeatherPrompt::class,
];
}프롬프트 이름, 제목, 설명
기본적으로 프롬프트의 이름과 제목은 클래스 이름에서 자동으로 생성됩니다. 예를 들어 DescribeWeatherPrompt는 이름이 describe-weather, 제목이 Describe Weather Prompt가 됩니다. $name과 $title 속성으로 직접 지정할 수 있습니다:
class DescribeWeatherPrompt extends Prompt
{
/**
* 프롬프트 이름.
*/
protected string $name = 'weather-assistant';
/**
* 프롬프트 제목.
*/
protected string $title = 'Weather Assistant Prompt';
// ...
}프롬프트 설명은 자동으로 생성되지 않습니다. $description 속성을 정의하여 항상 의미 있는 설명을 제공하세요:
class DescribeWeatherPrompt extends Prompt
{
/**
* 프롬프트 설명.
*/
protected string $description = '지정한 위치의 날씨를 자연어로 설명하는 프롬프트를 생성합니다.';
//
}NOTE
설명은 프롬프트 메타데이터의 핵심 요소입니다. AI 모델이 이 프롬프트를 언제, 어떻게 활용하면 좋은지 이해하는 데 직접 활용됩니다.
프롬프트 인수
프롬프트는 인수를 정의하여 AI 클라이언트가 템플릿을 특정 값으로 커스터마이즈할 수 있게 합니다. arguments 메서드로 허용할 인수를 정의합니다:
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Prompts\Argument;
class DescribeWeatherPrompt extends Prompt
{
/**
* 프롬프트 인수 반환.
*
* @return array<int, \Laravel\Mcp\Server\Prompts\Argument>
*/
public function arguments(): array
{
return [
new Argument(
name: 'tone',
description: '날씨 설명에 사용할 어조 (예: formal, casual, humorous).',
required: true,
),
];
}
}프롬프트 인수 유효성 검사
프롬프트 인수는 정의에 따라 기본 유효성 검사가 수행되지만, 더 복잡한 규칙이 필요할 수도 있습니다.
Laravel MCP는 Laravel의 유효성 검사 기능과 자연스럽게 통합됩니다. 프롬프트의 handle 메서드 안에서 인수를 검사할 수 있습니다:
<?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'];
// 지정된 어조로 프롬프트 응답 생성...
}
}유효성 검사 실패 시 AI 클라이언트는 제공된 오류 메시지를 바탕으로 동작합니다. 명확하고 조치 가능한 오류 메시지를 작성하는 것이 중요합니다:
$validated = $request->validate([
'tone' => ['required', 'string', 'max:50'],
], [
'tone.*' => '날씨 설명에 사용할 어조를 지정해야 합니다. 예: "formal", "casual", "humorous".',
]);프롬프트 의존성 주입
모든 프롬프트는 Laravel 서비스 컨테이너를 통해 resolve됩니다. 생성자에 타입 힌트를 선언하면 의존성이 자동으로 주입됩니다:
<?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
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
{
/**
* 프롬프트 요청 처리.
*/
public function handle(Request $request, WeatherRepository $weather): Response
{
$isAvailable = $weather->isServiceAvailable();
// ...
}
}조건부 프롬프트 등록
프롬프트 클래스에 shouldRegister 메서드를 구현하면 런타임에 프롬프트를