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

> Leer hoe je een Model Context Protocol (MCP)-server inbouwt in je Laravel-applicatie. Definieer tools, resources en prompts waarmee AI-codeeragents met je applicatie kunnen communiceren.

## Wat is MCP

Het **Model Context Protocol (MCP)** is een specificatie waarmee AI-clients (Claude, Cursor, GitHub Copilot enz.) en applicaties via een gestandaardiseerd protocol communiceren. Door een MCP-server te implementeren kunnen AI-agents toegang krijgen tot de data van je Laravel-applicatie en acties uitvoeren.

<Info>
  Laravel MCP is een officieel pakket dat is toegevoegd in Laravel 13. Het wordt geleverd als `laravel/mcp` en biedt alle functionaliteit die nodig is om MCP-servers te bouwen.
</Info>

Een MCP-server kan hoofdzakelijk drie soorten functionaliteit aanbieden.

| Functionaliteit | Beschrijving                                                                                   |
| --------------- | ---------------------------------------------------------------------------------------------- |
| **Tools**       | Functies die een AI-client kan aanroepen. Zoeken, bijwerken, integratie met externe API's enz. |
| **Resources**   | Data en contextinformatie die een AI-client kan inlezen                                        |
| **Prompts**     | Herbruikbare prompttemplates                                                                   |

## Installatie

Installeer het pakket met Composer.

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

Voer na de installatie het Artisan-commando `vendor:publish` uit om het bestand `routes/ai.php` te genereren.

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

Dit commando maakt het bestand `routes/ai.php` aan. Hier registreer je je MCP-servers.

## Een server maken

Genereer een serverklasse met het Artisan-commando `make:mcp-server`.

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

De serverklasse wordt gegenereerd in de directory `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,
    ];
}
```

### De server registreren

Heb je een server gemaakt, dan registreer je die in `routes/ai.php`. Er zijn twee manieren van registreren: als **webserver** en als **lokale server**.

#### Webserver

Een webserver is bereikbaar via HTTP POST-requests. Ideaal voor externe AI-clients en webgebaseerde integraties.

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

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

Net als bij gewone routes kun je middleware toepassen.

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

#### Lokale server

Een lokale server draait als Artisan-commando. Je gebruikt dit voor integratie met lokale AI-clients zoals Claude Desktop.

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

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

<Tip>
  Een lokale server wordt normaal gesproken automatisch gestart door de MCP-client. Je hoeft het Artisan-commando `mcp:start` niet handmatig uit te voeren.
</Tip>

## Tools

Tools zijn functies die een AI-client kan aanroepen. Je kunt er onder meer het ophalen van data, integratie met externe API's en databasebewerkingen mee implementeren.

### Een tool maken

Genereer een toolklasse met het Artisan-commando `make:mcp-tool`.

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

Registreer de gemaakte tool in de property `$tools` van de server.

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

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

Een implementatievoorbeeld van een eenvoudige toolklasse:

```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');

        // Weerdata ophalen...

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

### Naam en beschrijving van de tool

Uit de klassenaam worden automatisch een standaardnaam en -titel gegenereerd. Bij `CurrentWeatherTool` is de naam `current-weather` en de titel `Current Weather Tool`. Met de attributen `Name` en `Title` pas je die aan.

```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>
  De beschrijving van een tool (`Description`) wordt niet automatisch gegenereerd. Die is essentieel zodat het AI-model begrijpt hoe de tool moet worden gebruikt; stel dus altijd een betekenisvolle beschrijving in.
</Warning>

### Invoerschema

Met de methode `schema` definieer je het schema van de invoerparameters. Via de JSON-schemabuilder van Laravel geef je types en beperkingen op.

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

### Uitvoerschema

Met de methode `outputSchema` definieer je de structuur van de response. Dat maakt het voor AI-clients makkelijker om de response te parsen.

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

### Validatie

Binnen de `handle`-methode kun je de standaard validatiefunctionaliteit van Laravel gebruiken.

```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.',
    ]);

    // Verder werken met de gevalideerde data...
}
```

<Tip>
  Bij een validatiefout gebruikt de AI-client de foutmelding als leidraad voor een nieuwe poging. Zorg dus voor concrete en uitvoerbare foutmeldingen.
</Tip>

### Dependency injection

