This document describes how to test the MCP Tools module locally and in CI.
DDEV is the recommended way to run tests locally.
# Clone the repository
git clone https://github.com/code-wheel/mcp-tools.git
cd mcp-tools
# Start DDEV
ddev start
# Set up the module for testing
ddev setup-module# Run all tests
ddev test
# Run specific test class
ddev test AccessManagerTest
# Run specific test method
ddev test testCanWriteWithWriteScopeLocated in tests/src/Unit/ and modules/*/tests/src/Unit/.
Unit tests run without Drupal bootstrap and test isolated service logic:
AccessManagerTest- Access control logicRateLimiterTest- Rate limiting calculationsAuditLoggerTest- Log formatting and redactionContentServiceTest- Content operationsUserServiceTest- User operations with role filteringRoleServiceTest- Permission blockingMenuServiceTest- Menu link validation
Located in tests/src/Kernel/.
Kernel tests run with a minimal Drupal bootstrap:
AccessControlKernelTest- Integration with Drupal's permission systemRateLimiterKernelTest- State API integrationSecurityTest- Security bypass prevention
Located in tests/src/Functional/.
Functional tests run with a full Drupal installation:
SettingsFormTest- Admin UI form testing
The repo includes two end-to-end checks that validate the full MCP request/response flow without any client SDK:
scripts/mcp_stdio_e2e.py- Startsdrush mcp-tools:serveand runsinitialize→tools/list→tools/call.scripts/mcp_http_e2e.py- Serves Drupal and runs the same flow against/_mcp_tools, verifying API key auth, IP allowlist enforcement, read vs read/write scope enforcement, and config-only mode behavior.
GitHub Actions runs tests automatically on every push and PR:
- PHP Lint - Syntax checking for PHP 8.3+
- PHPCS - Drupal coding standards
- PHPUnit - All test suites
- Gitleaks - Security scanning for secrets
- Code Coverage - Unit + kernel + functional coverage for core modules (contrib-dependent submodules are excluded unless their dependencies are installed)
See .github/workflows/ci.yml for configuration.
If you have a local Drupal installation:
# From Drupal root
./vendor/bin/phpunit -c web/modules/contrib/mcp_tools/phpunit.xml
# With filter
./vendor/bin/phpunit -c web/modules/contrib/mcp_tools/phpunit.xml --filter=AccessManagerYou can also run the MCP transport checks from the module repo root:
python3 scripts/mcp_stdio_e2e.py --drupal-root /path/to/drupal
python3 scripts/mcp_http_e2e.py --drupal-root /path/to/drupal --base-url http://localhost:8888<?php
declare(strict_types=1);
namespace Drupal\Tests\mcp_tools_content\Unit\Service;
use Drupal\mcp_tools_content\Service\ContentService;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\mcp_tools_content\Service\ContentService
* @group mcp_tools
*/
class ContentServiceTest extends UnitTestCase {
protected ContentService $service;
protected function setUp(): void {
parent::setUp();
// Set up mocks and service
}
public function testSomething(): void {
$result = $this->service->doSomething();
$this->assertTrue($result);
}
}<?php
declare(strict_types=1);
namespace Drupal\Tests\mcp_tools\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* @group mcp_tools
*/
class ExampleKernelTest extends KernelTestBase {
protected static $modules = ['mcp_tools', 'system'];
protected function setUp(): void {
parent::setUp();
$this->installConfig(['mcp_tools']);
}
public function testSomething(): void {
$service = \Drupal::service('mcp_tools.example');
$this->assertNotNull($service);
}
}| Area | Current | Target |
|---|---|---|
| Core Services | Good | Maintain |
| Access Control | Good | Maintain |
| Security | Good | Expand |
| Submodule Services | Partial | Improve |
| Admin UI | Basic | Expand |
# Rebuild autoloader
ddev composer dump-autoload# Reset test database
ddev drush sql:drop -y && ddev drush site:install -y# Reset DDEV
ddev poweroff
ddev start