4cc3f5bee4
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (pull_request) Successful in 6s
Generic: Repo Health / Scripts governance (pull_request) Successful in 6s
Generic: Repo Health / Repository health (push) Successful in 14s
Generic: Repo Health / Repository health (pull_request) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been skipped
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been skipped
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
- Convert tabs to spaces (3,413 violations) - Fix line endings, trailing whitespace, brace placement - Break lines exceeding 150-char absolute limit - Replace heredoc tab closers with spaces - Fix empty elseif, forbidden function calls - Update phpcs.xml: exclude rules inappropriate for CLI scripts (SideEffects, MissingNamespace, MultipleClasses, HeaderOrder, empty catch blocks) Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
354 lines
12 KiB
PHP
354 lines
12 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* This file is part of a Moko Consulting project.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: MokoStandards.Enterprise.Plugins
|
|
* INGROUP: MokoStandards
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
* PATH: /lib/Enterprise/Plugins/McpServerPlugin.php
|
|
* BRIEF: Enterprise plugin for MCP (Model Context Protocol) server projects
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MokoEnterprise\Plugins;
|
|
|
|
use MokoEnterprise\AbstractProjectPlugin;
|
|
|
|
/**
|
|
* MCP Server Project Plugin
|
|
*
|
|
* Provides validation, metrics, and management capabilities for
|
|
* Model Context Protocol server projects — TypeScript/Node.js servers
|
|
* that expose external APIs as AI assistant tools.
|
|
*/
|
|
class McpServerPlugin extends AbstractProjectPlugin
|
|
{
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getProjectType(): string
|
|
{
|
|
return 'mcp-server';
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getPluginName(): string
|
|
{
|
|
return 'MCP Server Enterprise Plugin';
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function validateProject(array $config, string $projectPath): array
|
|
{
|
|
$errors = [];
|
|
$warnings = [];
|
|
|
|
// Check for required source files
|
|
$requiredSrc = ['src/index.ts', 'src/client.ts', 'src/config.ts', 'src/types.ts'];
|
|
foreach ($requiredSrc as $file) {
|
|
if (!file_exists("{$projectPath}/{$file}")) {
|
|
$errors[] = "Missing required source file: {$file}";
|
|
}
|
|
}
|
|
|
|
// Check for package.json with MCP SDK dependency
|
|
if (file_exists("{$projectPath}/package.json")) {
|
|
$content = @file_get_contents("{$projectPath}/package.json");
|
|
if ($content) {
|
|
if (strpos($content, '@modelcontextprotocol/sdk') === false) {
|
|
$errors[] = 'package.json missing @modelcontextprotocol/sdk dependency';
|
|
}
|
|
if (strpos($content, 'zod') === false) {
|
|
$warnings[] = 'package.json missing zod dependency (recommended for tool parameter validation)';
|
|
}
|
|
}
|
|
} else {
|
|
$errors[] = 'Missing package.json';
|
|
}
|
|
|
|
// Check for tsconfig.json
|
|
if (!file_exists("{$projectPath}/tsconfig.json")) {
|
|
$errors[] = 'Missing tsconfig.json';
|
|
}
|
|
|
|
// Check for setup wizard
|
|
if (!file_exists("{$projectPath}/scripts/setup.mjs")) {
|
|
$warnings[] = 'Missing scripts/setup.mjs — interactive setup wizard recommended';
|
|
}
|
|
|
|
// Check for config example
|
|
if (!file_exists("{$projectPath}/config.example.json")) {
|
|
$warnings[] = 'Missing config.example.json — example configuration recommended';
|
|
}
|
|
|
|
// Check for shebang in index.ts
|
|
if (file_exists("{$projectPath}/src/index.ts")) {
|
|
$content = @file_get_contents("{$projectPath}/src/index.ts");
|
|
if ($content && strpos($content, '#!/usr/bin/env node') === false) {
|
|
$warnings[] = 'src/index.ts should start with #!/usr/bin/env node shebang';
|
|
}
|
|
}
|
|
|
|
// Check for McpServer usage
|
|
if (file_exists("{$projectPath}/src/index.ts")) {
|
|
$content = @file_get_contents("{$projectPath}/src/index.ts");
|
|
if ($content && strpos($content, 'McpServer') === false) {
|
|
$errors[] = 'src/index.ts must import and use McpServer from @modelcontextprotocol/sdk';
|
|
}
|
|
}
|
|
|
|
// Check for StdioServerTransport
|
|
if (file_exists("{$projectPath}/src/index.ts")) {
|
|
$content = @file_get_contents("{$projectPath}/src/index.ts");
|
|
if ($content && strpos($content, 'StdioServerTransport') === false) {
|
|
$warnings[] = 'src/index.ts should use StdioServerTransport for Claude Code compatibility';
|
|
}
|
|
}
|
|
|
|
$this->log(
|
|
'MCP server project validation completed',
|
|
'info',
|
|
['errors' => count($errors), 'warnings' => count($warnings)]
|
|
);
|
|
|
|
return [
|
|
'valid' => empty($errors),
|
|
'errors' => $errors,
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function collectMetrics(string $projectPath, array $config): array
|
|
{
|
|
$metrics = [
|
|
'has_mcp_sdk' => false,
|
|
'has_zod' => false,
|
|
'has_setup_wizard' => file_exists("{$projectPath}/scripts/setup.mjs"),
|
|
'has_config_example' => file_exists("{$projectPath}/config.example.json"),
|
|
'tool_count' => 0,
|
|
'has_connection_management' => false,
|
|
'has_raw_api_passthrough' => false,
|
|
];
|
|
|
|
// Parse package.json for dependencies
|
|
if (file_exists("{$projectPath}/package.json")) {
|
|
$content = @file_get_contents("{$projectPath}/package.json");
|
|
if ($content) {
|
|
$metrics['has_mcp_sdk'] = strpos($content, '@modelcontextprotocol/sdk') !== false;
|
|
$metrics['has_zod'] = strpos($content, 'zod') !== false;
|
|
}
|
|
}
|
|
|
|
// Count registered tools in index.ts
|
|
if (file_exists("{$projectPath}/src/index.ts")) {
|
|
$content = @file_get_contents("{$projectPath}/src/index.ts");
|
|
if ($content) {
|
|
$metrics['tool_count'] = substr_count($content, 'server.tool(');
|
|
$metrics['has_connection_management'] = strpos($content, 'list_connections') !== false;
|
|
$metrics['has_raw_api_passthrough'] = strpos($content, 'api_request') !== false;
|
|
}
|
|
}
|
|
|
|
// Count total TypeScript lines
|
|
$tsFiles = $this->findFiles($projectPath, '**/*.ts');
|
|
$totalLines = 0;
|
|
foreach ($tsFiles as $file) {
|
|
if (is_file($file)) {
|
|
$totalLines += count(file($file));
|
|
}
|
|
}
|
|
$metrics['total_lines'] = $totalLines;
|
|
|
|
$this->recordMetric('mcp-server', 'tool_count', $metrics['tool_count']);
|
|
$this->recordMetric('mcp-server', 'total_lines', $totalLines);
|
|
|
|
$this->log('Collected MCP server metrics', 'info', $metrics);
|
|
|
|
return $metrics;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function healthCheck(string $projectPath, array $config): array
|
|
{
|
|
$issues = [];
|
|
$score = 100;
|
|
|
|
// Check for required source files
|
|
$requiredSrc = ['src/index.ts', 'src/client.ts', 'src/config.ts', 'src/types.ts'];
|
|
foreach ($requiredSrc as $file) {
|
|
if (!file_exists("{$projectPath}/{$file}")) {
|
|
$issues[] = [
|
|
'severity' => 'critical',
|
|
'message' => "Missing required file: {$file}",
|
|
];
|
|
$score -= 20;
|
|
}
|
|
}
|
|
|
|
// Check for MCP SDK
|
|
if (file_exists("{$projectPath}/package.json")) {
|
|
$content = @file_get_contents("{$projectPath}/package.json");
|
|
if ($content && strpos($content, '@modelcontextprotocol/sdk') === false) {
|
|
$issues[] = [
|
|
'severity' => 'critical',
|
|
'message' => 'Missing @modelcontextprotocol/sdk dependency',
|
|
];
|
|
$score -= 25;
|
|
}
|
|
}
|
|
|
|
// Check for at least one registered tool
|
|
if (file_exists("{$projectPath}/src/index.ts")) {
|
|
$content = @file_get_contents("{$projectPath}/src/index.ts");
|
|
if ($content) {
|
|
$toolCount = substr_count($content, 'server.tool(');
|
|
if ($toolCount === 0) {
|
|
$issues[] = [
|
|
'severity' => 'critical',
|
|
'message' => 'No MCP tools registered in src/index.ts',
|
|
];
|
|
$score -= 25;
|
|
} elseif ($toolCount < 5) {
|
|
$issues[] = [
|
|
'severity' => 'info',
|
|
'message' => "Only {$toolCount} tools registered — consider adding more coverage",
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for README
|
|
if (!$this->fileExists($projectPath, 'README.md')) {
|
|
$issues[] = ['severity' => 'warning', 'message' => 'Missing README.md'];
|
|
$score -= 5;
|
|
}
|
|
|
|
// Check for setup wizard
|
|
if (!file_exists("{$projectPath}/scripts/setup.mjs")) {
|
|
$issues[] = ['severity' => 'warning', 'message' => 'Missing interactive setup wizard'];
|
|
$score -= 10;
|
|
}
|
|
|
|
// Check for config example
|
|
if (!file_exists("{$projectPath}/config.example.json")) {
|
|
$issues[] = ['severity' => 'warning', 'message' => 'Missing config.example.json'];
|
|
$score -= 5;
|
|
}
|
|
|
|
$score = max(0, $score);
|
|
|
|
$this->log('MCP server health check completed', 'info', [
|
|
'score' => $score,
|
|
'issues_count' => count($issues),
|
|
]);
|
|
|
|
return [
|
|
'healthy' => $score >= 70,
|
|
'score' => $score,
|
|
'issues' => $issues,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getRequiredFiles(): array
|
|
{
|
|
return [
|
|
'src/index.ts — MCP server entry point with tool registrations',
|
|
'src/client.ts — HTTP client for target API',
|
|
'src/config.ts — Multi-connection config loader',
|
|
'src/types.ts — TypeScript interfaces',
|
|
'package.json — with @modelcontextprotocol/sdk and zod',
|
|
'tsconfig.json — TypeScript configuration',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getRecommendedFiles(): array
|
|
{
|
|
return [
|
|
'README.md — with tool reference table',
|
|
'config.example.json — example multi-connection config',
|
|
'scripts/setup.mjs — interactive setup wizard',
|
|
'docs/API.md — full tool parameter documentation',
|
|
'docs/ARCHITECTURE.md — component overview and design decisions',
|
|
'docs/INSTALLATION.md — prerequisites and setup guide',
|
|
'Makefile — build automation',
|
|
'.mcp.json — MCP server registration (gitignored)',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getConfigSchema(): array
|
|
{
|
|
return [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'target_api' => [
|
|
'type' => 'string',
|
|
'description' => 'Name of the target API (e.g. "Dolibarr", "Joomla")',
|
|
],
|
|
'auth_method' => [
|
|
'type' => 'string',
|
|
'enum' => ['bearer', 'api-key-header', 'basic', 'oauth2'],
|
|
'description' => 'Authentication mechanism used by the target API',
|
|
],
|
|
'api_prefix' => [
|
|
'type' => 'string',
|
|
'description' => 'API path prefix (e.g. "/api/index.php", "/api/v1")',
|
|
],
|
|
'transport' => [
|
|
'type' => 'string',
|
|
'enum' => ['stdio', 'sse', 'http'],
|
|
'description' => 'MCP transport type (default: stdio)',
|
|
],
|
|
],
|
|
'required' => ['target_api'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getBestPractices(): array
|
|
{
|
|
return [
|
|
'Use Zod schemas for all tool parameter validation',
|
|
'Include a raw API passthrough tool for uncovered endpoints',
|
|
'Support multiple named connections (staging, production, dev)',
|
|
'Include an interactive setup wizard (scripts/setup.mjs)',
|
|
'Use node:https (not fetch) for self-signed cert support',
|
|
'Normalize error responses in formatResponse() helper',
|
|
'Group tools by resource type with section comments',
|
|
'Follow naming convention: prefix_resource_action (snake_case)',
|
|
'Include a list_connections tool for debugging',
|
|
'Document all tools with parameter tables in docs/API.md',
|
|
'Store config in home directory (~/.project-name.json)',
|
|
'Support config path override via environment variable',
|
|
'Use StdioServerTransport for Claude Code compatibility',
|
|
'Include config.example.json with multi-connection example',
|
|
];
|
|
}
|
|
}
|