메인 콘텐츠로 건너뛰기

MCP 란

Model Context Protocol(MCP) 은 AI 클라이언트(Claude, Cursor, GitHub Copilot 등)와 애플리케이션이 표준화된 프로토콜로 통신하기 위한 사양입니다. MCP 서버를 구현함으로써 AI 에이전트는 여러분의 Laravel 애플리케이션의 데이터에 접근하거나 액션을 실행할 수 있게 됩니다.
Laravel MCP는 Laravel 13에서 추가된 공식 패키지입니다. laravel/mcp로 제공되며, MCP 서버의 구축에 필요한 일련의 기능을 제공합니다.
MCP 서버가 제공할 수 있는 기능은 주로 3가지입니다.
기능설명
도구(Tools)AI 클라이언트가 호출할 수 있는 함수. 검색·업데이트·외부 API 연계 등
리소스(Resources)AI 클라이언트가 읽어 들일 수 있는 데이터나 컨텍스트 정보
프롬프트(Prompts)재사용 가능한 프롬프트 템플릿

설치

Composer로 패키지를 설치합니다.
composer require laravel/mcp
설치 후 vendor:publish Artisan 명령어를 실행해 routes/ai.php 파일을 생성합니다.
php artisan vendor:publish --tag=ai-routes
이 명령어에 의해 routes/ai.php 파일이 작성됩니다. 여기에 MCP 서버의 등록을 기술합니다.

서버의 작성

make:mcp-server Artisan 명령어로 서버 클래스를 생성합니다.
php artisan make:mcp-server WeatherServer
app/Mcp/Servers 디렉터리에 서버 클래스가 생성됩니다.
<?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
{
    protected array $tools = [
        // GetCurrentWeatherTool::class,
    ];

    protected array $resources = [
        // WeatherGuidelinesResource::class,
    ];

    protected array $prompts = [
        // DescribeWeatherPrompt::class,
    ];
}

서버의 등록

서버를 작성했다면 routes/ai.php에서 등록합니다. 등록 방법에는 Web 서버로컬 서버의 2종류가 있습니다.

Web 서버

Web 서버는 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 명령어로서 동작합니다. Claude Desktop 등의 로컬 AI 클라이언트와의 연계에 사용합니다.
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::local('weather', WeatherServer::class);
로컬 서버는 일반적으로 MCP 클라이언트가 자동으로 기동합니다. 수동으로 mcp:start Artisan 명령어를 실행할 필요는 없습니다.

도구

도구는 AI 클라이언트가 호출할 수 있는 함수입니다. 데이터의 취득, 외부 API와의 연계, 데이터베이스의 조작 등을 구현할 수 있습니다.

도구의 작성

make:mcp-tool Artisan 명령어로 도구 클래스를 생성합니다.
php artisan make:mcp-tool CurrentWeatherTool
작성한 도구를 서버의 $tools 프로퍼티에 등록합니다.
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    protected array $tools = [
        CurrentWeatherTool::class,
    ];
}
기본적인 도구 클래스의 구현 예시입니다.
<?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 sunny, 22°C.');
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'location' => $schema->string()
                ->description('The location to get the weather for.')
                ->required(),
        ];
    }
}

도구의 이름과 설명

클래스 이름에서 기본 이름과 타이틀이 자동 생성됩니다. 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
{
    // ...
}
도구의 설명(Description)은 자동 생성되지 않습니다. AI 모델이 도구의 사용법을 이해하기 위해 필수이므로, 반드시 의미 있는 설명을 설정해 주십시오.

입력 스키마

schema 메서드로 입력 파라미터의 스키마를 정의합니다. Laravel의 JSON 스키마 빌더를 사용해 타입이나 제약을 지정할 수 있습니다.
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'),
    ];
}

출력 스키마

outputSchema 메서드로 응답의 구조를 정의할 수 있습니다. AI 클라이언트가 응답을 해석하기 쉬워집니다.
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(),
    ];
}

밸리데이션

handle 메서드 내에서 Laravel의 표준 밸리데이션 기능을 사용할 수 있습니다.
public function handle(Request $request): Response
{
    $validated = $request->validate([
        'location' => 'required|string|max:100',
        'units' => 'in:celsius,fahrenheit',
    ], [
        'location.required' => 'You must specify a location. For example, "New York City" or "Tokyo".',
        'units.in' => 'You must specify either "celsius" or "fahrenheit" for the units.',
    ]);

    // 밸리데이션 완료된 데이터를 사용해 처리...
}
밸리데이션 실패 시에 AI 클라이언트는 에러 메시지를 참고해 재시도합니다. 구체적이고 실행 가능한 에러 메시지를 제공해 주십시오.

의존성 주입

Laravel의 서비스 컨테이너를 통해 도구가 해결되므로, 생성자나 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 __construct(
        protected WeatherRepository $weather,
    ) {}

    public function handle(Request $request, WeatherRepository $weather): Response
    {
        $location = $request->get('location');
        $forecast = $weather->getForecastFor($location);

        return Response::text("Forecast: {$forecast}");
    }
}

어노테이션

도구에 어노테이션을 추가함으로써 AI 클라이언트에 도구의 거동에 관한 추가 정보를 제공할 수 있습니다.
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]도구가 환경을 변경하지 않음을 나타냄
#[IsDestructive]도구가 파괴적인 업데이트를 할 가능성이 있음을 나타냄
#[IsIdempotent]같은 인수로 반복 호출해도 부작용이 없음을 나타냄
#[IsOpenWorld]도구가 외부 엔티티와 대화할 가능성이 있음을 나타냄

조건부 등록

shouldRegister 메서드를 구현함으로써 실행 시에 도구를 조건부로 등록할 수 있습니다.
public function shouldRegister(Request $request): bool
{
    return $request?->user()?->subscribed() ?? false;
}
false를 반환하면 그 도구는 AI 클라이언트로부터 보이지 않게 됩니다.

응답

도구는 Laravel\Mcp\Response의 인스턴스를 반환해야 합니다.
return Response::text('Weather Summary: Sunny, 22°C');
return Response::error('Unable to fetch weather data. Please try again.');
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');

// 스토리지에서 직접 읽어 들임(MIME 타입은 자동 검출)
return Response::fromStorage('weather/radar.png');
public function handle(Request $request): array
{
    return [
        Response::text('Weather Summary: Sunny, 22°C'),
        Response::text("**Detailed Forecast**\n- Morning: 18°C\n- Afternoon: 25°C"),
    ];
}
AI 클라이언트가 해석하기 쉬운 구조화 데이터를 반환합니다.
return Response::structured([
    'temperature' => 22.5,
    'conditions' => 'Partly cloudy',
    'humidity' => 65,
]);
장시간 걸리는 처리로 도중 경과를 실시간 전송합니다.
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));
    }
}

프롬프트

프롬프트는 재사용 가능한 프롬프트 템플릿입니다. AI 클라이언트가 언어 모델과 대화할 때 사용하는 정형적인 쿼리를 표준화된 형태로 제공할 수 있습니다.

프롬프트의 작성

php artisan make:mcp-prompt DescribeWeatherPrompt
서버의 $prompts 프로퍼티에 등록합니다.
use App\Mcp\Prompts\DescribeWeatherPrompt;

class WeatherServer extends Server
{
    protected array $prompts = [
        DescribeWeatherPrompt::class,
    ];
}

프롬프트의 인수

arguments 메서드로 프롬프트의 파라미터를 정의합니다.
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Prompts\Argument;

class DescribeWeatherPrompt extends Prompt
{
    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,
            ),
        ];
    }
}

밸리데이션

프롬프트의 인수는 정의에 기반해 자동으로 밸리데이션되지만, 더 복잡한 밸리데이션 룰을 적용할 수도 있습니다. 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'];

        // 지정된 tone 을 사용해 프롬프트를 생성...
    }
}
밸리데이션 실패 시, AI 클라이언트는 에러 메시지를 참고해 재시도합니다. 구체적이고 실행 가능한 메시지를 제공해 주십시오.
$validated = $request->validate([
    'tone' => ['required', 'string', 'max:50'],
], [
    'tone.*' => 'tone 을 지정해 주십시오. 예: "formal", "casual", "humorous".',
]);

의존성 주입

