Vai al contenuto principale

Cos’è MCP

Il Model Context Protocol (MCP) è una specifica che consente a client AI (Claude, Cursor, GitHub Copilot, ecc.) e alle applicazioni di comunicare tramite un protocollo standardizzato. Implementando un server MCP fai in modo che gli agenti AI possano accedere ai dati della tua app Laravel ed eseguire azioni.
Laravel MCP è il pacchetto ufficiale aggiunto in Laravel 13, laravel/mcp. Offre tutto il necessario per costruire server MCP.
Un server MCP può offrire tre tipi di funzionalità:
FunzionalitàDescrizione
ToolFunzioni chiamabili dal client (ricerca, aggiornamento, chiamate API esterne, ecc.)
ResourceDati e contesto letti dal client
PromptTemplate di prompt riutilizzabili

Installazione

composer require laravel/mcp
Poi:
php artisan vendor:publish --tag=ai-routes
Viene creato routes/ai.php per registrare i server MCP.

Creazione del server

php artisan make:mcp-server WeatherServer
In app/Mcp/Servers:
<?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,
    ];
}

Registrazione

In routes/ai.php puoi registrarlo come web o locale.

Server web

Accessibile via POST HTTP. Adatto a client AI remoti e integrazioni web.
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/weather', WeatherServer::class);
Con middleware:
Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware(['throttle:mcp']);

Server locale

Comando Artisan. Adatto a client AI locali (es. Claude Desktop).
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::local('weather', WeatherServer::class);
Di solito il client MCP avvia automaticamente il server locale: non serve eseguire manualmente mcp:start.

Tool

I tool sono funzioni chiamabili dai client AI.

Creare un tool

php artisan make:mcp-tool CurrentWeatherTool
Registralo:
use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    protected array $tools = [
        CurrentWeatherTool::class,
    ];
}
Esempio:
<?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');

        // Recupero dei dati meteo...

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

Nome e titolo

Dedotti dal nome della classe (CurrentWeatherToolcurrent-weather, “Current Weather Tool”). Personalizzabili con Name e Title.
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
{
    // ...
}
La Description non è generata automaticamente ed è indispensabile perché l’AI capisca come usare il tool.

Schema di input

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

Schema di output

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

Validazione

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

    // Elabora con i dati validati...
}
Alle validation errors i client AI riprovano seguendo il messaggio: rendilo chiaro e azionabile.

Dependency injection

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

Annotazioni

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
{
    // ...
}
AnnotazioneDescrizione
#[IsReadOnly]Il tool non modifica l’ambiente
#[IsDestructive]Il tool può eseguire aggiornamenti distruttivi
#[IsIdempotent]Nessun side-effect con più chiamate con stessi input
#[IsOpenWorld]Il tool può interagire con entità esterne

Registrazione condizionale

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

Response

return Response::text('Weather Summary: Sunny, 22°C');
return Response::error('Unable to fetch weather data. Please try again.');
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');

return Response::fromStorage('weather/radar.png');
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"),
    ];
}
return Response::structured([
    'temperature' => 22.5,
    'conditions' => 'Partly cloudy',
    'humidity' => 65,
]);
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));
    }
}

Prompt

Prompt riutilizzabili.

Creare un prompt

php artisan make:mcp-prompt DescribeWeatherPrompt
use App\Mcp\Prompts\DescribeWeatherPrompt;

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

Argomenti

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

Validazione

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

        // Genera il prompt con il tono...
    }
}
Messaggi personalizzati:
$validated = $request->validate([
    'tone' => ['required', 'string', 'max:50'],
], [
    'tone.*' => 'Specifica un tono. Esempio: "formal", "casual", "humorous".',
]);

Dependency injection

<?php

namespace App\Mcp\Prompts;

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

class DescribeWeatherPrompt extends Prompt
{
    public function __construct(
        protected WeatherRepository $weather,
    ) {}
}
public function handle(Request $request, WeatherRepository $weather): Response
{
    $isAvailable = $weather->isServiceAvailable();

    // ...
}

Registrazione condizionale

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

Response del prompt

Messaggi utente e assistente con asAssistant().
<?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),
        ];
    }
}

Resource

Dati/informazioni che il client legge come contesto.

Creare una resource

php artisan make:mcp-resource WeatherGuidelinesResource
use App\Mcp\Resources\WeatherGuidelinesResource;

class WeatherServer extends Server
{
    protected array $resources = [
        WeatherGuidelinesResource::class,
    ];
}
<?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 e MIME type

Default: URI generato dal nome della classe (weather://resources/weather-guidelines). Personalizza con Uri e MimeType.
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
{
    // ...
}

Template di URI

Con HasUriTemplate:
<?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}");
    }
}
Le variabili dall’URI sono disponibili con get.

Richiesta della resource

