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 = '○'; ?>

  1. ✓
  2. ✓
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', ], ]; } }