is_mcp_enabled();
$site_url = home_url( '/wp-json/' );
$nonce = wp_create_nonce( 'webcal_mcp_test' );
$has_app_pw = $this->user_has_application_password();
?>
render_what_is_mcp_section(); ?>
render_quickstart_checklist( $is_enabled, $has_app_pw ); ?>
render_prompt_examples(); ?>
render_status_section( $is_wp69, $is_enabled ); ?>
render_compatible_clients(); ?>
render_tools_table(); ?>
render_configuration_section( $site_url ); ?>
render_try_it_section(); ?>
render_curl_section( $site_url ); ?>
__( 'Copied!', 'agenticdaisy-calendar' ),
'copy' => __( 'Copy', 'agenticdaisy-calendar' ),
'running' => __( 'Running...', 'agenticdaisy-calendar' ),
'error' => __( 'Error: ', 'agenticdaisy-calendar' ),
'runLabel' => __( 'Run webcalendar_get_calendar_info', 'agenticdaisy-calendar' ),
'nonce' => $nonce,
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
]
);
}
/**
* Render the "What is MCP?" section.
*/
private function render_what_is_mcp_section(): void {
?>
✓';
$pending = '○';
?>
get_prompt_examples();
?>
get_compatible_clients();
?>
|
✓
✗
—
|
|
|
|
is_read_only() ) : ?>
🔒
—
✓
—
|
|
✓
—
—
|
get_tools_data();
$read_only = $this->is_read_only();
?>
' . esc_html__( 'Change in Settings', 'agenticdaisy-calendar' ) . ''
);
?>
|
|
|
|
|
>
|
|
|
' . esc_html( $tool['required'] ) . '' : '' . esc_html__( 'None', 'agenticdaisy-calendar' ) . ''; ?> |
|
[
'webcalendar' => [
'url' => $endpoint,
'headers' => [
'Authorization' => $scheme . ' YOUR_BASE64_ENCODED_CREDENTIALS',
],
],
],
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
);
};
$mcp_json_bearer = $build_config( 'Bearer' );
$mcp_json_basic = $build_config( 'Basic' );
?>
' . esc_html__( 'Users → Profile → Application Passwords', 'agenticdaisy-calendar' ) . ''
);
?>
echo -n "your_username:xxxx xxxx xxxx xxxx xxxx xxxx" | base64
add_filter( 'wp_is_application_passwords_available', '__return_true' );
__( 'Permission denied.', 'agenticdaisy-calendar' ) ] );
}
try {
$container = \WebCalendar\WP\Container::get_instance();
$event_service = $container->get_event_service();
$category_svc = $container->get_category_service();
$user_login = is_user_logged_in() ? wp_get_current_user()->user_login : '';
$now = new \DateTimeImmutable();
$range = new \WebCalendar\Core\Domain\ValueObject\DateRange(
$now,
$now->modify( '+30 days' )
);
// A count for the diagnostics panel. webcal_mcp_capability() can
// be as low as webcal_can_view, so count what this caller may see,
// not the whole calendar (the 1.4.0 calendar-info fix, here too).
$events = $event_service->getEventsInDateRange(
$range,
\WebCalendar\WP\Security\EventVisibility::scope_for_viewer()
);
$total = count( \WebCalendar\WP\Security\EventVisibility::filter( $events ) );
$categories = $category_svc->getCategoriesForUser( $user_login );
wp_send_json_success(
[
'plugin_version' => defined( 'WEBCALENDAR_WP_VERSION' ) ? WEBCALENDAR_WP_VERSION : 'unknown',
'total_events_next_30d' => $total,
'total_categories' => count( $categories ),
'time_format' => webcal_time_format(),
'site_name' => get_bloginfo( 'name' ),
'mcp_tools_registered' => count( ( new \WebCalendar\WP\MCP\McpServer( new \WebCalendar\WP\Abilities() ) )->get_tool_names() ),
'status' => __( 'MCP tools are working correctly.', 'agenticdaisy-calendar' ),
]
);
} catch ( \Exception $e ) {
wp_send_json_error( [ 'message' => __( 'Test failed: ', 'agenticdaisy-calendar' ) . $e->getMessage() ] );
}
}
/**
* Check whether MCP is enabled in settings.
*
* @return bool
*/
private function is_mcp_enabled(): bool {
$raw = get_option( 'webcal_enable_mcp', 'yes' );
return is_scalar( $raw ) && (string) $raw === 'yes';
}
/**
* Check whether AI read-only mode is enabled.
*
* Mirrors McpServer::is_read_only() (option + filter) so the panel shows the
* same effective state the server enforces.
*
* @return bool
*/
private function is_read_only(): bool {
$raw = get_option( 'webcal_mcp_read_only', 'no' );
$enabled = is_scalar( $raw ) && (string) $raw === 'yes';
/** This filter is documented in src/MCP/McpServer.php */
return (bool) apply_filters( 'webcal_mcp_read_only', $enabled );
}
/**
* Capability each MCP tool requires, for the tools table.
*
* Mirrors the permission callbacks in McpServer's tool map, which are
* closures and so cannot be read back as a capability name.
*/
private const TOOL_CAPABILITIES = [
'webcalendar_list_events' => 'webcal_can_view',
'webcalendar_get_event' => 'webcal_can_view',
'webcalendar_list_users' => 'webcal_can_view',
'webcalendar_create_event' => 'webcal_can_edit',
'webcalendar_update_event' => 'webcal_can_edit',
'webcalendar_delete_event' => 'webcal_can_delete',
'webcalendar_list_categories' => 'webcal_can_view',
'webcalendar_get_calendar_info' => 'webcal_can_view',
'webcalendar_list_venues' => 'webcal_can_view',
'webcalendar_list_organizers' => 'webcal_can_view',
'webcalendar_describe_schema' => 'webcal_can_view',
// Registration counts are readable by anyone who can see the event;
// who registered is not — that is personal data about people who are
// not site users, so it takes the booking-management capability.
'webcalendar_get_event_registration' => 'webcal_can_view',
'webcalendar_list_attendees' => 'webcal_can_manage_bookings',
];
/**
* Get structured data for the available tools table.
*
* Names, descriptions, required inputs and the write flag come from the
* MCP server itself, so the table shows exactly what assistants are told.
*
* @return array
*/
private function get_tools_data(): array {
$server = new \WebCalendar\WP\MCP\McpServer( new \WebCalendar\WP\Abilities() );
return array_map(
fn( array $tool ): array => [
'name' => $tool['name'],
'description' => $tool['description'],
'required' => implode( ', ', $tool['required'] ),
'permission' => self::TOOL_CAPABILITIES[ $tool['name'] ] ?? 'webcal_can_view',
'mutating' => $tool['mutating'],
],
$server->get_tool_catalog()
);
}
/**
* Check whether the current user has at least one Application Password.
*
* @return bool
*/
private function user_has_application_password(): bool {
if ( ! function_exists( 'WP_Application_Passwords' ) && ! class_exists( 'WP_Application_Passwords' ) ) {
return false;
}
$user_id = get_current_user_id();
$passwords = \WP_Application_Passwords::get_user_application_passwords( $user_id );
return count( $passwords ) > 0;
}
/**
* Get example prompts for the prompt examples section.
*
* @return array
*/
private function get_prompt_examples(): array {
return [
[
'icon' => "\xF0\x9F\x93\x85",
'text' => __( 'What\'s on my calendar next week?', 'agenticdaisy-calendar' ),
'tool' => __( 'Uses: webcalendar_list_events', 'agenticdaisy-calendar' ),
],
[
'icon' => "\xE2\x9C\x8F\xEF\xB8\x8F",
'text' => __( 'Schedule a team standup every weekday at 9am starting Monday', 'agenticdaisy-calendar' ),
'tool' => __( 'Uses: webcalendar_create_event with recurrence', 'agenticdaisy-calendar' ),
],
[
'icon' => "\xF0\x9F\x94\x8D",
'text' => __( 'Show me the details for event #42', 'agenticdaisy-calendar' ),
'tool' => __( 'Uses: webcalendar_get_event', 'agenticdaisy-calendar' ),
],
[
'icon' => "\xF0\x9F\x94\x84",
'text' => __( 'Move Friday\'s design review to Monday at 2pm', 'agenticdaisy-calendar' ),
'tool' => __( 'Uses: webcalendar_list_events + webcalendar_update_event', 'agenticdaisy-calendar' ),
],
[
'icon' => "\xF0\x9F\x97\x82\xEF\xB8\x8F",
'text' => __( 'What categories do we have?', 'agenticdaisy-calendar' ),
'tool' => __( 'Uses: webcalendar_list_categories', 'agenticdaisy-calendar' ),
],
[
'icon' => "\xF0\x9F\x93\x8A",
'text' => __( 'Give me a summary of our calendar setup', 'agenticdaisy-calendar' ),
'tool' => __( 'Uses: webcalendar_get_calendar_info', 'agenticdaisy-calendar' ),
],
];
}
/**
* Get compatible AI client data.
*
* @return array
*/
private function get_compatible_clients(): array {
return [
[
'name' => 'Claude Desktop',
'type' => __( 'Desktop app', 'agenticdaisy-calendar' ),
'config' => 'claude_desktop_config.json',
],
[
'name' => 'Claude Code',
'type' => __( 'CLI / Terminal', 'agenticdaisy-calendar' ),
'config' => '~/.claude.json',
],
[
'name' => 'Cursor',
'type' => __( 'Code editor', 'agenticdaisy-calendar' ),
'config' => '.cursor/mcp.json',
],
[
'name' => 'Windsurf',
'type' => __( 'Code editor', 'agenticdaisy-calendar' ),
'config' => '~/.codeium/windsurf/mcp_config.json',
],
[
'name' => 'VS Code + Copilot',
'type' => __( 'Code editor', 'agenticdaisy-calendar' ),
'config' => '.vscode/mcp.json',
],
[
'name' => __( 'Any MCP client', 'agenticdaisy-calendar' ),
'type' => __( 'Various', 'agenticdaisy-calendar' ),
'config' => 'mcp.json',
],
];
}
}