Le resource non hanno schema di input né argomenti, ma puoi accedere alle info della richiesta.
<?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
    {
        // Info della richiesta...
    }
}

DI nelle resource

<?php

namespace App\Mcp\Resources;

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

class WeatherGuidelinesResource extends Resource
{
    public function __construct(
        protected WeatherRepository $weather,
    ) {}
}
public function handle(WeatherRepository $weather): Response
{
    return Response::text($weather->guidelines());
}

Annotazioni

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
{
    // ...
}
AnnotazioneTipoDescrizione
#[Audience]Role o arrayDestinatario (Role::User, Role::Assistant, o entrambi)
#[Priority]floatImportanza (0.0–1.0)
#[LastModified]stringData ISO 8601

Registrazione condizionale

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

Response di una resource

return Response::text($weatherData);
return Response::resourceLink(
    uri: 'file:///data/report.json',
    name: 'monthly-report',
    mimeType: 'application/json',
);
Passando una classe registrata eredita URI, nome, titolo, descrizione, MIME.
return Response::resourceLink(new WeatherForecastResource);

Blob

return Response::blob(file_get_contents(storage_path('weather/radar.png')));
#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
    // ...
}

Errore

return Response::error('Impossibile ottenere i dati meteo per il luogo richiesto.');

App

Laravel MCP supporta le MCP Apps: estensione del protocollo per applicazioni HTML interattive renderizzate in iframe sandbox all’interno dell’host. Puoi costruire dashboard, form, visualizzazioni. Un’app MCP ha due parti:
  • App resource — restituisce l’HTML autonomo
  • Tool — collegato alla resource con l’attribute #[RendersApp]

Creare una app resource

php artisan make:mcp-app-resource WeatherDashboardApp
Crea due file: PHP in app/Mcp/Resources e vista Blade in resources/views/mcp (dedotta dal nome della classe; WeatherDashboardAppmcp.weather-dashboard-app).
<?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
{
    public function handle(Request $request): Response
    {
        return Response::view('mcp.weather-dashboard-app', [
            'title' => $this->title(),
        ]);
    }
}
AppResource estende Resource e configura URI ui:// e MIME text/html;profile=mcp-app. Registrala nel server come le altre. La vista Blade usa <x-mcp::app>, che include l’SDK client-side MCP.
<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>
createMcpApp è fornita dall’SDK. Gestisce connessione dell’iframe, tema dell’host, helper come callServerTool, sendMessage, openLink. Vedi le specifiche MCP Apps.

Renderizzare l’app da un tool

Con #[RendersApp]:
<?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
{
    public function handle(Request $request): Response
    {
        return Response::text('Weather dashboard loaded.');
    }
}
Con una AppResource registrata, Laravel MCP annuncia automaticamente la capability io.modelcontextprotocol/ui.

Visibilità dei tool app

Con visibility limiti chi può chiamare il tool. Utile per tool riservati all’UI dell’app.
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Ui\Enums\Visibility;

#[RendersApp(resource: WeatherDashboardApp::class, visibility: [Visibility::App])]
class GetWeatherData extends Tool
{
    // ...
}
L’enum Visibility ha Model e App (default: entrambi). [Visibility::App] per backend usati dall’UI; [Visibility::Model] per nascondere all’UI.

Configurazione dell’app

Con #[AppMeta] configuri CSP dell’iframe, permessi del browser, script di libreria da includere nel <head>.
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
{
    // ...
}
Library::Tailwind, Library::Alpine ecc. hanno gli script CDN preimpostati; le origini vanno automaticamente in CSP. Permission::Camera, Microphone, Geolocation, ClipboardWrite per i permessi.
Per config dinamica, sovrascrivi appMeta sulla resource usando i builder fluenti AppMeta, Csp, Permissions in Laravel\Mcp\Server\Ui.

Sviluppo con Boost

Laravel MCP include un riferimento di Boost mcp-development per costruire MCP Apps. L’agente può generare automaticamente app resource, viste Blade e tool collegati. Per il riferimento completo del protocollo consulta la documentazione ufficiale MCP Apps.

Metadata

Puoi aggiungere _meta alle response.
return Response::text('The weather is sunny.')
    ->withMeta(['source' => 'weather-api', 'cached' => true]);
All’envelope della response:
return Response::make(
    Response::text('The weather is sunny.')
)->withMeta(['request_id' => '12345']);
Metadata di classe con $meta:
class CurrentWeatherTool extends Tool
{
    protected ?array $meta = [
        'version' => '2.0',
        'author' => 'Weather Team',
    ];
}

Icone

Con Icon puoi dichiarare icone su server, tool, resource, prompt.
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
{
    // ...
}
L’attribute è ripetibile: puoi fornire varianti di dimensione e tema. Oppure sovrascrivi icons() per definire icone in modo programmatico.
use Laravel\Mcp\Schema\Icon;

