Laravel Restify repositories provide first-class support for Model Context Protocol (MCP), enabling AI agents to efficiently interact with your APIs. This page covers MCP-specific repository features that optimize token usage and provide tailored data structures for AI consumption.
Enabling MCP Tools
To provide MCP server access to your repository, you must include the HasMcpTools
trait. By default, this trait provides an index tool for AI agents:
use Binaryk\LaravelRestify\Traits\HasMcpTools;
#[Model(Post::class)]
class PostRepository extends Repository
{
use HasMcpTools;
public function fields(RestifyRequest $request): array
{
return [
field('title')->required()->searchable()->sortable(),
field('content')->string(),
field('status')->matchable(),
field('category')->matchable(),
];
}
public static function related(): array
{
return [
'author' => BelongsTo::make('user', UserRepository::class),
'comments' => HasMany::make('comments', CommentRepository::class),
'tags' => BelongsToMany::make('tags', TagRepository::class),
];
}
}
Generated MCP Index Tool:
When you include the HasMcpTools
trait, Restify automatically generates an index tool. For example, with the post repository above that has matchable filters, the generated tool looks like this:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "posts-index-tool",
"description": "Retrieve a paginated list of Post records from the posts repository with filtering, sorting, and search capabilities.",
"inputSchema": {
"type": "object",
"properties": {
"page": {
"type": "number",
"description": "Page number for pagination"
},
"perPage": {
"type": "number",
"description": "Number of posts per page"
},
"include": {
"type": "string",
"description": "Comma-separated list of relationships to include with optional field selection.\n\nAvailable relationships:\n- author (fields: name)\n- comments (fields: content)\n- tags (fields: color, name)\n\nField Selection:\nYou can specify which fields to include for each relationship using square brackets.\nSyntax: relationship[field1|field2]\n\nNested Relationships:\nYou can include deeply nested relationships using dot notation with field selection at each level.\nSyntax: relationship[fields].nested[fields].deeper[fields] - supports unlimited nesting depth\nNote: Field selection works at every nesting level independently.\n\nExamples:\n- include=author,comments (include all fields)\n- include=author[name] (selective fields with comma syntax)\n- include=author[name] (selective fields with pipe syntax)\n- include=author.posts (nested relationship - all fields)\n- include=author[name].posts[title] (nested with field selection at each level)\n- include=author[name|email].posts[title].tags[id] (deep nesting - 3 levels with field selection)\n- include=author.posts[title],author.comments[body] (multiple nested from same parent)\n- include=author[name],tags[id] (multiple relationships with field selection)\n- include=author[email|name].posts[title],tags[id] (mixing deep nested and simple relationships)"
},
"search": {
"type": "string",
"description": "Search term to filter posts by title or description. Available searchable fields: title (e.g., search=term)"
},
"sort": {
"type": "string",
"description": "Sorting criteria for the posts. Available options: posts.id, title (e.g., sort=field or sort=-field for descending)"
},
"status": {
"type": "string",
"description": "Filter posts resource. Description: This is a exact match for status (e.g., status=published). It accepts negation by prefixing the column with a hyphen (e.g., -status=draft). The filter type is string."
},
"category": {
"type": "string",
"description": "Filter posts resource. Description: This is a exact match for category (e.g., category=technology). It accepts negation by prefixing the column with a hyphen (e.g., -category=news). The filter type is string."
}
},
"required": []
},
"annotations": {}
}
]
}
}
Notice how the generated tool automatically includes:
- Pagination parameters (
page
,perPage
) - Relationship inclusion with field selection syntax
- Search capabilities based on searchable fields (title)
- Sort options from sortable fields (title)
- Filter parameters for each matchable field (status, category)
Configuring MCP Tools
By default, the HasMcpTools
trait only enables the index tool. To enable other tools like show, store, update, or delete, you need to override the corresponding methods in your repository:
use Binaryk\LaravelRestify\Traits\HasMcpTools;
#[Model(Post::class)]
class PostRepository extends Repository
{
use HasMcpTools;
// Index tool is enabled by default
public function mcpAllowsIndex(): bool
{
return true; // Default: true
}
// Enable show tool for AI agents
public function mcpAllowsShow(): bool
{
return true; // Default: false
}
// Enable store (create) tool for AI agents
public function mcpAllowsStore(): bool
{
return true; // Default: false
}
// Enable update tool for AI agents
public function mcpAllowsUpdate(): bool
{
return true; // Default: false
}
// Enable delete tool for AI agents
public function mcpAllowsDelete(): bool
{
return true; // Default: false
}
// Enable action tools for AI agents
public function mcpAllowsActions(): bool
{
return true; // Default: false
}
// Enable getter tools for AI agents
public function mcpAllowsGetters(): bool
{
return true; // Default: false
}
}
Conditional Tool Access
You can conditionally enable tools based on user permissions or other criteria:
class PostRepository extends Repository
{
use HasMcpTools;
public function mcpAllowsShow(): bool
{
return true; // Allow all authenticated users to view posts
}
public function mcpAllowsStore(): bool
{
// Only allow content creators to create posts via AI
return request()->user()?->hasPermissionTo('create-posts') ?? false;
}
public function mcpAllowsUpdate(): bool
{
// Only allow editors to update posts via AI
return request()->user()?->hasRole('editor') ?? false;
}
public function mcpAllowsDelete(): bool
{
// Only allow admins to delete posts via AI
return request()->user()?->hasRole('admin') ?? false;
}
public function mcpAllowsActions(): bool
{
// Enable custom actions for power users
return request()->user()?->hasRole(['admin', 'editor']) ?? false;
}
}
Tool Security
Default Security Approach
Restify takes a secure by default approach:
- Only the index tool is enabled by default
- All other tools (show, store, update, delete) are disabled by default
- You must explicitly enable each tool you want AI agents to access
- Authorization policies still apply to all MCP requests
Best Practices for Tool Access
class PostRepository extends Repository
{
use HasMcpTools;
public function mcpAllowsShow(): bool
{
// Safe: Reading data is generally low risk
return true;
}
public function mcpAllowsStore(): bool
{
// Moderate risk: Consider user permissions
return request()->user()?->can('create', Post::class) ?? false;
}
public function mcpAllowsUpdate(): bool
{
// Higher risk: Require explicit permissions
return request()->user()?->hasPermissionTo('ai-edit-posts') ?? false;
}
public function mcpAllowsDelete(): bool
{
// Highest risk: Very restrictive
return request()->user()?->hasRole('super-admin') ?? false;
}
}
Field Optimization for AI Agents
For detailed information about optimizing field responses for AI agents, including MCP-specific field methods, token optimization, and conditional field visibility, see the MCP Fields documentation.
MCP Tool Examples
Laravel Restify automatically generates MCP tools for AI agents based on your repository configuration. Here's what the tools look like from an AI agent's perspective:
Index Tool Example
When you define a repository with fields and relationships, Restify generates an index tool:
Repository:
#[Model(Organization::class)]
class OrganizationRepository extends Repository
{
public function fields(RestifyRequest $request): array
{
return [
field('name')->searchable()->sortable(),
field('address')->matchable(),
field('city')->matchable(),
field('country')->matchable(),
];
}
public static function related(): array
{
return [
'users' => HasMany::make('users', UserRepository::class),
'teams' => HasMany::make('teams', TeamRepository::class),
'tags' => BelongsToMany::make('tags', TagRepository::class),
];
}
}
Generated MCP Tool:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "organizations-index-tool",
"description": "Retrieve a paginated list of Organization records from the organizations repository with filtering, sorting, and search capabilities.",
"inputSchema": {
"type": "object",
"properties": {
"page": {
"type": "number",
"description": "Page number for pagination"
},
"perPage": {
"type": "number",
"description": "Number of organizations per page"
},
"include": {
"type": "string",
"description": "Comma-separated list of relationships to include with optional field selection.\n\nAvailable relationships:\n- users (fields: name)\n- teams (fields: name)\n- tags (fields: color, name)\n\nField Selection:\nYou can specify which fields to include for each relationship using square brackets.\nSyntax: relationship[field1|field2]\n\nNested Relationships:\nYou can include deeply nested relationships using dot notation with field selection at each level.\nSyntax: relationship[fields].nested[fields].deeper[fields] - supports unlimited nesting depth\nNote: Field selection works at every nesting level independently.\n\nExamples:\n- include=users,teams (include all fields)\n- include=users[name] (selective fields with comma syntax)\n- include=users[name] (selective fields with pipe syntax)\n- include=users.posts (nested relationship - all fields)\n- include=users[name].posts[title] (nested with field selection at each level)\n- include=users[name|email].posts[title].tags[id] (deep nesting - 3 levels with field selection)\n- include=users.posts[title],users.comments[body] (multiple nested from same parent)\n- include=users[name],teams[id] (multiple relationships with field selection)\n- include=users[email|name].posts[title],teams[id] (mixing deep nested and simple relationships)"
},
"search": {
"type": "string",
"description": "Search term to filter organizations by name or description. Available searchable fields: name (e.g., search=term)"
},
"sort": {
"type": "string",
"description": "Sorting criteria for the organizations. Available options: organizations.id, name (e.g., sort=field or sort=-field for descending)"
},
"tags": {
"type": "string",
"description": "Filter organizations resource. This is an exact match for tags (e.g., tags=some_value). It accepts negation by prefixing the column with a hyphen (e.g., -tags=some_value). The filter type is string."
},
"address": {
"type": "string",
"description": "Filter organizations resource. This is an exact match for address (e.g., address=some_value). It accepts negation by prefixing the column with a hyphen (e.g., -address=some_value). The filter type is string."
},
"city": {
"type": "string",
"description": "Filter organizations resource. This is an exact match for city (e.g., city=some_value). It accepts negation by prefixing the column with a hyphen (e.g., -city=some_value). The filter type is string."
},
"country": {
"type": "string",
"description": "Filter organizations resource. This is an exact match for country (e.g., country=some_value). It accepts negation by prefixing the column with a hyphen (e.g., -country=some_value). The filter type is string."
}
}
}
}
]
}
}
Show Tool Example
Generated Show Tool:
{
"name": "organizations-show-tool",
"description": "Retrieve a specific Organization record by ID with optional relationship loading.",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The ID of the organization to retrieve"
},
"include": {
"type": "string",
"description": "Comma-separated list of relationships to include (e.g., include=users,teams,tags)"
}
},
"required": ["id"]
}
}
Store Tool Example
Generated Store Tool:
{
"name": "posts-store-tool",
"description": "Create a new Post record.",
"inputSchema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Title field for posts"
},
"content": {
"type": "string",
"description": "Content field for posts"
},
"status": {
"type": "string",
"description": "Status field for posts"
},
"category": {
"type": "string",
"description": "Category field for posts"
}
},
"required": [
"title"
]
}
}
Update Tool Example
Generated Update Tool:
{
"name": "posts-update-tool",
"description": "Update an existing Post record by ID.",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The ID of the post to update"
},
"title": {
"type": "string",
"description": "Title field for posts"
},
"content": {
"type": "string",
"description": "Content field for posts"
},
"status": {
"type": "string",
"description": "Status field for posts"
},
"category": {
"type": "string",
"description": "Category field for posts"
}
},
"required": [
"id"
]
}
}
Complete Tool Set
For a typical repository, Restify generates these MCP tools:
{resource}-index-tool
- List/search records with pagination and filtering{resource}-show-tool
- Get a specific record by ID{resource}-store-tool
- Create new records{resource}-update-tool
- Update existing records{resource}-destroy-tool
- Delete records (if authorized)
Tool Features Generated from Fields
Field Modifiers → Tool Properties:
field('name')->searchable() // → adds to "search" description
field('status')->matchable() // → adds "status" filter parameter
field('title')->sortable() // → adds to "sort" options
field('email')->required() // → adds to "required" array in schema
Relationships → Include Options:
HasMany::make('posts', PostRepository::class) // → "posts" in include options
BelongsTo::make('author', UserRepository::class) // → "author" in include options
Validation Rules → Schema:
field('email')->rules('email') // → type: "string", format: "email"
field('age')->rules('integer', 'min:18') // → type: "integer", minimum: 18
field('status')->rules('in:draft,published') // → enum: ["draft", "published"]
This automatic tool generation means you define your API once in the repository, and AI agents get a complete, type-safe interface with detailed documentation and examples.
This MCP repository system allows you to provide highly optimized APIs for AI agents while maintaining full functionality for human users, all from a single, unified codebase.