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

> Add a Model Context Protocol (MCP) server to your Laravel application so AI coding agents can interact with your data, run tools, and consume reusable resources and prompts.

## What is MCP?

**Model Context Protocol (MCP)** is a standardised protocol for communication between AI clients (Claude, Cursor, GitHub Copilot, etc.) and your application. By implementing an MCP server you let AI agents read your application's data and execute actions on your behalf.

<Info>
  Laravel MCP is an official package added in Laravel 13, available as `laravel/mcp`. It provides everything you need to build MCP servers.
</Info>

An MCP server can expose three types of capabilities:

| Capability    | Description                                                                       |
| ------------- | --------------------------------------------------------------------------------- |
| **Tools**     | Functions an AI client can call — search, update, or integrate with external APIs |
| **Resources** | Data and context an AI client can read                                            |
| **Prompts**   | Reusable, parameterised prompt templates                                          |

## Installation

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

After installation, publish the `routes/ai.php` file:

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

This creates `routes/ai.php` where you register your MCP servers.

## Creating a server

Generate a server class with Artisan:

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

The class is placed in `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,
    ];
}
```

### Registering a server

Register servers in `routes/ai.php`. You can register a server as a **web server** or a **local server**.

#### Web server

A web server listens for HTTP POST requests. Use it for remote AI clients or web-based integrations.

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

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

Apply middleware just like any other route:

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

#### Local server

A local server runs as an Artisan command. Use it with local AI clients such as Claude Desktop.

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

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

<Tip>
  The MCP client normally starts local servers automatically — you do not need to run `mcp:start` manually.
</Tip>

## Tools

Tools are functions an AI client can call. They can fetch data, update records, or integrate with external services.

### Creating a tool

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

Register the tool in the server's `$tools` array:

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

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

A basic tool implementation:

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

        // Fetch weather data...

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

### Tool name and description

The tool's name and title are automatically derived from the class name. `CurrentWeatherTool` becomes name `current-weather` and title `Current Weather Tool`. Override with `Name` and `Title` attributes:

```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>
  The `Description` attribute is not generated automatically. Always provide a meaningful description — it is how the AI model understands what the tool does and when to use it.
</Warning>

### Input schema

Define input parameters in the `schema` method using Laravel's JSON schema builder:

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

### Output schema

Define the shape of the response in `outputSchema` so AI clients can parse it reliably:

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

### Validation

Use Laravel's standard validation inside `handle`:

```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, e.g. "New York City" or "Tokyo".',
        'units.in'          => 'Units must be "celsius" or "fahrenheit".',
    ]);

    // Use $validated...
}
```

<Tip>
  When validation fails the AI client receives the error message and can retry. Write clear, actionable error messages to help the agent self-correct.
</Tip>

### Dependency injection

Tools are resolved through the service container, so you can type-hint dependencies in the constructor or `handle` method:

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

### Annotations

Add annotations to give AI clients additional hints about a tool's behaviour:

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

| Annotation         | Description                                                               |
| ------------------ | ------------------------------------------------------------------------- |
| `#[IsReadOnly]`    | Tool does not modify any state                                            |
| `#[IsDestructive]` | Tool may perform destructive updates                                      |
| `#[IsIdempotent]`  | Repeating the call with the same arguments has no additional side effects |
| `#[IsOpenWorld]`   | Tool may interact with external entities                                  |

### Conditional registration

Implement `shouldRegister` to expose a tool only when certain conditions are met:

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

When this returns `false`, the tool is invisible to the AI client.

### Responses

Tools must return a `Laravel\Mcp\Response` instance.

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

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

  <Accordion title="Image and audio responses">
    ```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');

    // Load directly from storage (MIME type is auto-detected)
    return Response::fromStorage('weather/radar.png');
    ```
  </Accordion>

  <Accordion title="Multiple content items">
    ```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="Structured response">
    ```php theme={null}
    return Response::structured([
        'temperature' => 22.5,
        'conditions'  => 'Partly cloudy',
        'humidity'    => 65,
    ]);
    ```
  </Accordion>

  <Accordion title="Streaming response">
    Stream progress updates for long-running operations:

    ```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 are reusable, parameterised prompt templates that standardise common queries an AI client sends to a language model.

### Creating a prompt

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

Register it in the server's `$prompts` array:

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

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

### Prompt arguments

Define prompt parameters in the `arguments` method:

```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 for the weather description (e.g., formal, casual, humorous).',
                required: true,
            ),
        ];
    }
}
```