class CurrentWeatherTool extends Tool
{
    /**
     * @return array<int, Icon>
     */
    public function icons(): array
    {
        return [
            Icon::from('mcp/tool.png', mimeType: 'image/png'),
        ];
    }
}
Icone da attribute e metodo si uniscono. I path si risolvono così:
  • URI (https:, data:) restano invariati
  • Path relativi passano dall’helper asset di Laravel

Autenticazione

I server web possono usare i middleware standard di Laravel.

Sanctum

use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

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

OAuth 2.1

Con Passport:
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware('auth:api');
Pubblica le viste e configurale:
php artisan vendor:publish --tag=mcp-views
use Laravel\Passport\Passport;

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

Autorizzazione

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

    // ...
}

Client MCP

Oltre al server, Laravel MCP offre un client per connettersi ad altri server MCP e usare i tool esposti. Utile per fornire tool esterni ai propri agenti AI.

Connessione

HTTP:
use Laravel\Mcp\Client;

$client = Client::web('https://mcp.example.com');
Locale:
use Laravel\Mcp\Client;

$client = Client::local('php', ['artisan', 'mcp:start']);
Il client fa lazy connect. Puoi gestire la connessione:
$client->connect();

$client->ping();

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

$client->disconnect();
Timeout:
$client = Client::web('https://mcp.example.com')->withTimeout(30);

Client con nome

Registra client riutilizzabili:
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;

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

$client = Mcp::client('github');
I client con nome sono risolti una volta per richiesta e disconnessi automaticamente a fine ciclo.

Autenticazione del client

Bearer token:
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(),
);
OAuth 2.1:
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'),
));
Se il server MCP supporta la Dynamic Client Registration, puoi omettere clientId e clientSecret.
Registra le route OAuth in routes/ai.php con oAuthRoutesFor. La closure riceve il nome del client e il TokenSet dopo lo scambio del codice.
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');
});
Vengono create due route con nome: mcp.oauth.{client}.connect (redirect al server di autorizzazione) e mcp.oauth.{client}.callback (scambio del codice, chiamata all’handler). Usano il middleware web (sovrascrivibile via argomento middleware). Per iniziare il flow:
return redirect()->route('mcp.oauth.github.connect');

Tool

use Laravel\Mcp\Facades\Mcp;

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

foreach ($tools as $tool) {
    $tool->name;
    $tool->title;
    $tool->description;
    $tool->inputSchema;
}
Paginazione gestita automaticamente. Limita con limit:
$tools = Mcp::client('github')->tools(limit: 10);
Chiamata:
use Laravel\Mcp\Facades\Mcp;

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

$result->text();
(string) $result;
$result->isError;
$result->structuredContent;
Dall’istanza del tool:
$tools = Mcp::client('github')->tools();

$result = $tools['current-weather']->call([
    'location' => 'New York',
]);
Se usi il Laravel AI SDK, passa i tool del client direttamente all’agente: Tool MCP.

Prompt

use Laravel\Mcp\Facades\Mcp;

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

foreach ($prompts as $prompt) {
    $prompt->name;
    $prompt->title;
    $prompt->description;
    $prompt->arguments;
}
Limita con limit:
$prompts = Mcp::client('github')->prompts(limit: 10);
Recupero:
use Laravel\Mcp\Facades\Mcp;

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

$result->text();
(string) $result;
$result->messages;
$result->description;

Resource

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;
}
$resources = Mcp::client('github')->resources(limit: 10);
Lettura:
use Laravel\Mcp\Facades\Mcp;

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

$result->content();
(string) $result;
$result->mimeType();
$result->contents;

Test

MCP Inspector

# Server web
php artisan mcp:inspector mcp/weather

# Server locale (nome "weather")
php artisan mcp:inspector weather
Copia la config del client. Se hai autenticazione, includi l’header Authorization.

Test unitari

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.');
});
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.');
}
Prompt e resource:
$response = WeatherServer::prompt(DescribeWeatherPrompt::class, ['tone' => 'casual']);
$response = WeatherServer::resource(WeatherGuidelinesResource::class);
Come utente autenticato:
$response = WeatherServer::actingAs($user)->tool(CurrentWeatherTool::class, [...]);
Asserzioni principali:
$response->assertOk();
$response->assertSee('...');
Errori:
$response->assertHasErrors();

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

$response->assertHasNoErrors();
Nome, titolo, descrizione:
$response->assertName('current-weather');
$response->assertTitle('Current Weather Tool');
$response->assertDescription('Fetches the current weather forecast for a specified location.');
Notifiche di streaming:
$response->assertSentNotification('processing/progress', [
    'step' => 1,
    'total' => 5,
]);

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

$response->assertNotificationCount(5);
Debug:
$response->dd();
$response->dump();
Ultima modifica il 13 luglio 2026