Skip to content

Commit d9687d7

Browse files
authored
10.3.0 (#687)
* feat: adding the McpTools facade * Fix styling * fix: refactoring * Fix styling * fix: fixing the mcp token * Fix styling * fix: docs --------- Co-authored-by: binaryk <[email protected]>
1 parent 2ee2ac5 commit d9687d7

15 files changed

+1338
-878
lines changed

docs-v3/content/docs/mcp/mcp.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,3 +415,220 @@ No code changes are required. The MCP server automatically adapts to the configu
415415
3. **Use wrapper mode** when working with AI agents that have limited context windows
416416
4. **Monitor token usage** to determine which mode is best for your application
417417
5. **Document your choice** so team members understand which mode is active
418+
419+
## Fine-Grained Tool Permissions
420+
421+
Laravel Restify's MCP integration includes a powerful permission system that allows you to control which tools each API token can access. This is essential for multi-tenant applications or when you need to restrict AI agent capabilities.
422+
423+
### How Permission Control Works
424+
425+
The `RestifyServer` class provides a `canUseTool()` method that is called whenever a tool is accessed. By default, this method returns `true` (all tools are accessible), but you can override it in your application server to implement custom permission logic.
426+
427+
**Key Behavior:**
428+
- `canUseTool()` is called during **tool discovery** (what tools the AI agent sees)
429+
- `canUseTool()` is called during **tool execution** (whether the operation is allowed)
430+
- In **wrapper mode**, permissions are checked for individual operations, not just the 4 wrapper tools
431+
- Tools without permission are completely hidden from the AI agent
432+
433+
### Implementing Token-Based Permissions
434+
435+
Create a custom MCP server that extends `RestifyServer` and implements permission checks:
436+
437+
```php
438+
<?php
439+
440+
namespace App\Mcp;
441+
442+
use Binaryk\LaravelRestify\MCP\RestifyServer;
443+
use App\Models\McpToken;
444+
445+
class ApplicationServer extends RestifyServer
446+
{
447+
public function canUseTool(string|object $tool): bool
448+
{
449+
// Extract tool name from string or object
450+
$toolName = is_string($tool) ? $tool : $tool->name();
451+
452+
// Get the API token from the request
453+
$bearerToken = request()->bearerToken();
454+
455+
if (!$bearerToken) {
456+
return false;
457+
}
458+
459+
// Find the MCP token record
460+
$mcpToken = McpToken::where('token', hash('sha256', $bearerToken))
461+
->first();
462+
463+
if (!$mcpToken) {
464+
return false;
465+
}
466+
467+
// Check if this tool is in the token's allowed tools list
468+
// $mcpToken->allowed_tools is a JSON array like:
469+
// ["users-index", "posts-store", "posts-update-status-action"]
470+
return in_array($toolName, $mcpToken->allowed_tools ?? [], true);
471+
}
472+
}
473+
```
474+
475+
Then register your custom server instead of the base `RestifyServer`:
476+
477+
```php
478+
use App\Mcp\ApplicationServer;
479+
use Laravel\Mcp\Facades\Mcp;
480+
481+
Mcp::web('restify', ApplicationServer::class)->name('mcp.restify');
482+
```
483+
484+
### Generating Tokens with Tool Selection
485+
486+
To create a token creation UI, you need to show users which tools are available and let them select which ones to grant access to:
487+
488+
```php
489+
use Binaryk\LaravelRestify\MCP\RestifyServer;
490+
491+
// Get all available tools
492+
$server = app(RestifyServer::class);
493+
$allTools = $server->getAllAvailableTools();
494+
495+
// Returns a collection with all tools, regardless of mode:
496+
// [
497+
// ['name' => 'users-index', 'title' => 'List Users', 'description' => '...', 'category' => 'CRUD Operations'],
498+
// ['name' => 'users-store', 'title' => 'Create User', 'description' => '...', 'category' => 'CRUD Operations'],
499+
// ['name' => 'posts-publish-action', 'title' => 'Publish Post', 'description' => '...', 'category' => 'Actions'],
500+
// ]
501+
502+
// Group tools by category for better UI
503+
$groupedTools = $allTools->toSelectOptions();
504+
505+
// Returns:
506+
// [
507+
// ['category' => 'CRUD Operations', 'tools' => [...]],
508+
// ['category' => 'Actions', 'tools' => [...]],
509+
// ['category' => 'Getters', 'tools' => [...]],
510+
// ]
511+
```
512+
513+
### Example Token Creation Flow
514+
515+
Here's a complete example of creating a token with specific tool permissions:
516+
517+
```php
518+
use App\Models\McpToken;
519+
use Illuminate\Support\Str;
520+
521+
// 1. Show available tools to user
522+
$server = app(RestifyServer::class);
523+
$availableTools = $server->getAllAvailableTools()->toSelectOptions();
524+
525+
// 2. User selects which tools to grant access to
526+
$selectedTools = [
527+
'users-index',
528+
'users-show',
529+
'posts-index',
530+
'posts-store',
531+
'posts-publish-action',
532+
];
533+
534+
// 3. Generate the token
535+
$plainTextToken = Str::random(64);
536+
537+
// 4. Store token with permissions
538+
$mcpToken = McpToken::create([
539+
'name' => 'AI Agent Token',
540+
'token' => hash('sha256', $plainTextToken),
541+
'allowed_tools' => $selectedTools, // Cast to JSON in model
542+
'user_id' => auth()->id(),
543+
'expires_at' => now()->addDays(30),
544+
]);
545+
546+
// 5. Return plain text token to user (only shown once)
547+
return response()->json([
548+
'token' => $plainTextToken,
549+
'allowed_tools' => $selectedTools,
550+
]);
551+
```
552+
553+
### Database Schema Example
554+
555+
Here's a suggested database schema for storing MCP tokens with permissions:
556+
557+
```php
558+
Schema::create('mcp_tokens', function (Blueprint $table) {
559+
$table->id();
560+
$table->string('name'); // Token description
561+
$table->string('token')->unique(); // Hashed token
562+
$table->json('allowed_tools')->nullable(); // Array of tool names
563+
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
564+
$table->timestamp('last_used_at')->nullable();
565+
$table->timestamp('expires_at')->nullable();
566+
$table->timestamps();
567+
});
568+
```
569+
570+
And the corresponding Eloquent model:
571+
572+
```php
573+
namespace App\Models;
574+
575+
use Illuminate\Database\Eloquent\Model;
576+
577+
class McpToken extends Model
578+
{
579+
protected $fillable = [
580+
'name',
581+
'token',
582+
'allowed_tools',
583+
'user_id',
584+
'expires_at',
585+
];
586+
587+
protected $casts = [
588+
'allowed_tools' => 'array',
589+
'expires_at' => 'datetime',
590+
'last_used_at' => 'datetime',
591+
];
592+
593+
public function user()
594+
{
595+
return $this->belongsTo(User::class);
596+
}
597+
}
598+
```
599+
600+
### Permission Behavior in Different Modes
601+
602+
**Direct Mode:**
603+
- Tools without permission are filtered out of the tools list
604+
- AI agent only sees tools they have access to
605+
- Attempting to use a restricted tool results in "tool not found"
606+
607+
**Wrapper Mode:**
608+
- The 4 wrapper tools are always visible (discover, get-operations, get-details, execute)
609+
- When discovering repositories, only those with ≥1 accessible operation are shown
610+
- When listing operations, only permitted operations appear
611+
- Attempting to get details or execute a restricted operation throws `AuthorizationException`
612+
613+
### Getting Authorized Tools at Runtime
614+
615+
You can get only the tools the current user/token can access:
616+
617+
```php
618+
$server = app(RestifyServer::class);
619+
$authorizedTools = $server->getAuthorizedTools();
620+
621+
// Returns only tools that pass the canUseTool() check
622+
// Useful for showing "Your API Access" in a dashboard
623+
```
624+
625+
### Security Best Practices
626+
627+
1. **Always hash tokens** before storing in the database (use `hash('sha256', $token)`)
628+
2. **Generate tokens with sufficient entropy** (at least 64 random characters)
629+
3. **Implement token expiration** and enforce it in `canUseTool()`
630+
4. **Log token usage** by updating `last_used_at` on each request
631+
5. **Revoke tokens** by deleting the database record
632+
6. **Use HTTPS** to protect tokens in transit
633+
7. **Implement rate limiting** per token to prevent abuse
634+
8. **Audit permission changes** when updating `allowed_tools`

0 commit comments

Comments
 (0)