본문으로 건너뛰기
버전: 13.x

Laravel MCP (Laravel MCP)

소개 (Introduction)

Laravel MCP는 AI 클라이언트가 Model Context Protocol을 통해 Laravel 애플리케이션과 상호작용할 수 있도록 하는 간단하고 우아한 방법을 제공합니다. 서버, Tool, Resource, Prompt를 정의할 수 있는 표현력 있고 유연한 인터페이스를 제공하여 AI 기반 상호작용을 애플리케이션에 쉽게 통합할 수 있습니다.

설치 (Installation)

시작하려면 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 서버를 등록하게 됩니다.

서버 생성 (Creating Servers)

make:mcp-server Artisan 명령어를 사용하여 MCP 서버를 생성할 수 있습니다. 서버는 Tool, Resource, Prompt와 같은 MCP 기능을 AI 클라이언트에 노출하는 중앙 통신 지점 역할을 합니다.

php artisan make:mcp-server WeatherServer

이 명령어는 app/Mcp/Servers 디렉토리에 새로운 서버 클래스를 생성합니다. 생성된 서버 클래스는 Laravel MCP의 기본 Laravel\Mcp\Server 클래스를 확장하며, 서버 설정과 Tool, Resource, Prompt 등록을 위한 속성과 프로퍼티를 제공합니다.

<?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는 서버를 등록하는 두 가지 메서드를 제공합니다. 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를 사용하세요.

도구 (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
{
/**
* 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(),
];
}
}

Tool 생성

Tool을 생성하려면 make:mcp-tool Artisan 명령어를 실행합니다.

php artisan make:mcp-tool CurrentWeatherTool

Tool을 생성한 후에는 서버의 $tools 프로퍼티에 등록합니다.

<?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,
];
}

Tool 이름, 제목, 설명

기본적으로 Tool의 이름과 제목은 클래스 이름에서 자동으로 생성됩니다. 예를 들어, CurrentWeatherTool은 이름이 current-weather이고 제목이 Current Weather Tool이 됩니다. NameTitle 속성을 사용하여 이 값을 사용자 지정할 수 있습니다.

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
{
// ...
}

Tool 설명은 자동으로 생성되지 않습니다. Description 속성을 사용하여 항상 의미 있는 설명을 제공해야 합니다.

use Laravel\Mcp\Server\Attributes\Description;

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

[!NOTE] 설명은 Tool 메타데이터에서 매우 중요한 부분으로, AI 모델이 Tool을 언제 그리고 어떻게 효과적으로 사용해야 하는지 이해하는 데 도움을 줍니다.

Tool 입력 스키마

Tool은 AI 클라이언트로부터 어떤 인수를 받을지 지정하는 입력 스키마를 정의할 수 있습니다. Laravel의 Illuminate\Contracts\JsonSchema\JsonSchema 빌더를 사용하여 Tool의 입력 요구사항을 정의합니다.

<?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'),
];
}
}

Tool 출력 스키마

Tool은 응답의 구조를 지정하는 출력 스키마를 정의할 수 있습니다. 이를 통해 파싱 가능한 Tool 결과가 필요한 AI 클라이언트와의 더 나은 통합이 가능합니다. outputSchema 메서드를 사용하여 Tool의 출력 구조를 정의합니다.

<?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(),
];
}
}

Tool 인수 유효성 검사

JSON Schema 정의는 Tool 인수에 대한 기본적인 구조를 제공하지만, 더 복잡한 유효성 검사 규칙을 적용하고 싶을 수도 있습니다.

Laravel MCP는 Laravel의 유효성 검사 기능과 원활하게 통합됩니다. Tool의 handle 메서드 내에서 들어오는 Tool 인수를 유효성 검사할 수 있습니다.

<?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 클라이언트는 제공된 오류 메시지를 기반으로 동작합니다. 따라서 명확하고 실행 가능한 오류 메시지를 제공하는 것이 중요합니다.

$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.',
]);

Tool 의존성 주입

모든 Tool은 Laravel 서비스 컨테이너를 통해 리졸브됩니다. 따라서 Tool의 생성자에서 필요한 의존성을 타입 힌트로 지정할 수 있습니다. 선언된 의존성은 자동으로 리졸브되어 Tool 인스턴스에 주입됩니다.

<?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,
) {}

// ...
}

생성자 주입 외에도 Tool의 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
{
/**
* Handle the tool request.
*/
public function handle(Request $request, WeatherRepository $weather): Response
{
$location = $request->get('location');

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

// ...
}
}

Tool 어노테이션

어노테이션을 사용하여 AI 클라이언트에 추가 메타데이터를 제공함으로써 Tool을 향상시킬 수 있습니다. 이러한 어노테이션은 AI 모델이 Tool의 동작과 기능을 이해하는 데 도움을 줍니다. 어노테이션은 속성(attribute)을 통해 Tool에 추가됩니다.

<?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]booleanTool이 환경을 수정하지 않음을 나타냅니다.
#[IsDestructive]booleanTool이 파괴적인 업데이트를 수행할 수 있음을 나타냅니다(읽기 전용이 아닌 경우에만 의미 있음).
#[IsIdempotent]boolean동일한 인수로 반복 호출해도 추가 효과가 없음을 나타냅니다(읽기 전용이 아닌 경우).
#[IsOpenWorld]booleanTool이 외부 엔티티와 상호작용할 수 있음을 나타냅니다.