### Prompt validation

Prompt arguments are automatically validated based on their definition, but you may enforce more complex validation rules by calling `validate` inside the `handle` method:

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

        // Generate the prompt response using the given tone...
    }
}
```

On validation failure, AI clients will act based on the error messages you provide. Provide clear and actionable messages:

```php theme={null}
$validated = $request->validate([
    'tone' => ['required', 'string', 'max:50'],
], [
    'tone.*' => 'You must specify a tone. Examples: "formal", "casual", "humorous".',
]);
```

### Prompt dependency injection

The Laravel service container resolves all prompts, so you may type-hint dependencies in the constructor or `handle` method:

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

You may also type-hint dependencies in the `handle` method:

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

    // ...
}
```

### Conditional prompt registration

Implement `shouldRegister` to conditionally register a prompt at 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;
    }
}
```

When `shouldRegister` returns `false`, the prompt will not appear in the list of available prompts and cannot be invoked by AI clients.

### Prompt responses

Return user and assistant messages from the `handle` method. Use `asAssistant()` to mark a message as coming from the assistant:

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

        return [
            Response::text("You are a helpful weather assistant. Describe the weather in a {$tone} tone.")->asAssistant(),
            Response::text('What is the current weather like in Tokyo?'),
        ];
    }
}
```

## Resources

Resources are data or information an AI client can load as context — documentation, configuration, or dynamic application data that improves the quality of AI responses.

### Creating a resource

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

Register it in the server's `$resources` array:

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

The URI is derived automatically from the class name (e.g. `weather://resources/weather-guidelines`). Customise with `Uri` and `MimeType` attributes:

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

### Resource templates

Implement `HasUriTemplate` to define dynamic resources with URI variables:

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

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

URI variables are automatically injected into the request and accessible via `get()`.

### Resource request

Unlike tools and prompts, resources cannot define input schemas or arguments. However, you can still interact with the request object within your resource's `handle` method:

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

### Resource dependency injection

The Laravel service container resolves all resources, so you may type-hint dependencies in the constructor or `handle` method:

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

You may also type-hint dependencies in the `handle` method:

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

### Resource annotations

Annotate resources with audience, priority, and last-modified date:

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

| Annotation        | Type          | Description                                                  |
| ----------------- | ------------- | ------------------------------------------------------------ |
| `#[Audience]`     | Role or array | Intended audience (`Role::User`, `Role::Assistant`, or both) |
| `#[Priority]`     | float         | Importance score from 0.0 to 1.0                             |
| `#[LastModified]` | string        | ISO 8601 timestamp of the last modification                  |

### Conditional resource registration

Implement `shouldRegister` to conditionally register a resource at 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;
    }
}
```

When `shouldRegister` returns `false`, the resource will not appear in the available list and cannot be accessed by AI clients.

### Resource responses

Resources must return an instance of `Laravel\Mcp\Response`. For text content, use the `text` method:

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

#### Resource link responses

Use `resourceLink` to return a URI pointer that the AI client fetches independently, rather than embedded content:

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

You may also pass a registered resource class or instance to automatically inherit its URI, name, title, description, and MIME type:

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

#### Blob responses

Return binary content using the `blob` method. The MIME type is determined by the resource's `#[MimeType]` attribute:

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

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

#### Error responses

Indicate an error using the `error` method:

```php theme={null}
return Response::error('Unable to fetch weather data for the specified location.');
```

## Apps

Laravel MCP supports [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview), an extension of the Model Context Protocol that allows tools to render interactive HTML applications within sandboxed iframes in supported hosts. This allows you to build dashboards, forms, visualizations, and other rich experiences that go beyond plain text responses.

An MCP app consists of two parts working together:

* An **app resource** that returns the self-contained HTML for your application.
* A **tool** that is linked to the app resource using the `#[RendersApp]` attribute. When the tool is called, the host fetches and renders the linked resource.

### Creating app resources

Create an app resource using the `make:mcp-app-resource` Artisan command:

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

This command creates two files: a PHP class in `app/Mcp/Resources` and a Blade view in `resources/views/mcp`. The view name is automatically inferred from the class name. For example, `WeatherDashboardApp` maps to `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` extends the base `Resource` class and automatically configures the `ui://` URI scheme and the `text/html;profile=mcp-app` MIME type required by the MCP Apps specification. Like any other resource, you must register it in your server's `$resources` array.

