Cómo construir un agente de IA con Laravel y MCP (2026)
Hace apenas un año, “integrar IA” en una aplicación Laravel significaba llamar a una API REST de OpenAI y devolver el texto al frontend. En 2026 el juego cambió: los agentes ya no se limitan a generar texto, ejecutan acciones reales sobre tus sistemas — crear usuarios, consultar pedidos, generar reportes — y lo hacen a través de un estándar abierto llamado Model Context Protocol (MCP).
En este tutorial vas a construir un servidor MCP en Laravel que expone tu lógica de negocio como herramientas invocables por Claude Desktop, Claude Code, Cursor o cualquier cliente compatible. Lo mejor: usaremos el paquete oficial laravel/mcp, mantenido por el equipo de Laravel.
¿Qué es MCP y por qué importa en 2026?
Model Context Protocol es un protocolo abierto creado por Anthropic en 2024 que estandariza cómo los modelos de IA se comunican con sistemas externos. Piensa en MCP como el “USB-C de la IA”: en lugar de escribir un wrapper distinto para cada cliente (uno para Claude, otro para Cursor, otro para tu propio agente), expones tus herramientas una sola vez siguiendo el protocolo y todos los clientes compatibles las pueden usar.
Un servidor MCP puede exponer tres cosas:
- Tools — acciones que el agente ejecuta (crear un usuario, enviar email, consultar la DB).
- Resources — datos de solo lectura que el agente puede leer (un archivo de config, el contenido de un post).
- Prompts — plantillas reutilizables que el cliente puede invocar.
Si ya integraste OpenAI siguiendo la guía para integrar la API de OpenAI en Laravel, MCP es el paso natural: en vez de que tu backend llame al modelo, el modelo llama a tu backend cuando lo necesita.
Requisitos previos
- PHP 8.2 o superior. Si aún no lo tienes, revisa cómo instalar PHP en Windows.
- Un proyecto Laravel 11 o 12 funcional. Si vienes de una versión anterior, mira cómo actualizar Laravel 9 a Laravel 10.
- Claude Desktop o Claude Code instalado para probar tu servidor.
- Composer y nociones básicas de rutas y controladores en Laravel.
Paso 1: Instalar el paquete oficial
Desde la raíz de tu proyecto:
composer require laravel/mcp
El paquete registra automáticamente un set de comandos Artisan con el prefijo make:mcp-* y mcp:*. Verifica que estén disponibles:
php artisan list mcp
Deberías ver al menos: make:mcp-server, make:mcp-tool, make:mcp-resource, make:mcp-prompt, mcp:start y mcp:inspector.
Paso 2: Crear el servidor
Vamos a crear un servidor llamado AppServer que actuará como el “punto de entrada” del agente a tu aplicación:
php artisan make:mcp-server AppServer
Esto genera app/Mcp/Servers/AppServer.php. Ábrelo y configúralo con metadata clara — los modelos usan esta información para decidir cuándo invocar tus herramientas:
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
#[Name('Ortamarco Blog Server')]
#[Version('1.0.0')]
#[Instructions(
'Servidor MCP del blog Ortamarco. Permite a los agentes listar ' .
'posts publicados, crear borradores y consultar estadísticas básicas.'
)]
class AppServer extends Server
{
protected array $tools = [
\App\Mcp\Tools\ListPostsTool::class,
\App\Mcp\Tools\CreateDraftTool::class,
];
protected array $resources = [];
protected array $prompts = [];
}
Tip: las
Instructionsson críticas. Un servidor con instrucciones vagas es ignorado por el modelo; sé específico sobre qué hace y cuándo debe usarse.
Paso 3: Crear tu primera herramienta
Vamos a construir una herramienta que liste los posts más recientes del blog. Genera el archivo:
php artisan make:mcp-tool ListPostsTool
Edita app/Mcp/Tools/ListPostsTool.php:
<?php
namespace App\Mcp\Tools;
use App\Models\BlogPost;
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(
'Lista los posts publicados del blog ordenados por fecha. ' .
'Útil cuando el usuario pregunta por contenido reciente o por un tema.'
)]
class ListPostsTool extends Tool
{
public function handle(Request $request): Response
{
$validated = $request->validate([
'limit' => 'sometimes|integer|min:1|max:50',
'search' => 'sometimes|string|max:100',
]);
$posts = BlogPost::query()
->where('status', 'published')
->when(
$validated['search'] ?? null,
fn ($q, $term) => $q->where('title', 'like', "%{$term}%")
)
->latest('published_at')
->limit($validated['limit'] ?? 10)
->get(['id', 'title', 'slug', 'published_at']);
return Response::json([
'count' => $posts->count(),
'posts' => $posts,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'limit' => $schema->integer()
->description('Número máximo de posts a devolver (1-50). Por defecto: 10'),
'search' => $schema->string()
->description('Término opcional para filtrar por título'),
];
}
}
Tres puntos clave de esta tool:
- El schema es contractual: el modelo lo lee y genera llamadas válidas. Describe cada parámetro como si se lo explicaras a un dev nuevo.
$request->validate()funciona igual que en un controller: si el modelo pasa datos inválidos, recibe un error claro que puede corregir en la siguiente llamada.- Devuelve datos estructurados con
Response::json()cuando el contenido es tabular. ReservaResponse::text()para mensajes en lenguaje natural.
Una segunda tool: crear borradores
Genera y rellena CreateDraftTool:
php artisan make:mcp-tool CreateDraftTool
<?php
namespace App\Mcp\Tools;
use App\Models\BlogPost;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Str;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
#[Description('Crea un borrador de post en el blog. Devuelve el ID y la URL de edición.')]
class CreateDraftTool extends Tool
{
public function handle(Request $request): Response
{
$data = $request->validate([
'title' => 'required|string|max:120',
'content' => 'required|string|min:50',
'tags' => 'sometimes|array',
'tags.*' => 'string|max:30',
]);
$post = BlogPost::create([
'title' => $data['title'],
'slug' => Str::slug($data['title']),
'content' => $data['content'],
'status' => 'draft',
'author_id' => config('mcp.default_author_id'),
]);
if (!empty($data['tags'])) {
$post->tags()->sync($data['tags']);
}
return Response::text(
"Borrador #{$post->id} creado: {$post->title}. " .
"Edítalo en /admin/posts/{$post->id}/edit"
);
}
public function schema(JsonSchema $schema): array
{
return [
'title' => $schema->string()
->description('Título del post (máx. 120 caracteres)')
->required(),
'content' => $schema->string()
->description('Contenido en Markdown, mínimo 50 caracteres')
->required(),
'tags' => $schema->array()
->description('Lista opcional de tags a asociar'),
];
}
}
Paso 4: Registrar las rutas del servidor
Abre routes/ai.php (créalo si no existe — Laravel 11+ lo carga automáticamente si está presente) o usa routes/web.php:
<?php
use App\Mcp\Servers\AppServer;
use Laravel\Mcp\Facades\Mcp;
// Endpoint HTTP/SSE para clientes remotos (Claude.ai, Cursor cloud, etc.)
Mcp::web('/mcp/blog', AppServer::class);
// Endpoint local stdio para CLIs (Claude Code, Claude Desktop local)
Mcp::local('blog-server', AppServer::class);
La diferencia entre los dos modos:
| Modo | Cuándo usarlo |
|---|---|
Mcp::web() | Cuando el cliente vive en otra máquina (claude.ai, deploys en la nube) |
Mcp::local() | Cuando el cliente corre en tu equipo y arranca el server como un proceso hijo |
Paso 5: Probar con el MCP Inspector
Antes de conectar Claude, valida que el servidor responde correctamente. Laravel trae un inspector integrado:
php artisan mcp:inspector
Abre la URL que imprime en consola (suele ser http://127.0.0.1:6274), selecciona tu server y prueba cada tool manualmente. Si el inspector no ve tus herramientas, casi siempre es porque olvidaste registrarlas en el array $tools del servidor.
Paso 6: Conectar Claude Desktop
Abre el archivo de configuración de Claude Desktop:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Añade tu servidor en la sección mcpServers:
{
"mcpServers": {
"ortamarco-blog": {
"command": "php",
"args": [
"/ruta/absoluta/a/tu/proyecto/artisan",
"mcp:start",
"blog-server"
]
}
}
}
Reinicia Claude Desktop. En la barra inferior del chat aparecerá un icono de herramientas: al hacer clic deberías ver list_posts_tool y create_draft_tool. Pruébalo con un prompt como:
“Lista los 5 posts más recientes del blog y sugiéreme uno que esté desactualizado.”
Claude invocará ListPostsTool automáticamente y razonará sobre la respuesta.
Consideraciones importantes
1. Seguridad
Una tool MCP es código que un modelo puede ejecutar a voluntad. Aplica los mismos principios que a una API pública:
- Valida siempre el input con
$request->validate(), nunca asumas que el modelo respeta el schema. - En tools destructivas (eliminar, enviar emails, cobrar) considera añadir un flag
dry_runo requerir confirmación. - Para el endpoint
Mcp::web()activa autenticación conMcp::oauthRoutes()y Laravel Passport — sin auth, cualquiera con la URL puede invocar tus tools. - Si manejas datos de usuarios, revisa también autenticación sin contraseña en Laravel para flujos modernos.
2. Rendimiento
Cada invocación de tool es una request HTTP/stdio adicional. Si tu tool consulta la base de datos, aplica las mismas técnicas que en cualquier endpoint público:
- Cachea resultados costosos con
Cache::remember(). - Pagina respuestas grandes en lugar de devolver miles de filas.
- Mueve trabajo pesado (envío de emails, generación de PDFs) a Queues.
Si esto te suena lejano, te recomiendo la guía de optimización de rendimiento en Laravel.
3. Testing
El paquete trae helpers de testing fluidos. Un test típico:
it('lista posts publicados', function () {
BlogPost::factory()->count(3)->published()->create();
AppServer::tool(ListPostsTool::class, ['limit' => 5])
->assertOk()
->assertSee('count');
});
Esto es oro: te permite iterar sobre tus tools sin tener que reabrir Claude cada vez.
4. Observabilidad
Loggea cada invocación con el nombre de la tool, los parámetros y el tiempo de ejecución. Cuando un agente se comporta raro, el log de tool calls es el primer lugar donde mirar — es el equivalente a un stack trace para agentes.
Preguntas frecuentes
¿Necesito Laravel 12 sí o sí? El paquete soporta Laravel 11+. En versiones anteriores no funcionará por la nueva estructura de routing y attributes de PHP 8.2+.
¿MCP reemplaza a las APIs REST tradicionales? No. MCP es complementario: tu API REST sigue sirviendo a tu frontend y a integraciones máquina-a-máquina. MCP es la capa para que agentes de IA consuman tu lógica de forma estructurada. Si necesitas refrescar conceptos REST, mira cómo crear una API REST con Laravel.
¿Funciona con otros modelos además de Claude? Sí. MCP es un estándar abierto. Cursor, Cline, Continue.dev y un número creciente de clientes lo soportan. Cualquier wrapper sobre GPT-4 o Gemini que implemente MCP también podrá invocar tu servidor.
¿Puedo desplegarlo en producción?
Sí, vía Mcp::web(). Recuerda activar OAuth, rate limiting y desplegar detrás del mismo proxy que tu API. Para tráfico alto considera correr el server en Octane o FrankenPHP.
Conclusión
MCP convierte tu aplicación Laravel en una plataforma sobre la que los agentes de IA pueden razonar y actuar, sin que tengas que mantener un wrapper distinto por cada cliente. El paquete laravel/mcp reduce todo el boilerplate a tres conceptos familiares: un servidor, una tool y una ruta.
Si quieres seguir construyendo:
- Cómo integrar la API de OpenAI en Laravel — para combinar MCP con generación de texto en el mismo backend.
- Cómo crear una API REST con Laravel — fundamentos que aplican igual a tus tools MCP.
- Optimización de rendimiento en Laravel — para cuando tu servidor MCP empiece a recibir tráfico real.
¿Qué herramienta vas a exponer primero en tu servidor MCP?