Skip to content

Latest commit

 

History

History
259 lines (171 loc) · 18.8 KB

v0.7.0.md

File metadata and controls

259 lines (171 loc) · 18.8 KB

Migration Guide: Version 0.7.0

Version 0.7.0 standardizes MCP Adapter as a canonical WordPress plugin, deprecates usage as a bundled Composer library, and introduces the dual-revision schema runtime.

Which sections apply depends on how your integration uses MCP Adapter:

  • If you bundle MCP Adapter into your plugin, follow Migrating to the canonical plugin. Bundled usage still works today, but will be removed in a future version.
  • If your integration directly uses Adapter or schema APIs, follow Migrating to the dual-revision schema runtime, even if MCP Adapter is already installed as a standalone plugin.
  • If you use the standalone plugin and only register WordPress Abilities through the existing declarations and callbacks, no registration changes are needed. See What does not change.

Migrating to the canonical plugin

1. Remove the bundled dependency

The following command will remove the MCP Adapter dependency from your plugin's composer.json and composer.lock, and uninstall it from your local development environment.

composer remove wordpress/mcp-adapter

If you are not using Composer and instead are manually including a copy of MCP Adapter (e.g. in a ./lib/mcp-adapter folder or via a git submodule), remove it manually and remove any require or include statements that load it.

2. Remove Jetpack Autoloader if you no longer need it

If you are using Jetpack Autoloader and do not need it for other packages, you should remove it as well:

composer remove automattic/jetpack-autoloader

Then switch your main plugin file back to the standard Composer autoloader:

// Before:
require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload_packages.php';

// After:
require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php';

Finally, clear out the generated files. Jetpack Autoloader writes vendor/autoload_packages.php, vendor/jetpack-autoloader/, and vendor/composer/jetpack_autoload_*.php, and removing the package does not always delete them. A stale autoload_packages.php left in a build is the kind of thing that works locally and fails in production:

rm -rf vendor
composer install
ls vendor/autoload_packages.php   # should now be "No such file or directory"

Drop the automattic/jetpack-autoloader entry from config.allow-plugins in your composer.json too, along with any COMPOSER_ROOT_VERSION you set in CI purely to make its version comparison work.

3. Declare MCP Adapter as a plugin dependency

To ensure MCP Adapter is installed and active, add it to the Requires Plugins header to your main plugin file:

<?php
/**
 * Plugin Name:      My Plugin
 * Description:      Adds MCP tools for my feature.
 * Requires Plugins: mcp-adapter
 */

This is what replaces the bundled copy and ensures the plugin cannot be activated without MCP Adapter. WordPress will not let your plugin activate until MCP Adapter is installed and active and direct the user to install it directly.

If your plugin doesn't require MCP Adapter for all functionality, but only uses it if available, you can skip this step and instead check for the existence of the WP\MCP\Core\McpAdapter class - or specific plugin version - before using any MCP Adapter functionality. See Checking availability with code for an example.

What still works (for now)

If you are currently bundling a copy MCP Adapter in your plugin, it will continue to work for now, but will cause a deprecation notice to be logged.

What if both copies are present?

If both the canonical plugin and a bundled copy are present, a _doing_it_wrong() notice will be logged, and an admin notice will be displayed on the WordPress dashboard informing users that a duplicate copy of MCP Adapter is present and is being used instead.

While not recommended, you can suppress the plugin's autoloader entirely and the resulting warnings, by conditionally defining the WP_MCP_AUTOLOAD constant:

// Disable the canonical plugin's autoloader in favor of your own bundled copy. This is NOT recommended.
if ( is_plugin_active( 'mcp-adapter/mcp-adapter.php' ) && ! defined( 'WP_MCP_AUTOLOAD' ) ) {
  define( 'WP_MCP_AUTOLOAD', false );
}

Migrating to the dual-revision schema runtime

MCP Adapter now supports exact MCP 2025-11-25 and 2026-07-28 through the revision-selected wordpress/php-mcp-schema record runtime. Clients that propose 2025-06-18 or 2024-11-05 keep working: the identifier is echoed back and the session is served through the 2025-11-25 schema. Ordinary Ability authors do not add protocol branches; direct Adapter/schema consumers must make the changes below.

What does not change

Ability registration remains revision-neutral. Keep existing:

  • input_schema and output_schema declarations;
  • execute and permission callbacks;
  • meta.mcp tool/resource/prompt configuration; and
  • Adapter pre-execution and result filters.

The Adapter supplies modern result/cache fields and omits removed standardized fields.

Select an exact schema

Protocol-facing server getters now require a selected schema. Reuse the server-owned schema cache:

use WP\McpSchema\Schemas;

$schema = $server->get_schemas()->forVersion( Schemas::V2026_07_28 );
$tools  = $server->get_tools( $schema );

There is no no-argument overload or implicit 2025 default. Schema identifiers are exactly 2025-11-25 and 2026-07-28. Legacy identifiers listed in McpVersionNegotiator::LEGACY_PROTOCOL_VERSIONS are negotiated by name and resolve to the 2025-11-25 schema through McpVersionNegotiator::schema_version_for().

