Skip to content

Instantly share code, notes, and snippets.

@mizner
Created March 30, 2026 00:00
Show Gist options
  • Select an option

  • Save mizner/3cabb4ef2e920dd20bf95ac9cfe90c6b to your computer and use it in GitHub Desktop.

Select an option

Save mizner/3cabb4ef2e920dd20bf95ac9cfe90c6b to your computer and use it in GitHub Desktop.
MCP Adapter Example Feb 2026
<?php
/**
* Summit abilities for the WordPress Abilities API.
*
* @package Summit
* @author Michael Mizner
* @license GNU General Public License v3
* @link https://logocentric.net/
*/
class Summit_Abilities {
/**
* Bootstrap hooks.
*
* @since 1.0.0
*
* @return void
*/
public static function init() {
add_action( 'wp_abilities_api_categories_init', array( __CLASS__, 'register_categories' ) );
add_action( 'wp_abilities_api_init', array( __CLASS__, 'register_abilities' ) );
}
/**
* Register ability categories.
*
* @since 1.0.0
*
* @return void
*/
public static function register_categories() {
wp_register_ability_category(
'summit-read',
array(
'label' => __( 'Summit Read', 'summit' ),
'description' => __( 'Read-only abilities for Summit event data.', 'summit' ),
)
);
wp_register_ability_category(
'summit-manage',
array(
'label' => __( 'Summit Manage', 'summit' ),
'description' => __( 'Create, update, and delete Summit data.', 'summit' ),
)
);
}
/**
* Register abilities.
*
* @since 1.0.0
*
* @return void
*/
public static function register_abilities() {
wp_register_ability(
'summit/get-event-info',
array(
'label' => __( 'Get Event Info', 'summit' ),
'description' => __( 'Retrieves global Summit event settings.', 'summit' ),
'category' => 'summit-read',
'output_schema' => array(
'type' => 'object',
'properties' => array(
'event_name' => array( 'type' => 'string' ),
'event_tagline' => array( 'type' => 'string' ),
'event_dates' => array( 'type' => 'string' ),
'venue_name' => array( 'type' => 'string' ),
'venue_address' => array( 'type' => 'string' ),
'venue_url' => array( 'type' => 'string' ),
),
),
'execute_callback' => array( __CLASS__, 'get_event_info' ),
'permission_callback' => '__return_true',
'meta' => array(
'mcp' => array( 'public' => true ),
'annotations' => array( 'readonly' => true ),
),
)
);
wp_register_ability(
'summit/update-event-info',
array(
'label' => __( 'Update Event Info', 'summit' ),
'description' => __( 'Updates global Summit event settings.', 'summit' ),
'category' => 'summit-manage',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'event_name' => array( 'type' => 'string' ),
'event_tagline' => array( 'type' => 'string' ),
'event_dates' => array( 'type' => 'string' ),
'venue_name' => array( 'type' => 'string' ),
'venue_address' => array( 'type' => 'string' ),
'venue_url' => array( 'type' => 'string' ),
),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
'updated' => array( 'type' => 'array' ),
),
),
'execute_callback' => array( __CLASS__, 'update_event_info' ),
'permission_callback' => array( __CLASS__, 'can_manage_options' ),
'meta' => array(
'mcp' => array( 'public' => true ),
'annotations' => array( 'destructive' => false, 'idempotent' => true ),
),
)
);
wp_register_ability(
'summit/get-speakers',
array(
'label' => __( 'Get Speakers', 'summit' ),
'description' => __( 'Retrieves Summit speakers with role and thumbnail.', 'summit' ),
'category' => 'summit-read',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'limit' => array( 'type' => 'integer', 'default' => 20 ),
'orderby' => array( 'type' => 'string', 'default' => 'menu_order' ),
),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array( 'type' => 'integer' ),
'name' => array( 'type' => 'string' ),
'role' => array( 'type' => 'string' ),
'slug' => array( 'type' => 'string' ),
'url' => array( 'type' => 'string' ),
'thumbnail' => array( 'type' => 'string' ),
),
),
),
'execute_callback' => array( __CLASS__, 'get_speakers' ),
'permission_callback' => '__return_true',
'meta' => array(
'mcp' => array( 'public' => true ),
'annotations' => array( 'readonly' => true ),
),
)
);
wp_register_ability(
'summit/get-speaker',
array(
'label' => __( 'Get Speaker', 'summit' ),
'description' => __( 'Retrieves a speaker with bio, social links, and role.', 'summit' ),
'category' => 'summit-read',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'id' => array( 'type' => 'integer' ),
'slug' => array( 'type' => 'string' ),
),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'id' => array( 'type' => 'integer' ),
'name' => array( 'type' => 'string' ),
'role' => array( 'type' => 'string' ),
'bio' => array( 'type' => 'string' ),
'social_links' => array( 'type' => 'array' ),
'accordions' => array( 'type' => 'array' ),
'thumbnail' => array( 'type' => 'string' ),
'url' => array( 'type' => 'string' ),
),
),
'execute_callback' => array( __CLASS__, 'get_speaker' ),
'permission_callback' => '__return_true',
'meta' => array(
'mcp' => array( 'public' => true ),
'annotations' => array( 'readonly' => true ),
),
)
);
wp_register_ability(
'summit/create-speaker',
array(
'label' => __( 'Create Speaker', 'summit' ),
'description' => __( 'Creates a new Summit speaker post.', 'summit' ),
'category' => 'summit-manage',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'name' => array( 'type' => 'string' ),
'role' => array( 'type' => 'string' ),
'bio' => array( 'type' => 'string' ),
'social_links' => array( 'type' => 'array' ),
'accordions' => array( 'type' => 'array' ),
),
'required' => array( 'name' ),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
'id' => array( 'type' => 'integer' ),
'url' => array( 'type' => 'string' ),
),
),
'execute_callback' => array( __CLASS__, 'create_speaker' ),
'permission_callback' => array( __CLASS__, 'can_edit_posts' ),
'meta' => array(
'mcp' => array( 'public' => true ),
),
)
);
wp_register_ability(
'summit/update-speaker',
array(
'label' => __( 'Update Speaker', 'summit' ),
'description' => __( 'Updates an existing Summit speaker.', 'summit' ),
'category' => 'summit-manage',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'id' => array( 'type' => 'integer' ),
'name' => array( 'type' => 'string' ),
'role' => array( 'type' => 'string' ),
'bio' => array( 'type' => 'string' ),
'social_links' => array( 'type' => 'array' ),
'accordions' => array( 'type' => 'array' ),
),
'required' => array( 'id' ),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
),
),
'execute_callback' => array( __CLASS__, 'update_speaker' ),
'permission_callback' => array( __CLASS__, 'can_edit_posts' ),
'meta' => array(
'mcp' => array( 'public' => true ),
),
)
);
wp_register_ability(
'summit/delete-speaker',
array(
'label' => __( 'Delete Speaker', 'summit' ),
'description' => __( 'Deletes a Summit speaker.', 'summit' ),
'category' => 'summit-manage',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'id' => array( 'type' => 'integer' ),
),
'required' => array( 'id' ),
'additionalProperties' => false,
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
),
),
'execute_callback' => array( __CLASS__, 'delete_speaker' ),
'permission_callback' => array( __CLASS__, 'can_delete_posts' ),
'meta' => array(
'mcp' => array( 'public' => true ),
'annotations' => array( 'destructive' => true ),
),
)
);
wp_register_ability(
'summit/get-schedule',
array(
'label' => __( 'Get Schedule', 'summit' ),
'description' => __( 'Parses the schedule template part and returns structured data.', 'summit' ),
'category' => 'summit-read',
'output_schema' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'day' => array( 'type' => 'string' ),
'date' => array( 'type' => 'string' ),
'entries' => array( 'type' => 'array' ),
),
),
),
'execute_callback' => array( __CLASS__, 'get_schedule' ),
'permission_callback' => '__return_true',
'meta' => array(
'mcp' => array( 'public' => true ),
'annotations' => array( 'readonly' => true ),
),
)
);
}
/**
* Check manage options capability.
*
* @since 1.0.0
*
* @return bool
*/
public static function can_manage_options() {
return current_user_can( 'manage_options' );
}
/**
* Check edit posts capability.
*
* @since 1.0.0
*
* @return bool
*/
public static function can_edit_posts() {
return current_user_can( 'edit_posts' );
}
/**
* Check delete posts capability.
*
* @since 1.0.0
*
* @return bool
*/
public static function can_delete_posts() {
return current_user_can( 'delete_posts' );
}
/**
* Get event info options.
*
* @since 1.0.0
*
* @return array
*/
public static function get_event_info() {
return array(
'event_name' => get_option( 'summit_event_name', '' ),
'event_tagline' => get_option( 'summit_event_tagline', '' ),
'event_dates' => get_option( 'summit_event_dates', '' ),
'venue_name' => get_option( 'summit_venue_name', '' ),
'venue_address' => get_option( 'summit_venue_address', '' ),
'venue_url' => get_option( 'summit_venue_url', '' ),
);
}
/**
* Update event info options.
*
* @since 1.0.0
*
* @param array $input Input values.
* @return array
*/
public static function update_event_info( $input ) {
$allowed = array(
'event_name' => 'summit_event_name',
'event_tagline' => 'summit_event_tagline',
'event_dates' => 'summit_event_dates',
'venue_name' => 'summit_venue_name',
'venue_address' => 'summit_venue_address',
'venue_url' => 'summit_venue_url',
);
$updated = array();
foreach ( $allowed as $key => $option ) {
if ( isset( $input[ $key ] ) ) {
$updated[ $key ] = $input[ $key ];
update_option( $option, sanitize_text_field( $input[ $key ] ) );
}
}
return array(
'success' => true,
'updated' => array_keys( $updated ),
);
}
/**
* Get speakers list.
*
* @since 1.0.0
*
* @param array|null $input Input args.
* @return array
*/
public static function get_speakers( $input = null ) {
if ( ! is_array( $input ) ) {
$input = array();
}
$args = array(
'post_type' => 'summit_speaker',
'posts_per_page' => isset( $input['limit'] ) ? absint( $input['limit'] ) : 20,
'orderby' => isset( $input['orderby'] ) ? sanitize_key( $input['orderby'] ) : 'menu_order',
'order' => 'ASC',
'post_status' => 'publish',
);
$posts = get_posts( $args );
$results = array();
foreach ( $posts as $post ) {
$results[] = array(
'id' => $post->ID,
'name' => $post->post_title,
'role' => get_post_meta( $post->ID, '_summit_speaker_role', true ),
'slug' => $post->post_name,
'url' => get_permalink( $post->ID ),
'thumbnail' => get_the_post_thumbnail_url( $post->ID, 'full' ),
);
}
return $results;
}
/**
* Get a single speaker.
*
* @since 1.0.0
*
* @param array $input Input args.
* @return array|WP_Error
*/
public static function get_speaker( $input ) {
$post = null;
if ( ! empty( $input['id'] ) ) {
$post = get_post( absint( $input['id'] ) );
} elseif ( ! empty( $input['slug'] ) ) {
$post = get_page_by_path( sanitize_title( $input['slug'] ), OBJECT, 'summit_speaker' );
}
if ( ! $post instanceof WP_Post ) {
return new WP_Error( 'summit_speaker_not_found', __( 'Speaker not found.', 'summit' ) );
}
$data = self::parse_speaker_content( $post->post_content );
return array(
'id' => $post->ID,
'name' => $post->post_title,
'role' => get_post_meta( $post->ID, '_summit_speaker_role', true ),
'bio' => $data['bio'],
'social_links' => $data['social_links'],
'accordions' => $data['accordions'],
'thumbnail' => get_the_post_thumbnail_url( $post->ID, 'full' ),
'url' => get_permalink( $post->ID ),
);
}
/**
* Create a speaker post.
*
* @since 1.0.0
*
* @param array $input Input values.
* @return array|WP_Error
*/
public static function create_speaker( $input ) {
$content = self::build_speaker_content( $input );
$post_id = wp_insert_post(
array(
'post_type' => 'summit_speaker',
'post_status' => 'publish',
'post_title' => isset( $input['name'] ) ? sanitize_text_field( $input['name'] ) : '',
'post_content' => $content,
)
);
if ( is_wp_error( $post_id ) ) {
return $post_id;
}
if ( ! empty( $input['role'] ) ) {
update_post_meta( $post_id, '_summit_speaker_role', sanitize_text_field( $input['role'] ) );
}
return array(
'success' => true,
'id' => $post_id,
'url' => get_permalink( $post_id ),
);
}
/**
* Update a speaker post.
*
* @since 1.0.0
*
* @param array $input Input values.
* @return array|WP_Error
*/
public static function update_speaker( $input ) {
$post = get_post( absint( $input['id'] ) );
if ( ! $post instanceof WP_Post ) {
return new WP_Error( 'summit_speaker_not_found', __( 'Speaker not found.', 'summit' ) );
}
$update = array(
'ID' => $post->ID,
);
if ( ! empty( $input['name'] ) ) {
$update['post_title'] = sanitize_text_field( $input['name'] );
}
if ( ! empty( $input['role'] ) ) {
update_post_meta( $post->ID, '_summit_speaker_role', sanitize_text_field( $input['role'] ) );
}
if ( isset( $input['bio'] ) || isset( $input['social_links'] ) || isset( $input['accordions'] ) ) {
$current = self::parse_speaker_content( $post->post_content );
$merged = array(
'bio' => isset( $input['bio'] ) ? $input['bio'] : $current['bio'],
'social_links' => isset( $input['social_links'] ) ? $input['social_links'] : $current['social_links'],
'accordions' => isset( $input['accordions'] ) ? $input['accordions'] : $current['accordions'],
);
$update['post_content'] = self::build_speaker_content( $merged );
}
$result = wp_update_post( $update, true );
if ( is_wp_error( $result ) ) {
return $result;
}
return array( 'success' => true );
}
/**
* Delete a speaker.
*
* @since 1.0.0
*
* @param array $input Input values.
* @return array|WP_Error
*/
public static function delete_speaker( $input ) {
$post_id = absint( $input['id'] );
$result = wp_delete_post( $post_id, true );
if ( ! $result ) {
return new WP_Error( 'summit_speaker_delete_failed', __( 'Unable to delete speaker.', 'summit' ) );
}
return array( 'success' => true );
}
/**
* Parse speaker content blocks.
*
* @since 1.0.0
*
* @param string $content Post content.
* @return array
*/
public static function parse_speaker_content( $content ) {
$blocks = parse_blocks( $content );
$bio = '';
$social_links = array();
$accordions = array();
foreach ( $blocks as $block ) {
if ( 'core/group' === $block['blockName'] && ! empty( $block['attrs']['className'] ) && 'speaker-bio' === $block['attrs']['className'] ) {
foreach ( $block['innerBlocks'] as $inner_block ) {
if ( 'core/paragraph' === $inner_block['blockName'] ) {
$bio = self::block_text( $inner_block );
break;
}
}
}
if ( 'core/social-links' === $block['blockName'] ) {
foreach ( $block['innerBlocks'] as $social_block ) {
if ( 'core/social-link' === $social_block['blockName'] ) {
$social_links[] = array(
'service' => isset( $social_block['attrs']['service'] ) ? $social_block['attrs']['service'] : '',
'url' => isset( $social_block['attrs']['url'] ) ? $social_block['attrs']['url'] : '',
);
}
}
}
if ( 'core/details' === $block['blockName'] ) {
$accordions[] = array(
'summary' => isset( $block['attrs']['summary'] ) ? $block['attrs']['summary'] : '',
'content' => self::block_text( $block ),
);
}
}
return array(
'bio' => $bio,
'social_links' => $social_links,
'accordions' => $accordions,
);
}
/**
* Build speaker block content.
*
* @since 1.0.0
*
* @param array $input Content pieces.
* @return string
*/
public static function build_speaker_content( $input ) {
$bio = isset( $input['bio'] ) ? $input['bio'] : '';
$social_links = isset( $input['social_links'] ) ? $input['social_links'] : array();
$accordions = isset( $input['accordions'] ) ? $input['accordions'] : array();
$bio_text = esc_html( $bio );
$bio_html = '<p>' . $bio_text . '</p>';
$group_inner_html = '<div class="wp-block-group speaker-bio"></div>';
$social_list_class = 'wp-block-social-links is-style-logos-only';
$social_inner_html = '<ul class="' . $social_list_class . '"></ul>';
$heading_text = esc_html__( 'More About This Speaker', 'summit' );
$heading_html = '<h2>' . $heading_text . '</h2>';
$blocks = array(
array(
'blockName' => 'core/group',
'attrs' => array( 'className' => 'speaker-bio' ),
'innerBlocks' => array(
array(
'blockName' => 'core/paragraph',
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $bio_html,
'innerContent' => array( $bio_html ),
),
),
'innerHTML' => $group_inner_html,
'innerContent' => array(
'<div class="wp-block-group speaker-bio">',
null,
'</div>',
),
),
array(
'blockName' => 'core/social-links',
'attrs' => array( 'className' => 'is-style-logos-only' ),
'innerBlocks' => self::build_social_blocks( $social_links ),
'innerHTML' => $social_inner_html,
'innerContent' => array(
'<ul class="' . $social_list_class . '">',
null,
'</ul>',
),
),
array(
'blockName' => 'core/heading',
'attrs' => array(
'level' => 2,
'content' => $heading_text,
),
'innerBlocks' => array(),
'innerHTML' => $heading_html,
'innerContent' => array( $heading_html ),
),
);
foreach ( self::normalize_accordions( $accordions ) as $accordion ) {
$summary_text = esc_html( $accordion['summary'] );
$summary_html = '<details><summary>' . $summary_text . '</summary></details>';
$summary_content = '<details><summary>' . $summary_text . '</summary>';
$accordion_content = '<p>' . esc_html( $accordion['content'] ) . '</p>';
$blocks[] = array(
'blockName' => 'core/details',
'attrs' => array( 'summary' => $accordion['summary'] ),
'innerBlocks' => array(
array(
'blockName' => 'core/paragraph',
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $accordion_content,
'innerContent' => array( $accordion_content ),
),
),
'innerHTML' => $summary_html,
'innerContent' => array(
$summary_content,
null,
'</details>',
),
);
}
return serialize_blocks( $blocks );
}
/**
* Build social link blocks.
*
* @since 1.0.0
*
* @param array $links Social links.
* @return array
*/
public static function build_social_blocks( $links ) {
$blocks = array();
if ( empty( $links ) ) {
$links = array(
array( 'service' => 'x' ),
array( 'service' => 'youtube' ),
array( 'service' => 'instagram' ),
array( 'service' => 'facebook' ),
);
}
foreach ( $links as $link ) {
$attrs = array();
if ( ! empty( $link['service'] ) ) {
$attrs['service'] = sanitize_key( $link['service'] );
}
if ( ! empty( $link['url'] ) ) {
$attrs['url'] = esc_url_raw( $link['url'] );
}
$blocks[] = array(
'blockName' => 'core/social-link',
'attrs' => $attrs,
'innerBlocks' => array(),
'innerHTML' => '',
'innerContent' => array(),
);
}
return $blocks;
}
/**
* Normalize accordion inputs.
*
* @since 1.0.0
*
* @param array $accordions Accordion data.
* @return array
*/
public static function normalize_accordions( $accordions ) {
if ( empty( $accordions ) ) {
return array(
array(
'summary' => __( 'Books & Publications', 'summit' ),
'content' => '',
),
array(
'summary' => __( 'Speaking Topics', 'summit' ),
'content' => '',
),
);
}
$normalized = array();
foreach ( $accordions as $accordion ) {
$normalized[] = array(
'summary' => isset( $accordion['summary'] ) ? sanitize_text_field( $accordion['summary'] ) : '',
'content' => isset( $accordion['content'] ) ? sanitize_text_field( $accordion['content'] ) : '',
);
}
return $normalized;
}
/**
* Extract text from a block.
*
* @since 1.0.0
*
* @param array $block Block data.
* @return string
*/
public static function block_text( $block ) {
if ( empty( $block['innerHTML'] ) ) {
return '';
}
return trim( wp_strip_all_tags( $block['innerHTML'] ) );
}
/**
* Parse schedule template part into structured data.
*
* @since 1.0.0
*
* @return array
*/
public static function get_schedule() {
$content = self::get_schedule_template_content();
if ( '' === $content ) {
return array();
}
$blocks = parse_blocks( $content );
$days = array();
self::extract_schedule_days( $blocks, $days );
return $days;
}
/**
* Get schedule template part content.
*
* @since 1.0.0
*
* @return string
*/
public static function get_schedule_template_content() {
$path = get_theme_file_path( 'parts/schedule.html' );
if ( ! file_exists( $path ) ) {
return '';
}
return (string) file_get_contents( $path );
}
/**
* Recursively extract schedule days.
*
* @since 1.0.0
*
* @param array $blocks Blocks array.
* @param array $days Days array passed by reference.
* @return void
*/
public static function extract_schedule_days( $blocks, &$days ) {
foreach ( $blocks as $block ) {
if ( 'core/columns' === $block['blockName'] ) {
$day = self::parse_schedule_columns( $block );
if ( ! empty( $day['day'] ) && ! empty( $day['date'] ) ) {
$days[] = $day;
}
}
if ( ! empty( $block['innerBlocks'] ) ) {
self::extract_schedule_days( $block['innerBlocks'], $days );
}
}
}
/**
* Parse a schedule columns block.
*
* @since 1.0.0
*
* @param array $block Columns block.
* @return array
*/
public static function parse_schedule_columns( $block ) {
$columns = isset( $block['innerBlocks'] ) ? $block['innerBlocks'] : array();
if ( count( $columns ) < 2 ) {
return array();
}
$day_column = $columns[0];
$detail_column = $columns[1];
$day_label = self::find_heading_text( $day_column );
$date_label = self::find_heading_text( $detail_column );
$entries = self::extract_schedule_entries( $detail_column );
return array(
'day' => $day_label,
'date' => $date_label,
'entries' => $entries,
);
}
/**
* Find the first heading text in a block tree.
*
* @since 1.0.0
*
* @param array $block Block data.
* @return string
*/
public static function find_heading_text( $block ) {
if ( 'core/heading' === $block['blockName'] ) {
return self::block_text( $block );
}
if ( empty( $block['innerBlocks'] ) ) {
return '';
}
foreach ( $block['innerBlocks'] as $inner_block ) {
$found = self::find_heading_text( $inner_block );
if ( '' !== $found ) {
return $found;
}
}
return '';
}
/**
* Extract schedule entries from a block tree.
*
* @since 1.0.0
*
* @param array $block Block data.
* @return array
*/
public static function extract_schedule_entries( $block ) {
$entries = array();
if ( 'core/group' === $block['blockName'] ) {
$paragraphs = array();
foreach ( $block['innerBlocks'] as $inner_block ) {
if ( 'core/paragraph' === $inner_block['blockName'] ) {
$paragraphs[] = self::block_text( $inner_block );
}
}
if ( count( $paragraphs ) >= 2 ) {
$entries[] = array(
'time' => $paragraphs[0],
'description' => $paragraphs[1],
);
}
}
if ( ! empty( $block['innerBlocks'] ) ) {
foreach ( $block['innerBlocks'] as $inner_block ) {
$entries = array_merge( $entries, self::extract_schedule_entries( $inner_block ) );
}
}
return $entries;
}
}
Summit_Abilities::init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment