Aprende a integrar un servidor Model Context Protocol (MCP) en tu aplicación Laravel. Puedes definir tools, resources y prompts con los que los agentes de IA interactúan con tu aplicación.
Model Context Protocol (MCP) es una especificación que estandariza la comunicación entre clientes de IA (Claude, Cursor, GitHub Copilot, etc.) y las aplicaciones. Al implementar un servidor MCP, los agentes de IA pueden acceder a los datos de tu aplicación Laravel y ejecutar acciones sobre ella.
Laravel MCP es un paquete oficial añadido en Laravel 13. Se distribuye como laravel/ai — más concretamente laravel/mcp — y ofrece todo lo necesario para construir servidores MCP.
Un servidor MCP puede ofrecer tres tipos de capacidades:
Capacidad
Descripción
Tools
Funciones invocables por el cliente de IA (búsqueda, actualización, integración con APIs externas, etc.)
Resources
Datos o información de contexto legible por el cliente
Un tool es una función invocable por el cliente de IA. Puedes implementar obtención de datos, integraciones con APIs externas, operaciones de base de datos, etc.
<?phpnamespace 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'); // Obtener los datos meteorológicos... 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(), ]; }}
Por defecto, el nombre y el título se generan a partir del nombre de la clase. Para CurrentWeatherTool, el nombre es current-weather y el título Current Weather Tool. Puedes personalizarlos con los atributos Name y Title.
La descripción (Description) no se genera automáticamente. Es imprescindible para que el modelo entienda cómo usar el tool: defínela siempre con un contenido significativo.
Define los parámetros de entrada en el método schema. Puedes usar el JSON schema builder de Laravel para especificar tipos y restricciones.
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'), ];}
Dentro de handle puedes usar la validación estándar de 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.', ]); // Utiliza los datos validados...}
Cuando la validación falla, el cliente de IA reintenta apoyándose en el mensaje de error. Proporciona mensajes concretos y accionables.
Los prompts son plantillas reutilizables. Aportan un formato estandarizado para las consultas típicas que el cliente de IA usa al conversar con un modelo de lenguaje.
Define los parámetros del prompt en el método arguments.
<?phpnamespace 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, ), ]; }}
Los argumentos se validan automáticamente según su definición, pero puedes aplicar reglas más complejas.Laravel MCP se integra sin fricciones con la validación de Laravel. Puedes validar los argumentos dentro de handle.
<?phpnamespace 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']; // Generar el prompt utilizando el tono indicado... }}
Cuando la validación falla, el cliente de IA reintenta usando los mensajes. Proporciona mensajes claros y prácticos.
handle puede devolver mensajes del usuario y del asistente. Con asAssistant() marcas un mensaje como del asistente.
<?phpnamespace 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), ]; }}
Los resources son datos o información que el cliente de IA puede leer como contexto. Puedes ofrecer documentación, configuraciones o datos dinámicos que mejoren las respuestas de la IA.
<?phpnamespace 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); }}
Por defecto, la URI se genera a partir del nombre de la clase (por ejemplo, weather://resources/weather-guidelines). Puedes personalizarla con los atributos Uri y MimeType.
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{ // ...}
Para definir resources dinámicos con variables en la URI, implementa la interfaz HasUriTemplate.
<?phpnamespace 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'); // Obtener y devolver el contenido del archivo... return Response::text("File {$fileId} for user {$userId}"); }}
Las variables de la URI se incorporan automáticamente a la request y se recuperan con get.
A diferencia de tools y prompts, los resources no definen un esquema de entrada ni argumentos. Aun así, dentro de handle puedes acceder a la información de la petición mediante el objeto request.
<?phpnamespace 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 { // Consultar información de la request... }}
El método resourceLink devuelve un enlace a un resource. En vez de incrustar el contenido, se envía un puntero URI que el cliente descarga por su cuenta.
Laravel MCP soporta las MCP Apps, una extensión del Model Context Protocol que permite que los tools rendericen aplicaciones HTML interactivas dentro de un iframe seguro alojado por el host compatible. Así puedes ofrecer dashboards, formularios, visualizaciones y experiencias enriquecidas más allá de las respuestas de texto plano.Las MCP Apps se apoyan en dos piezas que trabajan juntas:
App resource: devuelve el HTML autocontenido de la aplicación.
Tool: se enlaza al app resource mediante el atributo #[RendersApp]. Cuando se invoca el tool, el host obtiene el resource enlazado y lo renderiza.
El comando crea dos archivos: una clase PHP en app/Mcp/Resources y una vista Blade en resources/views/mcp. El nombre de la vista se deduce de la clase (por ejemplo, WeatherDashboardApp → mcp.weather-dashboard-app).
AppResource extiende Resource y configura automáticamente el esquema de URI ui:// y el MIME text/html;profile=mcp-app que exige la especificación de MCP Apps. Como cualquier otro resource, debes registrarlo en la propiedad $resources del servidor.La vista Blade generada utiliza el componente <x-mcp::app>, que renderiza un documento HTML completo con el SDK cliente de MCP empaquetado.
La función global createMcpApp la aporta el SDK empaquetado. Gestiona la conexión del iframe con el servidor, aplica el tema del host y expone helpers y callbacks como callServerTool, sendMessage, openLink, etc. Consulta la API cliente completa en la especificación de MCP Apps.
Para mostrar un app resource, enlázalo desde un tool con el atributo #[RendersApp]. Al invocarse el tool, Laravel MCP incluye la URI del resource en la metadata para que el host pueda renderizar la app en un iframe seguro.
<?phpnamespace 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.'); }}
Cuando se registra un AppResource, Laravel MCP anuncia automáticamente la capacidad io.modelcontextprotocol/ui. No requiere configuración adicional del servidor.
Cada tool #[RendersApp] puede restringir sus invocadores mediante el argumento visibility. Es útil para exponer tools privados destinados solo a la app (por ejemplo, para cargar o refrescar datos) que no debe ver el modelo.
El enum Visibility tiene los valores Model y App; por defecto se aplican ambos. Usa [Visibility::App] para acciones backend que solo debe llamar la UI y [Visibility::Model] para deshabilitar el tool desde la UI.
El atributo #[AppMeta] del app resource configura la Content Security Policy del iframe, los permisos del navegador y las librerías que se incluyen en el <head> de la vista.
El enum Library incluye scripts CDN preconfigurados para librerías populares (Tailwind, Alpine, etc.) y sus orígenes se combinan automáticamente en el CSP. Permission cubre permisos habituales del navegador (Camera, Microphone, Geolocation, ClipboardWrite…).
Si necesitas configuración dinámica, sobrescribe el método appMeta del resource utilizando los builders fluidos AppMeta, Csp y Permissions del namespace Laravel\Mcp\Server\Ui.
Laravel MCP incluye una skill de referencia específica de Boost para construir MCP Apps. Si tienes Laravel Boost instalado, tu agente puede invocar la skill mcp-development y generar automáticamente el app resource, la vista Blade y los tools enlazados.Para la referencia completa (API cliente, esquemas, etc.), consulta la documentación oficial de MCP Apps.
Los clientes MCP pueden mostrar iconos del servidor y de sus primitivas. El atributo Icon permite declarar iconos en el servidor, tools, resources y prompts.
El atributo es repetible, por lo que puedes declarar varias variantes para tamaños o temas (claro/oscuro).También puedes definir los iconos programáticamente sobrescribiendo el método icons. Es útil cuando los iconos dependen de condiciones en tiempo de ejecución.
use Laravel\Mcp\Schema\Icon;class CurrentWeatherTool extends Tool{ /** * Obtiene los iconos del tool. * * @return array<int, Icon> */ public function icons(): array { return [ Icon::from('mcp/tool.png', mimeType: 'image/png'), ]; }}
Los iconos declarados con atributos y con el método icons se combinan automáticamente. Las rutas se resuelven así:
Rutas con esquema (https:, data:, etc.) se usan tal cual.
Rutas relativas se resuelven con el helper asset de Laravel.
Autenticación OAuth con Laravel Passport, idónea cuando necesitas seguridad más robusta.
use App\Mcp\Servers\WeatherServer;use Laravel\Mcp\Facades\Mcp;Mcp::oauthRoutes();Mcp::web('/mcp/weather', WeatherServer::class) ->middleware('auth:api');
Para usar OAuth publica las vistas de autorización de Passport y configúralas en el service provider.
Con $request->user() obtienes el usuario autenticado y puedes comprobar permisos dentro de tools y resources.
public function handle(Request $request): Response{ if (! $request->user()->can('read-weather')) { return Response::error('Permission denied.'); } // Continuar el procesamiento...}
Laravel MCP no solo permite construir servidores: también incluye un cliente para conectar con otros servidores MCP. Con él puedes descubrir e invocar los tools expuestos por servidores externos. Es especialmente útil para exponer capacidades de servidores MCP externos a agentes de IA.
Para servidores accesibles por HTTP utiliza Client::web, pasando la URL del servidor.
use Laravel\Mcp\Client;$client = Client::web('https://mcp.example.com');
Para servidores locales que arrancan como comando, usa Client::local con el comando y sus argumentos.
use Laravel\Mcp\Client;$client = Client::local('php', ['artisan', 'mcp:start']);
El cliente conecta de forma perezosa: la conexión se establece automáticamente cuando obtienes el listado de tools o los invocas por primera vez. Si prefieres gestionarla manualmente, dispones de connect, connected, ping y disconnect.
En lugar de crear el cliente cada vez, puedes registrar clientes con nombre reutilizables. Normalmente se hace con el facade Mcp en el método boot de un service provider.
use Laravel\Mcp\Client;use Laravel\Mcp\Facades\Mcp;Mcp::registerClient('github', fn () => Client::web('https://mcp.example.com'));
Después puedes resolverlo por nombre.
use Laravel\Mcp\Facades\Mcp;$client = Mcp::client('github');
Un cliente con nombre se resuelve una sola vez por petición y se desconecta automáticamente al terminar el ciclo.
Si el servidor MCP admite registro dinámico de clientes, puedes omitir clientId y clientSecret: el cliente se registra automáticamente.
A continuación, en routes/ai.php registra las rutas OAuth del cliente con nombre mediante oAuthRoutesFor. La closure recibe el nombre del cliente y un TokenSet una vez intercambiado el código por un access token.
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');});
Se registran dos rutas con nombre: la ruta connect (mcp.oauth.{client}.connect), que redirige al usuario al servidor de autorización, y la ruta callback (mcp.oauth.{client}.callback), que intercambia el código y ejecuta el handler. Ambas usan el grupo de middleware web (personalizable con el argumento middleware).Para iniciar el flujo, redirige al usuario a la ruta connect.
Si construyes agentes con el Laravel AI SDK, puedes pasar los tools del cliente MCP directamente al agente para que el modelo los invoque durante sus respuestas. Consulta la sección MCP tools del AI SDK.
Para probar servidores MCP de forma interactiva, utiliza «MCP Inspector».
# Servidor webphp artisan mcp:inspector mcp/weather# Servidor local (registrado con el nombre "weather")php artisan mcp:inspector weather
El comando abre el inspector y te permite copiar la configuración del cliente. Si has configurado middleware de autenticación, incluye la cabecera Authorization en la conexión.
Puedes escribir tests unitarios de tools, resources y prompts.
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.');}
Los prompts y resources se prueban del mismo modo.
$response->assertOk(); // Sin errores$response->assertSee('...'); // Contiene el texto indicado
Verifica presencia o ausencia de errores con assertHasErrors / assertHasNoErrors.
$response->assertHasErrors();$response->assertHasErrors([ 'Something went wrong.',]);$response->assertHasNoErrors();
Comprueba el nombre, el título o la descripción del tool, resource o prompt.
$response->assertName('current-weather');$response->assertTitle('Current Weather Tool');$response->assertDescription('Fetches the current weather forecast for a specified location.');
Para respuestas en streaming, valida las notificaciones con assertSentNotification y assertNotificationCount.