어노테이션 값은 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
{
//
}

조건부 Tool 등록

Tool 클래스에 shouldRegister 메서드를 구현하여 런타임에 조건부로 Tool을 등록할 수 있습니다. 이 메서드를 사용하면 애플리케이션 상태, 설정 또는 요청 파라미터에 따라 Tool의 사용 가능 여부를 결정할 수 있습니다.

<?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;
}
}

Tool의 shouldRegister 메서드가 false를 반환하면, 사용 가능한 Tool 목록에 표시되지 않으며 AI 클라이언트가 호출할 수 없습니다.

Tool 응답

Tool은 반드시 Laravel\Mcp\Response 인스턴스를 반환해야 합니다. Response 클래스는 다양한 유형의 응답을 생성하기 위한 여러 편리한 메서드를 제공합니다.

단순 텍스트 응답의 경우 text 메서드를 사용합니다.

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');
}

Tool 실행 중 오류가 발생했음을 나타내려면 error 메서드를 사용합니다.

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

이미지 또는 오디오 콘텐츠를 반환하려면 imageaudio 메서드를 사용합니다.

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');

다중 콘텐츠 응답

Tool은 Response 인스턴스의 배열을 반환하여 여러 개의 콘텐츠를 반환할 수 있습니다.

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')
];
}

구조화된 응답

Tool은 structured 메서드를 사용하여 구조화된 콘텐츠를 반환할 수 있습니다. 이는 JSON으로 인코딩된 텍스트 표현과의 하위 호환성을 유지하면서 AI 클라이언트에게 파싱 가능한 데이터를 제공합니다.

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

구조화된 콘텐츠와 함께 사용자 정의 텍스트를 제공해야 하는 경우 Response 팩토리의 withStructuredContent 메서드를 사용합니다.

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

스트리밍 응답

장시간 실행되는 작업이나 실시간 데이터 스트리밍의 경우, Tool은 handle 메서드에서 제너레이터를 반환할 수 있습니다. 이를 통해 최종 응답 전에 클라이언트에 중간 업데이트를 보낼 수 있습니다.

<?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));
}
}
}

웹 기반 서버를 사용할 때, 스트리밍 응답은 자동으로 SSE(Server-Sent Events) 스트림을 열어 yield된 각 메시지를 이벤트로 클라이언트에 전송합니다.

프롬프트 (Prompts)

Prompts는 서버가 AI 클라이언트가 언어 모델과 상호작용하는 데 사용할 수 있는 재사용 가능한 프롬프트 템플릿을 공유할 수 있게 해줍니다. 일반적인 쿼리와 상호작용을 구조화하는 표준화된 방법을 제공합니다.

Prompt 생성

Prompt를 생성하려면 make:mcp-prompt Artisan 명령어를 실행합니다.

php artisan make:mcp-prompt DescribeWeatherPrompt

Prompt를 생성한 후에는 서버의 $prompts 프로퍼티에 등록합니다.

<?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,
];
}

Prompt 이름, 제목, 설명

