El 30 de abril de 2026, el equipo de Laravel publicó soporte para MCP Apps. Es una extensión del protocolo MCP que resuelve algo que llevaba meses siendo un límite claro: las herramientas MCP solo pueden devolver texto.
Da igual los datos que devuelvas. Una tabla, una lista de pedidos, un conjunto de métricas. El modelo lo convierte en un párrafo. El usuario lee, pregunta, el modelo vuelve a consultar. Funciona, pero es un paso atrás respecto a lo que llevamos años construyendo en la web.
MCP Apps rompe eso. Una herramienta puede devolver HTML, y ese HTML se renderiza dentro del cliente MCP, en un iframe, como parte de la conversación.
Cómo está estructurado
Hay dos piezas que trabajan juntas.
Por un lado, un App Resource: una clase que extiende AppResource en lugar de Resource, y que devuelve una vista Blade. Esta clase configura automáticamente el esquema URI ui:// y el MIME type text/html;profile=mcp-app que requiere la especificación. No hay que tocar nada de eso manualmente.
Por otro lado, una herramienta vinculada al App Resource mediante el atributo #[RendersApp]. Cuando el modelo invoca esa herramienta, el host recoge la URI del recurso y renderiza el HTML dentro del chat.
El App Resource se genera con:
php artisan make:mcp-app-resource OrdersDashboardAppEsto crea dos archivos: la clase PHP en app/Mcp/Resources y una vista Blade en resources/views/mcp. La vista ya viene preparada con el componente <x-mcp::app>, que incluye el SDK de JavaScript para comunicarse con el servidor MCP desde el interior del iframe.
La clase generada tiene esta pinta:
<?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('Dashboard interactivo de pedidos.')]
#[AppMeta]
class OrdersDashboardApp extends AppResource
{
public function handle(Request $request): Response
{
return Response::view('mcp.orders-dashboard-app', [
'title' => $this->title(),
]);
}
}Para vincularla a una herramienta, usas #[RendersApp]:
<?php
namespace App\Mcp\Tools;
use App\Mcp\Resources\OrdersDashboardApp;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Tool;
#[RendersApp(resource: OrdersDashboardApp::class)]
class ShowOrdersDashboard extends Tool
{
public function handle(Request $request): Response
{
return Response::text('Dashboard cargado.');
}
}Ambas clases tienen que estar registradas en tu servidor MCP: el App Resource en $resources, la herramienta en $tools. Laravel detecta automáticamente que hay un AppResource registrado y anuncia la capacidad io.modelcontextprotocol/ui durante el handshake, sin configuración adicional.
Lo que puede hacer la interfaz
Desde la vista Blade, el SDK expone createMcpApp, una función global que conecta el iframe con el servidor. Puedes llamar a herramientas, enviar mensajes al modelo o abrir enlaces, todo dentro del mismo contexto de conversación:
createMcpApp(async (app) => {
document.getElementById('refresh-btn').addEventListener('click', async () => {
const result = await app.callServerTool('get-recent-orders', {});
renderOrders(result.content[0]?.text);
});
});El usuario no abre una pestaña nueva. No pierde el hilo. El modelo sigue teniendo contexto de todo lo que ha pasado en la conversación.
Visibilidad de herramientas
Puedes definir herramientas que solo son visibles para la app, no para el modelo. Útil cuando tienes acciones de backend que la interfaz llama directamente y no quieres que el agente las invoque por su cuenta:
<?php
namespace App\Mcp\Tools;
use App\Mcp\Resources\OrdersDashboardApp;
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Ui\Enums\Visibility;
use Laravel\Mcp\Server\Tool;
#[RendersApp(resource: OrdersDashboardApp::class, visibility: [Visibility::App])]
class GetRecentOrders extends Tool
{
// Solo la app puede invocar esta herramienta, el modelo no la ve
}Configuración del iframe
El atributo #[AppMeta] controla la Content Security Policy del iframe, los permisos del navegador y las librerías que se cargan en el <head>. Laravel trae preconfiguraciones para Tailwind y Alpine:
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Server\Attributes\AppMeta;
use Laravel\Mcp\Server\Ui\Enums\Library;
use Laravel\Mcp\Server\AppResource;
#[AppMeta(
connectDomains: ['https://api.midominio.com'],
libraries: [Library::Tailwind, Library::Alpine],
)]
class OrdersDashboardApp extends AppResource
{
// ...
}Los dominios que añades en connectDomains se incluyen automáticamente en la CSP, así que no tienes que configurar nada más para que el iframe pueda hacer peticiones a tu API.
Cuándo tiene sentido
Si una herramienta devuelve un valor simple, texto es suficiente. MCP Apps tiene sentido cuando los datos necesitan interacción: listados paginados, dashboards, formularios de confirmación, resultados con filtros.
La diferencia real frente a abrir una web aparte es el contexto compartido. La app vive dentro de la conversación y puede comunicarse con el servidor en tiempo real, sin capas adicionales de autenticación ni APIs separadas.
Enlaces
Curso Laravel 12
Completo 2026
El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.
star Incluido en cualquier suscripción