Replace removed schema classes

Use flat generated records and construct them through the selected Schema:

use WP\McpSchema\Record\Tool;

$tool = $schema->fromArray(
    Tool::class,
    array(
        'name'        => 'weather',
        'inputSchema' => array( 'type' => 'object' ),
    )
);

Replace complete serialization methods with jsonSerialize() or direct JSON encoding. Records also provide named getters, get(), and has().

The removed API includes:

  • the old area-specific schema namespaces and factories;
  • generated enum objects and union factories;
  • get_protocol_dto();
  • static construction on generated values;
  • alternate complete array serializers; and
  • validation state and the mcp_adapter_validation_enabled filter.

There are no aliases or compatibility facades.

Component access

McpTool, McpResource, and McpPrompt expose get_protocol_record( Schema $schema ). Use is_available_for() when a caller needs to inspect per-revision availability. A component can be valid for one revision and absent from another.

The CLI reports neutral registration counts by default. Use wp mcp-adapter list --protocol=<revision> to count components available under one supported schema revision. Wire discovery returns only records valid for the selected revision.

Factory validation is deferred to projection. McpTool::fromArray(), McpResource::fromArray(), and McpPrompt::fromArray() return WP_Error only for structural problems: a missing name or URI, a missing handler, or an invalid resource URI. A schema problem, such as an invalid x-mcp-header annotation or an input schema the selected revision cannot represent, no longer fails construction. It surfaces when a revision is selected: is_available_for() returns false and get_projection_error( $revision ) returns the throwable. At registration the server logs a warning for each revision a component cannot project to and rejects the component only when no supported revision can represent it. Code that relied on is_wp_error() to catch schema problems should check is_available_for() for each revision it serves.

Validation and rejected components

The selected schema validates protocol fields. The Adapter no longer drops malformed icons or _meta, coerces annotation values, repairs prompt roles or content types, or filters invalid prompt arguments. Fix the source data instead of relying on partial output. This reverses the 0.6.0 behavior that omitted malformed _meta while keeping its payload. Explicit null in PHP configuration still counts as absent.

A component that fails projection in every supported revision is excluded from registration. Each failed revision is logged through the server's error handler; a completely rejected component also raises _doing_it_wrong with the component identifier, revisions, and schema error paths (for example, /icons/0/src). A component valid for one revision remains registered and is exposed only in that revision. Invalid handler output fails final projection with JSON-RPC -32603, Internal error: The server produced an invalid result.. For 2026 responses, result _meta is validated before the Adapter adds io.modelcontextprotocol/serverInfo. Lists and scalars are rejected; valid arrays and stdClass objects retain their fields, including numeric object keys, without mutating the supplied object. Explicit null counts as absent. Supplied serverInfo must be schema-valid; the response uses the Adapter server's name and version.

The Adapter retains tool/prompt name checks and resource URI scheme checks. Resource URIs are used without trimming, have no 2048-byte limit, and may have an empty path such as wordpress:. Resource lastModified remains an additional client-compatibility check: include a date, time, and time zone, for example 2026-09-09T12:00:00.123Z. An invalid value rejects the resource on both the Ability and fromArray() paths.

Request observability

For requests processed through McpWireOrchestrator, the existing mcp.request event now records status and duration after final response projection. Invalid handler output is recorded as an error with failure_reason: invalid_handler_result, rather than a successful handler outcome. server/discover also emits a completion event while continuing to bypass custom route_request() overrides. Direct calls to RequestRouter::route_request() still report the logical handler outcome; rejections before routing and notifications remain outside these completion events.

Review dashboard baselines: error rates can increase because invalid responses are now counted correctly, durations include projection, and discovery adds request events. The observability interface and event name are unchanged. Exceptions from logging or observability handlers during completion no longer replace the protocol response.

Configure both an error handler and an observability handler to retain diagnostics and request events. Custom servers passing null use no-op handlers. The Adapter's default server already enables error logging but uses a no-op observability handler. See logging configuration for examples.

Ability metadata and direct factories

  • meta.mcp, when set, must be an array. All three Ability converters return mcp_ability_invalid_meta for a non-array value.
  • Resource abilities support top-level meta.annotations, matching WordPress core and tool abilities. The old deprecation notice is removed. Resource meta.mcp.annotations overrides it; an empty array means no annotations. Mapping still drops core's null defaults, but does not coerce values.
  • Ability labels and descriptions retain whitespace. Tool title is always emitted from the Ability label. Direct resource names may be empty; an absent name defaults to the URI. The resource-name filter rejects non-strings only.
  • McpTool::fromArray() supplies { "type": "object" } only when inputSchema is absent. A supplied schema must declare its own root type; typeless or non-object values are no longer repaired before projection.
  • Explicit prompt arguments are reindexed and passed to the schema as given. Non-array meta.mcp.arguments returns mcp_prompt_invalid_arguments. An empty array retains the input-schema fallback. That fallback includes every property, including boolean property schemas, and preserves argument titles and descriptions.
  • mimeType, size, annotations, icons, and other optional protocol fields reach the schema without the previous type filtering. A value the schema accepts, such as negative resource size, is no longer silently omitted.