기본적으로 Prompt의 이름과 제목은 클래스 이름에서 자동으로 생성됩니다. 예를 들어, DescribeWeatherPrompt는 이름이 describe-weather이고 제목이 Describe Weather Prompt가 됩니다. NameTitle 속성을 사용하여 이 값을 사용자 지정할 수 있습니다.

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

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

Prompt 설명은 자동으로 생성되지 않습니다. Description 속성을 사용하여 항상 의미 있는 설명을 제공해야 합니다.

use Laravel\Mcp\Server\Attributes\Description;

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

[!NOTE] 설명은 Prompt 메타데이터에서 매우 중요한 부분으로, AI 모델이 Prompt를 언제 그리고 어떻게 최대한 활용해야 하는지 이해하는 데 도움을 줍니다.

Prompt 인수

Prompt는 AI 클라이언트가 프롬프트 템플릿을 특정 값으로 사용자 지정할 수 있도록 인수를 정의할 수 있습니다. arguments 메서드를 사용하여 Prompt가 받는 인수를 정의합니다.

<?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,
),
];
}
}

Prompt 인수 유효성 검사

Prompt 인수는 정의에 따라 자동으로 유효성 검사가 이루어지지만, 더 복잡한 유효성 검사 규칙을 적용하고 싶을 수도 있습니다.

Laravel MCP는 Laravel의 유효성 검사 기능과 원활하게 통합됩니다. Prompt의 handle 메서드 내에서 들어오는 Prompt 인수를 유효성 검사할 수 있습니다.

<?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 클라이언트는 제공된 오류 메시지를 기반으로 동작합니다. 따라서 명확하고 실행 가능한 오류 메시지를 제공하는 것이 중요합니다.

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

Prompt 의존성 주입

모든 Prompt는 Laravel 서비스 컨테이너를 통해 리졸브됩니다. 따라서 Prompt의 생성자에서 필요한 의존성을 타입 힌트로 지정할 수 있습니다. 선언된 의존성은 자동으로 리졸브되어 Prompt 인스턴스에 주입됩니다.

<?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,
) {}

//
}

생성자 주입 외에도 Prompt의 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
{
/**
* Handle the prompt request.
*/
public function handle(Request $request, WeatherRepository $weather): Response
{
$isAvailable = $weather->isServiceAvailable();

// ...
}
}

조건부 Prompt 등록

Prompt 클래스에 shouldRegister 메서드를 구현하여 런타임에 조건부로 Prompt를 등록할 수 있습니다. 이 메서드를 사용하면 애플리케이션 상태, 설정 또는 요청 파라미터에 따라 Prompt의 사용 가능 여부를 결정할 수 있습니다.

<?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;
}
}

Prompt의 shouldRegister 메서드가 false를 반환하면, 사용 가능한 Prompt 목록에 표시되지 않으며 AI 클라이언트가 호출할 수 없습니다.

Prompt 응답

Prompt는 단일 Laravel\Mcp\Response 또는 Laravel\Mcp\Response 인스턴스의 이터러블을 반환할 수 있습니다. 이러한 응답은 AI 클라이언트에 전송될 콘텐츠를 캡슐화합니다.

<?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 어시스턴트로부터 온 것으로 처리되어야 함을 나타낼 수 있으며, 일반 메시지는 사용자 입력으로 처리됩니다.

리소스 (Resources)

Resources는 서버가 AI 클라이언트가 언어 모델과 상호작용할 때 컨텍스트로 읽고 사용할 수 있는 데이터와 콘텐츠를 노출할 수 있게 해줍니다. 문서, 설정 또는 AI 응답에 도움이 되는 모든 데이터와 같은 정적 또는 동적 정보를 공유하는 방법을 제공합니다.

Resource 생성 (Creating Resources)

Resource를 생성하려면 make:mcp-resource Artisan 명령어를 실행합니다.

php artisan make:mcp-resource WeatherGuidelinesResource

Resource를 생성한 후에는 서버의 $resources 프로퍼티에 등록합니다.

<?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,
];
}

Resource 이름, 제목, 설명

기본적으로 Resource의 이름과 제목은 클래스 이름에서 자동으로 생성됩니다. 예를 들어, WeatherGuidelinesResource는 이름이 weather-guidelines이고 제목이 Weather Guidelines Resource가 됩니다. NameTitle 속성을 사용하여 이 값을 사용자 지정할 수 있습니다.

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

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

