Skip to content

Sync from dev 62f25a3: Add SVG sanitization via enshrined/svg-sanitiz… #112

Sync from dev 62f25a3: Add SVG sanitization via enshrined/svg-sanitiz…

Sync from dev 62f25a3: Add SVG sanitization via enshrined/svg-sanitiz… #112

Workflow file for this run

# =============================================================================
# CI Workflow for MCP Tools (public repo)
# Runs tests and code quality checks on PRs
# Tests against multiple Drupal versions for compatibility
# =============================================================================
name: CI
on:
push:
branches: [master, main, 1.0.x]
pull_request:
branches: [master, main, 1.0.x]
jobs:
php-lint:
name: PHP Lint
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.3', '8.4']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
- name: PHP Syntax Check
run: |
find . -name "*.php" -not -path "./.git/*" -print0 | xargs -0 -n1 php -l
phpcs:
name: PHP CodeSniffer
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- name: Install Drupal Coding Standards
run: |
composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer global require drupal/coder
- name: Run PHPCS
run: |
export PATH="$HOME/.composer/vendor/bin:$PATH"
phpcs --standard=Drupal,DrupalPractice . --extensions=php,module,install,inc --ignore=*/tests/*,*/vendor/*,*/node_modules/*,*/tmp/*,*/.ddev/*
phpunit:
name: PHPUnit (Drupal ${{ matrix.drupal-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- drupal-version: '10.3'
php-version: '8.3'
- drupal-version: '11.1'
php-version: '8.4'
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: drupal
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, pdo_mysql, gd, dom, xml
tools: composer
coverage: none
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: ${{ runner.os }}-composer-drupal${{ matrix.drupal-version }}-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-drupal${{ matrix.drupal-version }}-
- name: Install Drupal
run: |
composer create-project drupal/recommended-project:^${{ matrix.drupal-version }} drupal --no-interaction
cd drupal
composer require --dev drupal/core-dev:^${{ matrix.drupal-version }} phpspec/prophecy-phpunit:^2 drush/drush --with-all-dependencies --no-interaction
- name: Install Tool API dependency
run: |
cd drupal
composer require drupal/tool:1.0.0-alpha9 --no-interaction
- name: Install MCP dependencies
run: |
cd drupal
composer require mcp/sdk:^0.2.2 code-wheel/mcp-http-security:^1.0 code-wheel/mcp-error-codes:^1.2 code-wheel/mcp-events:^2.0 code-wheel/mcp-schema-builder:^1.1 code-wheel/mcp-tool-gateway:^1.1 enshrined/svg-sanitize:^0.22 -W --no-interaction
- name: Copy MCP Tools module
run: |
mkdir -p drupal/web/modules/contrib/mcp_tools
rsync -av \
--exclude='.git' \
--exclude='.github' \
--exclude='.ddev' \
--exclude='docs' \
--exclude='scripts' \
--exclude='drupal' \
--exclude='vendor' \
--exclude='.phpunit.cache' \
./ drupal/web/modules/contrib/mcp_tools/
- name: Install Drupal site
run: |
cd drupal
./vendor/bin/drush site:install minimal \
--db-url=mysql://root:root@127.0.0.1/drupal \
--site-name="MCP Tools Test" \
--account-name=admin \
--account-pass=admin \
-y
- name: Enable required modules
run: |
cd drupal
./vendor/bin/drush en tool dblog update -y
./vendor/bin/drush en mcp_tools -y
./vendor/bin/drush en mcp_tools_stdio mcp_tools_remote -y
./vendor/bin/drush cr
- name: Create phpunit.xml
run: |
cd drupal
cat > phpunit.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="web/core/tests/bootstrap.php"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutChangesToGlobalState="true">
<php>
<ini name="error_reporting" value="32767"/>
<ini name="memory_limit" value="-1"/>
<env name="SIMPLETEST_BASE_URL" value="http://localhost:8888"/>
<env name="SIMPLETEST_DB" value="mysql://root:root@127.0.0.1/drupal"/>
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value=""/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="disabled"/>
</php>
<testsuites>
<testsuite name="unit">
<directory>web/modules/contrib/mcp_tools/tests/src/Unit</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/tests/src/Unit</directory>
<!-- Contrib-dependent submodules: their tests need classes not installed in CI -->
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_metatag/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_pathauto/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_scheduler/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_sitemap/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_ultimate_cron/tests</exclude>
</testsuite>
<testsuite name="kernel">
<directory>web/modules/contrib/mcp_tools/tests/src/Kernel</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/tests/src/Kernel</directory>
</testsuite>
<testsuite name="functional">
<directory>web/modules/contrib/mcp_tools/tests/src/Functional</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/tests/src/Functional</directory>
</testsuite>
</testsuites>
</phpunit>
EOF
- name: Start PHP built-in server
run: |
cd drupal/web
php -S localhost:8888 .ht.router.php &
sleep 3
- name: Run Unit Tests
run: |
cd drupal
./vendor/bin/phpunit --testsuite unit --colors=always
- name: Run Kernel Tests
run: |
cd drupal
./vendor/bin/phpunit --testsuite kernel --colors=always
- name: Run Functional Tests
run: |
cd drupal
./vendor/bin/phpunit --testsuite functional --colors=always
env:
SIMPLETEST_BASE_URL: http://localhost:8888
SIMPLETEST_DB: mysql://root:root@127.0.0.1/drupal
mcp-e2e:
name: MCP E2E (STDIO + HTTP)
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: drupal
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo_mysql, gd, dom, xml
tools: composer
coverage: none
- name: Install Drupal
run: |
composer create-project drupal/recommended-project:^10.3 drupal --no-interaction
cd drupal
composer require --dev drupal/core-dev:^10.3 phpspec/prophecy-phpunit:^2 drush/drush --with-all-dependencies --no-interaction
composer require drupal/tool:1.0.0-alpha9 mcp/sdk:^0.2.2 code-wheel/mcp-http-security:^1.0 code-wheel/mcp-error-codes:^1.2 code-wheel/mcp-events:^2.0 code-wheel/mcp-schema-builder:^1.1 code-wheel/mcp-tool-gateway:^1.1 -W --no-interaction
- name: Copy MCP Tools module
run: |
mkdir -p drupal/web/modules/contrib/mcp_tools
rsync -av \
--exclude='.git' \
--exclude='.github' \
--exclude='.ddev' \
--exclude='docs' \
--exclude='scripts' \
--exclude='drupal' \
--exclude='vendor' \
--exclude='.phpunit.cache' \
./ drupal/web/modules/contrib/mcp_tools/
- name: Install Drupal site
run: |
cd drupal
./vendor/bin/drush site:install minimal \
--db-url=mysql://root:root@127.0.0.1/drupal \
--site-name="MCP Tools Test" \
--account-name=admin \
--account-pass=admin \
-y
- name: Enable modules
run: |
cd drupal
./vendor/bin/drush en tool dblog update -y
./vendor/bin/drush en mcp_tools -y
./vendor/bin/drush en mcp_tools_stdio mcp_tools_remote mcp_tools_cache -y
./vendor/bin/drush cr
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install MCP JS SDK (no-lock)
run: |
npm install --no-save --no-package-lock @modelcontextprotocol/sdk zod
- name: JS SDK STDIO compatibility
run: |
node scripts/mcp_js_sdk_compat.mjs --drupal-root drupal
- name: STDIO transport E2E
run: |
python3 scripts/mcp_stdio_e2e.py --drupal-root drupal
- name: HTTP transport E2E
run: |
python3 scripts/mcp_http_e2e.py --drupal-root drupal --base-url http://localhost:8888
code-coverage:
name: Code Coverage
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: drupal
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP with coverage
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo_mysql, gd, dom, xml
tools: composer
coverage: pcov
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: ${{ runner.os }}-composer-coverage-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-coverage-
- name: Install Drupal
run: |
composer create-project drupal/recommended-project:^10.3 drupal --no-interaction
cd drupal
composer require --dev drupal/core-dev:^10.3 phpspec/prophecy-phpunit:^2 drush/drush --with-all-dependencies --no-interaction
- name: Install Tool API dependency
run: |
cd drupal
composer require drupal/tool:1.0.0-alpha9 --no-interaction
- name: Install MCP dependencies
run: |
cd drupal
composer require mcp/sdk:^0.2.2 code-wheel/mcp-http-security:^1.0 code-wheel/mcp-error-codes:^1.2 code-wheel/mcp-events:^2.0 code-wheel/mcp-schema-builder:^1.1 code-wheel/mcp-tool-gateway:^1.1 enshrined/svg-sanitize:^0.22 -W --no-interaction
- name: Copy MCP Tools module
run: |
mkdir -p drupal/web/modules/contrib/mcp_tools
rsync -av \
--exclude='.git' \
--exclude='.github' \
--exclude='.ddev' \
--exclude='docs' \
--exclude='scripts' \
--exclude='drupal' \
--exclude='vendor' \
--exclude='.phpunit.cache' \
./ drupal/web/modules/contrib/mcp_tools/
- name: Install Drupal site
run: |
cd drupal
./vendor/bin/drush site:install minimal \
--db-url=mysql://root:root@127.0.0.1/drupal \
--site-name="MCP Tools Test" \
--account-name=admin \
--account-pass=admin \
-y
- name: Enable required modules
run: |
cd drupal
./vendor/bin/drush en tool dblog update -y
./vendor/bin/drush en mcp_tools -y
./vendor/bin/drush en mcp_tools_stdio mcp_tools_remote -y
./vendor/bin/drush cr
- name: Create phpunit.xml with coverage
run: |
cd drupal
cat > phpunit.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="web/core/tests/bootstrap.php"
colors="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutChangesToGlobalState="true">
<php>
<ini name="error_reporting" value="32767"/>
<ini name="memory_limit" value="-1"/>
<env name="SIMPLETEST_BASE_URL" value="http://localhost:8888"/>
<env name="SIMPLETEST_DB" value="mysql://root:root@127.0.0.1/drupal"/>
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value=""/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="disabled"/>
</php>
<testsuites>
<testsuite name="unit">
<directory>web/modules/contrib/mcp_tools/tests/src/Unit</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/tests/src/Unit</directory>
<!-- Contrib-dependent submodules: their tests need classes not installed in CI -->
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_metatag/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_pathauto/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_scheduler/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_sitemap/tests</exclude>
<exclude>web/modules/contrib/mcp_tools/modules/mcp_tools_ultimate_cron/tests</exclude>
</testsuite>
<testsuite name="kernel">
<directory>web/modules/contrib/mcp_tools/tests/src/Kernel</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/tests/src/Kernel</directory>
</testsuite>
<testsuite name="functional">
<directory>web/modules/contrib/mcp_tools/tests/src/Functional</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/tests/src/Functional</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">web/modules/contrib/mcp_tools/src</directory>
<directory suffix=".php">web/modules/contrib/mcp_tools/modules/*/src</directory>
</include>
<exclude>
<!-- Tool plugins are thin wrappers; focus coverage on service logic. -->
<directory>web/modules/contrib/mcp_tools/src/Plugin</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/src/Plugin</directory>
<!-- Drush command wrappers are tested via E2E and release workflows. -->
<directory>web/modules/contrib/mcp_tools/src/Commands</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/src/Commands</directory>
<!-- Optional contrib-dependent submodules are excluded from core coverage. -->
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_entity_clone/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_mcp_server/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_metatag/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_paragraphs/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_pathauto/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_redirect/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_scheduler/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_search_api/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_sitemap/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_ultimate_cron/src</directory>
<directory>web/modules/contrib/mcp_tools/modules/mcp_tools_webform/src</directory>
<directory>web/modules/contrib/mcp_tools/tests</directory>
<directory>web/modules/contrib/mcp_tools/modules/*/tests</directory>
</exclude>
</coverage>
</phpunit>
EOF
- name: Start PHP built-in server
run: |
cd drupal/web
php -S localhost:8888 .ht.router.php &
sleep 3
- name: Run tests with coverage
run: |
cd drupal
./vendor/bin/phpunit --testsuite unit,kernel,functional --coverage-clover=coverage.xml --colors=always
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: drupal/coverage.xml
flags: unittests
name: mcp-tools-coverage
fail_ci_if_error: false
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
tool-registration:
name: Verify All Tools Register
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: drupal
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, pdo_mysql, gd, dom, xml
tools: composer
- name: Install Drupal with all submodules
run: |
composer create-project drupal/recommended-project:^10.3 drupal --no-interaction
cd drupal
composer require drush/drush --no-interaction
composer require drupal/tool:1.0.0-alpha9 mcp/sdk:^0.2.2 code-wheel/mcp-http-security:^1.0 code-wheel/mcp-error-codes:^1.2 code-wheel/mcp-events:^2.0 code-wheel/mcp-schema-builder:^1.1 code-wheel/mcp-tool-gateway:^1.1 -W --no-interaction
- name: Copy module
run: |
mkdir -p drupal/web/modules/contrib/mcp_tools
rsync -av \
--exclude='.git' \
--exclude='.github' \
--exclude='.ddev' \
--exclude='docs' \
--exclude='scripts' \
--exclude='drupal' \
--exclude='vendor' \
--exclude='.phpunit.cache' \
./ drupal/web/modules/contrib/mcp_tools/
- name: Install Drupal
run: |
cd drupal
./vendor/bin/drush site:install minimal \
--db-url=mysql://root:root@127.0.0.1/drupal \
--site-name="MCP Tools Test" \
-y
- name: Enable all MCP Tools modules
run: |
cd drupal
./vendor/bin/drush en tool dblog update -y
./vendor/bin/drush en mcp_tools -y
./vendor/bin/drush en mcp_tools_stdio mcp_tools_remote -y
# Enable all submodules (skip those with unmet dependencies)
for module in mcp_tools_analysis mcp_tools_batch mcp_tools_blocks mcp_tools_cache mcp_tools_config \
mcp_tools_content mcp_tools_cron mcp_tools_entity_clone mcp_tools_image_styles \
mcp_tools_jsonapi mcp_tools_layout_builder mcp_tools_media mcp_tools_menus mcp_tools_metatag \
mcp_tools_migration mcp_tools_moderation mcp_tools_paragraphs mcp_tools_pathauto \
mcp_tools_recipes mcp_tools_redirect mcp_tools_scheduler mcp_tools_search_api \
mcp_tools_sitemap mcp_tools_structure mcp_tools_templates mcp_tools_theme \
mcp_tools_ultimate_cron mcp_tools_users mcp_tools_views mcp_tools_webform \
mcp_tools_remote_media; do
./vendor/bin/drush en $module -y 2>/dev/null || echo "Skipping $module (optional dependency not met)"
done
./vendor/bin/drush cr
- name: Count and verify registered tools
run: |
cd drupal
TOOL_COUNT=$(./vendor/bin/drush eval 'echo count(\Drupal::service("plugin.manager.tool")->getDefinitions());')
MCP_TOOL_COUNT=$(./vendor/bin/drush eval '$defs = \Drupal::service("plugin.manager.tool")->getDefinitions(); $defs = array_filter($defs, static fn($def): bool => str_starts_with($def->getProvider(), "mcp_tools")); echo count($defs);')
echo "========================================"
echo "Registered tools (total): $TOOL_COUNT"
echo "Registered tools (mcp_tools* providers): $MCP_TOOL_COUNT"
echo "========================================"
# Core-only install (no contrib deps) should still register all core tools:
# base module + core-only submodules = 144 tools.
if [ "$MCP_TOOL_COUNT" -lt 144 ]; then
echo "ERROR: Expected at least 144 MCP Tools tools, found $MCP_TOOL_COUNT"
exit 1
fi
echo "✓ Tool registration looks healthy ($MCP_TOOL_COUNT MCP Tools tools)"
dependency-audit:
name: Dependency Audit
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: composer
- name: Create test Drupal project
run: |
composer create-project drupal/recommended-project:^10.3 drupal --no-interaction
- name: Check for security vulnerabilities
run: |
cd drupal
echo "=== Security Audit ==="
composer audit --format=plain || true
- name: Check for outdated packages
run: |
cd drupal
echo "=== Outdated Packages ==="
composer outdated --direct --format=list || true
echo ""
echo "=== Drupal Core Version ==="
composer show drupal/core | grep -E "^(name|versions)" || true
- name: Check Drupal security advisories
run: |
cd drupal
echo "=== Drupal Security Advisories ==="
# Check if there are any security updates available
composer require --dev drupal/core-dev:^10.3 --with-all-dependencies --no-interaction 2>/dev/null || true
./vendor/bin/drush pm:security 2>/dev/null || echo "Drush not available for security check"
continue-on-error: true
documentation:
name: Documentation Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check README exists
run: |
if [ ! -f README.md ]; then
echo "ERROR: README.md not found"
exit 1
fi
LINES=$(wc -l < README.md)
echo "README.md exists with $LINES lines"