Omdat tools worden geresolved via de servicecontainer van Laravel, kun je afhankelijkheden type-hinten in de constructor of de `handle`-methode.

```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}");
    }
}
```

### Annotaties

Door annotaties aan een tool toe te voegen geef je AI-clients extra informatie over het gedrag van de tool.

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

De beschikbare annotaties zijn als volgt.

| Annotatie          | Beschrijving                                                                     |
| ------------------ | -------------------------------------------------------------------------------- |
| `#[IsReadOnly]`    | Geeft aan dat de tool de omgeving niet wijzigt                                   |
| `#[IsDestructive]` | Geeft aan dat de tool mogelijk destructieve wijzigingen uitvoert                 |
| `#[IsIdempotent]`  | Geeft aan dat herhaald aanroepen met dezelfde argumenten geen bijwerkingen heeft |
| `#[IsOpenWorld]`   | Geeft aan dat de tool mogelijk met externe entiteiten communiceert               |

### Voorwaardelijke registratie

Door de methode `shouldRegister` te implementeren registreer je tools voorwaardelijk tijdens runtime.

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

Geef je `false` terug, dan is de tool onzichtbaar voor de AI-client.

### Responses

Tools moeten een instantie van `Laravel\Mcp\Response` teruggeven.

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

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

  <Accordion title="Afbeeldings- en audioresponse">
    ```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');

    // Direct uit storage lezen (het MIME-type wordt automatisch gedetecteerd)
    return Response::fromStorage('weather/radar.png');
    ```
  </Accordion>

  <Accordion title="Response met meerdere inhoudsdelen">
    ```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="Gestructureerde response">
    Geeft gestructureerde data terug die een AI-client makkelijk kan parsen.

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

  <Accordion title="Streamingresponse">
    Verstuurt bij langdurige verwerking de voortgang in realtime.

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

Prompts zijn herbruikbare prompttemplates. Je kunt gestandaardiseerde varianten aanbieden van de terugkerende query's die AI-clients gebruiken in hun interactie met taalmodellen.

### Een prompt maken

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

Registreer de prompt in de property `$prompts` van de server.

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

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

### Promptargumenten

Met de methode `arguments` definieer je de parameters van de prompt.

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

### Validatie

Promptargumenten worden automatisch gevalideerd op basis van hun definitie, maar je kunt ook complexere validatieregels toepassen.

Laravel MCP werkt naadloos samen met de [validatiefunctionaliteit](/nl/validation) van Laravel. Je valideert de argumenten binnen de `handle`-methode van de prompt.

```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'];

        // De prompt genereren met de opgegeven tone...
    }
}
```

Bij een validatiefout gebruikt de AI-client de foutmelding als leidraad voor een nieuwe poging. Zorg voor concrete en uitvoerbare meldingen.

```php theme={null}
$validated = $request->validate([
    'tone' => ['required', 'string', 'max:50'],
], [
    'tone.*' => 'Geef een tone op. Bijvoorbeeld: "formal", "casual" of "humorous".',
]);
```

### Dependency injection

Omdat prompts worden geresolved via de servicecontainer van Laravel, kun je afhankelijkheden type-hinten in de constructor of de `handle`-methode.

```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,
    ) {}
}
```

Ook in de `handle`-methode kun je type-hinten; de servicecontainer resolvet en injecteert automatisch.

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

    // ...
}
```

### Voorwaardelijke registratie

Door de methode `shouldRegister` te implementeren registreer je prompts voorwaardelijk tijdens runtime.

```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;
    }
}
```

Geef je `false` terug, dan is de prompt onzichtbaar voor de AI-client en kan hij ook niet worden aangeroepen.

### Promptresponses

De `handle`-methode van een prompt kan gebruikers- en assistentberichten teruggeven. Met `asAssistant()` behandel je een bericht als afkomstig van de assistent.

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

Resources zijn data en informatie die een AI-client als context kan inlezen. Je kunt er informatie mee aanbieden die de kwaliteit van AI-antwoorden verbetert, zoals documentatie, configuratie-informatie en dynamische data.

### Een resource maken

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

Registreer de resource in de property `$resources` van de server.

```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 en MIME-type

Standaard wordt de URI automatisch gegenereerd uit de klassenaam (bijv. `weather://resources/weather-guidelines`). Met de attributen `Uri` en `MimeType` pas je die aan.

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