Resource 설명은 자동으로 생성되지 않습니다. Description 속성을 사용하여 항상 의미 있는 설명을 제공해야 합니다.

use Laravel\Mcp\Server\Attributes\Description;

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

[!NOTE] 설명은 Resource 메타데이터에서 매우 중요한 부분으로, AI 모델이 Resource를 언제 그리고 어떻게 효과적으로 사용해야 하는지 이해하는 데 도움을 줍니다.

Resource 템플릿

Resource 템플릿은 서버가 변수가 포함된 URI 패턴과 일치하는 동적 리소스를 노출할 수 있게 해줍니다. 각 리소스에 대해 정적 URI를 정의하는 대신, 템플릿 패턴을 기반으로 여러 URI를 처리하는 단일 리소스를 만들 수 있습니다.

Resource 템플릿 생성

Resource 템플릿을 생성하려면, Resource 클래스에 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
{
/**
* 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);
}
}

Resource가 HasUriTemplate 인터페이스를 구현하면, 정적 리소스가 아닌 리소스 템플릿으로 등록됩니다. 그러면 AI 클라이언트가 템플릿 패턴과 일치하는 URI를 사용하여 리소스를 요청할 수 있으며, URI에서 변수가 자동으로 추출되어 Resource의 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
{
// 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를 모두 제공하여 리소스 요청을 처리하는 데 필요한 전체 컨텍스트를 제공합니다.

Resource URI 및 MIME 타입

각 Resource는 고유한 URI로 식별되며, AI 클라이언트가 리소스의 형식을 이해하는 데 도움이 되는 MIME 타입이 연결되어 있습니다.

기본적으로 Resource의 URI는 리소스 이름을 기반으로 생성되므로, WeatherGuidelinesResource의 URI는 weather://resources/weather-guidelines이 됩니다. 기본 MIME 타입은 text/plain입니다.

UriMimeType 속성을 사용하여 이 값을 사용자 지정할 수 있습니다.

<?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 클라이언트가 리소스 콘텐츠를 적절하게 처리하고 해석하는 방법을 결정하는 데 도움을 줍니다.

Resource 요청

Tool과 Prompt와 달리, Resource는 입력 스키마나 인수를 정의할 수 없습니다. 하지만 Resource의 handle 메서드 내에서 요청 객체와 상호작용할 수 있습니다.

<?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
{
// ...
}
}

Resource 의존성 주입

모든 Resource는 Laravel 서비스 컨테이너를 통해 리졸브됩니다. 따라서 Resource의 생성자에서 필요한 의존성을 타입 힌트로 지정할 수 있습니다. 선언된 의존성은 자동으로 리졸브되어 Resource 인스턴스에 주입됩니다.

<?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,
) {}

// ...
}

생성자 주입 외에도 Resource의 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
{
/**
* Handle the resource request.
*/
public function handle(WeatherRepository $weather): Response
{
$guidelines = $weather->guidelines();

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

Resource 어노테이션

어노테이션을 사용하여 AI 클라이언트에 추가 메타데이터를 제공함으로써 Resource를 향상시킬 수 있습니다. 어노테이션은 속성(attribute)을 통해 Resource에 추가됩니다.

<?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 또는 array대상 사용자를 지정합니다(Role::User, Role::Assistant, 또는 둘 다).
#[Priority]float리소스 중요도를 나타내는 0.0에서 1.0 사이의 숫자 점수입니다.
#[LastModified]string리소스가 마지막으로 업데이트된 시점을 나타내는 ISO 8601 타임스탬프입니다.

조건부 Resource 등록

Resource 클래스에 shouldRegister 메서드를 구현하여 런타임에 조건부로 Resource를 등록할 수 있습니다. 이 메서드를 사용하면 애플리케이션 상태, 설정 또는 요청 파라미터에 따라 Resource의 사용 가능 여부를 결정할 수 있습니다.

<?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;
}
}

Resource의 shouldRegister 메서드가 false를 반환하면, 사용 가능한 Resource 목록에 표시되지 않으며 AI 클라이언트가 접근할 수 없습니다.

Resource 응답

Resource는 반드시 Laravel\Mcp\Response 인스턴스를 반환해야 합니다. Response 클래스는 다양한 유형의 응답을 생성하기 위한 여러 편리한 메서드를 제공합니다.

