> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel MCP

> 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.

## Qué es MCP

**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.

<Info>
  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.
</Info>

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                                                   |
| **Prompts**   | Plantillas de prompt reutilizables                                                                       |

## Instalación

Instala el paquete con Composer.

```shell theme={null}
composer require laravel/mcp
```

Después ejecuta `vendor:publish` para generar el archivo `routes/ai.php`.

```shell theme={null}
php artisan vendor:publish --tag=ai-routes
```

Este comando crea `routes/ai.php`, donde registrarás los servidores MCP.

## Crear el servidor

Genera una clase de servidor con `make:mcp-server`.

```shell theme={null}
php artisan make:mcp-server WeatherServer
```

La clase se ubica en el directorio `app/Mcp/Servers`.

```php theme={null}
<?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,
    ];
}
```

### Registrar el servidor

Registra el servidor en `routes/ai.php`. Existen dos modalidades: **servidor web** y **servidor local**.

#### Servidor web

Accesible mediante una petición HTTP POST. Perfecto para clientes de IA remotos o integraciones basadas en web.

```php theme={null}
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/weather', WeatherServer::class);
```

Puedes aplicarle middleware como en cualquier ruta.

```php theme={null}
Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware(['throttle:mcp']);
```

#### Servidor local

Se ejecuta como un comando Artisan. Se usa para integraciones locales con clientes como Claude Desktop.

```php theme={null}
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::local('weather', WeatherServer::class);
```

<Tip>
  El servidor local suele arrancarlo automáticamente el cliente MCP. Normalmente no necesitas ejecutar `mcp:start` manualmente.
</Tip>

## Tools

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.

### Crear un tool

Genera una clase de tool con `make:mcp-tool`.

```shell theme={null}
php artisan make:mcp-tool CurrentWeatherTool
```

Registra el tool en la propiedad `$tools` del servidor.

```php theme={null}
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    protected array $tools = [
        CurrentWeatherTool::class,
    ];
}
```

Ejemplo básico de tool.

```php theme={null}
<?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');

        // 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(),
        ];
    }
}
```

### Nombre y descripción del tool

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`.

```php theme={null}
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
{
    // ...
}
```

<Warning>
  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.
</Warning>

### Esquema de entrada

Define los parámetros de entrada en el método `schema`. Puedes usar el JSON schema builder de Laravel para especificar tipos y restricciones.

```php theme={null}
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'),
    ];
}
```

### Esquema de salida

Con `outputSchema` puedes definir la estructura de la respuesta, lo que facilita que el cliente la interprete.

```php theme={null}
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(),
    ];
}
```

### Validación

Dentro de `handle` puedes usar la validación estándar de Laravel.

```php theme={null}
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...
}
```

<Tip>
  Cuando la validación falla, el cliente de IA reintenta apoyándose en el mensaje de error. Proporciona mensajes concretos y accionables.
</Tip>

### Inyección de dependencias

El servidor resuelve los tools a través del contenedor de servicios, así que puedes tipar dependencias en el constructor o en `handle`.

```php theme={null}
<?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}");
    }
}
```

### Anotaciones

Añadir anotaciones a un tool proporciona al cliente información adicional sobre su comportamiento.

```php theme={null}
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
{
    // ...
}
```

Anotaciones disponibles:

| Anotación          | Descripción                                                                                |
| ------------------ | ------------------------------------------------------------------------------------------ |
| `#[IsReadOnly]`    | El tool no modifica el entorno                                                             |
| `#[IsDestructive]` | El tool puede realizar actualizaciones destructivas                                        |
| `#[IsIdempotent]`  | Ejecutarlo varias veces con los mismos argumentos no tiene efectos secundarios adicionales |
| `#[IsOpenWorld]`   | Puede interactuar con entidades externas                                                   |

### Registro condicional

Implementa `shouldRegister` para registrar el tool condicionalmente en tiempo de ejecución.

