Laravel MCP
업데이트됨번역일: 2026년 7월 28일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 7월 28일
- 번역 갱신
- 2026년 7월 28일
Laravel MCP
- 소개
- 설치
- 서버 생성
- 도구 (Tools)
- 프롬프트 (Prompts)
- 리소스 (Resources)
- 앱 (Apps)
- 메타데이터
- 아이콘
- 인증 (Authentication)
- 인가 (Authorization)
- MCP 클라이언트
- 서버 테스트
소개
MCP(Model Context Protocol)는 AI 모델이 외부 도구나 데이터 소스와 상호작용하는 방식을 표준화한 오픈 프로토콜입니다. Laravel MCP 패키지를 사용하면 Laravel 애플리케이션을 MCP 서버로 손쉽게 만들 수 있으며, AI 에이전트나 LLM 클라이언트가 여러분의 애플리케이션 기능을 직접 호출할 수 있게 됩니다.
Laravel MCP는 다음 세 가지 핵심 구성 요소를 제공합니다.
- Tools: AI 모델이 실행할 수 있는 함수 또는 동작 (예: 데이터 조회, 계산 수행)
- Prompts: AI 모델에게 제공하는 재사용 가능한 프롬프트 템플릿
- Resources: AI 모델이 읽을 수 있는 데이터나 파일 (예: 문서, DB 레코드)
설치
Composer로 패키지를 설치합니다.
composer require laravel/mcp설치 후 MCP 설정 파일을 퍼블리싱합니다.
php artisan vendor:publish --provider="Laravel\Mcp\McpServiceProvider"라우트 퍼블리싱
Laravel MCP는 기본적으로 MCP 엔드포인트 라우트를 자동으로 등록합니다. 라우트를 직접 관리하고 싶다면 아래 명령어로 퍼블리싱할 수 있습니다.
php artisan mcp:install이 명령을 실행하면 routes/mcp.php 파일이 생성됩니다. 이후에는 이 파일에서 라우트를 직접 수정할 수 있습니다.
서버 생성
MCP 서버는 AI 클라이언트가 연결할 수 있는 엔드포인트입니다. mcp:make-server Artisan 명령으로 새 서버를 생성합니다.
php artisan mcp:make-server MyServer생성된 서버 클래스는 app/Mcp/Servers 디렉터리에 위치합니다.
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server\Server;
class MyServer extends Server
{
//
}서버 등록
생성한 서버는 config/mcp.php 설정 파일의 servers 배열에 등록해야 합니다.
use App\Mcp\Servers\MyServer;
'servers' => [
MyServer::class,
],웹 서버
웹 서버는 HTTP를 통해 AI 클라이언트와 통신합니다. 원격 AI 서비스(예: Claude.ai, OpenAI 플러그인 등)에서 여러분의 Laravel 애플리케이션에 접근할 때 사용합니다.
웹 서버 방식에서 MCP 엔드포인트는 일반 HTTP 라우트로 노출되며, Server-Sent Events(SSE) 또는 Streamable HTTP 전송 방식을 지원합니다.
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server\Server;
use Laravel\Mcp\Server\Transport\HttpTransport;
class MyServer extends Server
{
public string $transport = HttpTransport::class;
}로컬 서버
로컬 서버는 표준 입출력(stdio)을 통해 동작하며, 주로 CLI 도구나 로컬 개발 환경에서 AI 에이전트와 직접 연동할 때 사용합니다. 예를 들어, Cursor IDE나 로컬에서 실행하는 AI 코딩 도구와 연결하는 경우가 이에 해당합니다.
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server\Server;
use Laravel\Mcp\Server\Transport\StdioTransport;
class MyServer extends Server
{
public string $transport = StdioTransport::class;
}로컬 서버는 Artisan 명령으로 실행합니다.
php artisan mcp:serve MyServer도구 (Tools)
도구는 AI 모델이 호출할 수 있는 함수입니다. 예를 들어 데이터베이스 조회, 외부 API 호출, 계산 수행 등을 도구로 만들어 AI가 필요에 따라 실행하도록 할 수 있습니다.
도구 생성
mcp:make-tool Artisan 명령으로 도구 클래스를 생성합니다.
php artisan mcp:make-tool SearchProducts생성된 클래스는 app/Mcp/Tools 디렉터리에 위치합니다.
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Tool\Tool;
class SearchProducts extends Tool
{
public string $name = 'search_products';
public string $description = '상품을 검색합니다.';
public function handle(): mixed
{
// 도구 실행 로직
}
}$name은 AI 클라이언트가 도구를 식별하는 고유 이름이며, $description은 AI 모델이 언제 이 도구를 사용해야 하는지 판단하는 데 활용됩니다. 설명을 명확하게 작성할수록 AI가 도구를 올바르게 사용합니다.
도구 입력 스키마
도구가 입력 인수를 받아야 한다면 $inputSchema를 정의합니다. JSON Schema 형식을 사용합니다.
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Tool\Tool;
class SearchProducts extends Tool
{
public string $name = 'search_products';
public string $description = '키워드로 상품을 검색합니다.';
public array $inputSchema = [
'type' => 'object',
'properties' => [
'keyword' => [
'type' => 'string',
'description' => '검색할 키워드',
],
'limit' => [
'type' => 'integer',
'description' => '결과 최대 개수',
'default' => 10,
],
],
'required' => ['keyword'],
];
public function handle(string $keyword, int $limit = 10): mixed
{
// 상품 검색 로직
}
}도구 출력 스키마
도구의 반환값 구조를 AI 모델에게 명시하고 싶다면 $outputSchema를 정의합니다. 출력 스키마도 JSON Schema 형식을 사용합니다.
public array $outputSchema = [
'type' => 'object',
'properties' => [
'products' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string'],
'price' => ['type' => 'number'],
],
],
],
],
];도구 인수 유효성 검사
rules 메서드를 정의하면 도구 실행 전에 인수를 Laravel Validation 규칙으로 검사합니다. 잘못된 인수가 전달되면 오류 응답을 반환합니다.
public function rules(): array
{
return [
'keyword' => ['required', 'string', 'min:1', 'max:100'],
'limit' => ['integer', 'min:1', 'max:100'],
];
}도구 의존성 주입
handle 메서드에 타입 힌트를 사용하면 Laravel 서비스 컨테이너가 자동으로 의존성을 주입합니다. 인수와 의존성을 함께 선언할 때, 도구 인수는 메서드 앞부분에, 의존성은 뒤에 배치합니다.
use App\Services\ProductService;
public function handle(string $keyword, int $limit = 10, ProductService $productService): mixed
{
return $productService->search($keyword, $limit);
}도구 어노테이션
어노테이션을 사용하면 AI 모델에게 도구의 동작 특성(읽기 전용 여부, 부작용 여부 등)을 알려줄 수 있습니다. 이는 AI가 더 안전하고 올바르게 도구를 활용하는 데 도움을 줍니다.
use Laravel\Mcp\Tool\Tool;
use Laravel\Mcp\Tool\Annotations\ToolAnnotations;
class SearchProducts extends Tool
{
public string $name = 'search_products';
public string $description = '상품을 검색합니다.';
public ?ToolAnnotations $annotations = new ToolAnnotations(
readOnlyHint: true, // 데이터를 변경하지 않는 읽기 전용 도구
idempotentHint: true, // 동일 입력에 동일 결과를 반환
openWorldHint: false, // 외부 시스템에 접근하지 않음
);
}조건부 도구 등록
특정 조건에서만 도구를 노출하고 싶을 때 shouldRegister 메서드를 사용합니다. 예를 들어 특정 기능 플래그가 활성화된 경우에만 도구를 등록할 수 있습니다.
public function shouldRegister(): bool
{
return config('features.product_search_enabled', false);
}도구 응답
handle 메서드에서 반환하는 값은 자동으로 MCP 응답 형식으로 변환됩니다. 문자열, 배열, 또는 ToolResponse 객체를 반환할 수 있습니다.
use Laravel\Mcp\Tool\ToolResponse;
public function handle(string $keyword): ToolResponse
{
$products = Product::search($keyword)->get();
return ToolResponse::text($products->toJson());
}이미지를 반환하는 경우 ToolResponse::image()를 사용합니다.
return ToolResponse::image($imageData, 'image/png');여러 콘텐츠를 함께 반환하려면 배열로 구성합니다.
return ToolResponse::make()
->addText('검색 결과:')
->addText($products->toJson());프롬프트 (Prompts)
프롬프트는 AI 모델에게 제공하는 재사용 가능한 메시지 템플릿입니다. 공통적으로 사용하는 지시문이나 맥락 정보를 프롬프트로 만들어 두면, AI 클라이언트가 필요할 때마다 요청하여 사용할 수 있습니다.
프롬프트 생성
mcp:make-prompt Artisan 명령으로 프롬프트 클래스를 생성합니다.
php artisan mcp:make-prompt ProductRecommendation생성된 클래스는 app/Mcp/Prompts 디렉터리에 위치합니다.
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Prompt\Prompt;
class ProductRecommendation extends Prompt
{
public string $name = 'product_recommendation';
public string $description = '사용자 취향에 맞는 상품 추천 프롬프트를 생성합니다.';
public function handle(): mixed
{
return '사용자의 구매 내역과 관심사를 분석하여 적합한 상품을 추천해 주세요.';
}
}프롬프트 인수
프롬프트가 동적 값을 받아야 한다면 $arguments를 정의합니다.
use Laravel\Mcp\Prompt\PromptArgument;
public array $arguments = [
new PromptArgument(
name: 'category',
description: '추천할 상품 카테고리',
required: true,
),
new PromptArgument(
name: 'budget',
description: '예산 범위 (원)',
required: false,
),
];
public function handle(string $category, ?string $budget = null): mixed
{
$prompt = "{$category} 카테고리에서 좋은 상품을 추천해 주세요.";
if ($budget) {
$prompt .= " 예산은 {$budget}원 이내입니다.";
}
return $prompt;
}프롬프트 인수 유효성 검사
도구와 마찬가지로 rules 메서드로 프롬프트 인수를 검사할 수 있습니다.
public function rules(): array
{
return [
'category' => ['required', 'string'],
'budget' => ['nullable', 'numeric', 'min:0'],
];
}프롬프트 의존성 주입
handle 메서드에 타입 힌트를 추가하면 서비스 컨테이너에서 의존성이 자동으로 주입됩니다.
use App\Services\CategoryService;
public function handle(string $category, CategoryService $categoryService): mixed
{
$categoryInfo = $categoryService->getInfo($category);
return "다음 카테고리 정보를 바탕으로 상품을 추천해 주세요: {$categoryInfo->description}";
}조건부 프롬프트 등록
shouldRegister 메서드를 구현하면 조건에 따라 프롬프트 등록 여부를 제어할 수 있습니다.
public function shouldRegister(): bool
{
return config('features.ai_recommendations_enabled', false);
}프롬프트 응답
handle 메서드는 문자열 또는 PromptResponse 객체를 반환할 수 있습니다. PromptResponse를 사용하면 역할(role)이 있는 메시지 목록을 구성할 수 있습니다.
use Laravel\Mcp\Prompt\PromptResponse;
use Laravel\Mcp\Prompt\Message;
public function handle(string $category): PromptResponse
{
return PromptResponse::make()
->addMessage(Message::user("{$category} 카테고리의 인기 상품을 추천해 주세요."))
->addMessage(Message::assistant('네, 분석하여 추천해 드리겠습니다.'));
}리소스 (Resources)
리소스는 AI 모델이 읽을 수 있는 데이터나 문서입니다. 도구가 "실행"에 초점을 맞춘다면, 리소스는 "읽기"에 초점을 맞춥니다.
리소스 생성
mcp:make-resource Artisan 명령으로 리소스 클래스를 생성합니다.
php artisan mcp:make-resource ProductCatalog생성된 클래스는 app/Mcp/Resources 디렉터리에 위치합니다.
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Resource\Resource;
class ProductCatalog extends Resource
{
public string $uri = 'catalog://products';
public string $name = 'product_catalog';
public string $description = '전체 상품 카탈로그 정보를 제공합니다.';
public string $mimeType = 'application/json';
public function handle(): mixed
{
return Product::all()->toJson();
}
}리소스 템플릿
URI 패턴을 사용하면 동적 리소스를 만들 수 있습니다. 예를 들어 특정 상품 ID에 해당하는 리소스를 제공하려면 URI 템플릿을 활용합니다.
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Resource\Resource;
class ProductDetail extends Resource
{
public string $uri = 'catalog://products/{id}';
public string $name = 'product_detail';
public string $description = '특정 상품의 상세 정보를 제공합니다.';
public string $mimeType = 'application/json';
public function handle(int $id): mixed
{
return Product::findOrFail($id)->toJson();
}
}URI의 {id} 부분이 handle 메서드의 $id 파라미터로 자동 바인딩됩니다.
리소스 URI 및 MIME 타입
리소스 URI는 AI 클라이언트가 리소스를 식별하는 고유 주소입니다. 일반적으로 scheme://path 형식을 사용하며, 여러분의 도메인에 맞는 커스텀 스킴을 자유롭게 정의할 수 있습니다.
$mimeType은 반환하는 콘텐츠의 형식을 나타냅니다.
| 콘텐츠 유형 | MIME 타입 |
|---|---|
| JSON 데이터 | application/json |
| 일반 텍스트 | text/plain |
| 마크다운 문서 | text/markdown |
| HTML | text/html |
리소스 요청
리소스 handle 메서드에서 현재 MCP 요청 정보에 접근하려면 ResourceRequest 객체를 주입합니다.
use Laravel\Mcp\Resource\ResourceRequest;
public function handle(ResourceRequest $request): mixed
{
$uri = $request->uri();
// 요청 URI를 기반으로 데이터 반환
}리소스 의존성 주입
handle 메서드에서 서비스 컨테이너의 의존성을 주입받을 수 있습니다.
use App\Services\CatalogService;
public function handle(CatalogService $catalogService): mixed
{
return $catalogService->getFullCatalog()->toJson();
}리소스 어노테이션
리소스에도 어노테이션을 달아 AI 모델에게 리소스의 특성을 알릴 수 있습니다.
use Laravel\Mcp\Resource\Annotations\ResourceAnnotations;
public ?ResourceAnnotations $annotations = new ResourceAnnotations(
audience: ['assistant'], // 이 리소스를 사용하는 대상
priority: 0.8, // 중요도 (0.0 ~ 1.0)
);조건부 리소스 등록
shouldRegister 메서드로 리소스 등록 여부를 조건부로 제어합니다.
public function shouldRegister(): bool
{
return auth()->check() && auth()->user()->hasRole('admin');
}리소스 응답
handle 메서드는 문자열, 배열 또는 ResourceResponse 객체를 반환합니다. 바이너리 데이터(이미지, 파일 등)를 반환하려면 ResourceResponse를 사용합니다.
use Laravel\Mcp\Resource\ResourceResponse;
public function handle(): ResourceResponse
{
$content = Storage::get('catalog.json');
return ResourceResponse::text($content);
}이미지나 바이너리 파일의 경우:
return ResourceResponse::blob(
Storage::get('product-image.png'),
'image/png'
);앱 (Apps)
앱은 MCP 리소스를 통해 AI 클라이언트에게 인터랙티브한 UI를 제공하는 기능입니다. Blade 뷰나 별도의 프런트엔드를 활용하여 AI 대화 흐름에 풍부한 인터페이스를 삽입할 수 있습니다.
앱 리소스 생성
mcp:make-app Artisan 명령으로 앱 리소스를 생성합니다.
php artisan mcp:make-app ProductApp도구에서 앱 렌더링
도구의 응답에서 앱 리소스를 렌더링하여 AI 클라이언트에게 UI를 제공할 수 있습니다.
use Laravel\Mcp\Tool\ToolResponse;
public function handle(): ToolResponse
{
return ToolResponse::make()
->addText('상품 검색 결과를 아래에서 확인하세요.')
->renderApp('product-app', ['products' => Product::all()]);
}앱 도구 가시성
앱 내에서 특정 도구만 노출하려면 $tools 프로퍼티를 설정합니다.
public array $tools = [
SearchProducts::class,
GetProductDetail::class,
];앱 설정
앱의 제목, 테마, 레이아웃 등 시각적 설정을 $config로 구성합니다.
public array $config = [
'title' => '상품 검색',
'theme' => 'light',
];Boost로 앱 빌드하기
Laravel Boost를 사용하면 Blade 컴포넌트 기반으로 MCP 앱 UI를 빠르게 구성할 수 있습니다. Boost는 MCP 앱에 최적화된 UI 컴포넌트 모음을 제공합니다.
자세한 내용은 Laravel Boost 문서를 참고하세요.
메타데이터
MCP 서버의 이름, 버전, 설명 등 메타데이터는 config/mcp.php에서 설정합니다.
'server' => [
'name' => '내 Laravel MCP 서버',
'version' => '1.0.0',
],이 정보는 AI 클라이언트가 서버에 처음 연결할 때 교환되는 핸드셰이크 과정에서 사용됩니다.
아이콘
도구, 프롬프트, 리소스에 아이콘을 지정하면 일부 AI 클라이언트의 UI에서 시각적으로 표시됩니다. 이모지 또는 URL을 사용할 수 있습니다.
public string $icon = '🔍';
// 또는 이미지 URL
public string $icon = 'https://example.com/icons/search.png';인증 (Authentication)
웹 서버 방식의 MCP 엔드포인트는 인증을 통해 무단 접근을 차단해야 합니다.
OAuth 2.1
Laravel MCP는 OAuth 2.1 기반 인증을 지원합니다. config/mcp.php에서 OAuth 설정을 구성합니다.
'auth' => [
'driver' => 'oauth',
'oauth' => [
// OAuth 설정
],
],NOTE
OAuth 2.1은 외부 AI 서비스가 사용자를 대신하여 MCP 서버에 접근할 때 적합합니다. 서비스 간 신뢰 관계가 필요한 프로덕션 환경에서 권장합니다.
Sanctum
내부 도구나 개인 프로젝트처럼 비교적 단순한 인증이 필요한 경우에는 Laravel Sanctum을 사용할 수 있습니다.
'auth' => [
'driver' => 'sanctum',
],Sanctum 토큰을 발급하여 MCP 클라이언트에 제공하면, 클라이언트는 Authorization: Bearer {token} 헤더로 요청합니다.
인가 (Authorization)
도구, 프롬프트, 리소스에서 Gate 또는 Policy를 활용하여 세밀한 접근 제어를 구현할 수 있습니다.
use Illuminate\Support\Facades\Gate;
public function handle(string $keyword): mixed
{
Gate::authorize('search-products');
return Product::search($keyword)->get()->toJson();
}shouldRegister 메서드를 활용하면 권한이 없는 사용자에게는 도구 자체를 노출하지 않을 수도 있습니다.
public function shouldRegister(): bool
{
return Gate::allows('use-product-search-tool');
}MCP 클라이언트
Laravel MCP는 서버 역할뿐만 아니라, 다른 MCP 서버에 연결하는 클라이언트 기능도 제공합니다. 이를 통해 외부 AI 서비스나 다른 Laravel MCP 서버의 도구와 리소스를 호출할 수 있습니다.
서버 연결
Mcp 파사드를 사용하여 외부 MCP 서버에 연결합니다.
use Laravel\Mcp\Facades\Mcp;
$client = Mcp::connect('https://external-mcp-server.example.com/mcp');Named 클라이언트
자주 연결하는 서버는 config/mcp.php에 이름을 붙여 미리 등록해 두면 편리합니다.
'clients' => [
'external' => [
'url' => 'https://external-mcp-server.example.com/mcp',
],
],등록한 클라이언트는 이름으로 간단히 가져올 수 있습니다.
$client = Mcp::client('external');클라이언트 인증
인증이 필요한 서버에 연결할 때는 토큰을 설정합니다.
'clients' => [
'external' => [
'url' => 'https://external-mcp-server.example.com/mcp',
'token' => env('EXTERNAL_MCP_TOKEN'),
],
],도구
연결된 서버의 도구 목록을 조회하고 실행합니다.
// 도구 목록 조회
$tools = $client->tools()->list();
// 도구 실행
$result = $client->tools()->call('search_products', [
'keyword' => '무선 이어폰',
'limit' => 5,
]);프롬프트
연결된 서버의 프롬프트를 조회하고 가져옵니다.
// 프롬프트 목록 조회
$prompts = $client->prompts()->list();
// 프롬프트 가져오기
$prompt = $client->prompts()->get('product_recommendation', [
'category' => '전자기기',
]);리소스
연결된 서버의 리소스를 조회하고 읽습니다.
// 리소스 목록 조회
$resources = $client->resources()->list();
// 리소스 읽기
$content = $client->resources()->read('catalog://products');서버 테스트
MCP Inspector
MCP Inspector는 MCP 서버를 브라우저에서 직접 탐색하고 테스트할 수 있는 공식 개발 도구입니다. 로컬 개발 중에 도구, 프롬프트, 리소스가 올바르게 동작하는지 빠르게 확인할 수 있습니다.
npx @modelcontextprotocol/inspector실행 후 브라우저에서 http://localhost:5173에 접속하여 로컬 MCP 서버의 URL을 입력하면 됩니다.
유닛 테스트
Laravel MCP는 도구, 프롬프트, 리소스를 테스트하기 위한 전용 테스트 헬퍼를 제공합니다.
use Laravel\Mcp\Testing\McpFake;
test('상품 검색 도구가 올바른 결과를 반환한다', function () {
$response = McpFake::tool(SearchProducts::class)
->call(['keyword' => '무선 이어폰', 'limit' => 5]);
$response->assertOk();
$response->assertTextContains('무선 이어폰');
});프롬프트 테스트:
test('상품 추천 프롬프트가 카테고리를 포함한다', function () {
$response = McpFake::prompt(ProductRecommendation::class)
->get(['category' => '전자기기']);
$response->assertOk();
$response->assertContains('전자기기');
});리소스 테스트:
test('상품 카탈로그 리소스가 JSON을 반환한다', function () {
$response = McpFake::resource(ProductCatalog::class)
->read();
$response->assertOk();
$response->assertMimeType('application/json');
});NOTE
MCP 기능은 빠르게 발전하는 생태계입니다. 프로덕션 환경에 적용하기 전에 반드시 사용 중인 AI 클라이언트의 MCP 지원 버전을 확인하고, MCP 공식 사양도 함께 참고하세요.
MCP
소개
Laravel MCP는 AI 클라이언트가 Model Context Protocol을 통해 여러분의 Laravel 애플리케이션과 상호작용할 수 있도록 간단하고 우아한 방법을 제공합니다. 서버, 도구(tool), 리소스(resource), 프롬프트(prompt)를 직관적인 플루언트 인터페이스로 정의할 수 있으며, 이를 통해 AI 기반의 애플리케이션 연동을 구현할 수 있습니다.
MCP
설치
Laravel MCP를 프로젝트에 추가하려면 Composer로 패키지를 설치합니다.
composer require laravel/mcp라우트 파일 퍼블리시
설치 후, 아래 Artisan 명령어를 실행하여 MCP 서버를 정의할 routes/ai.php 파일을 퍼블리시합니다.
php artisan vendor:publish --tag=ai-routes이 명령어를 실행하면 애플리케이션의 routes 디렉터리에 routes/ai.php 파일이 생성됩니다. MCP 서버 등록은 이 파일에서 관리합니다.
MCP
서버 생성
make:mcp-server Artisan 명령어를 사용하면 MCP 서버를 생성할 수 있습니다. 서버는 도구(tool), 리소스(resource), 프롬프트(prompt) 등 MCP 기능을 AI 클라이언트에 노출하는 중심 통신 지점 역할을 합니다.
php artisan make:mcp-server WeatherServer이 명령어를 실행하면 app/Mcp/Servers 디렉터리에 새 서버 클래스가 생성됩니다. 생성된 클래스는 Laravel MCP의 기본 클래스인 Laravel\Mcp\Server를 상속하며, PHP 속성(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('이 서버는 날씨 정보와 예보를 제공합니다.')]
class WeatherServer extends Server
{
/**
* 이 MCP 서버에 등록된 도구 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
// GetCurrentWeatherTool::class,
];
/**
* 이 MCP 서버에 등록된 리소스 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
*/
protected array $resources = [
// WeatherGuidelinesResource::class,
];
/**
* 이 MCP 서버에 등록된 프롬프트 목록.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
*/
protected array $prompts = [
// DescribeWeatherPrompt::class,
];
}서버 등록
서버를 생성했다면, 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를 활용하세요.
MCP
도구 (Tools)
도구(Tool)를 사용하면 MCP 서버에 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('지정된 위치의 현재 날씨 예보를 가져옵니다.')]
class CurrentWeatherTool extends Tool
{
/**
* 도구 요청을 처리합니다.
*/
public function handle(Request $request): Response
{
$location = $request->get('location');
// 날씨 정보 조회...
return Response::text('현재 날씨는...');
}
/**
* 도구의 입력 스키마를 반환합니다.
*
* @return array<string, \Illuminate\JsonSchema\Types\Type>
*/
public function schema(JsonSchema $schema): array
{
return [
'location' => $schema->string()
->description('날씨를 조회할 위치')
->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,
];
}도구 이름, 타이틀, 설명
기본적으로 도구의 이름과 타이틀은 클래스명에서 자동으로 유도됩니다. 예를 들어 CurrentWeatherTool은 이름이 current-weather, 타이틀이 Current Weather Tool로 설정됩니다. Name 및 Title 어트리뷰트를 사용하면 이 값들을 직접 지정할 수 있습니다:
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('지정된 위치의 현재 날씨 예보를 가져옵니다.')]
class CurrentWeatherTool extends Tool
{
//
}NOTE
설명(description)은 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('날씨를 조회할 위치')
->required(),
'units' => $schema->string()
->enum(['celsius', 'fahrenheit'])
->description('사용할 온도 단위')
->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('섭씨 온도')
->required(),
'conditions' => $schema->string()
->description('날씨 상태')
->required(),
'humidity' => $schema->integer()
->description('습도 (%)')
->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 클라이언트는 반환된 에러 메시지를 기반으로 동작합니다. 따라서 에러 메시지는 구체적이고 명확하게 작성하는 것이 매우 중요합니다:
$validated = $request->validate([
'location' => ['required', 'string', 'max:100'],
'units' => 'in:celsius,fahrenheit',
], [
'location.required' => '날씨를 조회할 위치를 반드시 지정해야 합니다. 예: "서울", "부산"',
'units.in' => '온도 단위는 "celsius" 또는 "fahrenheit" 중 하나여야 합니다.',
]);도구 의존성 주입
모든 도구는 Laravel 서비스 컨테이너를 통해 리졸브됩니다. 따라서 생성자에 의존성을 타입힌트하면 자동으로 주입됩니다:
<?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);
// ...
}
}도구 어노테이션
도구에 어노테이션을 추가하면 AI 클라이언트에 도구의 동작 방식과 특성에 관한 추가 메타데이터를 제공할 수 있습니다. 어노테이션은 PHP 어트리뷰트로 적용합니다:
<?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 | 도구가 외부 엔티티와 상호작용할 수 있음을 나타냅니다. |
어노테이션에 불리언 인자를 명시적으로 전달하여 값을 지정할 수도 있습니다:
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('날씨 요약: 맑음, 22°C');
}도구 실행 중 오류가 발생했음을 알리려면 error 메서드를 사용합니다:
return Response::error('날씨 데이터를 가져올 수 없습니다. 다시 시도해 주세요.');이미지나 오디오 콘텐츠를 반환하려면 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');Laravel 파일시스템 디스크에서 직접 이미지나 오디오를 불러오려면 fromStorage 메서드를 사용하세요. 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('날씨 요약: 맑음, 22°C'),
Response::text("**상세 예보**\n- 오전: 18°C\n- 오후: 25°C\n- 저녁: 21°C"),
];
}구조화된 응답
structured 메서드를 사용하면 구조화된 콘텐츠를 반환할 수 있습니다. AI 클라이언트가 파싱 가능한 데이터를 받을 수 있으며, 동시에 JSON으로 인코딩된 텍스트 표현도 하위 호환성을 위해 함께 제공됩니다:
return Response::structured([
'temperature' => 22.5,
'conditions' => '구름 조금',
'humidity' => 65,
]);구조화된 콘텐츠와 함께 커스텀 텍스트도 제공하고 싶다면 withStructuredContent 메서드를 사용하세요:
return Response::make(
Response::text('현재 날씨는 22.5°C, 맑음입니다.')
)->withStructuredContent([
'temperature' => 22.5,
'conditions' => '맑음',
]);스트리밍 응답
오래 걸리는 작업이나 실시간 데이터 스트리밍이 필요한 경우, 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 값을 이벤트로 클라이언트에 전송합니다.
MCP
프롬프트 (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 어트리뷰트를 사용하면 이 값을 직접 지정할 수 있습니다:
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 모델이 이 프롬프트를 언제, 어떻게 활용할지 판단하는 데 직접적인 영향을 미치므로, 명확하고 구체적으로 작성하는 것이 좋습니다.
프롬프트 인수
프롬프트는 인수(argument)를 정의하여 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 메서드 안에서 $request->validate()를 호출하면 됩니다:
<?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 메서드의 인수에 타입 힌트를 지정하는 메서드 주입도 사용할 수 있습니다. 서비스 컨테이너가 메서드 호출 시 자동으로 의존성을 resolve하여 주입합니다:
<?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 인스턴스의 이터러블을 반환할 수 있습니다. 이 응답 객체들이 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 어시스턴트의 발화로 처리됩니다. 이 메서드를 사용하지 않은 일반 메시지는 사용자 입력으로 처리됩니다.
MCP
리소스(Resources)
리소스는 MCP 서버가 AI 클라이언트에게 데이터나 콘텐츠를 노출할 수 있도록 해주는 기능입니다. AI 클라이언트는 이 리소스를 읽어 언어 모델과 상호작용할 때 컨텍스트로 활용합니다. 문서, 설정 파일, 또는 AI 응답의 품질을 높이는 데 도움이 되는 정적·동적 정보를 공유하는 데 적합합니다.
리소스 생성하기
리소스는 McpServer의 resource() 메서드로 등록합니다. 각 리소스에는 이름, URI, 반환할 콘텐츠가 필요합니다.
use Laravel\MCP\Facades\McpServer;
use ModelContextProtocol\Server\Resources\TextResourceContents;
McpServer::resource(
name: '애플리케이션 설정',
uri: 'config://app',
handler: function (): TextResourceContents {
return new TextResourceContents(
uri: 'config://app',
text: json_encode(config('app'), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
);
}
);NOTE
리소스 URI는 config://, docs://, db:// 등 임의의 스킴을 사용할 수 있습니다. 실제 HTTP URL일 필요는 없으며, 클라이언트가 리소스를 식별하는 논리적 식별자입니다.
다음 단계
리소스를 등록했다면, AI 클라이언트는 해당 URI를 통해 데이터를 요청할 수 있습니다. 정적인 설정값부터 데이터베이스 조회 결과까지 다양한 정보를 리소스로 노출해 언어 모델이 더 정확한 응답을 생성하도록 도울 수 있습니다.
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 어트리뷰트를 사용하면 이 값을 직접 지정할 수 있습니다:
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('날씨 API 사용에 대한 종합 가이드라인입니다.')]
class WeatherGuidelinesResource extends Resource
{
//
}NOTE
설명은 리소스 메타데이터에서 매우 중요한 역할을 합니다. 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('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("사용자 {$userId}의 프로필");
}
}Request 객체는 URI에서 추출된 변수와 원본 URI 모두를 제공하므로, 리소스 요청을 처리하는 데 필요한 전체 컨텍스트를 활용할 수 있습니다.
리소스 URI와 MIME 타입
각 리소스는 고유한 URI로 식별되며, MIME 타입을 통해 AI 클라이언트가 리소스의 형식을 파악할 수 있습니다.
기본적으로 URI는 리소스 이름을 기반으로 자동 생성됩니다. 예를 들어 WeatherGuidelinesResource의 URI는 weather://resources/weather-guidelines가 됩니다. 기본 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 클라이언트가 리소스 콘텐츠를 적절히 처리하고 해석하는 데 활용됩니다.
리소스 요청
툴이나 프롬프트와 달리, 리소스는 입력 스키마나 인수를 별도로 정의할 수 없습니다. 하지만 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
{
// ...
}
}리소스 의존성 주입
모든 리소스는 Laravel 서비스 컨테이너를 통해 resolve됩니다. 따라서 생성자에 필요한 의존성을 타입힌트로 선언하면 자동으로 주입됩니다:
<?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 메서드에 직접 의존성을 타입힌트할 수 있습니다. 메서드가 호출될 때 서비스 컨테이너가 자동으로 해당 의존성을 resolve하여 주입합니다:
<?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 클라이언트에 추가 메타데이터를 제공할 수 있습니다. 어노테이션은 PHP 어트리뷰트 형태로 리소스 클래스에 적용합니다:
<?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와 이름을 전달합니다. 리소스 링크는 콘텐츠를 직접 포함하는 것이 아니라 AI 클라이언트가 독립적으로 가져올 URI 포인터를 반환합니다:
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')));Blob 응답의 MIME 타입은 리소스 클래스에 설정된 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('지정한 위치의 날씨 데이터를 가져올 수 없습니다.');MCP Apps
Laravel MCP는 MCP Apps를 지원합니다. MCP Apps는 Model Context Protocol의 확장 기능으로, 지원되는 호스트에서 샌드박스 iframe 안에 인터랙티브한 HTML 애플리케이션을 렌더링할 수 있게 해줍니다. 이를 통해 단순한 텍스트 응답을 넘어 대시보드, 폼, 데이터 시각화 등 풍부한 UI 경험을 제공할 수 있습니다.
MCP App은 두 가지 구성 요소로 이루어집니다:
- 앱 리소스(App Resource): 애플리케이션의 독립적인 HTML을 반환하는 리소스
- 도구(Tool):
#[RendersApp]속성을 통해 앱 리소스와 연결된 도구. 도구가 호출되면 호스트가 연결된 리소스를 가져와 렌더링합니다.
앱 리소스 생성
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('날씨 정보를 표시하는 인터랙티브 대시보드입니다.')]
#[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">새로고침</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('날씨 대시보드를 불러왔습니다.');
}
}AppResource가 하나라도 등록되어 있으면 Laravel MCP가 자동으로 io.modelcontextprotocol/ui 기능을 광고하므로, 별도의 서버 설정은 필요하지 않습니다.
도구 가시성 설정
각 #[RendersApp] 도구는 visibility 인자를 통해 호출 가능한 대상을 제한할 수 있습니다. 이는 UI가 데이터를 로드하거나 갱신할 때 내부적으로 호출하는 도구를 모델에는 노출하지 않고 앱 전용으로 만들 때 유용합니다:
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 enum은 Library::Tailwind, Library::Alpine 등 자주 사용하는 프론트엔드 라이브러리의 CDN 스크립트를 미리 설정해 두며, 해당 CDN 출처는 CSP에 자동으로 추가됩니다. Permission enum은 Camera, Microphone, Geolocation, ClipboardWrite 등 브라우저 권한을 지원합니다.
동적으로 설정을 계산해야 하는 경우, 리소스의 appMeta 메서드를 오버라이드하고 Laravel\Mcp\Server\Ui 네임스페이스의 AppMeta, Csp, Permissions 빌더를 사용하면 됩니다.
Boost로 앱 빌드하기
Laravel MCP에는 MCP Apps 빌드를 위한 전용 Boost 스킬 레퍼런스가 포함되어 있습니다. Laravel Boost가 설치되어 있다면, AI 코딩 에이전트가 mcp-development 스킬을 호출해 앱 리소스, Blade 뷰, 연결된 도구를 자동으로 스캐폴딩할 수 있습니다.
클라이언트 사이드 API와 스키마 세부 사항을 포함한 전체 프로토콜 레퍼런스는 공식 MCP Apps 문서를 참고하세요.
MCP
메타데이터
Laravel MCP는 MCP 명세에 정의된 _meta 필드를 지원합니다. 일부 MCP 클라이언트나 통합 환경에서는 이 필드가 필수로 요구되기도 합니다. 메타데이터는 도구(tool), 리소스(resource), 프롬프트(prompt) 등 모든 MCP 프리미티브와 그 응답에 적용할 수 있습니다.
개별 응답 콘텐츠에 메타데이터를 첨부하려면 withMeta 메서드를 사용합니다.
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 도구 요청을 처리합니다.
*/
public function handle(Request $request): Response
{
return Response::text('날씨가 맑습니다.')
->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('날씨가 맑습니다.')
)->withMeta(['request_id' => '12345']);
}NOTE
withMeta를 콘텐츠 단위로 호출하면 개별 콘텐츠 항목에 메타데이터가 붙고, Response::make를 통해 호출하면 응답 전체를 감싸는 결과 객체에 메타데이터가 붙습니다. 두 방식의 적용 범위가 다르므로 사용 목적에 맞게 선택하세요.
도구, 리소스, 프롬프트 클래스 자체에 메타데이터를 첨부하려면 클래스에 $meta 프로퍼티를 정의합니다.
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
#[Description('현재 날씨 예보를 가져옵니다.')]
class CurrentWeatherTool extends Tool
{
protected ?array $meta = [
'version' => '2.0',
'author' => 'Weather Team',
];
// ...
}아이콘
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로 변환됩니다.
MCP 인증
라우트와 마찬가지로, 웹 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;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Passport::authorizationView(function ($parameters) {
return view('mcp.authorize', $parameters);
});
}이 뷰는 AI 에이전트의 인증 시도를 승인하거나 거부할 수 있도록 인증 과정 중 최종 사용자에게 표시됩니다.
NOTE
이 시나리오에서 OAuth는 기반이 되는 인증 모델로의 변환 레이어로 사용됩니다. 스코프(scope) 등 OAuth의 여러 측면은 현재 고려 대상에서 제외됩니다.
기존 Passport 설치 환경에서 사용
이미 Laravel Passport를 사용 중인 애플리케이션이라면, Laravel MCP는 기존 Passport 설치 환경에서 원활하게 동작합니다. 다만, OAuth가 주로 인증 모델의 변환 레이어로 사용되기 때문에 현재 커스텀 스코프는 지원되지 않습니다.
위에서 설명한 Mcp::oauthRoutes 메서드를 통해 Laravel MCP는 mcp:use라는 단일 스코프를 추가하고, 이를 외부에 공표하며 사용합니다.
Passport vs. Sanctum
OAuth 2.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 요청을 인증하면 됩니다.
MCP
인가 (Authorization)
MCP 툴과 리소스 내에서 $request->user() 메서드를 통해 현재 인증된 사용자에 접근할 수 있으며, 이를 활용해 인가 확인을 수행할 수 있습니다.
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
/**
* 툴 요청을 처리합니다.
*/
public function handle(Request $request): Response
{
if (! $request->user()->can('read-weather')) {
return Response::error('권한이 없습니다.');
}
// ...
}NOTE
$request->user()는 Laravel의 표준 인증 시스템과 통합되어 있으므로, 게이트(Gate)나 폴리시(Policy)를 그대로 활용할 수 있습니다.
MCP 클라이언트
서버를 구축하는 것 외에도, Laravel MCP는 외부 MCP 서버(자체 서버 또는 서드파티 서버)에 연결할 수 있는 클라이언트를 제공합니다. 이 클라이언트를 통해 애플리케이션이 MCP 서버에서 노출한 도구를 검색하고 호출할 수 있으며, AI 에이전트가 외부 MCP 서버의 기능을 활용할 수 있도록 하는 데 특히 유용합니다.
서버 연결
HTTP로 접근 가능한 MCP 서버에 연결하려면 Client::web 메서드에 서버 URL을 전달하면 됩니다:
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, ping, disconnect 메서드를 사용할 수 있습니다:
$client->connect();
$client->ping();
if ($client->connected()) {
// ...
}
$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');이름이 지정된 클라이언트는 요청당 한 번만 리졸브되며, 요청 생명주기가 끝나면 자동으로 연결이 해제됩니다.
클라이언트 인증
Bearer 토큰으로 보호된 웹 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
MCP 서버가 동적 클라이언트 등록(dynamic client registration)을 지원하는 경우, clientId와 clientSecret 인수를 생략할 수 있습니다. 이 경우 클라이언트가 자동으로 자신을 등록합니다.
다음으로, 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)입니다. 두 라우트 모두 기본적으로 web 미들웨어 그룹을 사용하며, middleware 인수로 변경할 수 있습니다.
인가 흐름을 시작하려면 사용자를 연결 라우트로 리다이렉트하면 됩니다:
return redirect()->route('mcp.oauth.github.connect');도구(Tools)
MCP 서버에서 노출하는 도구 목록은 tools 메서드로 가져올 수 있습니다. 반환값은 도구 이름을 키로 하는 컬렉션입니다:
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' => '서울',
]);
$result->text(); // 응답의 텍스트 내용
(string) $result; // text() 호출과 동일
$result->isError; // 도구가 오류를 반환했는지 여부
$result->structuredContent; // 구조화된 콘텐츠(있는 경우)목록에서 가져온 도구 인스턴스에서 직접 호출하는 방법도 있습니다:
$tools = Mcp::client('github')->tools();
$result = $tools['current-weather']->call([
'location' => '서울',
]);Laravel AI SDK로 에이전트를 구축하는 경우, MCP 클라이언트의 도구를 에이전트에 직접 제공하여 모델이 프롬프트에 응답하는 동안 도구를 호출하도록 할 수 있습니다. 자세한 내용은 AI SDK 문서의 MCP 도구 섹션을 참고하세요.
프롬프트(Prompts)
MCP 서버에서 노출하는 프롬프트 목록은 prompts 메서드로 가져올 수 있습니다. 반환값은 프롬프트 이름을 키로 하는 컬렉션입니다:
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' => '서울',
]);
$result->text(); // 메시지의 텍스트 내용
(string) $result; // text() 호출과 동일
$result->messages; // 프롬프트가 반환한 원시 메시지
$result->description; // 프롬프트 설명(있는 경우)리소스(Resources)
MCP 서버에서 노출하는 리소스 목록은 resources 메서드로 가져올 수 있습니다. 반환값은 리소스 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
서버 테스트
MCP 서버는 내장된 MCP Inspector를 사용하거나 유닛 테스트를 작성하여 검증할 수 있습니다.
MCP Inspector
MCP Inspector는 MCP 서버를 테스트하고 디버깅하기 위한 대화형 도구입니다. 이를 통해 서버에 연결하고, 인증을 확인하며, 툴(tool)·리소스(resource)·프롬프트(prompt)를 직접 실행해볼 수 있습니다.
등록된 서버라면 어디서든 Inspector를 실행할 수 있습니다.
# 웹 서버 경로로 실행...php artisan mcp:inspector mcp/weather# "weather"라는 이름의 로컬 서버로 실행...php artisan mcp:inspector weather이 명령을 실행하면 MCP Inspector가 시작되고, MCP 클라이언트에 복사할 수 있는 클라이언트 설정이 출력됩니다. 웹 서버가 인증 미들웨어로 보호되고 있다면, 연결 시 Authorization 베어러 토큰 등 필요한 헤더를 함께 포함해야 합니다.
유닛 테스트
MCP 서버, 툴, 리소스, 프롬프트 각각에 대해 유닛 테스트를 작성할 수 있습니다.
시작하려면 테스트 케이스를 새로 만들고, 해당 프리미티브(primitive)를 등록한 서버 클래스에서 직접 호출하면 됩니다. 예를 들어 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 — 디버깅 목적으로 응답의 원시(raw) 내용을 출력할 때 사용합니다.
$response->dd();
$response->dump();