Files
Jonathan Miller 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
style: fix all PHPCS PSR-12 violations across 74 files (7539 → 0 errors)
- 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>
2026-05-24 17:07:51 -05:00

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