feat: plugin scaffold with dependency check

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-25 15:13:57 +02:00
co-authored by Claude Opus 5.5
parent 2eef4f702b
commit 8c4498c207
13 changed files with 3225 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
/vendor/
/vendor.lit-bak/
.phpunit.result.cache
+36
View File
@@ -0,0 +1,36 @@
{
"name": "loosen-it/wp-lit-mcp",
"description": "MCP-Server für WordPress, Divi 5 und Pods mit OAuth für claude.ai",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.1",
"wp-media/mcp-oauth": "^1.1"
},
"require-dev": {
"brain/monkey": "^2.6",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"mockery/mockery": "^1.6",
"phpcompatibility/phpcompatibility-wp": "^2.1",
"phpunit/phpunit": "^9.6",
"squizlabs/php_codesniffer": "^3.10",
"wp-coding-standards/wpcs": "^3.1"
},
"autoload": {
"psr-4": { "LIT\\MCP\\": "src/" }
},
"autoload-dev": {
"psr-4": { "LIT\\MCP\\Tests\\": "tests/" }
},
"scripts": {
"test": "@php vendor/bin/phpunit",
"lint": "@php vendor/bin/phpcs"
},
"config": {
"sort-packages": true,
"platform": { "php": "8.1" },
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
Generated
+2918
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<ruleset name="wp-lit-mcp">
<file>src</file>
<file>wp-lit-mcp.php</file>
<exclude-pattern>/vendor/*</exclude-pattern>
<arg value="sp"/>
<arg name="extensions" value="php"/>
<arg name="basepath" value="."/>
<rule ref="PSR12"/>
<rule ref="WordPress.Security"/>
<rule ref="WordPress.DB.PreparedSQL"/>
<rule ref="WordPress.DB.PreparedSQLPlaceholders"/>
<rule ref="WordPress.WP.AlternativeFunctions"/>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="wp-lit-mcp"/>
</property>
</properties>
</rule>
<rule ref="PHPCompatibilityWP"/>
<config name="testVersion" value="8.1-"/>
</ruleset>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
</testsuites>
</phpunit>
+40
View File
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace LIT\MCP;
/**
* Checks that the libraries WP-LIT-MCP builds on are loadable.
*/
final class Dependencies
{
private const REQUIRED = [
'wordpress/mcp-adapter' => 'WP\\MCP\\Core\\McpAdapter',
'wp-media/mcp-oauth' => 'WPMedia\\MCP\\OAuth\\Bootstrap',
'WordPress Abilities API' => 'WP_Ability',
];
/** @var callable(string): bool */
private $classExists;
public function __construct(?callable $classExists = null)
{
$this->classExists = $classExists ?? static fn (string $class): bool => class_exists($class);
}
/**
* @return list<string> Human-readable names of missing components.
*/
public function missing(): array
{
$missing = [];
foreach (self::REQUIRED as $name => $class) {
if (! ($this->classExists)($class)) {
$missing[] = $name;
}
}
return $missing;
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace LIT\MCP;
/**
* Wires WP-LIT-MCP into WordPress.
*/
final class Plugin
{
private static string $pluginFile = '';
public static function boot(string $pluginFile): void
{
self::$pluginFile = $pluginFile;
$missing = (new Dependencies())->missing();
if ($missing !== []) {
self::noticeMissing($missing);
return;
}
}
public static function pluginDir(): string
{
return dirname(self::$pluginFile);
}
/**
* @param list<string> $missing
*/
public static function noticeMissing(array $missing): void
{
add_action('admin_notices', static function () use ($missing): void {
printf(
'<div class="notice notice-error"><p>%s</p></div>',
esc_html(sprintf(
/* translators: %s: comma separated list of missing components */
__('WP-LIT-MCP ist inaktiv. Es fehlt: %s', 'wp-lit-mcp'),
implode(', ', $missing)
))
);
});
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
if (! class_exists('WP_Error')) {
class WP_Error
{
public function __construct(
private string $code = '',
private string $message = '',
private mixed $data = null
) {
}
public function get_error_code(): string
{
return $this->code;
}
public function get_error_message(): string
{
return $this->message;
}
public function get_error_data(): mixed
{
return $this->data;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
if (! class_exists('WP_User')) {
class WP_User
{
public int $ID = 0;
public array $roles = [];
public function __construct(int $id = 0, array $roles = [])
{
$this->ID = $id;
$this->roles = $roles;
}
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit;
use LIT\MCP\Dependencies;
final class DependenciesTest extends TestCase
{
public function testReportsNothingMissingWhenAllClassesExist(): void
{
$deps = new Dependencies(static fn (string $class): bool => true);
self::assertSame([], $deps->missing());
}
public function testNamesEveryMissingComponent(): void
{
$deps = new Dependencies(static fn (string $class): bool => false);
self::assertSame(
['wordpress/mcp-adapter', 'wp-media/mcp-oauth', 'WordPress Abilities API'],
$deps->missing()
);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit;
use Brain\Monkey;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use MockeryPHPUnitIntegration;
protected function setUp(): void
{
parent::setUp();
Monkey\setUp();
}
protected function tearDown(): void
{
Monkey\tearDown();
parent::tearDown();
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
if (! defined('ABSPATH')) {
define('ABSPATH', __DIR__ . '/');
}
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/Stubs/WpError.php';
require_once __DIR__ . '/Stubs/WpUser.php';
+37
View File
@@ -0,0 +1,37 @@
<?php
/**
* Plugin Name: WP-LIT-MCP
* Description: MCP-Server für WordPress, Divi 5 und Pods – mit OAuth für claude.ai.
* Version: 0.1.0
* Requires at least: 6.9
* Requires PHP: 8.1
* Author: Jan-Niclas Loosen - Loosen-IT
* Author URI: https://loosen-it.de
* License: GPL-2.0-or-later
* Text Domain: wp-lit-mcp
*/
declare(strict_types=1);
defined('ABSPATH') || exit;
$lit_mcp_autoload = __DIR__ . '/vendor/autoload.php';
if (! is_readable($lit_mcp_autoload)) {
add_action('admin_notices', static function (): void {
echo '<div class="notice notice-error"><p>'
. esc_html__(
'WP-LIT-MCP: Abhängigkeiten fehlen. Bitte "composer install --no-dev" im Plugin-Ordner ausführen.',
'wp-lit-mcp'
)
. '</p></div>';
});
return;
}
require_once $lit_mcp_autoload;
add_action('plugins_loaded', static function (): void {
\LIT\MCP\Plugin::boot(__FILE__);
});