MCP Package
The MCP (Model Context Protocol) package provides AI agent tools for database queries, commerce operations, and workspace management with built-in security and quota enforcement.
Installation
bash
composer require host-uk/core-mcpQuick Start
php
<?php
namespace Mod\Blog;
use Core\Events\McpToolsRegistering;
class Boot
{
public static array $listens = [
McpToolsRegistering::class => 'onMcpTools',
];
public function onMcpTools(McpToolsRegistering $event): void
{
$event->tool('blog:create-post', Tools\CreatePostTool::class);
$event->tool('blog:list-posts', Tools\ListPostsTool::class);
}
}Key Features
Database Tools
- Query Database - SQL query execution with validation and security
- SQL Validation - Prevent destructive queries and SQL injection
- EXPLAIN Plans - Query optimization analysis
Commerce Tools
- Get Billing Status - Current billing and subscription status
- List Invoices - Invoice history and details
- Upgrade Plan - Tier upgrades with entitlement validation
Workspace Tools
- Workspace Context - Automatic workspace/namespace resolution
- Quota Enforcement - Tool usage limits and monitoring
- Tool Analytics - Usage tracking and statistics
Developer Tools
- Tool Discovery - Automatic tool registration
- Dependency Management - Tool dependency resolution
- Error Handling - Consistent error responses
Creating Tools
Basic Tool
php
<?php
namespace Mod\Blog\Tools;
use Core\Mcp\Tools\BaseTool;
class ListPostsTool extends BaseTool
{
public function getName(): string
{
return 'blog:list-posts';
}
public function getDescription(): string
{
return 'List all blog posts with optional filters';
}
public function getParameters(): array
{
return [
'status' => [
'type' => 'string',
'description' => 'Filter by status',
'enum' => ['published', 'draft'],
'required' => false,
],
'limit' => [
'type' => 'integer',
'description' => 'Number of posts to return',
'default' => 10,
'required' => false,
],
];
}
public function execute(array $params): array
{
$query = Post::query();
if (isset($params['status'])) {
$query->where('status', $params['status']);
}
$posts = $query->limit($params['limit'] ?? 10)->get();
return [
'posts' => $posts->map(fn ($post) => [
'id' => $post->id,
'title' => $post->title,
'slug' => $post->slug,
'status' => $post->status,
])->toArray(),
];
}
}Tool with Workspace Context
php
<?php
namespace Mod\Blog\Tools;
use Core\Mcp\Tools\BaseTool;
use Core\Mcp\Tools\Concerns\RequiresWorkspaceContext;
class CreatePostTool extends BaseTool
{
use RequiresWorkspaceContext;
public function getName(): string
{
return 'blog:create-post';
}
public function execute(array $params): array
{
// Workspace context automatically validated
$workspace = $this->getWorkspaceContext();
$post = Post::create([
'title' => $params['title'],
'content' => $params['content'],
'workspace_id' => $workspace->id,
]);
return [
'success' => true,
'post_id' => $post->id,
];
}
}Tool with Dependencies
php
<?php
namespace Mod\Blog\Tools;
use Core\Mcp\Tools\BaseTool;
use Core\Mcp\Dependencies\HasDependencies;
use Core\Mcp\Dependencies\ToolDependency;
class ImportPostsTool extends BaseTool
{
use HasDependencies;
public function getDependencies(): array
{
return [
new ToolDependency('blog:list-posts', DependencyType::REQUIRED),
new ToolDependency('media:upload', DependencyType::OPTIONAL),
];
}
public function execute(array $params): array
{
// Dependencies automatically validated
// ...
}
}Query Database Tool
Execute SQL queries with built-in security:
php
use Core\Mcp\Tools\QueryDatabase;
$tool = new QueryDatabase();
$result = $tool->execute([
'query' => 'SELECT * FROM posts WHERE status = ?',
'bindings' => ['published'],
'connection' => 'mysql',
]);
// Returns:
// [
// 'rows' => [...],
// 'count' => 10,
// 'execution_time_ms' => 5.23
// ]Security Features
- Whitelist-based validation - Only SELECT queries allowed by default
- No destructive operations - DROP, TRUNCATE, DELETE blocked
- Binding enforcement - Prevents SQL injection
- Connection validation - Only allowed connections accessible
- EXPLAIN analysis - Query optimization insights
Learn more about SQL Security →
Quota System
Enforce tool usage limits per workspace:
php
// config/mcp.php
'quotas' => [
'enabled' => true,
'limits' => [
'free' => ['calls' => 100, 'per' => 'day'],
'pro' => ['calls' => 1000, 'per' => 'day'],
'business' => ['calls' => 10000, 'per' => 'day'],
'enterprise' => ['calls' => null], // Unlimited
],
],Check quota before execution:
php
use Core\Mcp\Services\McpQuotaService;
$quotaService = app(McpQuotaService::class);
if (!$quotaService->canExecute($workspace, 'blog:create-post')) {
throw new QuotaExceededException('Daily tool quota exceeded');
}
$quotaService->recordExecution($workspace, 'blog:create-post');Tool Analytics
Track tool usage and performance:
php
use Core\Mcp\Services\ToolAnalyticsService;
$analytics = app(ToolAnalyticsService::class);
// Get tool stats
$stats = $analytics->getToolStats('blog:create-post', period: 'week');
// Returns: ToolStats with executions, errors, avg_duration_ms
// Get workspace usage
$usage = $analytics->getWorkspaceUsage($workspace, period: 'month');
// Get most used tools
$topTools = $analytics->getTopTools(limit: 10, period: 'week');Configuration
php
// config/mcp.php
return [
'enabled' => true,
'tools' => [
'auto_discover' => true,
'cache_enabled' => true,
],
'query_database' => [
'allowed_connections' => ['mysql', 'pgsql'],
'forbidden_keywords' => [
'DROP', 'TRUNCATE', 'DELETE', 'UPDATE', 'INSERT',
'ALTER', 'CREATE', 'GRANT', 'REVOKE',
],
'max_execution_time' => 5000, // ms
'enable_explain' => true,
],
'quotas' => [
'enabled' => true,
'limits' => [
'free' => ['calls' => 100, 'per' => 'day'],
'pro' => ['calls' => 1000, 'per' => 'day'],
'business' => ['calls' => 10000, 'per' => 'day'],
'enterprise' => ['calls' => null],
],
],
'analytics' => [
'enabled' => true,
'retention_days' => 90,
],
];Middleware
php
use Core\Mcp\Middleware\ValidateWorkspaceContext;
use Core\Mcp\Middleware\CheckMcpQuota;
use Core\Mcp\Middleware\ValidateToolDependencies;
Route::middleware([
ValidateWorkspaceContext::class,
CheckMcpQuota::class,
ValidateToolDependencies::class,
])->group(function () {
// MCP tool routes
});Best Practices
1. Use Workspace Context
php
// ✅ Good - workspace aware
class CreatePostTool extends BaseTool
{
use RequiresWorkspaceContext;
}
// ❌ Bad - no workspace context
class CreatePostTool extends BaseTool
{
public function execute(array $params): array
{
$post = Post::create($params); // No workspace_id!
}
}2. Validate Parameters
php
// ✅ Good - strict validation
public function getParameters(): array
{
return [
'title' => [
'type' => 'string',
'required' => true,
'maxLength' => 255,
],
];
}3. Handle Errors Gracefully
php
// ✅ Good - clear error messages
public function execute(array $params): array
{
try {
return ['success' => true, 'data' => $result];
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'code' => 'TOOL_EXECUTION_FAILED',
];
}
}4. Document Tools Well
php
// ✅ Good - comprehensive description
public function getDescription(): string
{
return 'Create a new blog post with title, content, and optional metadata. '
. 'Requires workspace context. Validates entitlements before creation.';
}Testing
php
<?php
namespace Tests\Feature\Mcp;
use Tests\TestCase;
use Mod\Blog\Tools\ListPostsTool;
class ListPostsToolTest extends TestCase
{
public function test_lists_posts(): void
{
Post::factory()->count(5)->create(['status' => 'published']);
$tool = new ListPostsTool();
$result = $tool->execute([
'status' => 'published',
'limit' => 10,
]);
$this->assertArrayHasKey('posts', $result);
$this->assertCount(5, $result['posts']);
}
}