The generated Blade view uses the `<x-mcp::app>` component, which renders a complete HTML document with the client-side MCP SDK bundled and ready to use:

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

The `createMcpApp` global is provided by the bundled SDK and handles connecting the iframe to the server, applying host theming, and exposing helpers such as `callServerTool`, `sendMessage`, `openLink`, and event callbacks. For the full client-side API, refer to the [MCP Apps specification](https://modelcontextprotocol.io/extensions/apps/overview).

### Rendering apps from tools

To display an app resource, link a tool to it using the `#[RendersApp]` attribute. When the tool is called, Laravel MCP includes the resource's URI in the tool metadata so the host can render the app in a sandboxed iframe:

```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>
  Laravel MCP automatically advertises the `io.modelcontextprotocol/ui` capability whenever any `AppResource` is registered, so no additional server configuration is required.
</Info>

### App tool visibility

Each `#[RendersApp]` tool can limit who may invoke it via the `visibility` argument. This is useful for exposing private, app-only tools that the UI calls to load or refresh data without making those tools visible to the model:

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

The `Visibility` enum has two cases, `Model` and `App`, and defaults to both. Use `[Visibility::App]` for backend actions the UI calls directly, or `[Visibility::Model]` to make a tool unavailable to the UI.

### App configuration

The `#[AppMeta]` attribute on your app resource configures the iframe's Content Security Policy, browser permissions, and any library scripts that should be included in the view's `<head>`:

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

The `Library` enum includes pre-configured CDN scripts for common front-end libraries such as `Library::Tailwind` and `Library::Alpine`, and their CDN origins are automatically merged into the CSP. The `Permission` enum covers browser permissions such as `Camera`, `Microphone`, `Geolocation`, and `ClipboardWrite`.

<Tip>
  For computed or dynamic configuration, override the `appMeta` method on your resource using the fluent `AppMeta`, `Csp`, and `Permissions` builders from the `Laravel\Mcp\Server\Ui` namespace.
</Tip>

### Building apps with Boost

Laravel MCP includes a dedicated [Boost](/en/boost) skill reference for building MCP Apps. If you have Laravel Boost installed, your AI coding agent can invoke the `mcp-development` skill and ask it to scaffold an app resource, Blade view, and linked tool for you.

For the complete protocol reference, including the full client-side API and schema details, see the official [MCP Apps documentation](https://modelcontextprotocol.io/extensions/apps/overview).

## Metadata

Attach MCP spec `_meta` fields to tool, resource, and prompt responses:

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

To attach metadata to the response envelope, use `Response::make`:

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

To attach metadata to the tool, resource, or prompt class itself, define a `$meta` property:

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

## Icons

MCP clients can display icons for your server and its primitives. Declare icons on a server, tool, resource, or prompt using the `Icon` attribute:

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

The `Icon` attribute is repeatable, so you may declare multiple icons to provide different sizes or light and dark theme variants.

Alternatively, define icons programmatically by overriding the `icons` method. This is useful when an icon depends on runtime conditions:

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

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

Icons defined via the attribute and the `icons` method are combined automatically. Icon paths are resolved as follows:

* Paths with a URI scheme such as `https:` or `data:` are used as-is.
* Relative paths are resolved to a URL using Laravel's `asset` helper.

## Authentication

Web servers support Laravel's standard middleware for authentication.

### Sanctum

Use token-based authentication with [Laravel Sanctum](https://laravel.com/docs/sanctum). The MCP client sends an `Authorization: Bearer <token>` header.

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

Use [Laravel Passport](https://laravel.com/docs/passport) for robust OAuth-based authentication:

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

Mcp::oauthRoutes();

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

When using OAuth, publish the MCP authorization views and register them with Passport:

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

## Authorization

Access the authenticated user inside a tool or resource via `$request->user()`:

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

    // Continue...
}
```

## MCP Client

In addition to building servers, Laravel MCP includes a client for connecting to other MCP servers, whether first-party or third-party. The client lets your application discover and call the tools exposed by an MCP server, which is especially useful for giving your [AI agents](/ai-sdk#mcp-tools) access to capabilities provided by external MCP servers.

### Connecting to Servers

Connect to an HTTP-accessible MCP server using the `Client::web` method, passing the server's URL:

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

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

To connect to a local MCP server that runs as a command, use the `Client::local` method, providing the command and any arguments needed to start the server:

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

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

The client connects lazily, automatically establishing the connection the first time you list or call tools. If you need to manage the connection manually, you may use the `connect`, `connected`, `ping`, and `disconnect` methods:

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

$client->ping();

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

$client->disconnect();
```

Customize the request timeout using the `withTimeout` method:

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

### Named Clients

Instead of constructing a client each time, register reusable named clients in the `boot` method of a service provider using the `Mcp` facade:

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

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

Resolve the client anywhere in your application by name:

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

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

Named clients are resolved once per request and automatically disconnected at the end of the request lifecycle.

### Client Authentication

To connect to a web MCP server protected by a bearer token, use the `withToken` method. You may pass a token string or a closure that lazily resolves the token:

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

For servers protected by [OAuth 2.1](#oauth), configure the client using the `withOAuth` method:

```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>
  The `clientId` and `clientSecret` arguments may be omitted when the MCP server supports [dynamic client registration](https://datatracker.ietf.org/doc/html/rfc7591), in which case the client registers itself automatically.
</Info>

Register the OAuth routes for the named client in `routes/ai.php` using the `oAuthRoutesFor` method:

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

This registers two named routes: a connect route (`mcp.oauth.{client}.connect`) that redirects the user to the authorization server, and a callback route (`mcp.oauth.{client}.callback`) that exchanges the authorization code and invokes your handler. To begin the authorization flow, redirect the user to the connect route:

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

### Tools

Retrieve the tools exposed by an MCP server using the `tools` method, which returns a collection keyed by tool name:

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

The client automatically paginates through all available tools. Limit the number returned using the `limit` argument:

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

Invoke a tool with the `callTool` method, passing the tool name and an array of arguments:

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

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

$result->text();             // The text content of the response
(string) $result;            // Equivalent to calling text()
$result->isError;            // Whether the tool reported an error
$result->structuredContent;  // Structured content, if any
```

You may also call a tool directly from a listed tool instance:

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

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

If you are building agents with the [Laravel AI SDK](/ai-sdk), you may provide tools from an MCP client directly to an agent. See the [MCP Tools](/ai-sdk#mcp-tools) section of the AI SDK documentation for more information.

### Prompts

Retrieve the prompts exposed by an MCP server using the `prompts` method, which returns a collection keyed by prompt name:

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

The client automatically paginates through all available prompts. Limit the number returned using the `limit` argument:

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

To retrieve a prompt, use the `getPrompt` method, passing the prompt name and an array of arguments. The returned `PromptResult` instance exposes the generated messages:

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

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

$result->text();        // The text content of the messages
(string) $result;       // Equivalent to calling text()
$result->messages;      // The raw messages returned by the prompt
$result->description;   // The prompt description, if any
```

### Resources

Retrieve the resources exposed by an MCP server using the `resources` method, which returns a collection keyed by 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;
}
```

The client automatically paginates through all available resources. Limit the number returned using the `limit` argument:

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

To read a resource, use the `readResource` method, passing the resource URI. The returned `ResourceReadResult` instance exposes the resource content:

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

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

$result->content();   // The content of the resource, decoding base64 blobs as needed
(string) $result;     // Equivalent to calling content()
$result->mimeType();  // The MIME type of the resource, if any
$result->contents;    // The raw contents returned by the resource
```

## Testing

### MCP Inspector

Use the interactive `mcp:inspector` command to debug your server:

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

# Local server (named 'weather')
php artisan mcp:inspector weather
```

The command launches the MCP Inspector and displays a client configuration you can copy. If your server uses authentication middleware, include the `Authorization` header when connecting.

### Unit tests

Write unit tests directly against your tools, resources, and 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 and resources follow the same pattern:

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

Authenticate as a specific user with `actingAs`:

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

Key assertion methods:

```php theme={null}
$response->assertOk();        // No error in the response
$response->assertSee('...');  // Response contains the given text
```

Assert that an error is present:

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

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

Assert that no errors are present:

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

Assert the name, title, or description of the tool, resource, or prompt:

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

Assert streaming notifications using `assertSentNotification` and `assertNotificationCount`:

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

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

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

Inspect the raw response for debugging:

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


## Related topics

- [Building an MCP Server with Laravel](/en/advanced/mcp-server.md)
- [Laravel AI SDK](/en/ai-sdk.md)
- [Laravel Console Starter](/en/packages/laravel-console-starter/index.md)
- [MCP](/en/packages/laravel-copilot-sdk/mcp.md)
- [Laravel Boost](/en/boost.md)
