forked from core/php-mcp
feat: absorb Front\Mcp frontage from php-framework
Move the MCP frontage ServiceProvider (Core\Front\Mcp\Boot), McpContext DTO, McpToolHandler interface, and MCP portal blade layout from php-framework into this package. Namespaces unchanged — added PSR-4 mapping for Core\Front\Mcp\ and auto-discovery provider. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
be85428d4c
commit
cd82959a0e
5 changed files with 323 additions and 2 deletions
|
|
@ -16,7 +16,8 @@
|
|||
"autoload": {
|
||||
"psr-4": {
|
||||
"Core\\Mcp\\": "src/Mcp/",
|
||||
"Core\\Website\\Mcp\\": "src/Website/Mcp/"
|
||||
"Core\\Website\\Mcp\\": "src/Website/Mcp/",
|
||||
"Core\\Front\\Mcp\\": "src/Front/Mcp/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
|
|
@ -26,7 +27,9 @@
|
|||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": []
|
||||
"providers": [
|
||||
"Core\\Front\\Mcp\\Boot"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
|
|
|
|||
50
src/Front/Mcp/Boot.php
Normal file
50
src/Front/Mcp/Boot.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
* Licensed under the European Union Public Licence (EUPL) v1.2.
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Front\Mcp;
|
||||
|
||||
use Core\LifecycleEventProvider;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* MCP frontage - MCP API stage.
|
||||
*
|
||||
* Provides mcp middleware group for MCP protocol routes.
|
||||
* Authentication middleware should be added by the core-mcp package.
|
||||
*/
|
||||
class Boot extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Configure mcp middleware group.
|
||||
*/
|
||||
public static function middleware(Middleware $middleware): void
|
||||
{
|
||||
$middleware->group('mcp', [
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
// Fire McpRoutesRegistering event for lazy-loaded modules
|
||||
LifecycleEventProvider::fireMcpRoutes();
|
||||
|
||||
// Fire McpToolsRegistering so modules can register tool handlers
|
||||
LifecycleEventProvider::fireMcpTools();
|
||||
}
|
||||
}
|
||||
48
src/Front/Mcp/Contracts/McpToolHandler.php
Normal file
48
src/Front/Mcp/Contracts/McpToolHandler.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
* Licensed under the European Union Public Licence (EUPL) v1.2.
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Front\Mcp\Contracts;
|
||||
|
||||
use Core\Front\Mcp\McpContext;
|
||||
|
||||
/**
|
||||
* Interface for MCP tool handlers.
|
||||
*
|
||||
* Each MCP tool is implemented as a handler class that provides:
|
||||
* - A JSON schema describing the tool for Claude
|
||||
* - A handle method that processes tool invocations
|
||||
*
|
||||
* Tool handlers are registered via the McpToolsRegistering event
|
||||
* and can be used by both stdio and HTTP MCP transports.
|
||||
*/
|
||||
interface McpToolHandler
|
||||
{
|
||||
/**
|
||||
* Get the JSON schema describing this tool.
|
||||
*
|
||||
* The schema follows the MCP tool specification:
|
||||
* - name: Tool identifier (snake_case)
|
||||
* - description: What the tool does (for Claude)
|
||||
* - inputSchema: JSON Schema for parameters
|
||||
*
|
||||
* @return array{name: string, description: string, inputSchema: array}
|
||||
*/
|
||||
public static function schema(): array;
|
||||
|
||||
/**
|
||||
* Handle a tool invocation.
|
||||
*
|
||||
* @param array $args Arguments from the tool call
|
||||
* @param McpContext $context Server context (session, notifications, etc.)
|
||||
* @return array Result to return to Claude
|
||||
*/
|
||||
public function handle(array $args, McpContext $context): array;
|
||||
}
|
||||
133
src/Front/Mcp/McpContext.php
Normal file
133
src/Front/Mcp/McpContext.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
* Licensed under the European Union Public Licence (EUPL) v1.2.
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Front\Mcp;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Context object passed to MCP tool handlers.
|
||||
*
|
||||
* Abstracts the transport layer (stdio vs HTTP) so tool handlers
|
||||
* can work with either transport without modification.
|
||||
*
|
||||
* Provides access to:
|
||||
* - Current session tracking
|
||||
* - Current plan context
|
||||
* - Notification sending
|
||||
* - Session logging
|
||||
*/
|
||||
class McpContext
|
||||
{
|
||||
/**
|
||||
* @param object|null $currentPlan AgentPlan model instance when Agentic module installed
|
||||
*/
|
||||
public function __construct(
|
||||
private ?string $sessionId = null,
|
||||
private ?object $currentPlan = null,
|
||||
private ?Closure $notificationCallback = null,
|
||||
private ?Closure $logCallback = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the current session ID if one is active.
|
||||
*/
|
||||
public function getSessionId(): ?string
|
||||
{
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current session ID.
|
||||
*/
|
||||
public function setSessionId(?string $sessionId): void
|
||||
{
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current plan if one is active.
|
||||
*
|
||||
* @return object|null AgentPlan model instance when Agentic module installed
|
||||
*/
|
||||
public function getCurrentPlan(): ?object
|
||||
{
|
||||
return $this->currentPlan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current plan.
|
||||
*
|
||||
* @param object|null $plan AgentPlan model instance
|
||||
*/
|
||||
public function setCurrentPlan(?object $plan): void
|
||||
{
|
||||
$this->currentPlan = $plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an MCP notification to the client.
|
||||
*
|
||||
* Notifications are one-way messages that don't expect a response.
|
||||
* Common notifications include progress updates, log messages, etc.
|
||||
*/
|
||||
public function sendNotification(string $method, array $params = []): void
|
||||
{
|
||||
if ($this->notificationCallback) {
|
||||
($this->notificationCallback)($method, $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message to the current session.
|
||||
*
|
||||
* Messages are recorded in the session log for handoff context
|
||||
* and audit trail purposes.
|
||||
*/
|
||||
public function logToSession(string $message, string $type = 'info', array $data = []): void
|
||||
{
|
||||
if ($this->logCallback) {
|
||||
($this->logCallback)($message, $type, $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the notification callback.
|
||||
*/
|
||||
public function setNotificationCallback(?Closure $callback): void
|
||||
{
|
||||
$this->notificationCallback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the log callback.
|
||||
*/
|
||||
public function setLogCallback(?Closure $callback): void
|
||||
{
|
||||
$this->logCallback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a session is currently active.
|
||||
*/
|
||||
public function hasSession(): bool
|
||||
{
|
||||
return $this->sessionId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a plan is currently active.
|
||||
*/
|
||||
public function hasPlan(): bool
|
||||
{
|
||||
return $this->currentPlan !== null;
|
||||
}
|
||||
}
|
||||
87
src/Front/View/Blade/layouts/mcp.blade.php
Normal file
87
src/Front/View/Blade/layouts/mcp.blade.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
@php
|
||||
$appName = config('core.app.name', 'Core PHP');
|
||||
$appUrl = config('app.url', 'https://core.test');
|
||||
$privacyUrl = config('core.urls.privacy', '/privacy');
|
||||
$termsUrl = config('core.urls.terms', '/terms');
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ $title ?? 'MCP Portal' }} - {{ $appName }}</title>
|
||||
<meta name="description" content="{{ $description ?? 'Connect AI agents via Model Context Protocol' }}">
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@fluxAppearance
|
||||
</head>
|
||||
<body class="min-h-screen bg-white dark:bg-zinc-900">
|
||||
<!-- Header -->
|
||||
<header class="border-b border-zinc-200 dark:border-zinc-800">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center space-x-8">
|
||||
<a href="{{ route('mcp.landing') }}" class="flex items-center space-x-2">
|
||||
<span class="text-xl font-bold text-zinc-900 dark:text-white">MCP Portal</span>
|
||||
</a>
|
||||
<nav class="hidden md:flex items-center space-x-6 text-sm">
|
||||
<a href="{{ route('mcp.servers.index') }}" class="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white">
|
||||
Servers
|
||||
</a>
|
||||
<a href="{{ route('mcp.connect') }}" class="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white">
|
||||
Setup Guide
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
@php
|
||||
$workspace = request()->attributes->get('mcp_workspace');
|
||||
@endphp
|
||||
@if($workspace)
|
||||
<a href="{{ route('mcp.dashboard') }}" class="text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white">
|
||||
Dashboard
|
||||
</a>
|
||||
<a href="{{ route('mcp.keys') }}" class="text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white">
|
||||
API Keys
|
||||
</a>
|
||||
<span class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ $workspace->name }}
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="text-sm text-cyan-600 hover:text-cyan-700 dark:text-cyan-400 dark:hover:text-cyan-300">
|
||||
Sign in
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ $appUrl }}" class="text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white">
|
||||
← {{ $appName }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t border-zinc-200 dark:border-zinc-800 mt-auto">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
© {{ date('Y') }} {{ $appName }}. All rights reserved.
|
||||
</p>
|
||||
<div class="flex items-center space-x-6 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
<a href="{{ $privacyUrl }}" class="hover:text-zinc-900 dark:hover:text-white">Privacy</a>
|
||||
<a href="{{ $termsUrl }}" class="hover:text-zinc-900 dark:hover:text-white">Terms</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@fluxScripts
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue