Skip to main content
LTHN Documentation
Back to website
MCP package

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.

vendor/lthn/php/docs/packages/mcp/index.md

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

composer require host-uk/core-mcp

Quick Start

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

Commerce Tools

Workspace Tools

Developer Tools

Creating Tools

Basic Tool

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

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

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:

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:

// 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:

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');

Learn more about Quotas →

Tool Analytics

Track tool usage and performance:

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');

Learn more about Analytics →

Configuration

// 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

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

// ✅ 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

// ✅ Good - strict validation
public function getParameters(): array
{
    return [
        'title' => [
            'type' => 'string',
            'required' => true,
            'maxLength' => 255,
        ],
    ];
}

3. Handle Errors Gracefully

// ✅ 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

// ✅ 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

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

Learn More