Laravel의 서비스 컨테이너를 통해 프롬프트가 해결되므로, 생성자나 handle 메서드에서 의존 관계를 타입 힌트할 수 있습니다.
<?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 메서드에서도 타입 힌트할 수 있으며, 서비스 컨테이너가 자동으로 해결·주입합니다.
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;
    }
}
false를 반환하면 그 프롬프트는 AI 클라이언트로부터 보이지 않게 되며, 호출도 할 수 없게 됩니다.

프롬프트의 응답

프롬프트의 handle 메서드에서는 사용자 메시지와 어시스턴트 메시지를 반환할 수 있습니다. asAssistant()로 어시스턴트 측 메시지로서 취급합니다.
<?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): 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 Tokyo?';

        return [
            Response::text($systemMessage)->asAssistant(),
            Response::text($userMessage),
        ];
    }
}

리소스

리소스는 AI 클라이언트가 컨텍스트로서 읽어 들일 수 있는 데이터나 정보입니다. 문서, 설정 정보, 동적 데이터 등, AI의 응답 품질을 향상시키는 정보를 제공할 수 있습니다.

리소스의 작성

php artisan make:mcp-resource WeatherGuidelinesResource
서버의 $resources 프로퍼티에 등록합니다.
use App\Mcp\Resources\WeatherGuidelinesResource;

class WeatherServer extends Server
{
    protected array $resources = [
        WeatherGuidelinesResource::class,
    ];
}
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Resource;

#[Description('Comprehensive guidelines for using the Weather API.')]
class WeatherGuidelinesResource extends Resource
{
    public function handle(Request $request): Response
    {
        $guidelines = "# Weather API Guidelines\n\n- Always specify a location...";

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

URI 와 MIME 타입

기본적으로 클래스 이름에서 URI가 자동 생성됩니다(예: weather://resources/weather-guidelines). UriMimeType 속성으로 커스터마이즈할 수 있습니다.
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 변수를 가진 동적 리소스를 정의하려면 HasUriTemplate 인터페이스를 구현합니다.
<?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
{
    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("File {$fileId} for user {$userId}");
    }
}
URI로부터의 변수는 자동으로 요청에 도입되어, get 메서드로 취득할 수 있습니다.

리소스의 요청

도구나 프롬프트와 달리 리소스는 입력 스키마나 인수를 정의할 수 없습니다. 다만 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의 서비스 컨테이너를 통해 리소스가 해결되므로, 생성자나 handle 메서드에서 의존 관계를 타입 힌트할 수 있습니다.
<?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 메서드에서도 타입 힌트할 수 있으며, 서비스 컨테이너가 자동으로 해결·주입합니다.
public function handle(WeatherRepository $weather): Response
{
    return Response::text($weather->guidelines());
}

리소스의 어노테이션

리소스에는 오디언스, 우선도, 최종 업데이트일 등의 어노테이션을 붙일 수 있습니다.
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]stringISO 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;
    }
}
false를 반환하면 그 리소스는 AI 클라이언트로부터 보이지 않게 되며, 접근도 할 수 없게 됩니다.

리소스의 응답

리소스는 Laravel\Mcp\Response의 인스턴스를 반환해야 합니다. 텍스트 콘텐츠에는 text 메서드를 사용합니다.
return Response::text($weatherData);

리소스 링크 응답

resourceLink 메서드로 리소스 링크를 반환할 수 있습니다. 매입 리소스와 달리 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 메서드를 사용합니다. MIME 타입은 리소스의 #[MimeType] 속성으로 설정합니다.
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
    // ...
}

에러 응답

에러를 나타내는 경우 error 메서드를 사용합니다.
return Response::error('지정된 장소의 날씨 데이터를 취득할 수 없었습니다.');

Laravel MCP는 MCP Apps를 지원합니다. 이것은 Model Context Protocol의 확장 기능으로, 도구가 지원된 호스트 내의 샌드박스 iframe 상에서 인터랙티브한 HTML 애플리케이션을 렌더링할 수 있게 합니다. 이에 따라 플레인 텍스트 응답을 넘어선 대시보드, 폼, 시각화, 그 외의 리치한 경험을 구축할 수 있습니다. MCP 앱은 다음 2개의 파트가 연계되어 동작합니다.
  • 앱 리소스 — 애플리케이션의 자기 완결형 HTML을 반환합니다.
  • 도구#[RendersApp] 어트리뷰트를 사용해 앱 리소스에 링크됩니다. 도구가 호출되면 호스트가 링크된 리소스를 취득해 렌더링합니다.