mcp_resource_missing_name, mcp_prompt_invalid_argument, and mcp_prompt_argument_missing_name are removed. Schema projection errors replace these checks. mcp_resource_name_filter_invalid now applies only to non-strings; mcp_resource_missing_uri applies only to an absent/null URI. Malformed URI values return mcp_resource_invalid_uri for direct factories or resource_uri_invalid for Ability conversion. Invalid resource timestamps return mcp_resource_invalid_annotations or resource_annotations_invalid, respectively.

Prompt result conveniences

The text, texts, messages, and role plus content shapes remain supported. Shape selection uses key presence, so array( 'messages' => 'bad' ) reaches schema validation instead of becoming JSON fallback text. Message lists are reindexed; message keys and result _meta are preserved. Empty messages stays empty. Result descriptions and text-shape annotations are carried as supplied. JSON fallback applies only when no recognized shape is set, and encoding failure returns a prompt execution error instead of invented {} content.

Removed helpers and converter methods

Prefer McpResource::fromAbility() and McpPrompt::fromAbility() for components. RegisterAbilityAsMcpResource and RegisterAbilityAsMcpPrompt are now @internal. Their make() methods and the resource get_resource() / get_data() and prompt get_prompt() / get_data() layers are removed. Direct internal consumers use build() and check for WP_Error before reading resource_data or prompt_data from its returned array; adapter_meta is returned alongside it.

Removed utility methods:

  • ContentBlockHelper::error_text(), json_text(), to_array_list(), and audio();
  • McpValidator::normalize_meta(), validate_base64(), validate_icons_array(), get_icon_validation_errors(), validate_icon_src(), validate_icon_size(), validate_icon_theme(), validate_roles_array(), validate_role(), and validate_priority().

Build revision-neutral content arrays for final schema projection instead of calling the removed content helpers. Protocol field validation belongs to the selected schema; application-specific checks belong to the integration.

Direct Adapter integrations

Protocol-facing Adapter internals now receive validated records and an exact request context:

Surface Current contract
Custom HTTP transports Delegate raw WP_REST_Request objects through HttpRequestHandler and HttpRequestContext.
Other custom transports Decode and process raw JSON through McpWireOrchestrator.
RequestRouter::route_request() Accepts a generated request Record, McpRequestContext, and transport name. Do not pass raw method and parameter arrays.
Method handlers Accept the exact generated request record and McpRequestContext; return logical arrays for final schema projection.
McpErrorFactory Returns logical JSON-RPC error arrays. Pass the selected revision to resource_not_found().
ContentBlockHelper Returns revision-neutral content arrays for final schema hydration.
McpPromptBuilderInterface::build() Returns the revision-neutral prompt configuration array.

See Custom transports and Error handling for complete examples.

Filters

Tool, resource, and prompt list filters keep their existing first two arguments and receive the selected schema third:

add_filter(
    'mcp_adapter_tools_list',
    static function ( array $tools, $server, $schema ): array {
        return $tools;
    },
    10,
    3
);

Filter payloads are generated records. A filtered list is validated when the final list-result record is constructed.

Revision removals and replacements

  • initialize, notifications/initialized, and ping are 2025-only.
  • server/discover and per-request metadata are 2026-only.
  • tools/list/all is not canonical in either supported revision and is no longer dispatchable.
  • Tool.execution is omitted from Adapter-owned 2026 output.
  • 2026 completed results include resultType: "complete".
  • 2026 discovery/list/resource-read results include ttlMs: 0 and cacheScope: "private".
  • 2026 resource misses use -32602; unsupported per-request versions use -32022.
  • Missing tools and prompts use standard Invalid Params (-32602) in both revisions.
  • 2025 tools/call responses omit structuredContent when a tool returns a JSON list, including an empty list, because the 2025-11-25 schema types that field as an object. The text content block still carries the encoded list. 2026 responses keep the list because the 2026-07-28 schema accepts any JSON value there.

Transport changes

The 2025 HTTP lifecycle remains session-based. Modern HTTP is sessionless and requires body metadata plus MCP-Protocol-Version, Mcp-Method, applicable Mcp-Name, and declared Mcp-Param-* headers. STDIO carries revision metadata in each modern request body and can alternate exact revisions line by line.

Batch requests are rejected before dispatch.

resources/read forwards only the protocol-defined parameters (uri, _meta, inputResponses, and requestState) to permission callbacks, the mcp_adapter_pre_resource_read filter, and resource handlers. Any other key in the request params is dropped before dispatch.

Verification

After migrating a direct integration, run:

npm run test:php
npm run lint:php
npm run lint:php:stan

Add raw-wire tests for each revision your integration sends and for any method or field that was removed between them.

Next steps