단순 텍스트 콘텐츠의 경우 text 메서드를 사용합니다.

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 콘텐츠를 제공합니다.

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

Blob 콘텐츠를 반환할 때, MIME 타입은 Resource에 설정된 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.');

메타데이터 (Metadata)

Laravel MCP는 MCP 사양에 정의된 _meta 필드도 지원하며, 이는 특정 MCP 클라이언트나 통합에서 필요합니다. 메타데이터는 Tool, Resource, Prompt를 포함한 모든 MCP 프리미티브와 그 응답에 적용할 수 있습니다.

withMeta 메서드를 사용하여 개별 응답 콘텐츠에 메타데이터를 첨부할 수 있습니다.

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::make로 응답을 감싸고 반환된 Response 팩토리 인스턴스에서 withMeta를 호출합니다.

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']);
}

Tool, Resource 또는 Prompt 자체에 메타데이터를 첨부하려면 클래스에 $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',
];

// ...
}

인증 (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가 이 뷰를 사용하도록 지시합니다. 일반적으로 이 메서드는 애플리케이션의 AppServiceProviderboot 메서드에서 호출해야 합니다.

use Laravel\Passport\Passport;

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

이 뷰는 인증 과정에서 최종 사용자에게 표시되어 AI 에이전트의 인증 시도를 승인하거나 거부할 수 있게 합니다.

Authorization screen example

[!NOTE] 이 시나리오에서는 단순히 OAuth를 기본 인증 가능 모델에 대한 변환 계층으로 사용하고 있습니다. 스코프와 같은 OAuth의 많은 측면은 무시됩니다.

기존 Passport 설치 사용

애플리케이션에서 이미 Laravel Passport를 사용하고 있는 경우, Laravel MCP는 기존 Passport 설치와 원활하게 작동합니다. 다만 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> 헤더를 제공하도록 합니다.

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

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

커스텀 MCP 인증

애플리케이션이 자체 커스텀 API 토큰을 발급하는 경우, Mcp::web 라우트에 원하는 미들웨어를 할당하여 MCP 서버를 인증할 수 있습니다. 커스텀 미들웨어는 Authorization 헤더를 수동으로 검사하여 들어오는 MCP 요청을 인증할 수 있습니다.

인가 (Authorization)

$request->user() 메서드를 통해 현재 인증된 사용자에 접근하여 MCP Tool과 Resource 내에서 인가 확인을 수행할 수 있습니다.

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.');
}

// ...
}

서버 테스트 (Testing Servers)

내장된 MCP Inspector를 사용하거나 유닛 테스트를 작성하여 MCP 서버를 테스트할 수 있습니다.

MCP Inspector

MCP Inspector는 MCP 서버를 테스트하고 디버깅하기 위한 대화형 도구입니다. 서버에 연결하고, 인증을 확인하고, Tool, Resource, Prompt를 시험해 볼 수 있습니다.

등록된 모든 서버에 대해 Inspector를 실행할 수 있습니다.

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

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

이 명령어는 MCP Inspector를 실행하고, MCP 클라이언트에 복사할 수 있는 클라이언트 설정을 제공하여 모든 것이 올바르게 구성되었는지 확인할 수 있습니다. 웹 서버가 인증 미들웨어로 보호되어 있는 경우, 연결 시 Authorization 베어러 토큰과 같은 필수 헤더를 포함해야 합니다.

유닛 테스트

MCP 서버, Tool, Resource, Prompt에 대한 유닛 테스트를 작성할 수 있습니다.

시작하려면, 새로운 테스트 케이스를 생성하고 해당 프리미티브를 등록한 서버에서 원하는 프리미티브를 호출합니다. 예를 들어, WeatherServer의 Tool을 테스트하려면 다음과 같이 합니다.

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.');
});
/**
* 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.');
}

마찬가지로 Prompt와 Resource를 테스트할 수 있습니다.

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

프리미티브를 호출하기 전에 actingAs 메서드를 체이닝하여 인증된 사용자로 동작할 수도 있습니다.

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

응답을 받은 후, 다양한 어설션 메서드를 사용하여 응답의 콘텐츠와 상태를 확인할 수 있습니다.

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.');

assertSentNotificationassertNotificationCount 메서드를 사용하여 알림이 전송되었는지 확인할 수 있습니다.

$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();