앱 리소스의 작성

make:mcp-app-resource Artisan 명령어로 앱 리소스를 작성할 수 있습니다.
php artisan make:mcp-app-resource WeatherDashboardApp
이 명령어는 2개의 파일을 작성합니다. app/Mcp/Resources 내의 PHP 클래스와 resources/views/mcp 내의 Blade 뷰입니다. 뷰 이름은 클래스 이름에서 자동으로 추측됩니다. 예를 들어 WeatherDashboardAppmcp.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
{
    /**
     * Handle the app resource request.
     */
    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
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        return Response::text('Weather dashboard loaded.');
    }
}
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에는 ModelApp의 2개의 케이스가 있으며, 기본은 양쪽입니다. 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::TailwindLibrary::Alpine 등 일반적인 프런트엔드 라이브러리의 CDN 스크립트가 사전 설정되어 있으며, CDN 오리진은 자동으로 CSP에 머지됩니다. Permission enum은 Camera·Microphone·Geolocation·ClipboardWrite 등의 브라우저 권한을 커버합니다.
동적인 설정이 필요한 경우, Laravel\Mcp\Server\Ui 네임스페이스의 AppMeta·Csp·Permissions 플루언트 빌더를 사용해 리소스의 appMeta 메서드를 오버라이드합니다.

Boost 를 사용한 앱 개발

Laravel MCP에는 MCP Apps를 구축하기 위한 전용 Boost 스킬 레퍼런스가 포함되어 있습니다. Laravel Boost가 설치되어 있다면, AI 코딩 에이전트가 mcp-development 스킬을 호출해 앱 리소스, Blade 뷰, 링크된 도구를 자동 생성할 수 있습니다. 프로토콜의 완전한 레퍼런스(클라이언트 사이드 API나 스키마의 상세 포함)에 대해서는 공식의 MCP Apps 문서를 참조해 주십시오.

메타데이터

MCP 사양의 _meta 필드를 도구, 리소스, 프롬프트의 응답에 부가할 수 있습니다.
// 응답 콘텐츠에의 메타데이터
return Response::text('The weather is sunny.')
    ->withMeta(['source' => 'weather-api', 'cached' => true]);
응답 엔벨로프 전체에 메타데이터를 붙이는 경우는 Response::make를 사용합니다.
return Response::make(
    Response::text('The weather is sunny.')
)->withMeta(['request_id' => '12345']);
도구·리소스·프롬프트의 클래스 자체에 메타데이터를 붙이려면 $meta 프로퍼티를 정의합니다.
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로 해결됩니다.

인증

Web 서버는 Laravel의 표준적인 미들웨어로 인증할 수 있습니다.

Sanctum

Laravel Sanctum을 사용한 토큰 인증입니다. MCP 클라이언트는 Authorization: Bearer <token> 헤더를 전송합니다.
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

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

OAuth 2.1

Laravel Passport를 사용한 OAuth 인증입니다. 더 견고한 보안이 필요한 경우에 적합합니다.
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware('auth:api');
OAuth 인증을 사용하는 경우, Passport의 인가 뷰를 공개해 서비스 프로바이더에 설정합니다.
php artisan vendor:publish --tag=mcp-views
// AppServiceProvider::boot()
use Laravel\Passport\Passport;

Passport::authorizationView(function ($parameters) {
    return view('mcp.authorize', $parameters);
});

인가

$request->user()로 인증된 사용자를 취득해, 도구나 리소스 내에서 인가 체크를 할 수 있습니다.
public function handle(Request $request): Response
{
    if (! $request->user()->can('read-weather')) {
        return Response::error('Permission denied.');
    }

    // 처리를 계속...
}

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 connect)하며, 도구의 목록 취득이나 호출을 처음 할 때 자동으로 접속을 확립합니다. 접속을 수동으로 관리하는 경우, connect, connected, ping, disconnect 메서드를 사용합니다.
$client->connect();