### Resourcetemplates

Om een dynamische resource met URI-variabelen te definiëren implementeer je de interface `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');

        // De bestandsinhoud ophalen en teruggeven...

        return Response::text("File {$fileId} for user {$userId}");
    }
}
```

Variabelen uit de URI worden automatisch in het request opgenomen en zijn op te halen met de `get`-methode.

### Resourcerequests

Anders dan bij tools en prompts kun je bij resources geen invoerschema of argumenten definiëren. Binnen de `handle`-methode heb je echter via het requestobject wel toegang tot requestinformatie.

```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
    {
        // Toegang tot de requestinformatie...
    }
}
```

### Dependency injection bij resources

Omdat resources worden geresolved via de servicecontainer van Laravel, kun je afhankelijkheden type-hinten in de constructor of de `handle`-methode.

```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,
    ) {}
}
```

Ook in de `handle`-methode kun je type-hinten; de servicecontainer resolvet en injecteert automatisch.

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

### Resource-annotaties

Aan resources kun je annotaties toevoegen voor onder meer doelgroep, prioriteit en laatste wijzigingsdatum.

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

| Annotatie         | Type          | Beschrijving                                            |
| ----------------- | ------------- | ------------------------------------------------------- |
| `#[Audience]`     | Role of array | De doelgroep (`Role::User`, `Role::Assistant` of beide) |
| `#[Priority]`     | float         | Belangrijkheidsscore (0.0-1.0)                          |
| `#[LastModified]` | string        | Laatste wijzigingsdatum in ISO 8601-formaat             |

### Voorwaardelijke registratie van resources

Door de methode `shouldRegister` te implementeren registreer je resources voorwaardelijk tijdens runtime.

```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;
    }
}
```

Geef je `false` terug, dan is de resource onzichtbaar voor de AI-client en ook niet toegankelijk.

### Resourceresponses

Resources moeten een instantie van `Laravel\Mcp\Response` teruggeven.

Voor tekstuele inhoud gebruik je de methode `text`.

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

#### Resourcelink-responses

Met de methode `resourceLink` geef je een resourcelink terug. Anders dan een ingebedde resource geef je hiermee een URI-pointer terug die de AI-client zelfstandig ophaalt.

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

Je kunt ook een geregistreerde resourceklasse of -instantie doorgeven. De URI, naam, titel, beschrijving en het MIME-type worden dan automatisch overgenomen.

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

#### Blob-responses

Om binaire inhoud terug te geven gebruik je de methode `blob`. Het MIME-type stel je in via het `#[MimeType]`-attribuut van de 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
{
    // ...
}
```

#### Foutresponses

Om een fout aan te geven gebruik je de methode `error`.

```php theme={null}
return Response::error('Kon de weerdata voor de opgegeven locatie niet ophalen.');
```

## Apps

Laravel MCP ondersteunt [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview). Dit is een uitbreiding van het Model Context Protocol waarmee tools interactieve HTML-applicaties kunnen renderen in een sandbox-iframe binnen ondersteunende hosts. Zo bouw je dashboards, formulieren, visualisaties en andere rijke ervaringen die verder gaan dan platte-tekstresponses.

Een MCP-app bestaat uit twee samenwerkende onderdelen:

* **App-resource** — geeft de zelfstandige HTML van de applicatie terug.
* **Tool** — wordt via het attribuut `#[RendersApp]` gekoppeld aan de app-resource. Wanneer de tool wordt aangeroepen, haalt de host de gekoppelde resource op en rendert die.

### Een app-resource maken

Met het Artisan-commando `make:mcp-app-resource` maak je een app-resource aan.

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

Dit commando maakt twee bestanden aan: een PHP-klasse in `app/Mcp/Resources` en een Blade-view in `resources/views/mcp`. De viewnaam wordt automatisch afgeleid uit de klassenaam. `WeatherDashboardApp` wordt bijvoorbeeld gemapt naar `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` erft over van de basisklasse `Resource` en stelt automatisch het `ui://`-URI-schema en het MIME-type `text/html;profile=mcp-app` in die de MCP Apps-specificatie vereist. Net als andere resources moet je de klasse registreren in de `$resources`-array van de server.

De gegenereerde Blade-view gebruikt de component `<x-mcp::app>`. Deze component rendert een compleet HTML-document met de client-side MCP SDK gebundeld.

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

