Laravel MCP
업데이트됨번역일: 2026년 9월 15일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 9월 15일
- 번역 갱신
- 2026년 9월 15일
Laravel MCP
MCP
소개
Laravel MCP는 모델 컨텍스트 프로토콜(Model Context Protocol)을 통해 AI 클라이언트가 라라벨 애플리케이션과 상호작용할 수 있도록 간단하고 우아한 방법을 제공하는 패키지입니다. 서버, 도구(Tool), 리소스, 프롬프트를 정의할 수 있는 유연하고 직관적인 인터페이스를 통해, AI가 여러분의 애플리케이션과 직접 상호작용하도록 만들 수 있습니다.
NOTE
본격적으로 시작하기에 앞서, MCP가 낯선 개념이라면 먼저 이렇게 이해하면 쉽습니다. MCP는 ChatGPT나 Claude 같은 AI 클라이언트가 단순히 텍스트를 주고받는 것을 넘어, 여러분의 애플리케이션이 제공하는 "도구(Tool)"를 실행하거나 "리소스"를 조회할 수 있게 해주는 표준 규격입니다. 예를 들어 AI에게 "이번 달 매출 상위 주문 5건을 보여줘"라고 요청하면, MCP 서버가 이를 실제 Eloquent 쿼리로 변환해 실행하고 결과를 돌려주는 식입니다.
설치
먼저 Composer 패키지 매니저를 사용해 프로젝트에 Laravel MCP를 설치합니다:
composer require laravel/mcp라우트 파일 게시하기
Laravel MCP를 설치한 후에는 vendor:publish Artisan 명령어를 실행해 MCP 서버를 정의할 routes/ai.php 파일을 게시해야 합니다:
php artisan vendor:publish --tag=ai-routes이 명령어를 실행하면 애플리케이션의 routes 디렉터리에 routes/ai.php 파일이 생성되며, 이 파일에서 MCP 서버를 등록하게 됩니다.
NOTE
라우트 파일이라는 이름이 붙어 있지만, routes/ai.php는 일반적인 웹 라우트 파일과는 조금 다른 역할을 합니다. HTTP 요청을 라우팅하는 대신, AI 에이전트가 사용할 MCP 서버와 그 서버가 제공하는 도구(tool), 리소스(resource), 프롬프트(prompt)를 등록하는 곳입니다.
서버 생성하기
make:mcp-server Artisan 명령어를 사용하면 MCP 서버를 생성할 수 있습니다. 서버는 도구(tool), 리소스(resource), 프롬프트(prompt) 같은 MCP 기능을 AI 클라이언트에 노출하는 중심 통신 지점 역할을 합니다.
php artisan make:mcp-server WeatherServer이 명령어를 실행하면 app/Mcp/Servers 디렉터리에 새로운 서버 클래스가 생성됩니다. 생성된 서버 클래스는 Laravel MCP의 기본 클래스인 Laravel\Mcp\Server를 상속하며, 서버를 설정하거나 도구·리소스·프롬프트를 등록하기 위한 속성(attribute)과 프로퍼티를 제공합니다.
<?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,
];
}NOTE
위 예시의 #[Name], #[Version], #[Instructions] 속성은 각각 서버의 이름, 버전, AI 클라이언트에게 전달할 설명(사용 지침)을 정의합니다. AI 에이전트는 이 정보를 참고해 언제, 어떻게 이 서버의 기능을 사용할지 판단하므로 되도록 명확하게 작성하는 것이 좋습니다.
서버 등록하기
서버를 생성한 뒤에는 반드시 routes/ai.php 파일에 등록해야 실제로 사용할 수 있습니다. Laravel MCP는 서버를 등록하는 두 가지 방법을 제공합니다. HTTP로 접근 가능한 서버를 위한 web과, 커맨드라인 서버를 위한 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 명령어 형태로 실행되며, Laravel Boost와 같이 로컬 환경에서 동작하는 AI 어시스턴트 연동을 구축할 때 적합합니다. local 메서드로 로컬 서버를 등록합니다.
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::local('weather', WeatherServer::class);로컬 서버를 등록한 후에는 일반적으로 mcp:start Artisan 명령어를 직접 실행할 필요가 없습니다. 대신 사용 중인 MCP 클라이언트(AI 에이전트)가 서버를 실행하도록 설정하거나, MCP Inspector를 이용해 테스트하면 됩니다.
캐시 힌트(Cache Hints)
Laravel MCP는 서버 디스커버리, 프리미티브(도구·리소스·프롬프트) 목록 조회, 리소스 읽기처럼 캐싱이 가능한 응답에 캐시 힌트를 포함해서 전달합니다. 기본적으로 이러한 응답은 private(비공개)로 표시되며 TTL(캐시 유효 시간)은 0밀리초로 설정됩니다.
Cacheable 속성을 사용하면 서버의 기본 캐시 힌트를 원하는 대로 커스터마이징할 수 있습니다.
use Laravel\Mcp\Enums\CacheScope;
use Laravel\Mcp\Server\Attributes\Cacheable;
#[Cacheable(ttlMs: 60_000, scope: CacheScope::Public)]
class WeatherServer extends Server
{
/**
* Get the cache hints for individual MCP methods.
*
* @return array<string, \Laravel\Mcp\Server\Attributes\Cacheable>
*/
protected function cacheHints(): array
{
return [
'tools/list' => new Cacheable(ttlMs: 30_000, scope: CacheScope::Public),
];
}
}CacheScope::Private는 동일한 인증 컨텍스트 내에서만 캐시된 응답을 재사용하도록 제한하며, CacheScope::Public은 여러 사용자 간에 응답을 공유할 수 있도록 허용합니다.
NOTE
캐시 힌트는 어디까지나 "권장 사항"일 뿐입니다. 실제로 응답을 캐시할지 여부는 MCP 클라이언트나 호스트가 최종적으로 결정합니다. 또한 cacheHints()에서 반환한 메서드별 힌트가 서버의 Cacheable 속성보다 우선 적용됩니다.
리소스 클래스에 직접 Cacheable 속성을 적용하면, 해당 리소스 하나에 한해 서버의 캐시 힌트를 재정의할 수 있습니다.
#[Cacheable(ttlMs: 300_000, scope: CacheScope::Public)]
class WeatherGuidelinesResource extends Resource
{
// ...
}리소스의 Cacheable 속성은 메서드별 힌트와 서버의 기본 힌트 모두보다 우선순위가 높습니다.
도구 (Tools)
도구(Tool)는 AI 클라이언트가 호출할 수 있는 기능을 서버에서 노출하는 컴포넌트입니다. 언어 모델이 특정 동작을 수행하거나, 코드를 실행하거나, 외부 시스템과 상호작용할 수 있도록 해줍니다:
<?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...');
}
/**
* 도구의 입력 스키마를 정의합니다.
*
* @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 명령어를 실행합니다:
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,
];
}검색 가능한 도구 카탈로그
도구가 많은 서버라면 모든 도구를 AI 클라이언트에 한꺼번에 노출하는 대신, 일부 도구를 검색 가능한 카탈로그(catalog)에 담아둘 수 있습니다. 검색 가능한 카탈로그는 두 가지 도구를 자동으로 노출합니다: 도구 이름·설명·입력 스키마를 기준으로 카탈로그를 검색하는 search_tools, 그리고 검색 결과로 반환된 도구 중 하나 이상을 실행하는 execute_tools입니다.
검색 가능한 카탈로그를 만들려면 서버의 $tools 속성에서 ToolSearch 클래스를 배열 키로 사용하면 됩니다:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Tools\CurrentWeatherTool;
use App\Mcp\Tools\HistoricalWeatherTool;
use App\Mcp\Tools\WeatherAlertsTool;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Tools\ToolSearch;
class WeatherServer extends Server
{
/**
* 이 MCP 서버에 등록된 도구 목록입니다.
*
* @var array<int|string, \Laravel\Mcp\Server\Tool|class-string<\Laravel\Mcp\Server\Tool>|array<int, \Laravel\Mcp\Server\Tool|class-string<\Laravel\Mcp\Server\Tool>>>
*/
protected array $tools = [
CurrentWeatherTool::class,
ToolSearch::class => [
HistoricalWeatherTool::class,
WeatherAlertsTool::class,
],
];
}위 예제에서 CurrentWeatherTool은 직접 노출되지만, 과거 날씨 조회 도구와 기상 특보 도구는 검색 가능한 카탈로그를 통해서만 접근할 수 있습니다. 카탈로그에 포함된 도구도 검색·실행 시 조건부 도구 등록 규칙이 그대로 적용됩니다.
execute_tools 한 번의 호출로 실행할 수 있는 최대 도구 개수와 최대 응답 크기는 각각 mcp.tool_search.max_tool_calls, mcp.tool_search.max_output_bytes 설정 값으로 제어합니다.
도구 이름, 제목, 설명
기본적으로 도구의 이름과 제목은 클래스명으로부터 자동 생성됩니다. 예를 들어 CurrentWeatherTool은 이름이 current-weather, 제목은 Current Weather Tool이 됩니다. Name, Title 속성(attribute)을 사용하면 이 값을 직접 지정할 수 있습니다:
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 속성을 사용해 항상 의미 있는 설명을 직접 작성해 주어야 합니다:
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
//
}NOTE
설명은 도구 메타데이터에서 매우 중요한 부분입니다. AI 모델이 이 도구를 언제, 어떻게 사용해야 하는지 판단하는 핵심 근거가 되기 때문입니다.
도구 입력 스키마
도구는 입력 스키마를 정의해 AI 클라이언트로부터 어떤 인자를 받을지 명시할 수 있습니다. Laravel의 Illuminate\Contracts\JsonSchema\JsonSchema 빌더를 사용해 도구의 입력 요구사항을 정의합니다:
<?php
namespace App\Mcp\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 도구의 입력 스키마를 정의합니다.
*
* @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'),
];
}
}도구 출력 스키마
도구는 출력 스키마를 정의해 응답 구조를 명시할 수 있습니다. 이는 응답을 파싱해서 사용해야 하는 AI 클라이언트와의 연동성을 높여줍니다. 출력 구조를 정의하려면 outputSchema 메서드를 사용합니다:
<?php
namespace App\Mcp\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;
class CurrentWeatherTool extends Tool
{
/**
* 도구의 출력 스키마를 정의합니다.
*
* @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 스키마는 도구 인자에 대한 기본적인 구조를 정의해 주지만, 더 복잡한 검증 규칙이 필요한 경우도 많습니다.
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 클라이언트는 여러분이 제공한 에러 메시지를 근거로 다음 행동을 결정합니다. 그러므로 명확하고 실행 가능한(actionable) 에러 메시지를 제공하는 것이 매우 중요합니다:
$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 서비스 컨테이너를 통해 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);
// ...
}
}도구 어노테이션
어노테이션(annotation)을 사용하면 AI 클라이언트에 추가 메타데이터를 전달해 도구의 동작 방식과 특성을 더 잘 이해시킬 수 있습니다. 어노테이션은 속성(attribute) 형태로 도구 클래스에 추가합니다:
<?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 | 해당 도구가 외부 개체(entity)와 상호작용할 수 있음을 나타냅니다. |
각 어노테이션은 boolean 인자를 전달해 값을 명시적으로 지정할 수도 있습니다:
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
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('Weather Summary: Sunny, 72°F');
}도구 실행 중 에러가 발생했음을 알리려면 error 메서드를 사용합니다:
return Response::error('Unable to fetch weather data. Please try again.');이미지나 오디오 콘텐츠를 반환하려면 image, audio 메서드를 사용합니다:
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 타입은 파일로부터 자동으로 감지됩니다:
return Response::fromStorage('weather/radar.png');필요하다면 사용할 디스크를 지정하거나 MIME 타입을 직접 지정할 수도 있습니다:
return Response::fromStorage('weather/radar.png', disk: 's3');
return Response::fromStorage('weather/radar.png', mimeType: 'image/webp');다중 콘텐츠 응답
Response 인스턴스의 배열을 반환하면 하나의 도구 호출에서 여러 콘텐츠를 함께 응답할 수 있습니다:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 도구 요청을 처리합니다.
*
* @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 메서드를 사용하면 구조화된 콘텐츠(structured content)를 반환할 수 있습니다. 이를 통해 AI 클라이언트가 파싱하기 쉬운 데이터를 제공하면서도, 하위 호환을 위해 JSON으로 인코딩된 텍스트 표현도 함께 유지됩니다:
return Response::structured([
'temperature' => 22.5,
'conditions' => 'Partly cloudy',
'humidity' => 65,
]);구조화된 콘텐츠와 함께 별도의 텍스트를 제공하고 싶다면, 응답 팩토리의 withStructuredContent 메서드를 사용합니다:
return Response::make(
Response::text('Weather is 22.5°C and sunny')
)->withStructuredContent([
'temperature' => 22.5,
'conditions' => 'Sunny',
]);스트리밍 응답
오래 걸리는 작업이나 실시간 데이터 스트리밍이 필요한 경우, 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));
}
}
}NOTE
웹 기반 서버를 사용하는 경우, 스트리밍 응답은 자동으로 SSE(Server-Sent Events) 스트림을 열어 yield로 전달되는 각 메시지를 이벤트 형태로 클라이언트에 순서대로 전송합니다.
프롬프트
프롬프트는 서버가 재사용 가능한 프롬프트 템플릿을 제공해 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 속성(Attribute)을 사용하면 이 값들을 원하는 대로 지정할 수 있습니다:
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('weather-assistant')]
#[Title('Weather Assistant Prompt')]
class DescribeWeatherPrompt extends Prompt
{
// ...
}프롬프트 설명은 자동으로 생성되지 않으므로, Description 속성을 사용해 항상 의미 있는 설명을 직접 작성해 주어야 합니다:
use Laravel\Mcp\Server\Attributes\Description;
#[Description('특정 지역의 날씨를 자연어로 설명하는 프롬프트입니다.')]
class DescribeWeatherPrompt extends Prompt
{
//
}NOTE
설명은 프롬프트 메타데이터에서 매우 중요한 부분입니다. AI 모델이 언제, 어떻게 해당 프롬프트를 활용해야 하는지 판단하는 데 결정적인 역할을 하기 때문입니다.
프롬프트 인자
프롬프트는 AI 클라이언트가 특정 값으로 템플릿을 커스터마이즈할 수 있도록 인자(argument)를 정의할 수 있습니다. 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: '날씨 설명에 사용할 어조입니다(예: 정중한, 친근한, 유머러스한 등).',
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 클라이언트는 여러분이 제공한 오류 메시지를 바탕으로 다음 행동을 결정하게 됩니다. 따라서 명확하고 실행 가능한(actionable) 오류 메시지를 제공하는 것이 매우 중요합니다:
$validated = $request->validate([
'tone' => ['required','string','max:50'],
],[
'tone.*' => '날씨 설명에 사용할 어조를 반드시 지정해야 합니다. 예: "정중한", "친근한", "유머러스한" 등.',
]);프롬프트 의존성 주입
모든 프롬프트는 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 메서드를 구현하면, 런타임에 조건에 따라 프롬프트를 등록할지 결정할 수 있습니다. 이 메서드를 활용하면 애플리케이션 상태, 설정 값, 요청 파라미터 등에 따라 프롬프트의 노출 여부를 제어할 수 있습니다:
<?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;
}
}shouldRegister 메서드가 false를 반환하면, 해당 프롬프트는 사용 가능한 프롬프트 목록에 나타나지 않으며 AI 클라이언트가 호출할 수도 없습니다.
프롬프트 응답
프롬프트는 단일 Laravel\Mcp\Response 인스턴스 또는 Laravel\Mcp\Response 인스턴스로 구성된 반복 가능한(iterable) 값을 반환할 수 있습니다. 이 응답 객체는 AI 클라이언트로 전송될 실제 콘텐츠를 담고 있습니다:
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;
class DescribeWeatherPrompt extends Prompt
{
/**
* 프롬프트 요청을 처리합니다.
*
* @return array<int, \Laravel\Mcp\Response>
*/
public function handle(Request $request): array
{
$tone = $request->string('tone');
$systemMessage = "당신은 유용한 날씨 도우미입니다. {$tone} 어조로 날씨를 설명해 주세요.";
$userMessage = "지금 뉴욕의 날씨는 어떤가요?";
return [
Response::text($systemMessage)->asAssistant(),
Response::text($userMessage),
];
}
}asAssistant() 메서드를 사용하면 해당 응답 메시지가 AI 어시스턴트로부터 온 것임을 나타낼 수 있습니다. 이 메서드를 호출하지 않은 일반 메시지는 사용자(user) 입력으로 취급됩니다.
MCP
리소스(Resources)
리소스를 사용하면 MCP 서버가 데이터나 콘텐츠를 노출하여, AI 클라이언트가 언어 모델과 상호작용할 때 이를 컨텍스트로 읽고 활용할 수 있도록 만들 수 있습니다. 문서, 설정값, 혹은 AI가 더 나은 응답을 하도록 도와주는 각종 정적/동적 데이터를 공유하는 용도로 사용됩니다.
NOTE
리소스는 "AI가 참고할 파일이나 데이터를 서버가 미리 준비해두는 것"이라고 생각하면 쉽습니다. 예를 들어 API 문서, 설정 파일, 로그 요약본 등을 리소스로 등록해두면, 클라이언트(예: Claude Desktop)가 필요할 때 이를 읽어서 답변에 활용할 수 있습니다.
MCP
리소스 만들기
리소스를 만들려면 make:mcp-resource Artisan 명령어를 실행합니다:
php artisan make:mcp-resource WeatherGuidelinesResource리소스를 만든 후에는 서버의 $resources 속성에 등록해야 합니다:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Resources\WeatherGuidelinesResource;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* 이 MCP 서버에 등록된 리소스 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
*/
protected array $resources = [
WeatherGuidelinesResource::class,
];
}리소스 이름, 제목, 설명
기본적으로 리소스의 이름과 제목은 클래스 이름에서 자동으로 생성됩니다. 예를 들어 WeatherGuidelinesResource는 이름이 weather-guidelines, 제목이 Weather Guidelines Resource가 됩니다. Name과 Title 속성(attribute)을 사용하면 이 값들을 직접 지정할 수 있습니다:
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 속성을 사용해 항상 의미 있는 설명을 직접 작성해 주세요:
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Comprehensive guidelines for using the Weather API.')]
class WeatherGuidelinesResource extends Resource
{
//
}NOTE
설명(description)은 리소스 메타데이터에서 매우 중요한 부분입니다. AI 모델이 이 리소스를 언제, 어떻게 활용해야 하는지 판단하는 데 핵심적인 근거가 되기 때문입니다.
리소스 템플릿
리소스 템플릿을 사용하면 변수를 포함한 URI 패턴과 매칭되는 동적 리소스를 서버에 노출할 수 있습니다. 리소스마다 고정된 URI를 정의하는 대신, 템플릿 패턴을 기반으로 여러 URI를 하나의 리소스에서 처리할 수 있습니다.
리소스 템플릿 만들기
리소스 템플릿을 만들려면 리소스 클래스에 HasUriTemplate 인터페이스를 구현하고, UriTemplate 인스턴스를 반환하는 uriTemplate 메서드를 정의합니다:
<?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
{
/**
* 이 리소스의 URI 템플릿을 반환합니다.
*/
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($content);
}
}리소스가 HasUriTemplate 인터페이스를 구현하면, 정적 리소스가 아니라 리소스 템플릿으로 등록됩니다. AI 클라이언트는 템플릿 패턴과 일치하는 URI로 리소스를 요청할 수 있으며, URI에서 추출된 변수들은 자동으로 handle 메서드에서 사용할 수 있게 됩니다.
URI 템플릿 문법
URI 템플릿은 중괄호로 감싼 플레이스홀더를 사용해 URI 내 가변 구간을 정의합니다:
new UriTemplate('file://users/{userId}');
new UriTemplate('file://users/{userId}/files/{fileId}');
new UriTemplate('https://api.example.com/{version}/{resource}/{id}');템플릿 변수 접근하기
URI가 리소스 템플릿과 일치하면, 추출된 변수들은 자동으로 요청 객체에 병합되며 get 메서드로 접근할 수 있습니다:
<?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
{
// 추출된 변수 접근
$userId = $request->get('userId');
// 필요하다면 전체 URI도 접근 가능
$uri = $request->uri();
// 사용자 프로필 조회...
return Response::text("Profile for user {$userId}");
}
}Request 객체는 추출된 변수뿐만 아니라 요청에 사용된 원본 URI도 함께 제공하므로, 리소스 요청을 처리할 때 필요한 전체 컨텍스트를 확인할 수 있습니다.
리소스 URI와 MIME 타입
각 리소스는 고유한 URI로 식별되며, AI 클라이언트가 리소스의 형식을 이해할 수 있도록 MIME 타입도 함께 갖습니다.
기본적으로 리소스의 URI는 리소스 이름을 기반으로 생성됩니다. 예를 들어 WeatherGuidelinesResource는 weather://resources/weather-guidelines라는 URI를 갖게 됩니다. 기본 MIME 타입은 text/plain입니다.
Uri와 MimeType 속성을 사용해 이 값들을 직접 지정할 수 있습니다:
<?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 클라이언트가 리소스 콘텐츠를 어떻게 처리하고 해석해야 하는지 판단하는 데 도움이 됩니다.
리소스 요청(Request)
도구(tool)나 프롬프트(prompt)와 달리, 리소스는 입력 스키마나 인자를 정의할 수 없습니다. 다만 리소스의 handle 메서드 안에서 요청 객체와 상호작용하는 것은 여전히 가능합니다:
<?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
{
// ...
}
}리소스 의존성 주입
모든 리소스는 라라벨 서비스 컨테이너를 통해 해석됩니다. 따라서 리소스 생성자에서 필요한 의존성을 타입힌트로 지정하면, 해당 의존성이 자동으로 해석되어 리소스 인스턴스에 주입됩니다:
<?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
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
{
/**
* 리소스 요청을 처리합니다.
*/
public function handle(WeatherRepository $weather): Response
{
$guidelines = $weather->guidelines();
return Response::text($guidelines);
}
}리소스 어노테이션
어노테이션을 사용하면 AI 클라이언트에게 추가적인 메타데이터를 제공할 수 있습니다. 어노테이션은 속성(attribute) 형태로 리소스에 추가합니다:
<?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::User, Role::Assistant, 또는 둘 다). |
#[Priority] | float | 리소스의 중요도를 나타내는 0.0~1.0 사이의 숫자 값입니다. |
#[LastModified] | string | 리소스가 마지막으로 수정된 시각을 나타내는 ISO 8601 형식의 타임스탬프입니다. |
조건부 리소스 등록
리소스 클래스에 shouldRegister 메서드를 구현하면 런타임에 조건부로 리소스를 등록할 수 있습니다. 이 메서드를 사용하면 애플리케이션 상태, 설정값, 요청 파라미터 등에 따라 리소스 노출 여부를 결정할 수 있습니다:
<?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;
}
}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($weatherData);
}리소스 링크 응답
리소스 링크를 반환하려면 resourceLink 메서드에 URI와 이름을 전달합니다. 리소스를 직접 포함(embed)하는 방식과 달리, 리소스 링크는 URI 포인터만 반환하며 AI 클라이언트가 이를 별도로 가져옵니다:
return Response::resourceLink(
uri: 'file:///data/report.json',
name: 'monthly-report',
mimeType: 'application/json',
);등록된 리소스 클래스나 인스턴스를 전달할 수도 있으며, 이 경우 해당 리소스의 URI, 이름, 제목, 설명, MIME 타입이 자동으로 그대로 사용됩니다:
return Response::resourceLink(new WeatherForecastResource);Blob 응답
블롭(blob) 콘텐츠를 반환하려면 blob 메서드에 블롭 콘텐츠를 전달합니다:
return Response::blob(file_get_contents(storage_path('weather/radar.png')));블롭 콘텐츠를 반환할 때, MIME 타입은 리소스에 설정된 값을 따릅니다:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Resource;
#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
//
}오류 응답
리소스 조회 중 오류가 발생했음을 알리려면 error() 메서드를 사용합니다:
return Response::error('Unable to fetch weather data for the specified location.');MCP
앱 (Apps)
Laravel MCP는 MCP Apps를 지원합니다. 이는 Model Context Protocol의 확장 기능으로, 도구(tool)가 샌드박스 처리된 iframe 안에서 인터랙티브한 HTML 애플리케이션을 렌더링할 수 있도록 해줍니다. 이를 활용하면 단순한 텍스트 응답을 넘어 대시보드, 폼, 시각화 등 풍부한 사용자 경험을 구현할 수 있습니다.
MCP 앱은 서로 연동되는 두 가지 요소로 구성됩니다.
- 애플리케이션의 자체 완결형(self-contained) HTML을 반환하는 앱 리소스(app resource)
#[RendersApp]속성을 통해 앱 리소스와 연결된 도구(tool). 이 도구가 호출되면 호스트는 연결된 리소스를 가져와 렌더링합니다.
앱 리소스 생성하기
make:mcp-app-resource Artisan 명령어로 앱 리소스를 생성할 수 있습니다.
php artisan make:mcp-app-resource WeatherDashboardApp이 명령어는 두 개의 파일을 생성합니다. app/Mcp/Resources에 위치하는 PHP 클래스와, resources/views/mcp에 위치하는 Blade 뷰입니다. 뷰 이름은 클래스 이름으로부터 자동으로 추론됩니다. 예를 들어 WeatherDashboardApp 클래스는 mcp.weather-dashboard-app 뷰와 매핑됩니다.
<?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
{
/**
* 앱 리소스 요청을 처리합니다.
*/
public function handle(Request $request): Response
{
return Response::view('mcp.weather-dashboard-app', [
'title' => $this->title(),
]);
}
}AppResource는 기본 Resource 클래스를 확장한 클래스로, MCP Apps 명세에서 요구하는 ui:// URI 스킴과 text/html;profile=mcp-app MIME 타입을 자동으로 설정해 줍니다. 다른 리소스와 마찬가지로, 이 리소스 역시 서버의 $resources 배열에 등록해야 합니다.
생성된 Blade 뷰는 <x-mcp::app> 컴포넌트를 사용합니다. 이 컴포넌트는 클라이언트 사이드 MCP SDK가 번들된, 즉시 사용 가능한 완전한 HTML 문서를 렌더링합니다.
<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 명세를 참고하세요.
도구에서 앱 렌더링하기
앱 리소스를 화면에 표시하려면, #[RendersApp] 속성을 사용해 도구와 리소스를 연결해야 합니다. 이렇게 연결된 도구가 호출되면, Laravel MCP는 도구의 메타데이터에 리소스의 URI를 포함시켜 호스트가 샌드박스 처리된 iframe 안에서 앱을 렌더링할 수 있도록 합니다.
<?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
{
/**
* 도구 요청을 처리합니다.
*/
public function handle(Request $request): Response
{
return Response::text('Weather dashboard loaded.');
}
}Laravel MCP는 하나 이상의 AppResource가 등록되어 있는 경우, 서버의 extensions 기능(capability) 안에 io.modelcontextprotocol/ui 확장을 자동으로 포함시켜 알려줍니다. 따라서 별도의 서버 설정은 필요하지 않습니다.
앱 도구의 가시성(Visibility)
각 #[RendersApp] 도구는 visibility 인자를 통해 누가 이 도구를 호출할 수 있는지 제한할 수 있습니다. 이는 UI가 데이터를 불러오거나 갱신하기 위해 호출하는 비공개(app-only) 도구를 만들되, 이를 모델에게는 노출시키고 싶지 않을 때 유용합니다.
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]을 사용하세요.
앱 설정하기
앱 리소스에 지정하는 #[AppMeta] 속성을 통해 iframe의 콘텐츠 보안 정책(Content Security Policy), 브라우저 권한, 그리고 뷰의 <head>에 포함되어야 할 라이브러리 스크립트를 설정할 수 있습니다.
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 열거형에는 Library::Tailwind, Library::Alpine처럼 자주 사용되는 프론트엔드 라이브러리를 위한 CDN 스크립트가 미리 설정되어 있으며, 해당 CDN 출처(origin)는 자동으로 CSP에 병합됩니다. Permission 열거형은 Camera, Microphone, Geolocation, ClipboardWrite와 같은 브라우저 권한을 다룹니다.
동적으로 계산해야 하는 설정값이 필요하다면, 리소스의 appMeta 메서드를 오버라이드하고 Laravel\Mcp\Server\Ui 네임스페이스에서 제공하는 플루언트 빌더인 AppMeta, Csp, Permissions를 사용하세요.
Boost를 활용해 앱 만들기
Laravel MCP는 MCP 앱을 만들기 위한 전용 Boost 스킬 레퍼런스를 제공합니다. Laravel Boost가 설치되어 있다면, AI 코딩 에이전트가 mcp-development 스킬을 호출하여 앱 리소스, Blade 뷰, 그리고 연결된 도구를 자동으로 스캐폴딩하도록 요청할 수 있습니다.
전체 클라이언트 사이드 API와 스키마에 대한 자세한 내용을 포함한 프로토콜 전체 레퍼런스는 공식 MCP Apps 문서를 참고하세요.
메타데이터
Laravel MCP는 일부 MCP 클라이언트나 통합 환경에서 요구하는 MCP 사양의 _meta 필드도 지원합니다. 메타데이터는 도구, 리소스, 프롬프트 등 모든 MCP 프리미티브뿐 아니라 그 응답에도 부여할 수 있습니다.
withMeta 메서드를 사용하면 개별 응답 콘텐츠에 메타데이터를 붙일 수 있습니다:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 도구 요청을 처리합니다.
*/
public function handle(Request $request): Response
{
return Response::text('The weather is sunny.')
->withMeta(['source' => 'weather-api', 'cached' => true]);
}응답 전체 봉투(envelope)에 적용되는 결과 수준의 메타데이터가 필요하다면, Response::make로 응답을 감싼 뒤 반환된 응답 팩토리 인스턴스에서 withMeta를 호출하세요:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
/**
* 도구 요청을 처리합니다.
*/
public function handle(Request $request): ResponseFactory
{
return Response::make(
Response::text('The weather is sunny.')
)->withMeta(['request_id' => '12345']);
}도구, 리소스, 프롬프트 자체에 메타데이터를 부여하려면 클래스에 $meta 프로퍼티를 정의하면 됩니다:
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',
];
// ...
}NOTE
_meta 필드는 클라이언트가 도구 실행 결과를 캐싱하거나 추적할 때, 혹은 특정 MCP 클라이언트 구현체가 요구하는 부가 정보를 전달할 때 유용합니다. 대부분의 경우 필수는 아니므로, 연동하려는 클라이언트가 명시적으로 요구하지 않는다면 생략해도 무방합니다.
아이콘
MCP 클라이언트는 서버와 그 안의 각 요소(프리미티브)에 아이콘을 표시할 수 있습니다. Icon 애트리뷰트를 사용하면 서버, 도구, 리소스, 프롬프트에 아이콘을 지정할 수 있습니다:
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 메서드를 오버라이드하여 아이콘을 코드로 직접 정의할 수도 있습니다. 이는 런타임 조건에 따라 아이콘이 달라져야 하는 경우에 유용합니다:
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 스킴이 포함된 경로는 그대로 사용됩니다.- 상대 경로는 Laravel의
asset헬퍼를 통해 URL로 변환됩니다.
인증(Authentication)
라우트와 마찬가지로, 미들웨어를 사용해 웹 MCP 서버를 인증할 수 있습니다. MCP 서버에 인증을 추가하면 사용자는 서버의 어떤 기능이든 사용하기 전에 인증을 거쳐야 합니다.
MCP 서버 접근을 인증하는 방법에는 두 가지가 있습니다: Laravel Sanctum을 통한 단순한 토큰 기반 인증, 또는 Authorization HTTP 헤더로 전달되는 토큰을 사용하는 방법입니다. 또는 Laravel Passport를 사용하여 OAuth로 인증할 수도 있습니다.
OAuth 2.1
웹 기반 MCP 서버를 보호하는 가장 견고한 방법은 Laravel Passport를 사용한 OAuth입니다.
OAuth를 통해 MCP 서버를 인증하려면, routes/ai.php 파일에서 Mcp::oauthRoutes 메서드를 호출하여 필요한 OAuth2 디스커버리 및 클라이언트 등록 라우트를 등록하세요. 그런 다음, routes/ai.php 파일의 Mcp::web 라우트에 Passport의 auth:api 미들웨어를 적용하세요:
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 인증 뷰를 퍼블리시해야 합니다:
php artisan vendor:publish --tag=mcp-views그런 다음, Passport::authorizationView 메서드를 사용하여 Passport가 이 뷰를 사용하도록 지시하세요. 일반적으로 이 메서드는 애플리케이션의 AppServiceProvider의 boot 메서드에서 호출해야 합니다:
use Laravel\Passport\Passport;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::authorizationView(function ($parameters) {
return view('mcp.authorize', $parameters);
});
}이 뷰는 인증 과정에서 최종 사용자에게 표시되어 AI 에이전트의 인증 시도를 거부하거나 승인할 수 있게 합니다.
NOTE
이 시나리오에서는 OAuth를 단순히 기저 인증 가능(authenticatable) 모델로의 변환 계층으로만 사용하고 있습니다. 범위(scope)와 같은 OAuth의 여러 측면은 다루지 않습니다.
기존 Passport 설치 사용하기
애플리케이션에서 이미 Laravel Passport를 사용하고 있다면, Laravel MCP는 기존 Passport 설치 내에서 원활하게 동작해야 하지만, OAuth가 기본적으로 기저 인증 가능 모델로의 변환 계층으로 사용되기 때문에 사용자 정의 범위(scope)는 현재 지원되지 않습니다.
위에서 설명한 Mcp::oauthRoutes 메서드를 통해, Laravel MCP는 단일 mcp:use 범위를 추가하고, 알리고, 사용합니다.
Passport와 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> 헤더를 제공하는지 확인하세요:
use App\Mcp\Servers\WeatherExample;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/demo', WeatherExample::class)
->middleware('auth:sanctum');사용자 정의 MCP 인증
애플리케이션이 자체적인 사용자 정의 API 토큰을 발급하는 경우, Mcp::web 라우트에 원하는 미들웨어를 지정하여 MCP 서버를 인증할 수 있습니다. 사용자 정의 미들웨어는 들어오는 MCP 요청을 인증하기 위해 Authorization 헤더를 직접 검사할 수 있습니다.
MCP
인증(Authorization)
$request->user() 메서드를 통해 현재 인증된 사용자에 접근할 수 있으며, 이를 활용해 MCP 도구(tool)와 리소스 내에서 권한 검사를 수행할 수 있습니다:
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 도구 요청을 처리합니다.
*/
public function handle(Request $request): Response
{
if (! $request->user()->can('read-weather')) {
return Response::error('권한이 없습니다.');
}
// ...
}NOTE
인증되지 않은 사용자가 MCP 서버에 접근하지 못하도록 하려면, 서버를 등록할 때 반드시 인증 미들웨어를 적용해야 합니다. 미들웨어 없이 $request->user()를 호출하면 null이 반환될 수 있으므로, 사용 전에 사용자가 존재하는지 확인하는 것이 안전합니다.
MCP
MCP 클라이언트
서버를 구축하는 기능 외에도, Laravel MCP는 다른 MCP 서버(자체 제작이든 서드파티든)에 연결할 수 있는 클라이언트를 제공합니다. 이 클라이언트를 사용하면 애플리케이션이 MCP 서버가 노출한 도구를 탐색하고 호출할 수 있으며, 특히 AI 에이전트에게 외부 MCP 서버가 제공하는 기능을 사용할 수 있게 해주고 싶을 때 유용합니다.
서버에 연결하기
Client::web 메서드에 서버 URL을 전달하여 HTTP로 접근 가능한 MCP 서버에 연결할 수 있습니다:
use Laravel\Mcp\Client;
$client = Client::web('https://mcp.example.com');명령어로 실행되는 로컬 MCP 서버에 연결하려면, Client::local 메서드에 서버를 시작하는 데 필요한 명령어와 인자를 전달하면 됩니다:
use Laravel\Mcp\Client;
$client = Client::local('php', ['artisan', 'mcp:start']);클라이언트는 지연 연결(lazy connection) 방식으로 동작하여, 도구 목록을 조회하거나 호출할 때 처음으로 연결을 자동으로 수립합니다. 연결을 직접 제어해야 한다면 connect, connected, disconnect 메서드를 사용할 수 있습니다:
$client->connect();
if ($client->connected()) {
$capabilities = $client->capabilities();
$server = $client->serverInfo();
}
$client->disconnect();withTimeout 메서드로 요청 타임아웃을 지정할 수 있습니다:
$client = Client::web('https://mcp.example.com')->withTimeout(30);이름이 지정된 클라이언트
매번 클라이언트를 새로 생성하는 대신, 재사용 가능한 이름이 지정된 클라이언트를 등록할 수 있습니다. 보통 서비스 프로바이더의 boot 메서드에서 Mcp 파사드를 사용해 등록합니다:
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;
Mcp::registerClient('github', fn () => Client::web('https://mcp.example.com'));등록한 뒤에는 애플리케이션 어디에서든 이름으로 클라이언트를 가져올 수 있습니다:
use Laravel\Mcp\Facades\Mcp;
$client = Mcp::client('github');이름이 지정된 클라이언트는 요청당 한 번만 리졸브되며, 요청 라이프사이클이 끝날 때 자동으로 연결이 종료됩니다.
클라이언트 인증
베어러 토큰으로 보호되는 웹 MCP 서버에 연결하려면 withToken 메서드를 사용하세요. 토큰 문자열을 직접 전달할 수도 있고, 토큰을 지연 방식으로 반환하는 클로저를 전달할 수도 있습니다:
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로 보호되는 서버에 연결하려면 withOAuth 메서드로 클라이언트를 구성하세요. 이는 여러분이 직접 만든 서버를 OAuth로 보호할 때와 대응되는 클라이언트 측 설정입니다:
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'),
));NOTE
clientId와 clientSecret 인자는 생략할 수 있습니다. 인가 서버가 Client ID Metadata Document를 지원하는 경우 Laravel이 이를 사용하며, 지원하지 않는 레거시 서버라면 동적 클라이언트 등록 방식으로 대체합니다.
인가 서버는 자신의 인가 서버 메타데이터에서 S256 PKCE 코드 챌린지 방식을 지원한다고 명시해야 합니다. PKCE 지원 여부가 명시되어 있지 않으면 Laravel은 인가 시도를 거부합니다.
다음으로, routes/ai.php 파일에서 oAuthRoutesFor 메서드를 사용해 이름이 지정된 클라이언트의 OAuth 라우트를 등록합니다. 여기에 전달하는 클로저는 인가 코드가 액세스 토큰으로 교환된 후, 클라이언트 이름과 결과로 생성된 TokenSet을 인자로 전달받습니다:
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');
});이 코드는 세 개의 이름이 지정된 라우트를 등록합니다: 사용자를 인가 서버로 리다이렉트하는 연결 라우트(mcp.oauth.{client}.connect), 인가 코드를 교환하고 여러분이 작성한 핸들러를 호출하는 콜백 라우트(mcp.oauth.{client}.callback), 그리고 공개적으로 접근 가능한 Client ID Metadata Document 라우트(mcp.oauth.{client}.client-metadata)입니다. 연결 라우트와 콜백 라우트는 기본적으로 web 미들웨어 그룹을 사용하며, middleware 인자로 이를 재정의할 수 있습니다. 메타데이터 라우트는 인가 서버가 해당 정보를 자유롭게 조회할 수 있어야 하므로 이 미들웨어를 사용하지 않습니다.
메타데이터 문서는 여러분의 애플리케이션을 공개(public) OAuth 클라이언트로 기술하며, 애플리케이션의 APP_URL을 이용해 클라이언트 ID와 콜백 URL을 생성합니다. 따라서 프로덕션 환경에서는 APP_URL 환경 변수가 올바르게 설정되어 있는지 반드시 확인해야 합니다. clientMetadataUri와 clientMetadata 인자를 사용하면 메타데이터 라우트 경로를 지정하고 추가 메타데이터를 제공할 수 있습니다:
use Laravel\Mcp\Client\OAuth\TokenSet;
use Laravel\Mcp\Facades\Mcp;
Mcp::oAuthRoutesFor(
'github',
function (string $client, TokenSet $token) {
// 토큰을 저장하는 로직...
return redirect('/dashboard');
},
clientMetadataUri: 'oauth/github/client.json',
clientMetadata: [
'client_name' => 'Acme Weather Dashboard',
'logo_uri' => 'https://acme.com/logo.png',
],
);인가 플로우를 시작하려면 사용자를 연결 라우트로 리다이렉트하면 됩니다:
return redirect()->route('mcp.oauth.github.connect');도구(Tools)
tools 메서드를 사용하면 MCP 서버가 노출하는 도구 목록을 조회할 수 있으며, 이름을 키로 하는 컬렉션이 반환됩니다:
use Laravel\Mcp\Facades\Mcp;
$tools = Mcp::client('github')->tools();
foreach ($tools as $tool) {
$tool->name;
$tool->title;
$tool->description;
$tool->inputSchema;
}클라이언트는 사용 가능한 모든 도구를 자동으로 페이지네이션하며 조회합니다. limit 인자로 조회할 도구 개수를 제한할 수 있습니다:
$tools = Mcp::client('github')->tools(limit: 10);도구를 호출하려면 callTool 메서드에 도구 이름과 인자 배열을 전달하세요. 반환되는 ToolResult 인스턴스를 통해 도구의 응답을 확인할 수 있습니다:
use Laravel\Mcp\Facades\Mcp;
$result = Mcp::client('github')->callTool('current-weather', [
'location' => 'New York',
]);
$result->text(); // 응답의 텍스트 콘텐츠...
(string) $result; // text()를 호출하는 것과 동일...
$result->isError; // 도구가 오류를 보고했는지 여부...
$result->structuredContent; // 구조화된 콘텐츠(있는 경우)...또는, 조회한 도구 인스턴스에서 바로 호출할 수도 있습니다:
$tools = Mcp::client('github')->tools();
$result = $tools['current-weather']->call([
'location' => 'New York',
]);Laravel AI SDK로 에이전트를 구축하고 있다면, MCP 클라이언트가 제공하는 도구를 에이전트에 직접 전달하여 모델이 프롬프트에 응답하는 동안 이를 호출하도록 할 수도 있습니다. 자세한 내용은 AI SDK 문서의 MCP 도구 섹션을 참고하세요.
프롬프트(Prompts)
prompts 메서드를 사용하면 MCP 서버가 노출하는 프롬프트 목록을 조회할 수 있으며, 이름을 키로 하는 컬렉션이 반환됩니다:
use Laravel\Mcp\Facades\Mcp;
$prompts = Mcp::client('github')->prompts();
foreach ($prompts as $prompt) {
$prompt->name;
$prompt->title;
$prompt->description;
$prompt->arguments;
}클라이언트는 사용 가능한 모든 프롬프트를 자동으로 페이지네이션하며 조회합니다. limit 인자로 조회할 프롬프트 개수를 제한할 수 있습니다:
$prompts = Mcp::client('github')->prompts(limit: 10);프롬프트를 조회하려면 getPrompt 메서드에 프롬프트 이름과 인자 배열을 전달하세요. 반환되는 PromptResult 인스턴스를 통해 생성된 메시지를 확인할 수 있습니다:
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)
resources 메서드를 사용하면 MCP 서버가 노출하는 리소스 목록을 조회할 수 있으며, URI를 키로 하는 컬렉션이 반환됩니다:
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 인자로 조회할 리소스 개수를 제한할 수 있습니다:
$resources = Mcp::client('github')->resources(limit: 10);리소스를 읽으려면 readResource 메서드에 리소스 URI를 전달하세요. 반환되는 ResourceReadResult 인스턴스를 통해 리소스 콘텐츠를 확인할 수 있습니다:
use Laravel\Mcp\Facades\Mcp;
$result = Mcp::client('github')->readResource('weather://guidelines');
$result->content(); // 리소스의 콘텐츠, 필요 시 base64 블롭을 디코딩함...
(string) $result; // content()를 호출하는 것과 동일...
$result->mimeType(); // 리소스의 MIME 타입(있는 경우)...
$result->contents; // 리소스가 반환한 원본 콘텐츠...서버 테스트하기
내장된 MCP Inspector를 사용하거나 유닛 테스트를 작성하여 MCP 서버를 테스트할 수 있습니다.
MCP Inspector
MCP Inspector는 MCP 서버를 테스트하고 디버깅할 수 있는 대화형 도구입니다. 이 도구를 사용하면 서버에 연결하고, 인증을 확인하고, 도구(Tool)·리소스·프롬프트를 직접 실행해볼 수 있습니다.
등록된 서버라면 어떤 것이든 Inspector로 실행할 수 있습니다:
# 웹 서버...php artisan mcp:inspector mcp/weather# "weather"라는 이름의 로컬 서버...php artisan mcp:inspector weather이 명령어를 실행하면 MCP Inspector가 실행되며, MCP 클라이언트에 그대로 복사해 넣을 수 있는 클라이언트 설정값을 제공합니다. 이를 통해 모든 설정이 올바르게 되어 있는지 확인할 수 있습니다. 만약 웹 서버가 인증 미들웨어로 보호되어 있다면, 연결 시 Authorization bearer 토큰과 같이 필요한 헤더를 반드시 함께 전달해야 합니다.
유닛 테스트
MCP 서버, 도구(Tool), 리소스, 프롬프트에 대한 유닛 테스트를 작성할 수 있습니다.
시작하려면 새로운 테스트 케이스를 만들고, 해당 프리미티브(도구·리소스·프롬프트)를 등록한 서버에서 원하는 프리미티브를 직접 호출하면 됩니다. 예를 들어, WeatherServer에 등록된 도구를 테스트하려면 다음과 같이 작성합니다:
Pest
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.');
});PHPUnit
/**
* 도구를 테스트합니다.
*/
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.');
}마찬가지로 프롬프트와 리소스도 다음과 같이 테스트할 수 있습니다:
$response = WeatherServer::prompt(...);
$response = WeatherServer::resource(...);또한 프리미티브를 호출하기 전에 actingAs 메서드를 체이닝하면, 인증된 사용자로서 동작하는 것처럼 테스트할 수 있습니다:
$response = WeatherServer::actingAs($user)->tool(...);응답을 받은 후에는 다양한 단언(assertion) 메서드를 사용해 응답의 내용과 상태를 검증할 수 있습니다.
assertOk 메서드를 사용하면 응답이 성공적인지, 즉 에러가 없는지 확인할 수 있습니다:
$response->assertOk();assertSee 메서드를 사용하면 응답에 특정 텍스트가 포함되어 있는지 확인할 수 있습니다:
$response->assertSee('The current weather in New York City is 72°F and sunny.');assertHasErrors 메서드를 사용하면 응답에 에러가 포함되어 있는지 확인할 수 있습니다:
$response->assertHasErrors();
$response->assertHasErrors([
'Something went wrong.',
]);assertHasNoErrors 메서드를 사용하면 응답에 에러가 없는지 확인할 수 있습니다:
$response->assertHasNoErrors();assertName(), assertTitle(), assertDescription() 메서드를 사용하면 응답에 포함된 특정 메타데이터를 검증할 수 있습니다:
$response->assertName('current-weather');
$response->assertTitle('Current Weather Tool');
$response->assertDescription('Fetches the current weather forecast for a specified location.');assertSentNotification 및 assertNotificationCount 메서드를 사용하면 알림(notification)이 전송되었는지 확인할 수 있습니다:
$response->assertSentNotification('processing/progress', [
'step' => 1,
'total' => 5,
]);
$response->assertSentNotification('processing/progress', [
'step' => 2,
'total' => 5,
]);
$response->assertNotificationCount(5);마지막으로, 응답의 원본 내용을 직접 확인하고 싶다면 디버깅 목적으로 dd 또는 dump 메서드를 사용해 응답을 출력할 수 있습니다:
$response->dd();
$response->dump();