$client->ping();

if ($client->connected()) {
    // ...
}

$client->disconnect();
withTimeout 메서드로 요청 타임아웃을 커스터마이즈할 수 있습니다.
$client = Client::web('https://mcp.example.com')->withTimeout(30);

이름 있는 클라이언트

클라이언트를 매번 구축하는 대신, 재사용 가능한 이름 있는 클라이언트를 등록할 수 있습니다. 통상은 Mcp 파사드를 사용하고, 서비스 프로바이더의 boot 메서드 내에서 수행합니다.
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 토큰으로 보호되어 있는 Web 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 메서드를 사용합니다.
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'),
));
MCP 서버가 동적 클라이언트 등록을 지원하고 있는 경우, clientIdclientSecret은 생략할 수 있습니다. 클라이언트가 자동 등록합니다.
다음으로, routes/ai.php 파일에서 이름 있는 클라이언트의 OAuth 라우트를 oAuthRoutesFor 메서드를 사용해 등록합니다. 전달하는 클로저는 인가 코드와 액세스 토큰이 교환된 후에 클라이언트 이름과 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');
});
이에 따라 2개의 이름이 있는 라우트가 등록됩니다. 사용자를 인가 서버로 리다이렉트하는 connect 라우트(mcp.oauth.{client}.connect)와, 인가 코드를 교환해 핸들러를 호출하는 callback 라우트(mcp.oauth.{client}.callback)입니다. 어느 쪽도 web 미들웨어 그룹을 사용합니다(middleware 인수로 덮어쓰기 가능). 인가 플로우를 개시하려면 사용자를 connect 라우트로 리다이렉트합니다.
return redirect()->route('mcp.oauth.github.connect');

도구

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 메서드로 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 메서드로 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 의 blob 은 자동으로 디코드)
(string) $result;     // content() 와 동등
$result->mimeType();  // 리소스의 MIME 타입(존재하는 경우)
$result->contents;    // 리소스가 반환한 콘텐츠(원시 데이터)

테스트

MCP Inspector

MCP 서버의 동작 확인에는 인터랙티브한 디버그 도구 “MCP Inspector”를 사용합니다.
# Web 서버
php artisan mcp:inspector mcp/weather

# 로컬 서버(이름이 "weather" 인 경우)
php artisan mcp:inspector weather
명령어를 실행하면 MCP Inspector가 기동해, 클라이언트 설정을 복사할 수 있습니다. 인증 미들웨어를 설정하고 있는 경우, Authorization 헤더를 포함해 접속해 주십시오.

유닛 테스트

도구·리소스·프롬프트에 대해 유닛 테스트를 쓸 수 있습니다.
test('tool', function () {
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'Tokyo',
        'units' => 'celsius',
    ]);

    $response
        ->assertOk()
        ->assertSee('The current weather in Tokyo is 22°C and sunny.');
});
public function test_tool(): void
{
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'Tokyo',
        'units' => 'celsius',
    ]);

    $response
        ->assertOk()
        ->assertSee('The current weather in Tokyo is 22°C and sunny.');
}
프롬프트와 리소스도 동일하게 테스트할 수 있습니다.
$response = WeatherServer::prompt(DescribeWeatherPrompt::class, ['tone' => 'casual']);
$response = WeatherServer::resource(WeatherGuidelinesResource::class);
인증된 사용자로서 실행하려면 actingAs를 사용합니다.
$response = WeatherServer::actingAs($user)->tool(CurrentWeatherTool::class, [...]);
주요 어서션 메서드는 다음과 같습니다.
$response->assertOk();           // 에러가 없음을 확인
$response->assertSee('...');     // 특정 텍스트가 포함됨을 확인
에러의 유무를 검증하려면 assertHasErrors / assertHasNoErrors를 사용합니다.
$response->assertHasErrors();

$response->assertHasErrors([
    'Something went wrong.',
]);

$response->assertHasNoErrors();
도구·리소스·프롬프트의 이름이나 타이틀, 설명을 검증할 수 있습니다.
$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();
마지막 수정일 2026년 7월 13일