De globale functie `createMcpApp` wordt geleverd door de gebundelde SDK. Die regelt het verbinden van het iframe met de server, het toepassen van het host-thema en het beschikbaar stellen van helpers zoals `callServerTool`, `sendMessage` en `openLink`, plus eventcallbacks. Zie de [MCP Apps-specificatie](https://modelcontextprotocol.io/extensions/apps/overview) voor de volledige client-side API.

### Een app renderen vanuit een tool

Om een app-resource weer te geven koppel je die met het attribuut `#[RendersApp]` aan een tool. Wanneer de tool wordt aangeroepen, neemt Laravel MCP de URI van de resource op in de metadata van de tool, zodat de host de app in een sandbox-iframe kan renderen.

```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>
  Wanneer een `AppResource` is geregistreerd, adverteert Laravel MCP automatisch de capability `io.modelcontextprotocol/ui`. Extra serverconfiguratie is niet nodig.
</Info>

### Zichtbaarheid van app-tools

Elke `#[RendersApp]`-tool kan met het argument `visibility` beperken door wie hij kan worden aangeroepen. Dit is handig voor private, app-specifieke tools die de UI aanroept om data te laden of bij te werken, en die je voor het model verborgen wilt houden.

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

De `Visibility`-enum heeft twee cases, `Model` en `App`; standaard gelden beide. Gebruik `[Visibility::App]` voor backend-acties die de UI direct aanroept, en `[Visibility::Model]` om de tool onbereikbaar te maken vanuit de UI.

### App-configuratie

Met het `#[AppMeta]`-attribuut van een app-resource stel je de Content Security Policy van het iframe, de browserrechten en de libraryscripts voor de `<head>` van de view in.

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

De `Library`-enum bevat vooraf geconfigureerde CDN-scripts voor gangbare frontend-libraries zoals `Library::Tailwind` en `Library::Alpine`; de CDN-origins worden automatisch samengevoegd met de CSP. De `Permission`-enum dekt browserrechten zoals `Camera`, `Microphone`, `Geolocation` en `ClipboardWrite`.

<Tip>
  Heb je dynamische configuratie nodig, override dan de `appMeta`-methode van de resource met de fluente builders `AppMeta`, `Csp` en `Permissions` uit de namespace `Laravel\Mcp\Server\Ui`.
</Tip>

### App-ontwikkeling met Boost

Laravel MCP bevat een speciale [Boost](/nl/boost)-skillreferentie voor het bouwen van MCP Apps. Is Laravel Boost geïnstalleerd, dan kan een AI-codeeragent de skill `mcp-development` aanroepen en automatisch app-resources, Blade-views en gekoppelde tools genereren.

Voor de volledige protocolreferentie (inclusief details over de client-side API en schema's) raadpleeg je de officiële [MCP Apps-documentatie](https://modelcontextprotocol.io/extensions/apps/overview).

## Metadata

Je kunt het `_meta`-veld uit de MCP-specificatie toevoegen aan responses van tools, resources en prompts.

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

Wil je metadata toevoegen aan de hele response-envelope, gebruik dan `Response::make`.

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

Om metadata toe te voegen aan de tool-, resource- of promptklasse zelf, definieer je de property `$meta`.

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

## Iconen

MCP-clients kunnen iconen tonen voor de server en zijn primitieven. Met het `Icon`-attribuut declareer je iconen op servers, tools, resources en 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
{
    // ...
}
```

Het `Icon`-attribuut is herhaalbaar, zodat je meerdere iconen kunt declareren voor verschillende formaten en licht/donker-themavarianten.

Als alternatief kun je de `icons`-methode overriden om iconen programmatisch te definiëren. Dat is handig wanneer de iconen afhangen van runtime-omstandigheden.

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

class CurrentWeatherTool extends Tool
{
    /**
     * De iconen van de tool ophalen.
     *
     * @return array<int, Icon>
     */
    public function icons(): array
    {
        return [
            Icon::from('mcp/tool.png', mimeType: 'image/png'),
        ];
    }
}
```

Iconen gedefinieerd via attributen en via de `icons`-methode worden automatisch samengevoegd. Iconpaden worden als volgt opgelost:

* Paden met een URI-schema zoals `https:` of `data:` worden ongewijzigd gebruikt.
* Relatieve paden worden met de `asset`-helper van Laravel omgezet naar een URL.

## Authenticatie

Webservers kun je authenticeren met de standaard middleware van Laravel.

### Sanctum

Tokenauthenticatie met [Laravel Sanctum](https://laravel.com/docs/sanctum). De MCP-client stuurt de header `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

OAuth-authenticatie met [Laravel Passport](https://laravel.com/docs/passport). Geschikt wanneer je robuustere beveiliging nodig hebt.

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

Mcp::oauthRoutes();

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

Gebruik je OAuth-authenticatie, publiceer dan de autorisatieviews van Passport en configureer ze in een serviceprovider.

```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);
});
```

## Autorisatie

Met `$request->user()` haal je de geauthenticeerde gebruiker op en voer je autorisatiechecks uit binnen tools en resources.

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

    // Verwerking voortzetten...
}
```

## MCP-client

Laravel MCP biedt niet alleen het bouwen van servers, maar ook een client om verbinding te maken met andere MCP-servers. Met de client kun je tools ontdekken en aanroepen die externe MCP-servers publiceren. Dit is vooral nuttig om de functionaliteit van externe MCP-servers beschikbaar te stellen aan [AI-agents](/nl/ai-sdk#mcp-tools).

### Verbinden met een server

Voor MCP-servers die via HTTP bereikbaar zijn gebruik je de methode `Client::web` met de URL van de server.

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

$client = Client::web('https://mcp.example.com');
```

Voor lokale MCP-servers die als commando starten gebruik je de methode `Client::local` met het commando en de argumenten.

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

$client = Client::local('php', ['artisan', 'mcp:start']);
```

De client maakt lazy verbinding: de verbinding wordt automatisch opgezet wanneer voor het eerst tools worden opgesomd of aangeroepen. Wil je de verbinding handmatig beheren, gebruik dan de methoden `connect`, `connected`, `ping` en `disconnect`.

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

$client->ping();

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

$client->disconnect();
```

Met de methode `withTimeout` pas je de requesttime-out aan.

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

### Benoemde clients

In plaats van elke keer een client op te bouwen, kun je herbruikbare benoemde clients registreren. Meestal doe je dat via de `Mcp`-facade in de `boot`-methode van een serviceprovider.

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

Mcp::registerClient('github', fn () => Client::web('https://mcp.example.com'));
```

Na registratie resolve je de client op naam.

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

$client = Mcp::client('github');
```

Benoemde clients worden per request slechts één keer geresolved en aan het einde van de requestlifecycle automatisch verbroken.

### Clientauthenticatie

Om verbinding te maken met een web-MCP-server die met een Bearer-token is beschermd, gebruik je de methode `withToken`. Je kunt een tokenstring doorgeven of een closure die het token lazy resolvet.

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

Voor servers die zijn beschermd met [OAuth 2.1](#oauth-2-1) gebruik je de methode `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>
  Ondersteunt de MCP-server [dynamische clientregistratie](https://datatracker.ietf.org/doc/html/rfc7591), dan kun je `clientId` en `clientSecret` weglaten. De client registreert zich dan automatisch.
</Info>

Registreer vervolgens in het bestand `routes/ai.php` de OAuth-routes voor de benoemde client met de methode `oAuthRoutesFor`. De doorgegeven closure ontvangt de clientnaam en een `TokenSet` nadat de autorisatiecode is ingewisseld voor een 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');
});
```

Hiermee worden twee benoemde routes geregistreerd: een connect-route (`mcp.oauth.{client}.connect`) die de gebruiker naar de autorisatieserver redirect, en een callback-route (`mcp.oauth.{client}.callback`) die de autorisatiecode inwisselt en de handler aanroept. Beide gebruiken de `web`-middlewaregroep (te overschrijven via het argument `middleware`).

Om de autorisatieflow te starten redirect je de gebruiker naar de connect-route.

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

### Tools

Met de methode `tools` haal je de tools op die de MCP-server publiceert. Ze worden teruggegeven als een collection met de naam als sleutel.

```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;
}
```

De client handelt paginatie automatisch af en haalt alle tools op. Met het argument `limit` beperk je het aantal.

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

Om een tool aan te roepen gebruik je de methode `callTool` met de toolnaam en een array met argumenten. Via de teruggegeven `ToolResult`-instantie haal je de response op.

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

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

$result->text();             // De tekstinhoud van de response
(string) $result;            // Gelijk aan text()
$result->isError;            // Of de tool een fout heeft gemeld
$result->structuredContent;  // Gestructureerde inhoud (indien aanwezig)
```

Je kunt ook direct aanroepen vanaf een opgesomde toolinstantie.

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

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

Bouw je agents met de [Laravel AI SDK](/nl/ai-sdk), dan kun je de tools van de MCP-client direct aan een agent doorgeven, zodat het model ze kan aanroepen tijdens het beantwoorden van een prompt. Zie de sectie [MCP-tools](/nl/ai-sdk#mcp-tools) van de AI SDK voor details.

### Prompts

Met de methode `prompts` haal je de prompts op die de MCP-server publiceert. Ze worden teruggegeven als een collection met de naam als sleutel.

```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;
}
```

De client handelt paginatie automatisch af en haalt alle prompts op. Met het argument `limit` beperk je het aantal.

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

Om een prompt op te halen gebruik je de methode `getPrompt` met de promptnaam en een array met argumenten. Via de teruggegeven `PromptResult`-instantie haal je de gegenereerde berichten op.

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

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

$result->text();        // De tekstinhoud van de berichten
(string) $result;       // Gelijk aan text()
$result->messages;      // De berichten die de prompt heeft teruggegeven (ruwe data)
$result->description;   // De beschrijving van de prompt (indien aanwezig)
```

### Resources

Met de methode `resources` haal je de resources op die de MCP-server publiceert. Ze worden teruggegeven als een collection met de URI als sleutel.

```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;
}
```

De client handelt paginatie automatisch af en haalt alle resources op. Met het argument `limit` beperk je het aantal.

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

Om een resource in te lezen gebruik je de methode `readResource` met de URI van de resource. Via de teruggegeven `ResourceReadResult`-instantie haal je de inhoud op.

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

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

$result->content();   // De inhoud van de resource (base64-blobs worden automatisch gedecodeerd)
(string) $result;     // Gelijk aan content()
$result->mimeType();  // Het MIME-type van de resource (indien aanwezig)
$result->contents;    // De inhoud die de resource heeft teruggegeven (ruwe data)
```

## Testen

### MCP Inspector

Om de werking van je MCP-server te controleren gebruik je de interactieve debugtool "MCP Inspector".

```shell theme={null}
# Webserver
php artisan mcp:inspector mcp/weather

# Lokale server (met de naam "weather")
php artisan mcp:inspector weather
```

Als je het commando uitvoert start de MCP Inspector en kun je de clientconfiguratie kopiëren. Heb je authenticatiemiddleware ingesteld, verbind dan inclusief de Authorization-header.

### Unit tests

Je kunt unit tests schrijven voor tools, resources en 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>

Prompts en resources test je op dezelfde manier.

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

Om als geauthenticeerde gebruiker te draaien gebruik je `actingAs`.

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

De belangrijkste assertion-methoden zijn als volgt.

```php theme={null}
$response->assertOk();           // Controleren dat er geen fouten zijn
$response->assertSee('...');     // Controleren dat een specifieke tekst voorkomt
```

Om te verifiëren of er fouten zijn gebruik je `assertHasErrors` / `assertHasNoErrors`.

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

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

$response->assertHasNoErrors();
```

Je kunt de naam, titel en beschrijving van tools, resources en prompts verifiëren.

```php theme={null}
$response->assertName('current-weather');
$response->assertTitle('Current Weather Tool');
$response->assertDescription('Fetches the current weather forecast for a specified location.');
```

Om de notificaties van een streamingresponse te verifiëren gebruik je `assertSentNotification` en `assertNotificationCount`.

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

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

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

Om de inhoud van een response te debuggen gebruik je de methoden `dd` of `dump`.

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


## Related topics

- [Een MCP-server bouwen met Laravel](/nl/advanced/mcp-server.md)
- [Laravel AI-agents ondersteunen nu MCP-servers](/nl/blog/ai-sdk-mcp-client.md)
- [Laravel AI SDK](/nl/ai-sdk.md)
- [進階主題](/zh-TW/advanced/index.md)
- [进阶主题](/zh-CN/advanced/index.md)
