403 )
);
}
return true;
}
/**
* Initialize the settings page
*/
public function init() {
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_init', array( $this, 'handle_mcp_actions' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
/**
* Enqueue CSS and JavaScript for settings page
*
* @param string $hook The current admin page hook.
*/
public function enqueue_assets( $hook ) {
// Only load on settings page.
// Hook format: {parent_slug}_page_{menu_slug}.
if ( false === strpos( $hook, 'abilities-bridge-settings' ) ) {
return;
}
wp_enqueue_style(
'abilities-bridge-settings-page',
ABILITIES_BRIDGE_PLUGIN_URL . 'admin/css/settings-page.css',
array(),
ABILITIES_BRIDGE_VERSION
);
wp_enqueue_script(
'abilities-bridge-settings-page',
ABILITIES_BRIDGE_PLUGIN_URL . 'admin/js/settings-page.js',
array( 'jquery' ),
ABILITIES_BRIDGE_VERSION,
true
);
wp_localize_script(
'abilities-bridge-settings-page',
'abilitiesBridgeSettings',
array(
'nonce' => wp_create_nonce( 'abilities_bridge_settings' ),
'memoryConsentGiven' => (bool) get_option( 'abilities_bridge_memory_consent', false ),
'i18n' => array(
'copied' => __( 'Copied!', 'abilities-bridge' ),
'copyFailed' => __( 'Failed to copy to clipboard', 'abilities-bridge' ),
'restorePromptConfirm' => __( 'Are you sure you want to restore the default system prompt? Your customizations will be lost.', 'abilities-bridge' ),
),
'defaultSystemPrompt' => Abilities_Bridge_Claude_API::get_default_system_prompt(),
)
);
}
/**
* Register settings
*/
public function register_settings() {
register_setting(
'abilities_bridge_settings',
'abilities_bridge_api_key',
array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
)
);
register_setting(
'abilities_bridge_settings',
'abilities_bridge_enable_memory',
array(
'type' => 'boolean',
'sanitize_callback' => 'rest_sanitize_boolean',
'default' => false,
)
);
register_setting(
'abilities_bridge_settings',
'abilities_bridge_memory_consent',
array(
'type' => 'boolean',
'sanitize_callback' => 'rest_sanitize_boolean',
'default' => false,
)
);
add_settings_section(
'abilities_bridge_api_section',
__( 'Anthropic API Configuration', 'abilities-bridge' ),
array( $this, 'render_section' ),
'abilities-bridge-settings'
);
add_settings_field(
'abilities_bridge_api_key',
__( 'API Key', 'abilities-bridge' ),
array( $this, 'render_api_key_field' ),
'abilities-bridge-settings',
'abilities_bridge_api_section'
);
// System Prompt setting.
register_setting(
'abilities_bridge_settings',
'abilities_bridge_system_prompt',
array(
'type' => 'string',
'sanitize_callback' => 'wp_kses_post',
'default' => '',
)
);
add_settings_section(
'abilities_bridge_prompt_section',
__( 'System Prompt', 'abilities-bridge' ),
array( $this, 'render_prompt_section' ),
'abilities-bridge-settings'
);
add_settings_field(
'abilities_bridge_system_prompt',
__( 'Claude System Prompt', 'abilities-bridge' ),
array( $this, 'render_system_prompt_field' ),
'abilities-bridge-settings',
'abilities_bridge_prompt_section'
);
// Memory settings (for separate Memory tab).
// Memory tool settings.
register_setting(
'abilities_bridge_builtin_tools',
'abilities_bridge_enable_memory',
array(
'type' => 'boolean',
'sanitize_callback' => 'rest_sanitize_boolean',
'default' => false,
)
);
register_setting(
'abilities_bridge_builtin_tools',
'abilities_bridge_memory_consent',
array(
'type' => 'boolean',
'sanitize_callback' => array( $this, 'validate_memory_consent' ),
'default' => false,
)
);
}
/**
* Validate memory consent
*
* Ensures that if memory is being enabled, consent must be given.
*
* @param bool $value The consent value being saved.
* @return bool The validated consent value.
*/
public function validate_memory_consent( $value ) {
// Sanitize the boolean value first.
$value = rest_sanitize_boolean( $value );
// Check if memory is being enabled (from POST data).
// Settings API sanitize callbacks are called after nonce verification by options.php.
$enable_memory_raw = filter_input( INPUT_POST, 'abilities_bridge_enable_memory', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$enable_memory = rest_sanitize_boolean( $enable_memory_raw );
// If memory is enabled but consent is not given, show error and prevent enabling.
if ( $enable_memory && ! $value ) {
add_settings_error(
'abilities_bridge_memory_consent',
'memory_consent_required',
__( 'You must check the consent box to enable the Memory Tool.', 'abilities-bridge' ),
'error'
);
// Also prevent memory from being enabled by resetting it.
update_option( 'abilities_bridge_enable_memory', false );
// Return false to prevent consent from being saved as true.
return false;
}
return $value;
}
/**
* Render section description
*/
public function render_section() {
?>
Anthropic Console.', 'abilities-bridge' ),
array(
'a' => array(
'href' => array(),
'target' => array(),
),
)
),
esc_url( 'https://console.anthropic.com/' )
);
?>
Database (abilities_bridge_memories table)'
);
?>
get_current_page();
if ( 'abilities-bridge-settings' !== $page ) {
return;
}
// Only process POST requests.
$method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
if ( 'POST' !== $method ) {
return;
}
// Verify capability BEFORE processing any POST data.
if ( ! current_user_can( 'manage_options' ) ) {
wp_die(
esc_html__( 'You do not have sufficient permissions to perform this action.', 'abilities-bridge' ),
esc_html__( 'Permission Denied', 'abilities-bridge' ),
array( 'response' => 403 )
);
}
// Check if nonce is present - if not, this isn't an MCP form submission.
if ( ! isset( $_POST['_wpnonce'] ) ) {
return;
}
// Handle OAuth credential generation.
if ( isset( $_POST['generate_oauth'] ) ) {
// Verify nonce BEFORE processing.
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'abilities_bridge_mcp_generate_oauth' ) ) {
wp_die(
esc_html__( 'Security token validation failed. Please refresh the page and try again.', 'abilities-bridge' ),
esc_html__( 'Security Check Failed', 'abilities-bridge' ),
array( 'response' => 403 )
);
}
// Check if consent has been given.
if ( ! Abilities_Bridge_Welcome_Wizard::is_setup_complete() ) {
wp_die( esc_html__( 'You must complete the welcome wizard and provide consent before generating OAuth credentials.', 'abilities-bridge' ) );
}
$oauth = new Abilities_Bridge_MCP_OAuth();
$credentials = $oauth->generate_credentials();
// Store in transient to display after redirect.
set_transient( 'abilities_bridge_new_credentials', $credentials, 60 );
// Redirect to avoid resubmission (include nonce for credentials-generated flag).
$redirect_url = add_query_arg( 'credentials-generated', '1', admin_url( 'admin.php?page=abilities-bridge-settings' ) );
$redirect_url = wp_nonce_url( $redirect_url, 'abilities_bridge_settings_nav' );
wp_safe_redirect( $redirect_url );
exit;
}
// Handle client revocation.
if ( isset( $_POST['revoke_client'] ) ) {
// Verify nonce BEFORE processing.
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'abilities_bridge_mcp_revoke_client' ) ) {
wp_die(
esc_html__( 'Security token validation failed. Please refresh the page and try again.', 'abilities-bridge' ),
esc_html__( 'Security Check Failed', 'abilities-bridge' ),
array( 'response' => 403 )
);
}
$oauth = new Abilities_Bridge_MCP_OAuth();
if ( ! isset( $_POST['client_id'] ) ) {
wp_die( esc_html__( 'Missing client ID', 'abilities-bridge' ) );
}
$client_id = sanitize_text_field( wp_unslash( $_POST['client_id'] ) );
if ( $oauth->revoke_client( $client_id ) ) {
add_settings_error(
'abilities_bridge_messages',
'client_revoked',
__( 'Client credentials revoked successfully.', 'abilities-bridge' ),
'success'
);
} else {
add_settings_error(
'abilities_bridge_messages',
'client_revoke_failed',
__( 'Failed to revoke client credentials.', 'abilities-bridge' ),
'error'
);
}
// Redirect to avoid resubmission.
wp_safe_redirect( admin_url( 'admin.php?page=abilities-bridge-settings' ) );
exit;
}
}
/**
* Render MCP section description and setup instructions.
*/
public function render_mcp_section() {
// Get MCP endpoint URL.
$mcp_endpoint = rest_url( 'abilities-bridge-mcp/v1/mcp' );
// Check for existing OAuth credentials.
$oauth = new Abilities_Bridge_MCP_OAuth();
$existing_clients = $oauth->get_user_clients();
// Check for newly generated credentials (from transient after redirect).
// Verify nonce before accessing the credentials-generated URL parameter.
$generated_client_id = null;
$generated_client_secret = null;
$show_credentials = false;
// Only check credentials-generated if nonce is valid.
if ( $this->verify_settings_nonce() ) {
$credentials_flag = filter_input( INPUT_GET, 'credentials-generated', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$show_credentials = ! empty( $credentials_flag );
}
if ( $show_credentials ) {
$credentials = get_transient( 'abilities_bridge_new_credentials' );
if ( $credentials ) {
$generated_client_id = $credentials['client_id'];
$generated_client_secret = $credentials['client_secret'];
delete_transient( 'abilities_bridge_new_credentials' );
}
}
?>
⚠️ SECURITY NOTICE: ONE-TIME VIEW
check_requirements();
?>
render_builtin_tools_section(); ?>
render_mcp_section(); ?>
Upgrade to Abilities Bridge Pro
'PHP Version',
'status' => version_compare( PHP_VERSION, '7.4', '>=' ),
'details' => 'Current: ' . PHP_VERSION . ' (Required: 7.4+)',
);
// Check JSON extension.
$requirements[] = array(
'name' => 'JSON Extension',
'status' => function_exists( 'json_encode' ),
'details' => function_exists( 'json_encode' ) ? 'Available' : 'Missing - required for API communication',
);
// Check cURL extension.
$requirements[] = array(
'name' => 'cURL Extension',
'status' => function_exists( 'curl_init' ),
'details' => function_exists( 'curl_init' ) ? 'Available' : 'Missing - required for API communication',
);
// Check file permissions for uploads directory using WP_Filesystem.
$upload_dir = wp_upload_dir();
$content_dir = $upload_dir['basedir'] . '/abilities-bridge';
global $wp_filesystem;
if ( empty( $wp_filesystem ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
WP_Filesystem();
}
$parent_writable = $wp_filesystem->is_writable( $upload_dir['basedir'] );
$dir_exists = $wp_filesystem->is_dir( $content_dir );
$dir_writable = $dir_exists && $wp_filesystem->is_writable( $content_dir );
$requirements[] = array(
'name' => 'Uploads Directory Writable',
'status' => $parent_writable || $dir_writable,
'details' => $parent_writable ? 'Uploads directory writable' : ( $dir_exists ? 'Plugin directory exists' : 'Cannot create plugin directory' ),
);
return $requirements;
}
}