Testing the MCP tools¶
The tools live in the native_observability_mcp submodule; the logic they call lives in the base module (src/Query). The two are tested separately.
What is tested where¶
| Layer | Tests | Needs |
|---|---|---|
| Queries in the base module | tests/src/Kernel/Query, tests/src/Unit/Query |
native_observability and the data submodules. Neither tool nor mcp_server |
| Tool plugins, guard, call log, vocabulary | modules/native_observability_mcp/tests/src/Kernel |
tool |
| Resource providers and prompts | McpServerIntegrationTest |
tool, mcp_server |
The plugins are called through the Tool API runner (tool.runner), which is what the bridge calls, so mcp_server_tool_bridge is not needed to test them. ScopeResolver is replaced by a stub that grants fixed scopes: the real one reads the token of a simple_oauth request.
Contract tests¶
ToolContractTest builds, for each tool, the input schema and the output schema the way mcp_server_tool_bridge advertises them in tools/list, and compares them with modules/native_observability_mcp/tests/fixtures/<tool>.schema.json. It also checks that:
- inputs are flat, every property has a description, and no schema uses
$ref,oneOf,anyOf,allOforprefixItemsor a type array with several types (what the Neuron client cannot convert). Optional inputs carry theNotNullconstraint for this reason: without it the Tool API adds anullbranch asoneOf; - every field of every output has a description and no output member is required;
- a real response of every tool validates against its own output schema.
A change to an input or an output fails the comparison until the file is regenerated on purpose:
NATIVE_OBSERVABILITY_UPDATE_FIXTURES=/some/writable/dir phpunit .../ToolContractTest.php
The test writes the new files there and skips. Review them and copy them to tests/fixtures.
Running¶
vendor/bin/phpunit -c web/core web/modules/custom/native_observability/tests/src/Kernel/Query
vendor/bin/phpunit -c web/core web/modules/custom/native_observability/modules/native_observability_mcp/tests
Suggested CI matrix for the MCP stack¶
The project does not apply this matrix. .gitlab-ci.yml is the drupal.org template. Its edits for the MCP stack are two: the previous-major jobs run on PHP 8.3, and the composer job adds simple_oauth and mcp_server_oauth as dev dependencies so the OAuth tests run on the current major. The submodule needs drupal/tool, and the integration test needs drupal/mcp_server; both are pre-releases that change between betas, so the useful matrix is over their versions, not over PHP or core alone.
Variables to add to the project CI settings (drupal.org: project page, CI), not to the file:
| Variable | Value | Why |
|---|---|---|
_PHPUNIT_EXTRA |
--group native_observability_mcp in the MCP job only |
Run the submodule tests on their own |
SKIP_PHPUNIT_NEXT_MAJOR |
1 |
The submodule is ^11 only |
A job added through the include mechanism of the template (a local file .gitlab-ci/mcp.yml listed under include:), so the template stays untouched:
phpunit (mcp stack):
extends: phpunit
parallel:
matrix:
- TOOL: ["1.0.0-beta12", "1.0.x-dev"]
MCP_SERVER: ["2.0.0-beta7", "2.0.x-dev"]
variables:
_PHPUNIT_EXTRA: "--group native_observability_mcp"
before_script:
- composer require --dev "drupal/tool:$TOOL" "drupal/mcp_server:$MCP_SERVER" "drupal/mcp_server_tool_bridge:^1.0@beta"
allow_failure:
exit_codes: 1
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
allow_failure on the -dev combinations only (a second job without the -dev entries, not allowed to fail, keeps the pinned versions green). The pinned pair is the one the tests were run against on the bench; the -dev pair tells early when a new beta breaks the contract fixtures, which is the point of having them.
Known-bad versions¶
None found. composer.json declares no conflict. The Neuron client (not a dependency of the module) cannot read a oneOf input schema; the tools avoid it, see above.