```php theme={null}
public function shouldRegister(Request $request): bool
{
    return $request?->user()?->subscribed() ?? false;
}
```

Si devuelve `false`, el tool no es visible para el cliente.

### Respuestas

Los tools deben devolver una instancia de `Laravel\Mcp\Response`.

<AccordionGroup>
  <Accordion title="Respuesta de texto">
    ```php theme={null}
    return Response::text('Weather Summary: Sunny, 22°C');
    ```
  </Accordion>

  <Accordion title="Respuesta de error">
    ```php theme={null}
    return Response::error('Unable to fetch weather data. Please try again.');
    ```
  </Accordion>

  <Accordion title="Imagen o audio">
    ```php theme={null}
    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');

    // Directamente desde el storage (MIME detectado automáticamente)
    return Response::fromStorage('weather/radar.png');
    ```
  </Accordion>

  <Accordion title="Respuesta con varios contenidos">
    ```php theme={null}
    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"),
        ];
    }
    ```
  </Accordion>

  <Accordion title="Respuesta estructurada">
    Devuelve datos estructurados fáciles de interpretar por el cliente.

    ```php theme={null}
    return Response::structured([
        'temperature' => 22.5,
        'conditions' => 'Partly cloudy',
        'humidity' => 65,
    ]);
    ```
  </Accordion>

  <Accordion title="Respuesta en streaming">
    Envía progreso en tiempo real durante procesos largos.

    ```php theme={null}
    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));
        }
    }
    ```
  </Accordion>
</AccordionGroup>

## Prompts

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.

### Crear un prompt

```shell theme={null}
php artisan make:mcp-prompt DescribeWeatherPrompt
```

Regístralo en la propiedad `$prompts` del servidor.

```php theme={null}
use App\Mcp\Prompts\DescribeWeatherPrompt;

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

### Argumentos del prompt

Define los parámetros del prompt en el método `arguments`.

```php theme={null}
<?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,
            ),
        ];
    }
}
```

### Validación

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](/es/validation). Puedes validar los argumentos dentro de `handle`.

```php theme={null}
<?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'];

        // 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.

```php theme={null}
$validated = $request->validate([
    'tone' => ['required', 'string', 'max:50'],
], [
    'tone.*' => 'Debes indicar un tone. Ejemplos: "formal", "casual", "humorous".',
]);
```

### Inyección de dependencias

Los prompts también se resuelven mediante el contenedor de servicios, así que puedes inyectar dependencias en el constructor o en `handle`.

```php theme={null}
<?php

namespace App\Mcp\Prompts;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    public function __construct(
        protected WeatherRepository $weather,
    ) {}
}
```

También puedes tipar el método `handle`, y el contenedor las resolverá.

```php theme={null}
public function handle(Request $request, WeatherRepository $weather): Response
{
    $isAvailable = $weather->isServiceAvailable();

    // ...
}
```

### Registro condicional

Implementa `shouldRegister` para registrar el prompt condicionalmente.

```php theme={null}
<?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;
    }
}
```

Si devuelve `false`, el prompt no se ve ni se puede invocar.

### Respuesta del prompt

`handle` puede devolver mensajes del usuario y del asistente. Con `asAssistant()` marcas un mensaje como del asistente.

```php theme={null}
<?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),
        ];
    }
}
```

## Resources

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.

### Crear un resource

```shell theme={null}
php artisan make:mcp-resource WeatherGuidelinesResource
```

Regístralo en la propiedad `$resources` del servidor.

```php theme={null}
use App\Mcp\Resources\WeatherGuidelinesResource;

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

```php theme={null}
<?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 y tipo MIME

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`.

```php theme={null}
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
{
    // ...
}
```

### Plantillas de resource

Para definir resources dinámicos con variables en la URI, implementa la interfaz `HasUriTemplate`.

```php theme={null}
<?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');

        // 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`.

