Skip to content

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, allOf or prefixItems or a type array with several types (what the Neuron client cannot convert). Optional inputs carry the NotNull constraint for this reason: without it the Tool API adds a null branch as oneOf;
  • 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.