Migration Guide: Version 0.3.0
October 30, 2025 · View on GitHub
Version 0.3.0 introduces significant improvements to the observability system and streamlines the transport layer with a unified HTTP transport implementation.
What's Changed
Observability System Refactoring
The observability system has been completely refactored to use a metadata-driven architecture with consistent event naming and status tags.
Breaking Changes
1. Unified Event Names with Status Tags (MAJOR CHANGE)
All events now use consistent base names with a status tag instead of separate event names for success/failure.
Before (v0.2.x):
// Different event names for success and failure
$handler->record_event('mcp.request.success', ['method' => 'tools/call']);
$handler->record_event('mcp.request.error', ['method' => 'tools/call']);
$handler->record_event('mcp.tool.execution_success', ['tool_name' => 'my-tool']);
$handler->record_event('mcp.tool.execution_failed', ['tool_name' => 'my-tool']);
$handler->record_event('mcp.component.registered', ['component_type' => 'tool']);
$handler->record_event('mcp.component.registration_failed', ['component_type' => 'tool']);
After (v0.3.0):
// Single event name with status tag
$handler->record_event('mcp.request', ['status' => 'success', 'method' => 'tools/call']);
$handler->record_event('mcp.request', ['status' => 'error', 'method' => 'tools/call']);
// Tool events are consolidated into request events with metadata
$handler->record_event('mcp.request', [
'status' => 'success',
'method' => 'tools/call',
'component_type' => 'tool',
'tool_name' => 'my-tool',
'ability_name' => 'my_ability'
]);
$handler->record_event('mcp.component.registration', ['status' => 'success', 'component_type' => 'tool']);
$handler->record_event('mcp.component.registration', ['status' => 'failed', 'component_type' => 'tool']);
Benefits:
- Easier filtering: Query all requests with one event name, filter by status
- Better grouping: Calculate success rates easily
- Consistent API: Same pattern everywhere
- Richer context: Tool/prompt/resource metadata automatically included
Migration for Monitoring Systems:
If you're using external monitoring systems that query event names:
-- Before: Query success events
SELECT * FROM events WHERE event_name = 'mcp.request.success'
-- After: Query with status filter
SELECT * FROM events WHERE event_name = 'mcp.request' AND tags->>'status' = 'success'
2. Metadata-Driven Observability
Observability events are now recorded centrally at the transport layer (RequestRouter) instead of in individual handlers. Handlers attach _metadata to responses, which flows up to the transport layer.
Impact: If you've created custom MCP handlers (Tools, Prompts, Resources), no migration needed - the system is backward compatible. However, if you were manually calling observability_handler->record_event() in custom code, update to return _metadata instead.
Before (v0.2.x):
class CustomToolsHandler {
public function call_tool(array $params): array {
// Manual observability call
$this->observability_handler->record_event(
'mcp.tool.execution_success',
['tool_name' => $params['name']]
);
return ['result' => 'success'];
}
}
After (v0.3.0):
class CustomToolsHandler {
public function call_tool(array $params): array {
// Return metadata instead
return [
'result' => 'success',
'_metadata' => [
'component_type' => 'tool',
'tool_name' => $params['name'],
]
];
}
}
The RequestRouter automatically:
- Extracts
_metadatafrom responses - Merges with request context (method, transport, server_id)
- Records event with duration timing
- Strips
_metadatabefore returning to client
3. Removed Helper Method
Removed: McpObservabilityHelperTrait::record_error_event()
This helper method appended _failed suffix to event names, which conflicts with the new status tag pattern.
Before (v0.2.x):
$this->record_error_event('mcp.tool.execution', $exception, ['tool_name' => 'my-tool']);
// Created event: mcp.tool.execution_failed
After (v0.3.0):
// Use standard record_event with status and error categorization
$this->record_event('mcp.request', [
'status' => 'error',
'tool_name' => 'my-tool',
'error_type' => get_class($exception),
'error_category' => self::categorize_error($exception),
]);
Note: categorize_error() method is still available in the helper trait.
4. Enhanced Event Tags
All events now include richer context automatically:
Request Events (mcp.request):
status:success|errormethod: MCP method nametransport: Transport typeserver_id: Server IDcomponent_type: Tool/resource/prompt (when applicable)tool_name,ability_name,prompt_name,resource_uri: Component detailsfailure_reason: Specific failure reason (not_found, permission_denied, execution_failed, etc.)error_code,error_type,error_category: Error details
Component Registration Events (mcp.component.registration):
status:success|failedcomponent_type: Tool/resource/prompt typecomponent_name: Component nameserver_id: Server IDerror_type: Exception type (for failures)
Session and Request Tracking:
request_id: JSON-RPC request ID for request correlationsession_id: MCP session ID (null for non-session transports like CLI)new_session_id: Newly created session ID (only on initialize)params: Sanitized request parameters (tool names, URIs, argument counts)
Permission Error Details:
When WordPress abilities return WP_Error from has_permission(), the specific error message and code are now automatically extracted and used:
// Example: Ability returns WP_Error with validation details
$wp_error = new WP_Error(
'ability_invalid_input',
'Ability "wpcom-mcp/user-notifications" has invalid input. Reason: input[action] is not one of list, get_settings, get_devices, and test_delivery.'
);
// Old behavior:
// - Error message: Generic "Access denied for tool: X"
// - failure_reason: Always "permission_denied"
// New behavior:
// - Error message: Full WP_Error message with details
// - failure_reason: WP_Error code ("ability_invalid_input")
Benefits:
- More specific failure reasons in logs (e.g.,
ability_invalid_inputvs genericpermission_denied) - Easier to track and alert on specific permission failure types
- Error messages include full validation context from abilities
- Can monitor specific error patterns (rate limits, quota exceeded, etc.)
5. Instance-Based Handlers (No Longer Static)
Observability handlers now use instance methods instead of static methods, matching the error handler pattern.
Before (v0.2.x):
// Handlers were passed as class names
$adapter->create_server(
'my-server',
'mcp/v1',
'/mcp',
'My Server',
'Description',
'1.0.0',
[ HttpTransport::class ],
ErrorLogMcpErrorHandler::class,
NullMcpObservabilityHandler::class // Class name
);
// Static method calls
MyObservabilityHandler::record_event('event.name', ['tag' => 'value']);
MyObservabilityHandler::record_timing('metric.name', 123.45, ['tag' => 'value']);
After (v0.3.0):
// Handlers are still passed as class names to create_server
// (the server instantiates them internally)
$adapter->create_server(
'my-server',
'mcp/v1',
'/mcp',
'My Server',
'Description',
'1.0.0',
[ HttpTransport::class ],
ErrorLogMcpErrorHandler::class,
NullMcpObservabilityHandler::class // Still class name, instantiated by server
);
// Instance method calls (when implementing custom handlers)
class MyObservabilityHandler implements McpObservabilityHandlerInterface {
public function record_event(string $event, array $tags = [], ?float $duration_ms = null): void {
// Implementation
}
}
2. Unified Event/Timing Interface
The record_timing() method has been removed. Use record_event() with the optional $duration_ms parameter instead.
Before (v0.2.x):
// Separate methods for events and timing
$handler::record_event('mcp.request.success', ['method' => 'tools/call']);
$handler::record_timing('mcp.request.duration', 45.23, ['method' => 'tools/call']);
// This created 2 separate log entries
After (v0.3.0):
// Unified method with optional duration parameter
$handler->record_event('mcp.request.success', ['method' => 'tools/call'], 45.23);
// This creates 1 log entry with timing included
// Output: [MCP Observability] EVENT mcp.request.success 45.23ms [method=tools/call,...]
3. Removed Events
The following events have been removed to reduce log volume:
mcp.request.count- No longer emitted (redundant with success/error events)
Before: Each request generated 3-4 log entries:
mcp.request.countmcp.request.successORmcp.request.errormcp.request.duration
After: Each request generates 1 log entry:
mcp.request.success(with duration) ORmcp.request.error(with duration)
Migration Steps for Custom Handlers
If you have custom observability handlers, update them:
Step 1: Convert from static to instance methods
// Before
class MyHandler implements McpObservabilityHandlerInterface {
public static function record_event(string $event, array $tags = []): void {
// ...
}
public static function record_timing(string $metric, float $duration_ms, array $tags = []): void {
// ...
}
}
// After
class MyHandler implements McpObservabilityHandlerInterface {
public function record_event(string $event, array $tags = [], ?float $duration_ms = null): void {
// Handle both events and timing in one method
// If $duration_ms is not null, include it in your tracking
}
}
Step 2: Remove record_timing() method
The record_timing() method no longer exists in the interface. Consolidate all tracking into record_event().
Step 3: Update Helper Trait Usage
If using McpObservabilityHelperTrait::record_error_event(), it's now an instance method:
// Before
static::record_error_event('operation', $exception, ['context' => 'value']);
// After
$this->record_error_event('operation', $exception, ['context' => 'value']);
Transport Layer Changes
Removed Transports
The following transport classes have been removed:
Unified HTTP Transport
HttpTransport is now the sole HTTP transport implementation:
- ✅ Full MCP 2025-06-18 specification compliance
- ✅ Supports both WordPress REST API and JSON-RPC 2.0 formats
- ✅ Handles streaming responses (SSE) and standard JSON responses
- ✅ Built-in session management and batch request support
Using HttpTransport
All MCP servers use HttpTransport by default:
use WP\MCP\Core\McpAdapter;
use WP\MCP\Transport\HttpTransport;
add_action('mcp_adapter_init', function($adapter) {
$adapter->create_server(
'my-server-id',
'my-namespace',
'mcp',
'My MCP Server',
'Server description',
'1.0.0',
[ HttpTransport::class ], // Use HttpTransport
ErrorLogMcpErrorHandler::class,
['my-plugin/my-ability']
);
});
Advanced Usage
Custom Authentication
Use transport permission callbacks for custom authentication:
add_action('mcp_adapter_init', function($adapter) {
$adapter->create_server(
'secure-server',
'secure',
'mcp',
'Secure Server',
'Custom auth example',
'1.0.0',
[ HttpTransport::class ],
ErrorLogMcpErrorHandler::class,
NullMcpObservabilityHandler::class,
['my-plugin/ability'],
[], // resources
[], // prompts
function() {
// Custom permission logic
$api_key = $_SERVER['HTTP_X_API_KEY'] ?? '';
return validate_api_key($api_key);
}
);
});
See the Transport Permissions Guide for more authentication patterns.
Custom Transport Implementations
For specialized requirements (message queues, custom protocols, etc.), create custom transports:
use WP\MCP\Transport\Contracts\McpRestTransportInterface;
use WP\MCP\Transport\Infrastructure\McpTransportContext;
use WP\MCP\Transport\Infrastructure\McpTransportHelperTrait;
class MyCustomTransport implements McpRestTransportInterface {
use McpTransportHelperTrait;
private McpTransportContext $context;
public function __construct(McpTransportContext $context) {
$this->context = $context;
$this->register_routes();
}
public function register_routes(): void {
// Register your custom routes
}
public function check_permission(\WP_REST_Request $request) {
return is_user_logged_in();
}
public function handle_request(\WP_REST_Request $request): \WP_REST_Response {
$body = $request->get_json_params();
$result = $this->context->request_router->route_request(
$body['method'],
$body['params'] ?? [],
$body['id'] ?? 0,
$this->get_transport_name()
);
return rest_ensure_response($result);
}
}
See the Custom Transports Guide for detailed implementation instructions.
Next Steps
- Custom Transports - Learn about custom transport implementations
- Transport Permissions - Implement custom authentication
- Error Handling - Configure error management
- Architecture Overview - Understand system design