### Request en un resource

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.

```php theme={null}
<?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
    {
        // Consultar información de la request...
    }
}
```

### Inyección de dependencias en resources

Los resources también se resuelven por el contenedor. Puedes inyectar dependencias en el constructor o en `handle`.

```php theme={null}
<?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` también puede tipar dependencias.

```php theme={null}
public function handle(WeatherRepository $weather): Response
{
    return Response::text($weather->guidelines());
}
```

### Anotaciones de un resource

A los resources se les pueden añadir anotaciones como audiencia, prioridad o fecha de última modificación.

```php theme={null}
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
{
    // ...
}
```

| Anotación         | Tipo         | Descripción                                         |
| ----------------- | ------------ | --------------------------------------------------- |
| `#[Audience]`     | Role o array | Audiencia (`Role::User`, `Role::Assistant` o ambas) |
| `#[Priority]`     | float        | Prioridad (0.0 a 1.0)                               |
| `#[LastModified]` | string       | Fecha ISO 8601 de última modificación               |

### Registro condicional del resource

Implementa `shouldRegister` para registrar el resource de forma condicional.

```php theme={null}
<?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;
    }
}
```

Si devuelve `false`, el resource no se ve ni es accesible.

### Respuestas del resource

Un resource debe devolver una instancia de `Laravel\Mcp\Response`.

Para contenido textual utiliza `text`.

```php theme={null}
return Response::text($weatherData);
```

#### Respuesta de tipo enlace

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.

```php theme={null}
return Response::resourceLink(
    uri: 'file:///data/report.json',
    name: 'monthly-report',
    mimeType: 'application/json',
);
```

También puedes pasar una clase o instancia de resource ya registrado; la URI, nombre, título, descripción y MIME se heredan automáticamente.

```php theme={null}
return Response::resourceLink(new WeatherForecastResource);
```

#### Respuesta binaria (blob)

Para contenidos binarios utiliza `blob`. El MIME se toma del atributo `#[MimeType]` del resource.

```php theme={null}
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
```

```php theme={null}
#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
    // ...
}
```

#### Respuesta de error

Para indicar un error utiliza `error`.

```php theme={null}
return Response::error('No se han podido obtener los datos meteorológicos del lugar indicado.');
```

## Apps

Laravel MCP soporta las [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview), 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.

### Crear un app resource

Genera un app resource con `make:mcp-app-resource`.

```shell theme={null}
php artisan make:mcp-app-resource WeatherDashboardApp
```

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`).

```php theme={null}
<?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` 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.

```blade theme={null}
<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>
```

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](https://modelcontextprotocol.io/extensions/apps/overview).

### Renderizar la app desde un tool

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.

```php theme={null}
<?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.');
    }
}
```

<Info>
  Cuando se registra un `AppResource`, Laravel MCP anuncia automáticamente la capacidad `io.modelcontextprotocol/ui`. No requiere configuración adicional del servidor.
</Info>

### Visibilidad de los tools de la app

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.

```php theme={null}
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Ui\Enums\Visibility;

#[RendersApp(resource: WeatherDashboardApp::class, visibility: [Visibility::App])]
class GetWeatherData extends Tool
{
    // ...
}
```

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.

### Configuración de la app

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.

```php theme={null}
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
{
    // ...
}
```

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`...).

<Tip>
  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`.
</Tip>

### Desarrollo de apps con Boost

Laravel MCP incluye una skill de referencia específica de [Boost](/es/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](https://modelcontextprotocol.io/extensions/apps/overview).

## Metadatos

Puedes añadir el campo `_meta` (definido por la especificación MCP) a las respuestas de tools, resources y prompts.

```php theme={null}
// Metadatos del contenido de la respuesta
return Response::text('The weather is sunny.')
    ->withMeta(['source' => 'weather-api', 'cached' => true]);
