
Laravel Mcp
- 177 installs
- 60 repo stars
- Updated May 16, 2026
- asyrafhussin/agent-skills
laravel-mcp is a skill for building MCP (Model Context Protocol) servers with Laravel that expose tools, prompts, and resources to AI clients.
About
This skill guides building MCP (Model Context Protocol) servers with Laravel to expose tools, prompts, and resources to AI clients. It contains 7 rules across 5 categories covering server creation, tools, prompts, resources, authentication, and testing. Developers use it when building MCP servers or tools for AI client integration, including protecting them with OAuth or Sanctum.
- Build MCP (Model Context Protocol) servers with Laravel
- 7 rules across 5 categories: servers, tools, prompts/resources, auth, testing
- Covers exposing tools, prompts, and resources to AI clients with OAuth/Sanctum
Laravel Mcp by the numbers
- 177 all-time installs (skills.sh)
- Ranked #3,076 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
laravel-mcp capabilities & compatibility
- Capabilities
- mcp server building · agent building · api development
- Use cases
- api development · orchestration
- Runs
- Local or remote
- Pricing
- Free
What laravel-mcp says it does
Comprehensive guide for building MCP (Model Context Protocol) servers with Laravel. Contains 7 rules across 5 categories for exposing tools, prompts, and resources to AI clients.
Protect servers with OAuth 2.1, Sanctum, or custom auth
npx skills add https://github.com/asyrafhussin/agent-skills --skill laravel-mcpAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 177 |
|---|---|
| repo stars | ★ 60 |
| Last updated | May 16, 2026 |
| Repository | asyrafhussin/agent-skills ↗ |
What it does
Build a Laravel MCP server exposing tools, prompts, and resources to AI clients, with auth and MCP Inspector testing.
Who is it for?
Laravel developers building MCP servers and tools for AI clients
Skip if: Non-Laravel MCP implementations or non-MCP APIs
When should I use this skill?
Building MCP servers, tools, prompts, or resources for AI client integration in Laravel
What you get
A Laravel MCP server exposes well-schemaed tools, prompts, and resources with proper auth and tests.
- Laravel MCP server
- MCP tools with schemas
- auth-protected endpoints
By the numbers
- 7 rules across 5 categories
- 5 rule categories by priority
Files
Laravel MCP
Comprehensive guide for building MCP (Model Context Protocol) servers with Laravel. Contains 7 rules across 5 categories for exposing tools, prompts, and resources to AI clients.
When to Apply
Reference these guidelines when:
- Creating MCP servers (web or local)
- Building tools that AI clients can call
- Defining prompts for reusable AI interactions
- Exposing resources for AI context
- Protecting MCP servers with OAuth or Sanctum
- Testing MCP servers, tools, prompts, and resources
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Servers | CRITICAL | server- |
| 2 | Tools | HIGH | tool- |
| 3 | Prompts & Resources | MEDIUM | prompt-, resource- |
| 4 | Authentication | HIGH | auth- |
| 5 | Testing | HIGH | test- |
Quick Reference
1. Servers (CRITICAL)
server-create-register- Create and register web or local MCP servers
2. Tools (HIGH)
tool-create- Create tools with input/output schemas, DI, and annotationstool-responses- Text, error, structured, streaming, and multi-content responses
3. Prompts & Resources (MEDIUM)
prompt-create- Create prompts with arguments, validation, and responsesresource-create- Create resources and resource templates with URI patterns
4. Authentication (HIGH)
auth-protect- Protect servers with OAuth 2.1, Sanctum, or custom auth
5. Testing (HIGH)
test-unit- Test with MCP Inspector and unit tests
Essential Patterns
Creating an MCP Server
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
#[Name('Weather Server')]
#[Version('1.0.0')]
#[Instructions('This server provides weather information.')]
class WeatherServer extends Server
{
protected array $tools = [
CurrentWeatherTool::class,
];
protected array $resources = [];
protected array $prompts = [];
}Registering in routes/ai.php
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/weather', WeatherServer::class);
Mcp::local('weather', WeatherServer::class);Creating a Tool
<?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 for a location.')]
class CurrentWeatherTool extends Tool
{
public function handle(Request $request): Response
{
$location = $request->get('location');
return Response::text("The weather in {$location} is sunny, 72°F.");
}
public function schema(JsonSchema $schema): array
{
return [
'location' => $schema->string()
->description('The location to get the weather for.')
->required(),
];
}
}How to Use
Read individual rule files for detailed explanations and code examples.
Each rule file contains:
- YAML frontmatter with metadata (title, impact, tags)
- Brief explanation of why it matters
- Bad Example with explanation
- Good Example with explanation
- Laravel 13 and PHP 8.3 specific context and references
Full Compiled Document
For the complete guide with all rules expanded: AGENTS.md
Laravel MCP - Complete Guide
Version: 1.0.0 Laravel Version: 13.x PHP Version: 8.3+ Organization: Laravel Community Date: March 2026
Overview
Comprehensive guide for building MCP (Model Context Protocol) servers with Laravel. Contains 7 rules across 5 categories covering server creation, tool development, prompts, resources, authentication, and testing.
Key Features
- MCP server creation with web and local transport
- Tool development with JSON input/output schemas
- Tool annotations (IsReadOnly, IsDestructive, IsIdempotent, IsOpenWorld)
- Structured and streaming tool responses
- Prompt templates with arguments and validation
- Resources and resource templates with URI patterns
- OAuth 2.1 authentication via Passport
- Sanctum token-based authentication
- Authorization with request user context
- MCP Inspector for interactive debugging
- Unit tests with assertOk, assertSee, assertHasErrors
Categories
This guide is organized into 5 categories:
1. Servers (CRITICAL) — Creating and registering MCP servers 2. Tools (HIGH) — Building tools with schemas, validation, and responses 3. Prompts & Resources (MEDIUM) — Prompt templates and data resources 4. Authentication (HIGH) — OAuth 2.1, Sanctum, and authorization 5. Testing (HIGH) — MCP Inspector and unit tests
References
---
title: Create and Register MCP Servers impact: CRITICAL impactDescription: Foundation for all MCP client interactions tags: server, create, register, web, local, routes ---
Create and Register MCP Servers
Impact: CRITICAL (Foundation for all MCP client interactions)
MCP servers expose tools, prompts, and resources to AI clients. Create a server class with make:mcp-server, then register it in routes/ai.php as a web (HTTP) or local (Artisan CLI) server.
Bad Example
// Raw HTTP endpoints — no MCP protocol, not discoverable by AI clients
Route::post('/api/weather', function (Request $request) {
$location = $request->input('location');
$weather = WeatherService::get($location);
return response()->json(['weather' => $weather]);
});
// AI clients can't discover this endpoint, its parameters, or its purposeGood Example
// php artisan make:mcp-server WeatherServer
namespace App\Mcp\Servers;
use App\Mcp\Tools\CurrentWeatherTool;
use App\Mcp\Prompts\DescribeWeatherPrompt;
use App\Mcp\Resources\WeatherGuidelinesResource;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
#[Name('Weather Server')]
#[Version('1.0.0')]
#[Instructions('This server provides weather information and forecasts.')]
class WeatherServer extends Server
{
protected array $tools = [
CurrentWeatherTool::class,
];
protected array $resources = [
WeatherGuidelinesResource::class,
];
protected array $prompts = [
DescribeWeatherPrompt::class,
];
}// Register in routes/ai.php (publish with: php artisan vendor:publish --tag=ai-routes)
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
// Web server — accessible via HTTP POST, ideal for remote AI clients
Mcp::web('/mcp/weather', WeatherServer::class);
// With middleware
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware(['throttle:mcp']);
// Local server — runs as Artisan command, ideal for local AI assistants
Mcp::local('weather', WeatherServer::class);Why
- Discoverable: AI clients automatically discover tools, prompts, and resources
- Protocol-compliant: Follows the MCP specification — works with any MCP client
- Two transports: Web (HTTP) for remote clients, local (stdio) for CLI assistants
- Middleware support: Apply throttling, auth, and other middleware to web servers
- Declarative: PHP attributes configure name, version, and instructions
Reference: Laravel MCP — Creating Servers
---
title: Create Tools with Schemas and Configuration impact: HIGH impactDescription: Expose application functionality to AI clients tags: tool, create, schema, input, output, validation, di, annotations ---
Create Tools with Schemas and Configuration
Impact: HIGH (Expose application functionality to AI clients)
Tools let AI clients call your application code. Each tool has a description, input schema, optional output schema, validation, dependency injection, and annotations.
Bad Example
// Tool with no schema — AI client doesn't know what arguments to send
class WeatherTool extends Tool
{
public function handle(Request $request): Response
{
// What arguments does this accept? No schema defined.
$location = $request->get('location'); // AI client guesses
return Response::text('Sunny');
}
}Good Example
// php artisan make:mcp-tool CurrentWeatherTool
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
{
$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.',
]);
$weather = $this->weather->getForecastFor($validated['location']);
return Response::text("The weather in {$validated['location']} is {$weather}.");
}
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('Temperature units.')
->default('celsius'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'temperature' => $schema->number()
->description('Temperature value')
->required(),
'conditions' => $schema->string()
->description('Weather conditions')
->required(),
];
}
}// Customize name and title with attributes
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('get-weather')]
#[Title('Get Weather Forecast')]
#[Description('Fetches the current weather forecast.')]
class CurrentWeatherTool extends Tool { /* ... */ }// Dependency injection — constructor and handle method
use App\Repositories\WeatherRepository;
class CurrentWeatherTool extends Tool
{
public function __construct(
private readonly WeatherRepository $weather,
) {}
public function handle(Request $request, WeatherRepository $weather): Response
{
return Response::text($weather->getForecastFor($request->get('location')));
}
}// Annotations — describe tool behavior to AI clients
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
use Laravel\Mcp\Server\Tools\Annotations\IsOpenWorld;
#[IsReadOnly]
#[IsIdempotent]
class CurrentWeatherTool extends Tool { /* ... */ }
#[IsDestructive]
#[IsOpenWorld]
class DeleteRecordTool extends Tool { /* ... */ }// Conditional registration — show/hide tools based on state
class PremiumWeatherTool extends Tool
{
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}Why
- Discoverable: Input schema tells AI clients exactly what arguments to send
- Validated: Laravel validation with clear error messages guides AI clients to fix inputs
- Type-safe: Output schema lets AI clients parse structured results
- Injectable: Constructor and handle-method DI — no manual service resolution
- Annotated: Annotations help AI clients understand side effects before calling
Reference: Laravel MCP — Tools
---
title: Tool Responses impact: HIGH impactDescription: Return text, errors, structured data, and streaming content from tools tags: tool, response, text, error, structured, streaming, image, audio ---
Tool Responses
Impact: HIGH (Return text, errors, structured data, and streaming content from tools)
Tools return Response instances with multiple content types: text, errors, images, audio, structured data, and streaming generators.
Bad Example
// Returning raw strings — no protocol compliance, no error handling
class WeatherTool extends Tool
{
public function handle(Request $request): string
{
// Wrong return type, no error handling
return 'Sunny, 72°F';
}
}Good Example
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
// Text response
public function handle(Request $request): Response
{
return Response::text('Weather Summary: Sunny, 72°F');
}// Error response
public function handle(Request $request): Response
{
if (!$this->weather->isAvailable()) {
return Response::error('Unable to fetch weather data. Please try again.');
}
return Response::text($this->weather->get($request->get('location')));
}// Image and audio responses
public function handle(Request $request): Response
{
return Response::image(
file_get_contents(storage_path('weather/radar.png')),
'image/png'
);
}
// From filesystem disk — MIME type auto-detected
return Response::fromStorage('weather/radar.png');
return Response::fromStorage('weather/radar.png', disk: 's3');// Multiple content responses
public function handle(Request $request): array
{
return [
Response::text('Weather Summary: Sunny, 72°F'),
Response::text("**Forecast**\n- Morning: 65°F\n- Afternoon: 78°F"),
];
}// Structured response — parseable data for AI clients
public function handle(Request $request): Response
{
return Response::structured([
'temperature' => 22.5,
'conditions' => 'Partly cloudy',
'humidity' => 65,
]);
}
// Structured with custom text
return Response::make(
Response::text('Weather is 22.5°C and sunny')
)->withStructuredContent([
'temperature' => 22.5,
'conditions' => 'Sunny',
]);// Streaming response — for long-running operations
use Generator;
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));
}
}// Metadata on responses
public function handle(Request $request): Response
{
return Response::text('Sunny, 72°F')
->withMeta(['source' => 'weather-api', 'cached' => true]);
}
// Result-level metadata
return Response::make(
Response::text('Sunny, 72°F')
)->withMeta(['request_id' => '12345']);Why
- Protocol-compliant:
Responseclass handles MCP serialization correctly - Multiple formats: Text, images, audio, structured data — all through one API
- Streaming: Generators enable progress updates for long-running tools
- Structured: AI clients can parse structured responses programmatically
- Metadata: Attach source, caching, and tracking info to responses
Reference: Laravel MCP — Tool Responses
---
title: Create Prompts with Arguments impact: MEDIUM impactDescription: Reusable prompt templates for AI clients tags: prompt, create, arguments, validation, response ---
Create Prompts with Arguments
Impact: MEDIUM (Reusable prompt templates for AI clients)
Prompts are reusable templates that AI clients can invoke with arguments. They provide a standardized way to structure common queries.
Bad Example
// Hardcoded prompt in a tool — not reusable, not discoverable
class WeatherTool extends Tool
{
public function handle(Request $request): Response
{
// Prompt logic mixed into tool — should be a separate prompt
$tone = $request->get('tone', 'formal');
$systemMessage = "Describe the weather in a {$tone} tone.";
return Response::text($systemMessage);
}
}Good Example
// php artisan make:mcp-prompt DescribeWeatherPrompt
namespace App\Mcp\Prompts;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Prompts\Argument;
#[Description('Generates a natural-language weather description in a given tone.')]
class DescribeWeatherPrompt extends Prompt
{
public function arguments(): array
{
return [
new Argument(
name: 'tone',
description: 'The tone to use (e.g., formal, casual, humorous).',
required: true,
),
];
}
public function handle(Request $request): array
{
$validated = $request->validate([
'tone' => 'required|string|max:50',
], [
'tone.*' => 'Specify a tone like "formal", "casual", or "humorous".',
]);
$tone = $validated['tone'];
return [
Response::text("You are a weather assistant. Describe the weather in a {$tone} tone.")->asAssistant(),
Response::text('What is the current weather in New York City?'),
];
}
}// Register in server
class WeatherServer extends Server
{
protected array $prompts = [
DescribeWeatherPrompt::class,
];
}// Customize name and title
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('weather-assistant')]
#[Title('Weather Assistant Prompt')]
#[Description('Generates a weather description.')]
class DescribeWeatherPrompt extends Prompt { /* ... */ }// Dependency injection in prompts
use App\Repositories\WeatherRepository;
class DescribeWeatherPrompt extends Prompt
{
public function __construct(
private readonly WeatherRepository $weather,
) {}
public function handle(Request $request, WeatherRepository $weather): Response
{
$isAvailable = $weather->isServiceAvailable();
// ...
}
}// Conditional prompt registration
class PremiumWeatherPrompt extends Prompt
{
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}Why
- Reusable: Define prompt templates once, invoke from any AI client
- Typed arguments: Arguments have names, descriptions, and required flags
- Validated: Laravel validation with clear error messages
- Multi-message: Return system and user messages with
asAssistant() - Conditional: Show/hide prompts based on user state or subscription
Reference: Laravel MCP — Prompts
---
title: Create Resources and Resource Templates impact: MEDIUM impactDescription: Expose data and documents for AI client context tags: resource, create, template, uri, mime, blob ---
Create Resources and Resource Templates
Impact: MEDIUM (Expose data and documents for AI client context)
Resources provide data that AI clients read as context. Static resources have fixed URIs; resource templates use URI patterns with variables for dynamic content.
Bad Example
// Exposing data through tools instead of resources
// Tools are for actions — resources are for data
class GetGuidelinesTool extends Tool
{
public function handle(Request $request): Response
{
// This is read-only data, not an action — should be a resource
return Response::text(file_get_contents(storage_path('guidelines.md')));
}
}Good Example
// php artisan make:mcp-resource WeatherGuidelinesResource
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\Attributes\Uri;
use Laravel\Mcp\Server\Resource;
#[Description('Comprehensive guidelines for using the Weather API.')]
#[Uri('weather://resources/guidelines')]
#[MimeType('text/plain')]
class WeatherGuidelinesResource extends Resource
{
public function handle(Request $request): Response
{
$guidelines = file_get_contents(storage_path('weather/guidelines.md'));
return Response::text($guidelines);
}
}// Register in server
class WeatherServer extends Server
{
protected array $resources = [
WeatherGuidelinesResource::class,
];
}// Resource template — dynamic URIs with variables
use Illuminate\Support\Facades\Storage;
use Laravel\Mcp\Server\Contracts\HasUriTemplate;
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');
$content = Storage::get("users/{$userId}/files/{$fileId}");
return Response::text($content);
}
}// Blob response for binary content (images, PDFs)
#[MimeType('image/png')]
class WeatherRadarResource extends Resource
{
public function handle(Request $request): Response
{
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
}
}// Resource annotations
use Laravel\Mcp\Enums\Role;
use Laravel\Mcp\Server\Annotations\Audience;
use Laravel\Mcp\Server\Annotations\LastModified;
use Laravel\Mcp\Server\Annotations\Priority;
#[Audience(Role::User)]
#[LastModified('2026-01-12T15:00:58Z')]
#[Priority(0.9)]
class UserDashboardResource extends Resource { /* ... */ }// Conditional registration
class PremiumDataResource extends Resource
{
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}Why
- Semantic: Resources are data, tools are actions — AI clients understand the difference
- Templates: URI patterns enable dynamic resources without defining each one individually
- Typed: MIME type tells AI clients how to interpret the content
- Annotated: Priority and audience hints help AI clients choose relevant resources
- Conditional: Show resources based on auth state or subscription
Reference: Laravel MCP — Resources
---
title: Protect MCP Servers with Authentication impact: HIGH impactDescription: Secure MCP servers from unauthorized AI client access tags: auth, oauth, sanctum, passport, authorization, middleware ---
Protect MCP Servers with Authentication
Impact: HIGH (Secure MCP servers from unauthorized AI client access)
Protect web MCP servers with OAuth 2.1 (Passport), Sanctum tokens, or custom middleware. Use $request->user() for authorization within tools and resources.
Bad Example
// Unprotected MCP server — anyone can call your tools
Mcp::web('/mcp/admin', AdminServer::class);
// No auth middleware — all tools are publicly accessibleGood Example
// OAuth 2.1 with Passport — recommended for MCP specification compliance
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
// Register OAuth discovery and client registration routes
Mcp::oauthRoutes();
// Protect server with Passport auth
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware('auth:api');// Sanctum — simpler, token-based authentication
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware('auth:sanctum');
// AI clients send: Authorization: Bearer <token>// Custom middleware for custom token auth
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware(['custom-api-auth', 'throttle:mcp']);// Authorization inside tools — check user permissions
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
public function handle(Request $request): Response
{
if (!$request->user()->can('read-weather')) {
return Response::error('Permission denied.');
}
return Response::text($this->weather->get($request->get('location')));
}// Passport authorization view setup (AppServiceProvider::boot)
use Laravel\Passport\Passport;
public function boot(): void
{
Passport::authorizationView(function ($parameters) {
return view('mcp.authorize', $parameters);
});
}
// Publish the view:
// php artisan vendor:publish --tag=mcp-viewsWhy
- MCP spec compliance: OAuth 2.1 is the documented MCP authentication mechanism
- Sanctum fallback: Use Sanctum if Passport is not already installed
- Middleware: Apply throttling, CORS, and custom middleware alongside auth
- Authorization:
$request->user()->can()for fine-grained tool access control - Per-tool control: Return
Response::error()for unauthorized actions
Reference: Laravel MCP — Authentication
---
title: Test MCP Servers with Inspector and Unit Tests impact: HIGH impactDescription: Verify MCP tools, prompts, and resources work correctly tags: testing, inspector, unit-test, assert, tool, prompt, resource ---
Test MCP Servers with Inspector and Unit Tests
Impact: HIGH (Verify MCP tools, prompts, and resources work correctly)
Use the MCP Inspector for interactive debugging and write unit tests with assertions for tools, prompts, and resources.
Bad Example
// Testing MCP tools by making raw HTTP calls — fragile and verbose
test('weather tool works', function () {
$response = $this->postJson('/mcp/weather', [
'jsonrpc' => '2.0',
'method' => 'tools/call',
'params' => ['name' => 'current-weather', 'arguments' => ['location' => 'NYC']],
'id' => 1,
]);
// Manual JSON-RPC parsing — error-prone
$this->assertTrue($response->json('result.content.0.text') !== null);
});Good Example
# MCP Inspector — interactive testing and debugging
php artisan mcp:inspector mcp/weather # Web server
php artisan mcp:inspector weather # Local server named "weather"// Pest — test a tool
use App\Mcp\Servers\WeatherServer;
use App\Mcp\Tools\CurrentWeatherTool;
test('weather tool returns forecast', function () {
$response = WeatherServer::tool(CurrentWeatherTool::class, [
'location' => 'New York City',
'units' => 'fahrenheit',
]);
$response
->assertOk()
->assertSee('New York City');
});// PHPUnit — test a tool
use App\Mcp\Servers\WeatherServer;
use App\Mcp\Tools\CurrentWeatherTool;
class WeatherToolTest extends TestCase
{
public function test_weather_tool_returns_forecast(): void
{
$response = WeatherServer::tool(CurrentWeatherTool::class, [
'location' => 'New York City',
'units' => 'fahrenheit',
]);
$response
->assertOk()
->assertSee('The current weather in New York City is 72°F and sunny.');
}
}// Test prompts and resources
$response = WeatherServer::prompt(DescribeWeatherPrompt::class, [
'tone' => 'casual',
]);
$response->assertOk()->assertSee('casual');
$response = WeatherServer::resource(WeatherGuidelinesResource::class);
$response->assertOk();// Test as authenticated user
$response = WeatherServer::actingAs($user)->tool(
CurrentWeatherTool::class,
['location' => 'NYC'],
);
$response->assertOk();// Available assertions
$response->assertOk(); // No errors
$response->assertHasErrors(); // Has errors
$response->assertHasErrors(['Something went wrong.']);
$response->assertHasNoErrors();
$response->assertSee('expected text'); // Contains text
$response->assertName('current-weather'); // Tool name
$response->assertTitle('Current Weather Tool'); // Tool title
$response->assertDescription('Fetches...'); // Tool description
// Streaming notification assertions
$response->assertSentNotification('processing/progress', [
'step' => 1,
'total' => 5,
]);
$response->assertNotificationCount(5);
// Debug — inspect raw response
$response->dd();
$response->dump();Why
- Inspector: Interactive UI to test tools, prompts, and resources without writing code
- First-class assertions:
assertOk(),assertSee(),assertHasErrors()— clean and readable - Auth testing:
actingAs()tests authorization logic in tools and resources - Streaming: Assert notifications sent during long-running tool operations
- Metadata: Assert tool name, title, and description match expectations
Reference: Laravel MCP — Testing
---
How to Use This Guide
1. For AI Agents: Reference specific rules by category and rule name when generating or reviewing code 2. For Developers: Use as a comprehensive reference for Laravel MCP best practices 3. For Code Review: Check implementations against these patterns 4. For Testing: Use MCP Inspector and unit test patterns to verify all MCP primitives
{
"version": "1.0.0",
"organization": "Laravel Community",
"date": "March 2026",
"laravelVersion": "13.x",
"phpVersion": "8.3+",
"rulesTotal": 7,
"abstract": "Comprehensive Laravel MCP guide for AI agents and LLMs. Contains 7 rules across 5 categories covering MCP server creation, tool development with schemas and responses, prompt and resource definitions, authentication with OAuth and Sanctum, and unit testing. All examples use PHP 8.3 syntax and Laravel 13 conventions.",
"references": [
"https://laravel.com/docs/13.x/mcp",
"https://modelcontextprotocol.io/docs/getting-started/intro",
"https://github.com/laravel/mcp"
],
"categories": [
{
"name": "Servers",
"prefix": "server",
"impact": "CRITICAL",
"description": "Creating and registering MCP servers"
},
{
"name": "Tools",
"prefix": "tool",
"impact": "HIGH",
"description": "Building tools with schemas, validation, and responses"
},
{
"name": "Prompts & Resources",
"prefix": "prompt, resource",
"impact": "MEDIUM",
"description": "Reusable prompt templates and data resources for AI clients"
},
{
"name": "Authentication",
"prefix": "auth",
"impact": "HIGH",
"description": "Protecting MCP servers with OAuth 2.1, Sanctum, or custom auth"
},
{
"name": "Testing",
"prefix": "test",
"impact": "HIGH",
"description": "Testing MCP servers with Inspector and unit tests"
}
],
"keyFeatures": [
"MCP server creation with web and local transport",
"Tool development with JSON input/output schemas",
"Tool annotations (IsReadOnly, IsDestructive, IsIdempotent, IsOpenWorld)",
"Structured and streaming tool responses",
"Prompt templates with arguments and validation",
"Resources and resource templates with URI patterns",
"OAuth 2.1 authentication via Passport",
"Sanctum token-based authentication",
"Authorization with request user context",
"MCP Inspector for interactive debugging",
"Unit tests with assertOk, assertSee, assertHasErrors"
]
}
Laravel MCP
Comprehensive guide for building MCP (Model Context Protocol) servers with Laravel. 7 rules across 5 categories.
Version: 1.0.0
Overview
This skill provides guidance for:
- Creating and registering MCP servers (web and local)
- Building tools with input/output schemas and responses
- Defining prompts with arguments and validation
- Exposing resources and resource templates
- Protecting servers with OAuth 2.1, Sanctum, or custom auth
- Testing with MCP Inspector and unit tests
Categories
1. Servers (Critical)
Create and register MCP servers for web (HTTP) and local (Artisan CLI) transports.
2. Tools (High)
Build tools with JSON schemas, validation, dependency injection, annotations, and multiple response types.
3. Prompts & Resources (Medium)
Define reusable prompt templates and expose data resources for AI client context.
4. Authentication (High)
Protect MCP servers with OAuth 2.1 (Passport), Sanctum tokens, or custom middleware.
5. Testing (High)
Test servers with MCP Inspector and write unit tests with assertions.
Rules
| Rule | Category | Impact |
|---|---|---|
server-create-register | Servers | CRITICAL |
tool-create | Tools | HIGH |
tool-responses | Tools | HIGH |
prompt-create | Prompts & Resources | MEDIUM |
resource-create | Prompts & Resources | MEDIUM |
auth-protect | Authentication | HIGH |
test-unit | Testing | HIGH |
Protect MCP Servers with Authentication
Impact: HIGH (Secure MCP servers from unauthorized AI client access)
Protect web MCP servers with OAuth 2.1 (Passport), Sanctum tokens, or custom middleware. Use $request->user() for authorization within tools and resources.
Bad Example
// Unprotected MCP server — anyone can call your tools
Mcp::web('/mcp/admin', AdminServer::class);
// No auth middleware — all tools are publicly accessibleGood Example
// OAuth 2.1 with Passport — recommended for MCP specification compliance
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
// Register OAuth discovery and client registration routes
Mcp::oauthRoutes();
// Protect server with Passport auth
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware('auth:api');// Sanctum — simpler, token-based authentication
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware('auth:sanctum');
// AI clients send: Authorization: Bearer <token>// Custom middleware for custom token auth
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware(['custom-api-auth', 'throttle:mcp']);// Authorization inside tools — check user permissions
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
public function handle(Request $request): Response
{
if (!$request->user()->can('read-weather')) {
return Response::error('Permission denied.');
}
return Response::text($this->weather->get($request->get('location')));
}// Passport authorization view setup (AppServiceProvider::boot)
use Laravel\Passport\Passport;
public function boot(): void
{
Passport::authorizationView(function ($parameters) {
return view('mcp.authorize', $parameters);
});
}
// Publish the view:
// php artisan vendor:publish --tag=mcp-viewsWhy
- MCP spec compliance: OAuth 2.1 is the documented MCP authentication mechanism
- Sanctum fallback: Use Sanctum if Passport is not already installed
- Middleware: Apply throttling, CORS, and custom middleware alongside auth
- Authorization:
$request->user()->can()for fine-grained tool access control - Per-tool control: Return
Response::error()for unauthorized actions
Reference: Laravel MCP — Authentication
Create Prompts with Arguments
Impact: MEDIUM (Reusable prompt templates for AI clients)
Prompts are reusable templates that AI clients can invoke with arguments. They provide a standardized way to structure common queries.
Bad Example
// Hardcoded prompt in a tool — not reusable, not discoverable
class WeatherTool extends Tool
{
public function handle(Request $request): Response
{
// Prompt logic mixed into tool — should be a separate prompt
$tone = $request->get('tone', 'formal');
$systemMessage = "Describe the weather in a {$tone} tone.";
return Response::text($systemMessage);
}
}Good Example
// php artisan make:mcp-prompt DescribeWeatherPrompt
namespace App\Mcp\Prompts;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Prompts\Argument;
#[Description('Generates a natural-language weather description in a given tone.')]
class DescribeWeatherPrompt extends Prompt
{
public function arguments(): array
{
return [
new Argument(
name: 'tone',
description: 'The tone to use (e.g., formal, casual, humorous).',
required: true,
),
];
}
public function handle(Request $request): array
{
$validated = $request->validate([
'tone' => 'required|string|max:50',
], [
'tone.*' => 'Specify a tone like "formal", "casual", or "humorous".',
]);
$tone = $validated['tone'];
return [
Response::text("You are a weather assistant. Describe the weather in a {$tone} tone.")->asAssistant(),
Response::text('What is the current weather in New York City?'),
];
}
}// Register in server
class WeatherServer extends Server
{
protected array $prompts = [
DescribeWeatherPrompt::class,
];
}// Customize name and title
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('weather-assistant')]
#[Title('Weather Assistant Prompt')]
#[Description('Generates a weather description.')]
class DescribeWeatherPrompt extends Prompt { /* ... */ }// Dependency injection in prompts
use App\Repositories\WeatherRepository;
class DescribeWeatherPrompt extends Prompt
{
public function __construct(
private readonly WeatherRepository $weather,
) {}
public function handle(Request $request, WeatherRepository $weather): Response
{
$isAvailable = $weather->isServiceAvailable();
// ...
}
}// Conditional prompt registration
class PremiumWeatherPrompt extends Prompt
{
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}Why
- Reusable: Define prompt templates once, invoke from any AI client
- Typed arguments: Arguments have names, descriptions, and required flags
- Validated: Laravel validation with clear error messages
- Multi-message: Return system and user messages with
asAssistant() - Conditional: Show/hide prompts based on user state or subscription
Reference: Laravel MCP — Prompts
Create Resources and Resource Templates
Impact: MEDIUM (Expose data and documents for AI client context)
Resources provide data that AI clients read as context. Static resources have fixed URIs; resource templates use URI patterns with variables for dynamic content.
Bad Example
// Exposing data through tools instead of resources
// Tools are for actions — resources are for data
class GetGuidelinesTool extends Tool
{
public function handle(Request $request): Response
{
// This is read-only data, not an action — should be a resource
return Response::text(file_get_contents(storage_path('guidelines.md')));
}
}Good Example
// php artisan make:mcp-resource WeatherGuidelinesResource
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\Attributes\Uri;
use Laravel\Mcp\Server\Resource;
#[Description('Comprehensive guidelines for using the Weather API.')]
#[Uri('weather://resources/guidelines')]
#[MimeType('text/plain')]
class WeatherGuidelinesResource extends Resource
{
public function handle(Request $request): Response
{
$guidelines = file_get_contents(storage_path('weather/guidelines.md'));
return Response::text($guidelines);
}
}// Register in server
class WeatherServer extends Server
{
protected array $resources = [
WeatherGuidelinesResource::class,
];
}// Resource template — dynamic URIs with variables
use Illuminate\Support\Facades\Storage;
use Laravel\Mcp\Server\Contracts\HasUriTemplate;
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');
$content = Storage::get("users/{$userId}/files/{$fileId}");
return Response::text($content);
}
}// Blob response for binary content (images, PDFs)
#[MimeType('image/png')]
class WeatherRadarResource extends Resource
{
public function handle(Request $request): Response
{
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
}
}// Resource annotations
use Laravel\Mcp\Enums\Role;
use Laravel\Mcp\Server\Annotations\Audience;
use Laravel\Mcp\Server\Annotations\LastModified;
use Laravel\Mcp\Server\Annotations\Priority;
#[Audience(Role::User)]
#[LastModified('2026-01-12T15:00:58Z')]
#[Priority(0.9)]
class UserDashboardResource extends Resource { /* ... */ }// Conditional registration
class PremiumDataResource extends Resource
{
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}Why
- Semantic: Resources are data, tools are actions — AI clients understand the difference
- Templates: URI patterns enable dynamic resources without defining each one individually
- Typed: MIME type tells AI clients how to interpret the content
- Annotated: Priority and audience hints help AI clients choose relevant resources
- Conditional: Show resources based on auth state or subscription
Reference: Laravel MCP — Resources
Create and Register MCP Servers
Impact: CRITICAL (Foundation for all MCP client interactions)
MCP servers expose tools, prompts, and resources to AI clients. Create a server class with make:mcp-server, then register it in routes/ai.php as a web (HTTP) or local (Artisan CLI) server.
Bad Example
// Raw HTTP endpoints — no MCP protocol, not discoverable by AI clients
Route::post('/api/weather', function (Request $request) {
$location = $request->input('location');
$weather = WeatherService::get($location);
return response()->json(['weather' => $weather]);
});
// AI clients can't discover this endpoint, its parameters, or its purposeGood Example
// php artisan make:mcp-server WeatherServer
namespace App\Mcp\Servers;
use App\Mcp\Tools\CurrentWeatherTool;
use App\Mcp\Prompts\DescribeWeatherPrompt;
use App\Mcp\Resources\WeatherGuidelinesResource;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
#[Name('Weather Server')]
#[Version('1.0.0')]
#[Instructions('This server provides weather information and forecasts.')]
class WeatherServer extends Server
{
protected array $tools = [
CurrentWeatherTool::class,
];
protected array $resources = [
WeatherGuidelinesResource::class,
];
protected array $prompts = [
DescribeWeatherPrompt::class,
];
}// Register in routes/ai.php (publish with: php artisan vendor:publish --tag=ai-routes)
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
// Web server — accessible via HTTP POST, ideal for remote AI clients
Mcp::web('/mcp/weather', WeatherServer::class);
// With middleware
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware(['throttle:mcp']);
// Local server — runs as Artisan command, ideal for local AI assistants
Mcp::local('weather', WeatherServer::class);Why
- Discoverable: AI clients automatically discover tools, prompts, and resources
- Protocol-compliant: Follows the MCP specification — works with any MCP client
- Two transports: Web (HTTP) for remote clients, local (stdio) for CLI assistants
- Middleware support: Apply throttling, auth, and other middleware to web servers
- Declarative: PHP attributes configure name, version, and instructions
Reference: Laravel MCP — Creating Servers
Test MCP Servers with Inspector and Unit Tests
Impact: HIGH (Verify MCP tools, prompts, and resources work correctly)
Use the MCP Inspector for interactive debugging and write unit tests with assertions for tools, prompts, and resources.
Bad Example
// Testing MCP tools by making raw HTTP calls — fragile and verbose
test('weather tool works', function () {
$response = $this->postJson('/mcp/weather', [
'jsonrpc' => '2.0',
'method' => 'tools/call',
'params' => ['name' => 'current-weather', 'arguments' => ['location' => 'NYC']],
'id' => 1,
]);
// Manual JSON-RPC parsing — error-prone
$this->assertTrue($response->json('result.content.0.text') !== null);
});Good Example
# MCP Inspector — interactive testing and debugging
php artisan mcp:inspector mcp/weather # Web server
php artisan mcp:inspector weather # Local server named "weather"// Pest — test a tool
use App\Mcp\Servers\WeatherServer;
use App\Mcp\Tools\CurrentWeatherTool;
test('weather tool returns forecast', function () {
$response = WeatherServer::tool(CurrentWeatherTool::class, [
'location' => 'New York City',
'units' => 'fahrenheit',
]);
$response
->assertOk()
->assertSee('New York City');
});// PHPUnit — test a tool
use App\Mcp\Servers\WeatherServer;
use App\Mcp\Tools\CurrentWeatherTool;
class WeatherToolTest extends TestCase
{
public function test_weather_tool_returns_forecast(): void
{
$response = WeatherServer::tool(CurrentWeatherTool::class, [
'location' => 'New York City',
'units' => 'fahrenheit',
]);
$response
->assertOk()
->assertSee('The current weather in New York City is 72°F and sunny.');
}
}// Test prompts and resources
$response = WeatherServer::prompt(DescribeWeatherPrompt::class, [
'tone' => 'casual',
]);
$response->assertOk()->assertSee('casual');
$response = WeatherServer::resource(WeatherGuidelinesResource::class);
$response->assertOk();// Test as authenticated user
$response = WeatherServer::actingAs($user)->tool(
CurrentWeatherTool::class,
['location' => 'NYC'],
);
$response->assertOk();// Available assertions
$response->assertOk(); // No errors
$response->assertHasErrors(); // Has errors
$response->assertHasErrors(['Something went wrong.']);
$response->assertHasNoErrors();
$response->assertSee('expected text'); // Contains text
$response->assertName('current-weather'); // Tool name
$response->assertTitle('Current Weather Tool'); // Tool title
$response->assertDescription('Fetches...'); // Tool description
// Streaming notification assertions
$response->assertSentNotification('processing/progress', [
'step' => 1,
'total' => 5,
]);
$response->assertNotificationCount(5);
// Debug — inspect raw response
$response->dd();
$response->dump();Why
- Inspector: Interactive UI to test tools, prompts, and resources without writing code
- First-class assertions:
assertOk(),assertSee(),assertHasErrors()— clean and readable - Auth testing:
actingAs()tests authorization logic in tools and resources - Streaming: Assert notifications sent during long-running tool operations
- Metadata: Assert tool name, title, and description match expectations
Reference: Laravel MCP — Testing
Create Tools with Schemas and Configuration
Impact: HIGH (Expose application functionality to AI clients)
Tools let AI clients call your application code. Each tool has a description, input schema, optional output schema, validation, dependency injection, and annotations.
Bad Example
// Tool with no schema — AI client doesn't know what arguments to send
class WeatherTool extends Tool
{
public function handle(Request $request): Response
{
// What arguments does this accept? No schema defined.
$location = $request->get('location'); // AI client guesses
return Response::text('Sunny');
}
}Good Example
// php artisan make:mcp-tool CurrentWeatherTool
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
{
$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.',
]);
$weather = $this->weather->getForecastFor($validated['location']);
return Response::text("The weather in {$validated['location']} is {$weather}.");
}
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('Temperature units.')
->default('celsius'),
];
}
public function outputSchema(JsonSchema $schema): array
{
return [
'temperature' => $schema->number()
->description('Temperature value')
->required(),
'conditions' => $schema->string()
->description('Weather conditions')
->required(),
];
}
}// Customize name and title with attributes
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
#[Name('get-weather')]
#[Title('Get Weather Forecast')]
#[Description('Fetches the current weather forecast.')]
class CurrentWeatherTool extends Tool { /* ... */ }// Dependency injection — constructor and handle method
use App\Repositories\WeatherRepository;
class CurrentWeatherTool extends Tool
{
public function __construct(
private readonly WeatherRepository $weather,
) {}
public function handle(Request $request, WeatherRepository $weather): Response
{
return Response::text($weather->getForecastFor($request->get('location')));
}
}// Annotations — describe tool behavior to AI clients
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
use Laravel\Mcp\Server\Tools\Annotations\IsOpenWorld;
#[IsReadOnly]
#[IsIdempotent]
class CurrentWeatherTool extends Tool { /* ... */ }
#[IsDestructive]
#[IsOpenWorld]
class DeleteRecordTool extends Tool { /* ... */ }// Conditional registration — show/hide tools based on state
class PremiumWeatherTool extends Tool
{
public function shouldRegister(Request $request): bool
{
return $request?->user()?->subscribed() ?? false;
}
}Why
- Discoverable: Input schema tells AI clients exactly what arguments to send
- Validated: Laravel validation with clear error messages guides AI clients to fix inputs
- Type-safe: Output schema lets AI clients parse structured results
- Injectable: Constructor and handle-method DI — no manual service resolution
- Annotated: Annotations help AI clients understand side effects before calling
Reference: Laravel MCP — Tools
Tool Responses
Impact: HIGH (Return text, errors, structured data, and streaming content from tools)
Tools return Response instances with multiple content types: text, errors, images, audio, structured data, and streaming generators.
Bad Example
// Returning raw strings — no protocol compliance, no error handling
class WeatherTool extends Tool
{
public function handle(Request $request): string
{
// Wrong return type, no error handling
return 'Sunny, 72°F';
}
}Good Example
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
// Text response
public function handle(Request $request): Response
{
return Response::text('Weather Summary: Sunny, 72°F');
}// Error response
public function handle(Request $request): Response
{
if (!$this->weather->isAvailable()) {
return Response::error('Unable to fetch weather data. Please try again.');
}
return Response::text($this->weather->get($request->get('location')));
}// Image and audio responses
public function handle(Request $request): Response
{
return Response::image(
file_get_contents(storage_path('weather/radar.png')),
'image/png'
);
}
// From filesystem disk — MIME type auto-detected
return Response::fromStorage('weather/radar.png');
return Response::fromStorage('weather/radar.png', disk: 's3');// Multiple content responses
public function handle(Request $request): array
{
return [
Response::text('Weather Summary: Sunny, 72°F'),
Response::text("**Forecast**\n- Morning: 65°F\n- Afternoon: 78°F"),
];
}// Structured response — parseable data for AI clients
public function handle(Request $request): Response
{
return Response::structured([
'temperature' => 22.5,
'conditions' => 'Partly cloudy',
'humidity' => 65,
]);
}
// Structured with custom text
return Response::make(
Response::text('Weather is 22.5°C and sunny')
)->withStructuredContent([
'temperature' => 22.5,
'conditions' => 'Sunny',
]);// Streaming response — for long-running operations
use Generator;
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));
}
}// Metadata on responses
public function handle(Request $request): Response
{
return Response::text('Sunny, 72°F')
->withMeta(['source' => 'weather-api', 'cached' => true]);
}
// Result-level metadata
return Response::make(
Response::text('Sunny, 72°F')
)->withMeta(['request_id' => '12345']);Why
- Protocol-compliant:
Responseclass handles MCP serialization correctly - Multiple formats: Text, images, audio, structured data — all through one API
- Streaming: Generators enable progress updates for long-running tools
- Structured: AI clients can parse structured responses programmatically
- Metadata: Attach source, caching, and tracking info to responses
Reference: Laravel MCP — Tool Responses
Related skills
FAQ
How is an MCP server registered?
In routes/ai.php with Mcp::web('/mcp/weather', WeatherServer::class) or Mcp::local('weather', WeatherServer::class).
How are servers protected?
With OAuth 2.1, Sanctum, or custom auth via the auth-protect rule.