```

Para añadir metadatos al envelope completo de la respuesta, utiliza `Response::make`.

```php theme={null}
return Response::make(
    Response::text('The weather is sunny.')
)->withMeta(['request_id' => '12345']);
```

Para añadir metadatos a la propia clase de tool, resource o prompt, define la propiedad `$meta`.

```php theme={null}
class CurrentWeatherTool extends Tool
{
    protected ?array $meta = [
        'version' => '2.0',
        'author' => 'Weather Team',
    ];
}
```

## Iconos

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.

```php theme={null}
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
{
    // ...
}
```

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.

```php theme={null}
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

Los servidores web se autentican con el middleware estándar de Laravel.

### Sanctum

Autenticación por token con [Laravel Sanctum](https://laravel.com/docs/sanctum). El cliente MCP envía la cabecera `Authorization: Bearer <token>`.

```php theme={null}
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

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

### OAuth 2.1

Autenticación OAuth con [Laravel Passport](https://laravel.com/docs/passport), idónea cuando necesitas seguridad más robusta.

```php theme={null}
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.

```shell theme={null}
php artisan vendor:publish --tag=mcp-views
```

```php theme={null}
// AppServiceProvider::boot()
use Laravel\Passport\Passport;

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

## Autorización

Con `$request->user()` obtienes el usuario autenticado y puedes comprobar permisos dentro de tools y resources.

```php theme={null}
public function handle(Request $request): Response
{
    if (! $request->user()->can('read-weather')) {
        return Response::error('Permission denied.');
    }

    // Continuar el procesamiento...
}
```

## Cliente MCP

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](/es/ai-sdk#mcp-tools).

### Conectar con un servidor

Para servidores accesibles por HTTP utiliza `Client::web`, pasando la URL del servidor.

```php theme={null}
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.

```php theme={null}
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`.

```php theme={null}
$client->connect();

$client->ping();

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

$client->disconnect();
```

Con `withTimeout` puedes personalizar el timeout de la petición.

```php theme={null}
$client = Client::web('https://mcp.example.com')->withTimeout(30);
```

### Clientes con nombre

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.

```php theme={null}
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.

```php theme={null}
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.

### Autenticación del cliente

Para conectar con servidores web MCP protegidos por Bearer token, utiliza `withToken`. Puedes pasar la cadena o una closure de resolución perezosa.

```php theme={null}
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(),
);
```

Para servidores protegidos con [OAuth 2.1](#oauth-21), utiliza `withOAuth`.

```php theme={null}
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'),
));
```

<Info>
  Si el servidor MCP admite [registro dinámico de clientes](https://datatracker.ietf.org/doc/html/rfc7591), puedes omitir `clientId` y `clientSecret`: el cliente se registra automáticamente.
</Info>

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.

```php theme={null}
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.

```php theme={null}
return redirect()->route('mcp.oauth.github.connect');
```

### Tools

Con `tools` obtienes los tools expuestos por un servidor MCP. Devuelve una colección con el nombre como clave.

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$tools = Mcp::client('github')->tools();

foreach ($tools as $tool) {
    $tool->name;
    $tool->title;
    $tool->description;
    $tool->inputSchema;
}
```

El cliente maneja automáticamente la paginación para traerlos todos. Con el argumento `limit` puedes acotar el resultado.

```php theme={null}
$tools = Mcp::client('github')->tools(limit: 10);
```

Para invocar un tool utiliza `callTool` con su nombre y los argumentos. Devuelve un `ToolResult`.

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$result = Mcp::client('github')->callTool('current-weather', [
    'location' => 'New York',
]);

$result->text();             // Contenido textual
(string) $result;            // Equivalente a text()
$result->isError;            // Indica si el tool reportó un error
$result->structuredContent;  // Contenido estructurado (si existe)
```

También puedes invocarlo directamente sobre una instancia obtenida en el listado.

```php theme={null}
$tools = Mcp::client('github')->tools();

$result = $tools['current-weather']->call([
    'location' => 'New York',
]);
```

Si construyes agentes con el [Laravel AI SDK](/es/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](/es/ai-sdk#mcp-tools) del AI SDK.

### Prompts

Con `prompts` obtienes los prompts del servidor MCP en forma de colección indexada por nombre.

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$prompts = Mcp::client('github')->prompts();

foreach ($prompts as $prompt) {
    $prompt->name;
    $prompt->title;
    $prompt->description;
    $prompt->arguments;
}
```

El cliente pagina automáticamente. Con `limit` acotas el listado.

```php theme={null}
$prompts = Mcp::client('github')->prompts(limit: 10);
```

Para obtener un prompt utiliza `getPrompt` con su nombre y los argumentos. Devuelve un `PromptResult`.

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$result = Mcp::client('github')->getPrompt('describe-weather', [
    'location' => 'New York',
]);

$result->text();        // Texto del mensaje
(string) $result;       // Equivalente a text()
$result->messages;      // Mensajes devueltos por el prompt (raw)
$result->description;   // Descripción del prompt (si existe)
```

### Resources

Con `resources` obtienes los resources del servidor MCP en una colección indexada por URI.

```php theme={null}
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;
}
```

El cliente pagina automáticamente. Con `limit` acotas el listado.

```php theme={null}
$resources = Mcp::client('github')->resources(limit: 10);
```

Para leer un resource utiliza `readResource` con la URI. Devuelve un `ResourceReadResult`.

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

$result = Mcp::client('github')->readResource('weather://guidelines');

$result->content();   // Contenido (los blobs base64 se decodifican automáticamente)
(string) $result;     // Equivalente a content()
$result->mimeType();  // Tipo MIME (si está presente)
$result->contents;    // Contenido devuelto (raw)
```

## Pruebas

### MCP Inspector

Para probar servidores MCP de forma interactiva, utiliza «MCP Inspector».

```shell theme={null}
# Servidor web
php 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.

### Tests unitarios

Puedes escribir tests unitarios de tools, resources y prompts.

<CodeGroup>
  ```php Pest theme={null}
  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.');
  });
  ```

  ```php PHPUnit theme={null}
  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.');
  }
  ```
</CodeGroup>

Los prompts y resources se prueban del mismo modo.

```php theme={null}
$response = WeatherServer::prompt(DescribeWeatherPrompt::class, ['tone' => 'casual']);
$response = WeatherServer::resource(WeatherGuidelinesResource::class);
```

Para ejecutar como usuario autenticado, utiliza `actingAs`.

```php theme={null}
$response = WeatherServer::actingAs($user)->tool(CurrentWeatherTool::class, [...]);
```

Aserciones principales:

```php theme={null}
$response->assertOk();           // Sin errores
$response->assertSee('...');     // Contiene el texto indicado
```

Verifica presencia o ausencia de errores con `assertHasErrors` / `assertHasNoErrors`.

```php theme={null}
$response->assertHasErrors();

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

$response->assertHasNoErrors();
```

Comprueba el nombre, el título o la descripción del tool, resource o prompt.

```php theme={null}
$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`.

```php theme={null}
$response->assertSentNotification('processing/progress', [
    'step' => 1,
    'total' => 5,
]);

$response->assertSentNotification('processing/progress', [
    'step' => 2,
    'total' => 5,
]);

$response->assertNotificationCount(5);
```

Para depurar la respuesta, utiliza `dd` o `dump`.

```php theme={null}
$response->dd();
$response->dump();
```


## Related topics

- [Crear un servidor MCP con Laravel](/es/advanced/mcp-server.md)
- [Laravel AI SDK](/es/ai-sdk.md)
- [Laravel Console Starter](/es/packages/laravel-console-starter/index.md)
- [MCP](/es/packages/laravel-copilot-sdk/mcp.md)
- [Laravel Boost](/es/boost.md)
