feat: add edit prompt capability to Prompts tab

This commit is contained in:
2026-05-31 13:02:13 +00:00
parent e8c1729a1d
commit 598a599b80
+817 -1
View File
@@ -15,4 +15,820 @@ class AdminMenu {
add_action('admin_init', [$this, 'registerSettings']); add_action('admin_init', [$this, 'registerSettings']);
add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']); add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']);
add_action('admin_init', [$this, 'handleApiKeyActions']); add_action('admin_init', [$this, 'handleApiKeyActions']);
} }
public function addMenuPages(): void {
$this->hook_suffixes[] = add_menu_page(
'AskTam MCP', 'AskTam MCP', 'manage_options',
self::PAGE_SLUG, [$this, 'renderPage'], 'dashicons-rest-api', 58
);
$submenus = [
self::PAGE_SLUG => 'Tools',
self::PAGE_SLUG . '-connection' => 'Connection',
self::PAGE_SLUG . '-prompts' => 'Prompts',
self::PAGE_SLUG . '-skills' => 'Skills',
self::PAGE_SLUG . '-changelog' => 'Changelog',
self::PAGE_SLUG . '-api-keys' => 'API Keys',
self::PAGE_SLUG . '-activity' => 'Activity Log',
self::PAGE_SLUG . '-settings' => 'Settings',
];
foreach ($submenus as $slug => $label) {
$this->hook_suffixes[] = add_submenu_page(
self::PAGE_SLUG, $label, $label, 'manage_options', $slug, [$this, 'renderPage']
);
}
}
public function registerSettings(): void {
register_setting(self::SETTINGS_GROUP, self::OPTION_DISABLED_TOOLS, [
'type' => 'array', 'default' => [],
'sanitize_callback' => [$this, 'sanitizeDisabledTools'],
]);
register_setting('asktam_mcp_settings_group', 'asktam_mcp_settings', ['type'=>'array','default'=>[]]);
}
public function sanitizeDisabledTools($input): array {
$enabled = is_array($input) ? array_map('sanitize_text_field', $input) : [];
return array_values(array_diff($this->getAllToolSlugs(), $enabled));
}
public function enqueueAssets(string $hook): void {
if (!in_array($hook, $this->hook_suffixes, true)) return;
wp_enqueue_style('asktam-mcp-admin', plugins_url('assets/css/admin.css', ASKTAM_MCP_FILE), [], ASKTAM_MCP_VERSION);
wp_enqueue_script('asktam-mcp-admin', plugins_url('assets/js/admin.js', ASKTAM_MCP_FILE), [], ASKTAM_MCP_VERSION, true);
wp_localize_script('asktam-mcp-admin', 'asktamMcpAdmin', [
'copied' => 'Copied!',
'sseEndpoint' => rest_url('asktam-mcp/v1/sse'),
'siteUrl' => site_url(),
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('asktam_mcp_admin'),
]);
}
private function getActiveTab(): string {
$page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : '';
$map = [
self::PAGE_SLUG . '-connection' => 'connection',
self::PAGE_SLUG . '-prompts' => 'prompts',
self::PAGE_SLUG . '-skills' => 'skills',
self::PAGE_SLUG . '-changelog' => 'changelog',
self::PAGE_SLUG . '-api-keys' => 'api-keys',
self::PAGE_SLUG . '-activity' => 'activity',
self::PAGE_SLUG . '-settings' => 'settings',
];
return $map[$page] ?? 'tools';
}
public function renderPage(): void {
if (!current_user_can('manage_options')) return;
$tab = $this->getActiveTab();
$this->renderHeader($tab);
match($tab) {
'tools' => $this->renderToolsTab(),
'connection' => $this->renderConnectionTab(),
'prompts' => $this->renderPromptsTab(),
'skills' => $this->renderSkillsTab(),
'changelog' => $this->renderChangelogTab(),
'api-keys' => $this->renderApiKeysTab(),
'activity' => $this->renderActivityTab(),
'settings' => $this->renderSettingsTab(),
default => $this->renderToolsTab(),
};
$this->renderFooter();
}
// ─── Header ───────────────────────────────────────────────────────────────
private function renderHeader(string $active): void {
$registry = new \AskTamMCP\MCP\Registry();
$total = count($registry->getToolDefinitions());
$disabled = get_option(self::OPTION_DISABLED_TOOLS, []);
$enabled = $total - count(is_array($disabled) ? $disabled : []);
$sse = rest_url('asktam-mcp/v1/sse');
?>
<div class="wrap asktam-mcp-admin">
<div class="asktam-mcp-header">
<span class="asktam-mcp-header-icon"><svg viewBox="0 0 24 24"><path d="M13 10V3L4 14h7v7l9-11h-7z"/></svg></span>
<div class="asktam-mcp-header-info">
<h2 class="asktam-mcp-header-title">AskTam MCP <span class="asktam-mcp-header-version">v<?php echo esc_html(ASKTAM_MCP_VERSION); ?></span></h2>
<p class="asktam-mcp-header-subtitle">Full AI control over WordPress via Model Context Protocol.</p>
</div>
</div>
<div class="asktam-mcp-stats">
<div class="asktam-mcp-stat">
<span class="asktam-mcp-stat-icon asktam-mcp-stat-icon--tools"><svg viewBox="0 0 20 20"><path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg></span>
<span class="asktam-mcp-stat-content"><span class="asktam-mcp-stat-value"><?php echo esc_html($total); ?></span><span class="asktam-mcp-stat-label">Total Tools</span></span>
</div>
<div class="asktam-mcp-stat">
<span class="asktam-mcp-stat-icon asktam-mcp-stat-icon--active"><svg viewBox="0 0 20 20"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"/></svg></span>
<span class="asktam-mcp-stat-content"><span class="asktam-mcp-stat-value"><?php echo esc_html($enabled); ?></span><span class="asktam-mcp-stat-label">Active</span></span>
</div>
<div class="asktam-mcp-stat" style="flex:3">
<span class="asktam-mcp-stat-icon asktam-mcp-stat-icon--endpoint"><svg viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd"/></svg></span>
<span class="asktam-mcp-stat-content"><span class="asktam-mcp-stat-value" style="font-size:11px;font-weight:500"><?php echo esc_html($sse); ?></span><span class="asktam-mcp-stat-label">SSE Endpoint</span></span>
</div>
</div>
<nav class="asktam-mcp-nav nav-tab-wrapper">
<?php
$tabs = [
'tools' => ['Tools', admin_url('admin.php?page='.self::PAGE_SLUG)],
'connection' => ['Connection', admin_url('admin.php?page='.self::PAGE_SLUG.'-connection')],
'prompts' => ['Prompts', admin_url('admin.php?page='.self::PAGE_SLUG.'-prompts')],
'skills' => ['Skills', admin_url('admin.php?page='.self::PAGE_SLUG.'-skills')],
'changelog' => ['Changelog', admin_url('admin.php?page='.self::PAGE_SLUG.'-changelog')],
'api-keys' => ['API Keys', admin_url('admin.php?page='.self::PAGE_SLUG.'-api-keys')],
'activity' => ['Activity', admin_url('admin.php?page='.self::PAGE_SLUG.'-activity')],
'settings' => ['Settings', admin_url('admin.php?page='.self::PAGE_SLUG.'-settings')],
];
foreach ($tabs as $key => [$label, $url]) {
$cls = $key === $active ? 'nav-tab nav-tab-active' : 'nav-tab';
echo '<a href="'.esc_url($url).'" class="'.$cls.'">'.esc_html($label).'</a>';
}
?>
</nav>
<div class="asktam-mcp-tab-content">
<?php
}
private function renderFooter(): void { echo '</div></div>'; }
// ─── Tools Tab ────────────────────────────────────────────────────────────
private function renderToolsTab(): void {
$all_tools = $this->getAllToolsGrouped();
$disabled = get_option(self::OPTION_DISABLED_TOOLS, []);
if (!is_array($disabled)) $disabled = [];
$all_slugs = $this->getAllToolSlugs();
$enabled_count = count($all_slugs) - count($disabled);
?>
<form method="post" action="options.php" id="asktam-mcp-tools-form">
<?php settings_fields(self::SETTINGS_GROUP); ?>
<div class="asktam-mcp-bulk-actions">
<button type="button" class="button asktam-mcp-enable-all">Enable All</button>
<button type="button" class="button asktam-mcp-disable-all">Disable All</button>
<button type="submit" class="button button-primary asktam-mcp-bulk-save">Save Changes</button>
</div>
<p class="asktam-mcp-tools-summary"><strong><?php echo esc_html($enabled_count); ?> of <?php echo esc_html(count($all_slugs)); ?></strong> tools enabled.</p>
<?php foreach ($all_tools as $cat_id => $cat):
$cat_total = count($cat['tools']);
$cat_enabled = array_reduce(array_keys($cat['tools']), fn($c, $s) => $c + (!in_array($s, $disabled, true) ? 1 : 0), 0);
?>
<div class="asktam-mcp-category" data-category="<?php echo esc_attr($cat_id); ?>">
<h2 class="asktam-mcp-category-header">
<?php echo esc_html($cat['label']); ?>
<span class="asktam-mcp-category-count"><?php echo esc_html("$cat_enabled / $cat_total"); ?></span>
<span class="asktam-mcp-category-actions">
<button type="button" class="button-link asktam-mcp-cat-enable-all">All</button>
<span class="asktam-mcp-separator">&middot;</span>
<button type="button" class="button-link asktam-mcp-cat-disable-all">None</button>
</span>
</h2>
<div class="asktam-mcp-tools-grid">
<?php foreach ($cat['tools'] as $slug => $tool):
$enabled = !in_array($slug, $disabled, true); ?>
<label class="asktam-mcp-tool-card <?php echo $enabled ? 'is-enabled' : 'is-disabled'; ?>">
<input type="checkbox" name="<?php echo esc_attr(self::OPTION_DISABLED_TOOLS); ?>[]" value="<?php echo esc_attr($slug); ?>" <?php checked($enabled); ?>>
<span class="asktam-mcp-toggle" aria-hidden="true"><span class="asktam-mcp-toggle-track"></span></span>
<span class="asktam-mcp-tool-info">
<span class="asktam-mcp-tool-name">
<?php echo esc_html($tool['label']); ?>
<?php foreach ($tool['badges'] as $b): ?><span class="asktam-mcp-badge asktam-mcp-badge--<?php echo esc_attr($b); ?>"><?php echo esc_html($b); ?></span><?php endforeach; ?>
</span>
<span class="asktam-mcp-tool-desc"><?php echo esc_html($tool['description']); ?></span>
<code class="asktam-mcp-tool-slug"><?php echo esc_html($slug); ?></code>
</span>
</label>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
<?php submit_button('Save Changes'); ?>
</form>
<?php
}
// ─── Connection Tab ───────────────────────────────────────────────────────
private function renderConnectionTab(): void {
$sse = rest_url('asktam-mcp/v1/sse');
$site_url = site_url();
$username = wp_get_current_user()->user_login;
$registry = new \AskTamMCP\MCP\Registry();
$total = count($registry->getToolDefinitions());
$disabled = get_option(self::OPTION_DISABLED_TOOLS, []);
$enabled = $total - count(is_array($disabled) ? $disabled : []);
?>
<div class="asktam-mcp-connection">
<div class="asktam-mcp-section">
<h2>Server Status</h2>
<p class="description">Current status of your AskTam MCP server.</p>
<div class="asktam-mcp-status-grid">
<div class="asktam-mcp-status-card">
<span class="asktam-mcp-status-card-icon asktam-mcp-status-card-icon--ok"><svg viewBox="0 0 20 20"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"/></svg></span>
<span class="asktam-mcp-status-card-info"><span class="asktam-mcp-status-card-label">AskTam MCP</span><span class="asktam-mcp-status-card-value">Active</span></span>
</div>
<div class="asktam-mcp-status-card">
<span class="asktam-mcp-status-card-icon asktam-mcp-status-card-icon--ok"><svg viewBox="0 0 20 20"><path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg></span>
<span class="asktam-mcp-status-card-info"><span class="asktam-mcp-status-card-label">Tools Enabled</span><span class="asktam-mcp-status-card-value"><?php echo esc_html("$enabled / $total"); ?></span></span>
</div>
<div class="asktam-mcp-status-card">
<span class="asktam-mcp-status-card-icon asktam-mcp-status-card-icon--ok"><svg viewBox="0 0 20 20"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/></svg></span>
<span class="asktam-mcp-status-card-info"><span class="asktam-mcp-status-card-label">Auth</span><span class="asktam-mcp-status-card-value">Bearer Token</span></span>
</div>
</div>
<div class="asktam-mcp-endpoint">
<code><?php echo esc_html($sse); ?></code>
<textarea id="asktam-sse-copy" class="asktam-mcp-copy-source"><?php echo esc_textarea($sse); ?></textarea>
<button type="button" class="button asktam-mcp-copy-btn" data-target="asktam-sse-copy">Copy</button>
</div>
</div>
<div class="asktam-mcp-section">
<h2>Connect Your AI Client</h2>
<p class="description">Connect to this site from any AI client. Enter your API key (from the API Keys tab) as the Bearer token.</p>
<h3>Step 1: Get Your API Key</h3>
<p class="description">Go to the <a href="<?php echo esc_url(admin_url('admin.php?page='.self::PAGE_SLUG.'-api-keys')); ?>">API Keys tab</a> and generate or copy an existing key.</p>
<h3>Step 2: Generate Client Configs</h3>
<p class="description">Enter your API key below to generate ready-to-paste config blocks for each client.</p>
<div class="asktam-mcp-cred-form">
<div class="asktam-mcp-cred-field">
<label for="asktam-api-key-input">API Key</label>
<input type="text" id="asktam-api-key-input" placeholder="mcp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
<p class="description">Paste your API key from the API Keys tab.</p>
</div>
<button type="button" class="button asktam-mcp-generate-btn" id="asktam-generate-configs">Generate Configs</button>
</div>
<div id="asktam-http-configs" style="display:none">
<div class="asktam-mcp-config-card" style="margin-top:20px">
<div class="asktam-mcp-config-card-header">
<span class="asktam-mcp-config-card-title">claude.ai / Claude Desktop &mdash; Custom Connector</span>
<button type="button" class="button asktam-mcp-copy-btn" data-target="asktam-claude-ai-copy">Copy</button>
</div>
<pre><code id="asktam-claude-ai-code"></code></pre>
<textarea id="asktam-claude-ai-copy" class="asktam-mcp-copy-source"></textarea>
</div>
<div class="asktam-mcp-config-card">
<div class="asktam-mcp-config-card-header">
<span class="asktam-mcp-config-card-title">Claude Desktop &mdash; claude_desktop_config.json</span>
<button type="button" class="button asktam-mcp-copy-btn" data-target="asktam-desktop-copy">Copy</button>
</div>
<pre><code id="asktam-desktop-code"></code></pre>
<textarea id="asktam-desktop-copy" class="asktam-mcp-copy-source"></textarea>
</div>
<div class="asktam-mcp-config-card">
<div class="asktam-mcp-config-card-header">
<span class="asktam-mcp-config-card-title">Cursor &mdash; .cursor/mcp.json</span>
<button type="button" class="button asktam-mcp-copy-btn" data-target="asktam-cursor-copy">Copy</button>
</div>
<pre><code id="asktam-cursor-code"></code></pre>
<textarea id="asktam-cursor-copy" class="asktam-mcp-copy-source"></textarea>
</div>
<div class="asktam-mcp-config-card">
<div class="asktam-mcp-config-card-header">
<span class="asktam-mcp-config-card-title">Windsurf &mdash; mcp_config.json</span>
<button type="button" class="button asktam-mcp-copy-btn" data-target="asktam-windsurf-copy">Copy</button>
</div>
<pre><code id="asktam-windsurf-code"></code></pre>
<textarea id="asktam-windsurf-copy" class="asktam-mcp-copy-source"></textarea>
</div>
<div class="asktam-mcp-config-card">
<div class="asktam-mcp-config-card-header">
<span class="asktam-mcp-config-card-title">npx mcp-remote &mdash; any stdio client</span>
<button type="button" class="button asktam-mcp-copy-btn" data-target="asktam-remote-copy">Copy</button>
</div>
<pre><code id="asktam-remote-code"></code></pre>
<textarea id="asktam-remote-copy" class="asktam-mcp-copy-source"></textarea>
</div>
</div>
</div>
</div>
<?php
}
// ─── Prompts Tab ──────────────────────────────────────────────────────────
private function getBuiltinPrompts(): array {
return [
'LOCAL_BUSINESS' => ['title'=>'Local Business', 'industry'=>'General', 'description'=>'Multi-purpose small business landing page with hero, services, testimonials, and contact section.', 'content'=>"Build a complete local business landing page using Elementor MCP tools.\n\nCreate a page with:\n1. Hero section with headline, subheadline, and CTA button\n2. Services grid (3 columns, 6 services with icons)\n3. About section with image and text\n4. Testimonials section (3 testimonials)\n5. Contact form section\n\nUse professional colors and modern layout. Publish when complete.",'builtin'=>true],
'DENTAL_CLINIC' => ['title'=>'Dental Clinic', 'industry'=>'Health & Wellness', 'description'=>'Professional dental practice with services grid, team profiles, insurance info, and appointment booking.', 'content'=>"Build a dental clinic landing page using Elementor MCP tools.\n\nCreate a page with:\n1. Hero with 'Your Smile Matters' headline and appointment CTA\n2. Services grid: Cleanings, Whitening, Implants, Orthodontics, Emergency, Cosmetic\n3. Team section with 3 doctor profiles\n4. Insurance accepted section\n5. Appointment booking form\n\nUse clean white/blue palette.",'builtin'=>true],
'WEB_DEVELOPER_PORTFOLIO' => ['title'=>'Web Developer Portfolio','industry'=>'Professional Services','description'=>'Developer portfolio with project showcase, tech stack, GitHub stats, and contact form.', 'content'=>"Build a web developer portfolio using Elementor MCP tools.\n\nCreate:\n1. Hero with name, role, and hire me CTA\n2. Skills section with tech stack badges\n3. Projects grid (6 projects with thumbnails)\n4. Experience timeline\n5. Contact section with social links\n\nUse dark modern theme.",'builtin'=>true],
'HAIR_SALON' => ['title'=>'Hair Salon', 'industry'=>'Lifestyle', 'description'=>'Stylish salon page with services menu, stylist profiles, gallery, and online booking.', 'content'=>"Build a hair salon landing page using Elementor MCP tools.\n\nCreate:\n1. Hero with glamorous image and book now CTA\n2. Services menu with prices\n3. Stylist profiles (4 stylists)\n4. Before/after gallery\n5. Online booking form\n\nUse elegant pink/gold palette.",'builtin'=>true],
'CAR_WASH' => ['title'=>'Car Wash', 'industry'=>'Lifestyle', 'description'=>'Car wash site with wash packages, add-on services, membership plans, and booking form.', 'content'=>"Build a car wash landing page using Elementor MCP tools.\n\nCreate:\n1. Hero with car wash imagery and pricing CTA\n2. Wash packages comparison table (Basic/Deluxe/Premium)\n3. Add-on services section\n4. Membership plans section\n5. Location and hours\n\nUse bold blue/white palette.",'builtin'=>true],
'RESTAURANT' => ['title'=>'Restaurant', 'industry'=>'Food & Beverage', 'description'=>'Restaurant landing page with menu sections, chef profiles, reservations, and gallery.', 'content'=>"Build a restaurant landing page using Elementor MCP tools.\n\nCreate:\n1. Hero with food photography and reservation CTA\n2. Menu sections (Starters, Mains, Desserts)\n3. Chef profile section\n4. Photo gallery\n5. Reservation form\n\nUse warm, appetizing colors.",'builtin'=>true],
'LAW_FIRM' => ['title'=>'Law Firm', 'industry'=>'Professional Services','description'=>'Professional law firm with practice areas, attorney profiles, case results, and consultation form.', 'content'=>"Build a law firm landing page using Elementor MCP tools.\n\nCreate:\n1. Hero with 'Justice You Can Trust' and consultation CTA\n2. Practice areas grid (6 areas)\n3. Attorney profiles (3 attorneys)\n4. Case results section\n5. Free consultation form\n\nUse professional navy/gold palette.",'builtin'=>true],
'REAL_ESTATE' => ['title'=>'Real Estate Agent', 'industry'=>'Real Estate', 'description'=>'Real estate agent page with featured listings, about section, testimonials, and contact.', 'content'=>"Build a real estate agent landing page using Elementor MCP tools.\n\nCreate:\n1. Hero with agent photo and search form\n2. Featured listings grid (6 properties)\n3. About the agent section\n4. Market stats section\n5. Client testimonials\n6. Contact form\n\nUse clean, trustworthy design.",'builtin'=>true],
'FITNESS_GYM' => ['title'=>'Fitness & Gym', 'industry'=>'Health & Wellness', 'description'=>'Gym landing page with membership plans, classes schedule, trainer profiles, and trial offer.', 'content'=>"Build a fitness gym landing page using Elementor MCP tools.\n\nCreate:\n1. Hero with bold headline and free trial CTA\n2. Membership plans (3 tiers)\n3. Classes schedule section\n4. Trainer profiles (4 trainers)\n5. Transformation gallery\n6. Sign-up form\n\nUse energetic red/black palette.",'builtin'=>true],
'PHOTOGRAPHY' => ['title'=>'Photography Studio', 'industry'=>'Creative', 'description'=>'Photography portfolio with gallery, packages, booking system, and client testimonials.', 'content'=>"Build a photography studio landing page using Elementor MCP tools.\n\nCreate:\n1. Hero with portfolio image and booking CTA\n2. Masonry photo gallery\n3. Services and packages\n4. Client testimonials\n5. Booking inquiry form\n\nUse minimal, editorial design.",'builtin'=>true],
];
}
private function renderPromptsTab(): void {
if (!empty($_GET['prompt_added'])) echo '<div class="notice notice-success is-dismissible"><p>Prompt added successfully.</p></div>';
if (!empty($_GET['prompt_updated'])) echo '<div class="notice notice-success is-dismissible"><p>Prompt updated successfully.</p></div>';
if (!empty($_GET['prompt_deleted'])) echo '<div class="notice notice-success is-dismissible"><p>Prompt deleted.</p></div>';
if (!empty($_GET['prompt_error'])) echo '<div class="notice notice-error is-dismissible"><p>Error: ' . esc_html(urldecode($_GET['prompt_error'])) . '</p></div>';
$builtin = $this->getBuiltinPrompts();
$custom = get_option('asktam_mcp_custom_prompts', []);
if (!is_array($custom)) $custom = [];
$all = array_merge($builtin, $custom);
$editing_slug = isset($_GET['prompt_edit']) ? sanitize_key($_GET['prompt_edit']) : '';
$editing_data = ($editing_slug && isset($custom[$editing_slug])) ? $custom[$editing_slug] : null;
?>
<div class="asktam-mcp-prompts">
<div class="asktam-mcp-prompts-intro">
<h2>Sample Prompts</h2>
<p class="description">Ready-to-use landing page blueprints. Copy any prompt and paste it into your AI client — it will automatically build a complete Elementor page using the MCP tools.</p>
</div>
<?php if ($editing_data): ?>
<!-- ── EDIT FORM ─────────────────────────────────────────── -->
<div class="asktam-mcp-section" style="margin-bottom:32px;background:#fff8e1;border:2px solid #f59e0b;border-radius:8px;padding:24px;">
<h3 style="margin-top:0;color:#92400e;">&#9998; Edit Prompt: <?php echo esc_html($editing_data['title']); ?></h3>
<form method="post" action="">
<?php wp_nonce_field('asktam_mcp_edit_prompt_' . $editing_slug, 'asktam_mcp_nonce'); ?>
<input type="hidden" name="asktam_mcp_action" value="edit_prompt">
<input type="hidden" name="prompt_slug" value="<?php echo esc_attr($editing_slug); ?>">
<table class="form-table" style="margin-bottom:12px">
<tr>
<th style="width:150px"><label for="edit_prompt_title">Title</label></th>
<td><input type="text" id="edit_prompt_title" name="prompt_title" class="regular-text" value="<?php echo esc_attr($editing_data['title']); ?>" required></td>
</tr>
<tr>
<th><label for="edit_prompt_industry">Tag / Industry</label></th>
<td><input type="text" id="edit_prompt_industry" name="prompt_industry" class="regular-text" value="<?php echo esc_attr($editing_data['industry'] ?? ''); ?>"></td>
</tr>
<tr>
<th><label for="edit_prompt_description">Description</label></th>
<td><input type="text" id="edit_prompt_description" name="prompt_description" class="large-text" value="<?php echo esc_attr($editing_data['description']); ?>" required></td>
</tr>
<tr>
<th><label for="edit_prompt_content">Prompt Content</label></th>
<td>
<textarea id="edit_prompt_content" name="prompt_content" rows="10" class="large-text" required><?php echo esc_textarea($editing_data['content']); ?></textarea>
<p class="description">This is the full text that AI clients receive when the user clicks &ldquo;Copy Prompt&rdquo;.</p>
</td>
</tr>
</table>
<div style="display:flex;gap:10px;align-items:center">
<?php submit_button('Save Changes', 'primary', 'submit', false); ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG . '-prompts')); ?>" class="button">Cancel</a>
</div>
</form>
</div>
<?php else: ?>
<!-- ── ADD FORM ──────────────────────────────────────────── -->
<div class="asktam-mcp-section" style="margin-bottom:32px;background:#f9f9f9;border:1px solid #ddd;border-radius:8px;padding:24px;">
<h3 style="margin-top:0">&#43; Add New Prompt</h3>
<form method="post" action="">
<?php wp_nonce_field('asktam_mcp_add_prompt', 'asktam_mcp_nonce'); ?>
<input type="hidden" name="asktam_mcp_action" value="add_prompt">
<table class="form-table" style="margin-bottom:12px">
<tr>
<th style="width:150px"><label for="prompt_title">Title</label></th>
<td><input type="text" id="prompt_title" name="prompt_title" class="regular-text" placeholder="e.g. Medical Clinic" required></td>
</tr>
<tr>
<th><label for="prompt_industry">Tag / Industry</label></th>
<td><input type="text" id="prompt_industry" name="prompt_industry" class="regular-text" placeholder="e.g. Health &amp; Wellness"></td>
</tr>
<tr>
<th><label for="prompt_description">Description</label></th>
<td><input type="text" id="prompt_description" name="prompt_description" class="large-text" placeholder="One-line summary shown on the card" required></td>
</tr>
<tr>
<th><label for="prompt_content">Prompt Content</label></th>
<td><textarea id="prompt_content" name="prompt_content" rows="8" class="large-text" placeholder="The full prompt text that gets copied to clipboard..." required></textarea>
<p class="description">This is the full text that AI clients receive when the user clicks &ldquo;Copy Prompt&rdquo;.</p></td>
</tr>
</table>
<?php submit_button('Add Prompt', 'primary', 'submit', false); ?>
</form>
</div>
<?php endif; ?>
<div class="asktam-mcp-prompts-grid">
<?php foreach ($all as $slug => $meta):
$is_custom = !empty($meta['custom']);
$copy_id = 'asktam-prompt-' . sanitize_title($slug);
$content = $meta['content'] ?? "Build a {$meta['title']} page using Elementor MCP tools with professional layout and design.";
$is_editing_this = ($editing_slug === $slug);
?>
<div class="asktam-mcp-prompt-card" style="position:relative<?php echo $is_editing_this ? ';border:2px solid #f59e0b;' : ''; ?>">
<?php if ($is_custom): ?>
<span style="position:absolute;top:10px;right:10px;background:#e8f5e9;color:#2e7d32;font-size:10px;padding:2px 8px;border-radius:10px;font-weight:600;line-height:1.6">Custom</span>
<?php endif; ?>
<div class="asktam-mcp-prompt-header">
<h3 class="asktam-mcp-prompt-title"><?php echo esc_html($meta['title']); ?></h3>
<span class="asktam-mcp-prompt-tag"><?php echo esc_html($meta['industry'] ?? ''); ?></span>
</div>
<p class="asktam-mcp-prompt-desc"><?php echo esc_html($meta['description']); ?></p>
<div class="asktam-mcp-prompt-actions" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<button type="button" class="button asktam-mcp-copy-btn" data-target="<?php echo esc_attr($copy_id); ?>">
<svg viewBox="0 0 20 20" width="14" height="14" style="vertical-align:middle;margin-right:4px"><path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"/><path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"/></svg>
Copy Prompt
</button>
<?php if ($is_custom): ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=' . self::PAGE_SLUG . '-prompts&prompt_edit=' . urlencode($slug))); ?>" class="button" style="color:#92400e;border-color:#f59e0b">&#9998; Edit</a>
<form method="post" style="margin:0" onsubmit="return confirm('Delete this prompt?')">
<?php wp_nonce_field('asktam_mcp_delete_prompt_' . $slug, 'asktam_mcp_nonce'); ?>
<input type="hidden" name="asktam_mcp_action" value="delete_prompt">
<input type="hidden" name="prompt_slug" value="<?php echo esc_attr($slug); ?>">
<button type="submit" class="button" style="color:#b91c1c;border-color:#b91c1c">&#128465; Delete</button>
</form>
<?php endif; ?>
</div>
<textarea id="<?php echo esc_attr($copy_id); ?>" class="asktam-mcp-copy-source"><?php echo esc_textarea($content); ?></textarea>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
}
// ─── Skills Tab ───────────────────────────────────────────────────────────
private function renderSkillsTab(): void { ?>
<div class="asktam-mcp-skills">
<div class="asktam-mcp-pro-prompts-header">
<div class="asktam-mcp-pro-prompts-heading">
<h2>AskTam MCP Agent Skills</h2>
<p class="description">Pre-written agent skills that teach Claude and other AI clients exactly how to use AskTam MCP tools — WordPress editing, Elementor page building, WooCommerce management, and more.</p>
</div>
</div>
<div class="asktam-mcp-coming-soon" role="status">
<span class="asktam-mcp-coming-soon__icon" aria-hidden="true"><svg viewBox="0 0 20 20" width="16" height="16"><path fill="currentColor" d="M10 18a8 8 0 100-16 8 8 0 000 16zm.75-12.75a.75.75 0 00-1.5 0v5.5a.75.75 0 00.34.628l3.75 2.5a.75.75 0 10.82-1.255L10.75 10.4V5.25z"/></svg></span>
<div class="asktam-mcp-coming-soon__text"><strong>Quick install:</strong> Copy the skill content from any guide below and paste it into your AI client's system prompt or custom instructions field.</div>
</div>
<div class="asktam-mcp-skills-guides">
<details class="asktam-mcp-skills-guide" open>
<summary><strong>Claude (claude.ai / Claude Desktop)</strong><span class="asktam-mcp-skills-guide__hint">Custom instructions</span></summary>
<div class="asktam-mcp-skills-guide__body">
<p>Add the AskTam MCP skill to Claude's custom instructions:</p>
<ol>
<li>Go to claude.ai → Settings → <strong>Custom Instructions</strong></li>
<li>In the "How would you like Claude to respond?" field, add: <code>When connected to AskTam MCP, you have direct access to WordPress. Use the available tools to read and modify posts, pages, media, users, WooCommerce, and Elementor content. Always confirm destructive actions before executing.</code></li>
<li>Save. All future conversations with the MCP connector active will follow these instructions.</li>
</ol>
</div>
</details>
<details class="asktam-mcp-skills-guide">
<summary><strong>Claude Code (terminal)</strong><span class="asktam-mcp-skills-guide__hint">CLAUDE.md file</span></summary>
<div class="asktam-mcp-skills-guide__body">
<p>Claude Code reads instructions from <code>CLAUDE.md</code> in your project root:</p>
<ol>
<li>Create or edit <code>CLAUDE.md</code> in your project root</li>
<li>Add a section: <code>## WordPress MCP\nWhen AskTam MCP tools are available, use them to directly manage the WordPress site. Available tool categories: Posts, Pages, Media, Users, Taxonomy, Options, Plugins, Themes, Menus, Comments, SEO, WooCommerce, Elementor.</code></li>
<li>Claude Code will pick this up automatically on every session.</li>
</ol>
</div>
</details>
<details class="asktam-mcp-skills-guide">
<summary><strong>Cursor</strong><span class="asktam-mcp-skills-guide__hint">Project Rules</span></summary>
<div class="asktam-mcp-skills-guide__body">
<ol>
<li>Open Cursor → Settings → <strong>Rules</strong> → Project Rules</li>
<li>Create a new rule named "AskTam MCP" set to "Agent Requested"</li>
<li>Paste: <code>When AskTam MCP tools are available, use them to manage WordPress directly. Use elementor_* tools for page building, wc_* for WooCommerce, and standard tools for content management.</code></li>
</ol>
</div>
</details>
<details class="asktam-mcp-skills-guide">
<summary><strong>Any other MCP client</strong><span class="asktam-mcp-skills-guide__hint">Universal fallback</span></summary>
<div class="asktam-mcp-skills-guide__body">
<p>If your client supports a system prompt or custom instructions field, paste:</p>
<p><em>"When connected to AskTam MCP, you have direct WordPress access. Tools are grouped by: Posts/Pages (CRUD), Media, Users, Taxonomy, Options, Plugins, Themes, Menus, Comments, SEO (Yoast/RankMath), Site utilities, WooCommerce, and 30 Elementor tools for page building. Always use tools directly without asking for confirmation unless the action is destructive."</em></p>
</div>
</details>
</div>
</div>
<?php
}
// ─── Changelog Tab ────────────────────────────────────────────────────────
private function renderChangelogTab(): void {
$changelog = [
['version'=>'2.1.0','items'=>[
'New: Tools admin page with 93 tools grouped by category with toggle cards',
'New: Connection tab with multi-client config generator (claude.ai, Claude Desktop, Cursor, Windsurf, npx mcp-remote)',
'New: Prompts tab with 10 ready-to-use Elementor page blueprints',
'New: Skills tab with per-client installation guides',
'New: Changelog tab',
'Fix: Added missing `countPosts`, `getPostMeta`, `updatePostMeta`, `setFeaturedImage` methods to PostTools',
'Fix: Added missing `countMedia` method to MediaTools',
'Fix: Added missing `getPermalinkStructure` method to SiteTools',
'Improved: AdminMenu now calls `->init()` correctly via Plugin.php',
]],
['version'=>'2.0.0','items'=>[
'New: Merged elementor-mcp tools — 30 Elementor tools, all unlocked (no Freemius)',
'New: CommentTools — list, approve, spam, delete comments',
'New: MenuTools — list menus, get menu items, update menu item',
'New: OptionTools — get, update, list WordPress options',
'New: PluginTools — list, activate, deactivate plugins',
'New: SeoTools — get/update Yoast and RankMath SEO meta',
'New: TaxonomyTools — list, create, update, delete terms',
'New: ThemeTools — list themes, get active, switch theme',
'New: ElementorData and ElementorTools data layer',
'New: Registry auto-registers Elementor tools when Elementor active',
'Total: 93 tools registered',
]],
['version'=>'1.0.0','items'=>[
'New: Initial release — AskTam MCP server with SSE transport',
'New: OAuth 2.1 + DCR + PKCE authentication',
'New: PostTools, PageTools, MediaTools, UserTools, SiteTools, WooTools',
'New: API Key management with scope control',
'New: Activity log with 90-day retention',
]],
];
?>
<div class="asktam-mcp-changelog">
<div class="asktam-mcp-changelog-intro">
<h2>Changelog</h2>
<p class="description">Version history for AskTam MCP Plugin.</p>
</div>
<div class="asktam-mcp-changelog-list">
<?php foreach ($changelog as $i => $entry): ?>
<div class="asktam-mcp-changelog-version <?php echo $i===0 ? 'is-latest' : ''; ?>">
<div class="asktam-mcp-changelog-version-header">
<h3>Version <?php echo esc_html($entry['version']); ?></h3>
<?php if ($i===0): ?><span class="asktam-mcp-changelog-badge">Latest</span><?php endif; ?>
</div>
<ul class="asktam-mcp-changelog-items">
<?php foreach ($entry['items'] as $item):
$escaped = esc_html($item);
$escaped = preg_replace('/^(New:|Fix:|Improved:|Total:)/', '<strong>$1</strong>', $escaped);
$escaped = preg_replace('/`([^`]+)`/', '<code>$1</code>', $escaped);
?><li><?php echo wp_kses($escaped, ['strong'=>[],'code'=>[]]); ?></li><?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
}
// ─── API Keys Tab ─────────────────────────────────────────────────────────
private function renderApiKeysTab(): void {
global $wpdb;
if (!empty($_GET['created'])) echo '<div class="notice notice-success"><p>API key created.</p></div>';
if (!empty($_GET['deleted'])) echo '<div class="notice notice-success"><p>API key deleted.</p></div>';
if (!empty($_GET['rotated'])) echo '<div class="notice notice-success"><p>JWT secret rotated. All clients must re-authenticate.</p></div>';
$keys = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}asktam_mcp_keys WHERE is_active=1 ORDER BY created_at DESC");
$sse_url = rest_url('asktam-mcp/v1/sse');
?>
<div class="asktam-mcp-connection">
<div class="asktam-mcp-section">
<h2>SSE Endpoint</h2>
<div class="asktam-mcp-endpoint">
<code><?php echo esc_html($sse_url); ?></code>
<textarea id="sse-url-copy" class="asktam-mcp-copy-source"><?php echo esc_textarea($sse_url); ?></textarea>
<button type="button" class="button asktam-mcp-copy-btn" data-target="sse-url-copy">Copy</button>
</div>
</div>
<div class="asktam-mcp-section">
<h2>Generate API Key</h2>
<form method="post" action="">
<?php wp_nonce_field('asktam_mcp_create_key','asktam_mcp_nonce'); ?>
<input type="hidden" name="asktam_mcp_action" value="create_key">
<table class="form-table">
<tr><th><label for="key_label">Label</label></th><td><input type="text" id="key_label" name="key_label" placeholder="e.g. Claude Desktop" class="regular-text"></td></tr>
<tr><th>Scopes</th><td>
<label><input type="checkbox" name="scopes[]" value="read" checked> Read</label>&nbsp;&nbsp;
<label><input type="checkbox" name="scopes[]" value="write"> Write</label>&nbsp;&nbsp;
<label><input type="checkbox" name="scopes[]" value="admin"> Admin</label>&nbsp;&nbsp;
<label><input type="checkbox" name="scopes[]" value="woocommerce"> WooCommerce</label>
<p class="description">Read = list/get &middot; Write = create/update/delete &middot; Admin = full access &middot; WooCommerce = WC mutations</p>
</td></tr>
</table>
<?php submit_button('Generate','primary','submit',false); ?>
</form>
</div>
<?php if ($keys): ?>
<div class="asktam-mcp-section">
<h2>Connection Instructions</h2>
<p class="description">Add as custom connector: claude.ai → Settings → Connectors → Add custom connector</p>
<?php
$json = json_encode(['mcpServers'=>['wordpress'=>['url'=>$sse_url,'headers'=>['Authorization'=>'Bearer YOUR_API_KEY']]]],JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
?>
<div class="asktam-mcp-config-card">
<div class="asktam-mcp-config-card-header">
<span class="asktam-mcp-config-card-title">claude.ai / Claude Desktop &mdash; claude_desktop_config.json</span>
<textarea id="desktop-config-copy" class="asktam-mcp-copy-source"><?php echo esc_textarea($json); ?></textarea>
<button type="button" class="button asktam-mcp-copy-btn" data-target="desktop-config-copy">Copy</button>
</div>
<pre><code><?php echo esc_html($json); ?></code></pre>
</div>
</div>
<?php endif; ?>
<div class="asktam-mcp-section">
<h2>API Keys</h2>
<?php if ($keys): ?>
<table class="wp-list-table widefat fixed striped">
<thead><tr><th>Label</th><th>Key (masked)</th><th>Scopes</th><th>Created</th><th>Last Used</th><th>Actions</th></tr></thead>
<tbody>
<?php foreach ($keys as $key):
$masked = 'mcp_' . substr(md5($key->api_key),0,4) . '••••••••••••' . substr($key->api_key,-4);
?><tr>
<td><?php echo esc_html($key->name); ?></td>
<td>
<code><?php echo esc_html($masked); ?></code>
<textarea id="key-copy-<?php echo (int)$key->id; ?>" class="asktam-mcp-copy-source"><?php echo esc_textarea($key->api_key); ?></textarea>
<button type="button" class="button-small asktam-mcp-copy-btn" data-target="key-copy-<?php echo (int)$key->id; ?>">Copy</button>
</td>
<td><?php echo esc_html(implode(', ', array_filter(explode(',', $key->scopes)))); ?></td>
<td><?php echo esc_html(date('Y-m-d', strtotime($key->created_at))); ?></td>
<td><?php echo $key->last_used ? esc_html(date('Y-m-d', strtotime($key->last_used))) : '—'; ?></td>
<td>
<form method="post" style="display:inline">
<?php wp_nonce_field('asktam_mcp_delete_key_'.$key->id,'asktam_mcp_nonce'); ?>
<input type="hidden" name="asktam_mcp_action" value="delete_key">
<input type="hidden" name="key_id" value="<?php echo (int)$key->id; ?>">
<button type="submit" class="button-link" onclick="return confirm('Delete this key?')">Delete</button>
</form>
</td>
</tr><?php endforeach; ?>
</tbody>
</table>
<?php else: ?><p>No API keys yet. Generate one above.</p><?php endif; ?>
</div>
<div class="asktam-mcp-section">
<h2>Security</h2>
<div style="background:#fff3cd;border:1px solid #ffc107;border-radius:6px;padding:16px;">
<strong>⚠ Rotate JWT Secret</strong>
<p>Immediately invalidates <strong>all active JWT tokens</strong>.</p>
<form method="post">
<?php wp_nonce_field('asktam_mcp_rotate_jwt','asktam_mcp_nonce'); ?>
<input type="hidden" name="asktam_mcp_action" value="rotate_jwt">
<button type="submit" class="button" style="background:#dc3545;border-color:#dc3545;color:#fff" onclick="return confirm('Rotate JWT? All clients must re-authenticate.')">Rotate JWT Secret Now</button>
</form>
</div>
</div>
</div>
<?php
}
// ─── Activity Tab ─────────────────────────────────────────────────────────
private function renderActivityTab(): void {
global $wpdb;
$logs = $wpdb->get_results("SELECT l.*, k.name as key_name FROM {$wpdb->prefix}asktam_mcp_log l LEFT JOIN {$wpdb->prefix}asktam_mcp_keys k ON l.key_id=k.id ORDER BY l.created_at DESC LIMIT 200");
?>
<table class="wp-list-table widefat fixed striped">
<thead><tr><th>Time</th><th>Key</th><th>Tool</th><th>Method</th><th>Status</th><th>Duration</th><th>IP</th></tr></thead>
<tbody>
<?php if ($logs): foreach ($logs as $l): ?><tr>
<td><?php echo esc_html($l->created_at); ?></td>
<td><?php echo esc_html($l->key_name ?: "key #{$l->key_id}"); ?></td>
<td><code><?php echo esc_html($l->tool); ?></code></td>
<td><?php echo esc_html($l->method); ?></td>
<td><span style="color:<?php echo $l->status ? '#10b981':'#ef4444'; ?>"><?php echo $l->status ? '✓':'✗'; ?></span></td>
<td><?php echo esc_html($l->duration); ?>ms</td>
<td><?php echo esc_html($l->ip); ?></td>
</tr><?php endforeach; else: ?><tr><td colspan="7">No activity yet.</td></tr><?php endif; ?>
</tbody>
</table>
<?php
}
// ─── Settings Tab ─────────────────────────────────────────────────────────
private function renderSettingsTab(): void {
$s = get_option('asktam_mcp_settings', []);
?>
<form method="post" action="options.php" style="max-width:600px">
<?php settings_fields('asktam_mcp_settings_group'); ?>
<table class="form-table">
<tr><th>Rate Limit (req/min per key)</th><td><input type="number" name="asktam_mcp_settings[rate_limit]" value="<?php echo esc_attr($s['rate_limit']??60); ?>" min="1" max="600"></td></tr>
<tr><th>Require HTTPS</th><td><input type="checkbox" name="asktam_mcp_settings[require_https]" value="1" <?php checked(!empty($s['require_https'])); ?>><p class="description">Block all MCP requests over HTTP.</p></td></tr>
<tr><th>Log Requests</th><td><input type="checkbox" name="asktam_mcp_settings[log_requests]" value="1" <?php checked(!empty($s['log_requests'])); ?>></td></tr>
<tr><th>Allow Raw SQL <small>(admin scope)</small></th><td><input type="checkbox" name="asktam_mcp_settings[allow_raw_sql]" value="1" <?php checked(!empty($s['allow_raw_sql'])); ?>><p class="description">Allows admin-scoped keys to run SELECT queries.</p></td></tr>
<tr><th>WooCommerce Tools</th><td><input type="checkbox" name="asktam_mcp_settings[wc_enabled]" value="1" <?php checked(!empty($s['wc_enabled'])); ?>><?php if (class_exists('WooCommerce')): ?><p class="description">WooCommerce detected ✓</p><?php endif; ?></td></tr>
</table>
<?php submit_button('Save Changes'); ?>
</form>
<?php
}
// ─── Form Handlers ────────────────────────────────────────────────────────
public function handleApiKeyActions(): void {
if (empty($_POST['asktam_mcp_action']) || !current_user_can('manage_options')) return;
$action = sanitize_text_field($_POST['asktam_mcp_action']);
if ($action === 'create_key') {
if (!wp_verify_nonce($_POST['asktam_mcp_nonce']??'','asktam_mcp_create_key')) wp_die('Invalid nonce');
global $wpdb;
$label = sanitize_text_field($_POST['key_label']??'API Key');
$scopes = isset($_POST['scopes'])&&is_array($_POST['scopes']) ? implode(',',array_map('sanitize_text_field',$_POST['scopes'])) : 'read';
$key = 'mcp_'.bin2hex(random_bytes(20));
$wpdb->insert("{$wpdb->prefix}asktam_mcp_keys",['name'=>$label,'api_key'=>$key,'scopes'=>$scopes,'created_at'=>current_time('mysql'),'is_active'=>1]);
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-api-keys&created=1')); exit;
}
if ($action === 'delete_key') {
$key_id = (int)($_POST['key_id']??0);
if (!wp_verify_nonce($_POST['asktam_mcp_nonce']??'','asktam_mcp_delete_key_'.$key_id)) wp_die('Invalid nonce');
global $wpdb;
$wpdb->update("{$wpdb->prefix}asktam_mcp_keys",['is_active'=>0],['id'=>$key_id]);
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-api-keys&deleted=1')); exit;
}
if ($action === 'rotate_jwt') {
if (!wp_verify_nonce($_POST['asktam_mcp_nonce']??'','asktam_mcp_rotate_jwt')) wp_die('Invalid nonce');
update_option('asktam_mcp_jwt_secret', bin2hex(random_bytes(32)));
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-api-keys&rotated=1')); exit;
}
if ($action === 'add_prompt') {
if (!wp_verify_nonce($_POST['asktam_mcp_nonce']??'','asktam_mcp_add_prompt')) wp_die('Invalid nonce');
$title = sanitize_text_field($_POST['prompt_title']??'');
$industry = sanitize_text_field($_POST['prompt_industry']??'');
$desc = sanitize_text_field($_POST['prompt_description']??'');
$content = sanitize_textarea_field($_POST['prompt_content']??'');
if (!$title || !$desc || !$content) {
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-prompts&prompt_error='.urlencode('Title, description and content are required'))); exit;
}
$slug = 'custom_' . sanitize_title($title) . '_' . time();
$custom = get_option('asktam_mcp_custom_prompts', []);
if (!is_array($custom)) $custom = [];
$custom[$slug] = ['title'=>$title,'industry'=>$industry,'description'=>$desc,'content'=>$content,'custom'=>true];
update_option('asktam_mcp_custom_prompts', $custom);
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-prompts&prompt_added=1')); exit;
}
if ($action === 'edit_prompt') {
$slug = sanitize_key($_POST['prompt_slug']??'');
if (!$slug || !wp_verify_nonce($_POST['asktam_mcp_nonce']??'','asktam_mcp_edit_prompt_'.$slug)) wp_die('Invalid nonce');
$title = sanitize_text_field($_POST['prompt_title']??'');
$industry = sanitize_text_field($_POST['prompt_industry']??'');
$desc = sanitize_text_field($_POST['prompt_description']??'');
$content = sanitize_textarea_field($_POST['prompt_content']??'');
if (!$title || !$desc || !$content) {
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-prompts&prompt_error='.urlencode('Title, description and content are required'))); exit;
}
$custom = get_option('asktam_mcp_custom_prompts', []);
if (!is_array($custom)) $custom = [];
if (!isset($custom[$slug])) {
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-prompts&prompt_error='.urlencode('Prompt not found'))); exit;
}
$custom[$slug] = ['title'=>$title,'industry'=>$industry,'description'=>$desc,'content'=>$content,'custom'=>true];
update_option('asktam_mcp_custom_prompts', $custom);
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-prompts&prompt_updated=1')); exit;
}
if ($action === 'delete_prompt') {
$slug = sanitize_key($_POST['prompt_slug']??'');
if (!wp_verify_nonce($_POST['asktam_mcp_nonce']??'','asktam_mcp_delete_prompt_'.$slug)) wp_die('Invalid nonce');
$custom = get_option('asktam_mcp_custom_prompts', []);
if (!is_array($custom)) $custom = [];
unset($custom[$slug]);
update_option('asktam_mcp_custom_prompts', $custom);
wp_redirect(admin_url('admin.php?page='.self::PAGE_SLUG.'-prompts&prompt_deleted=1')); exit;
}
}
// ─── Tool Catalog ─────────────────────────────────────────────────────────
public function getAllToolsGrouped(): array {
return [
'posts'=>['label'=>'Posts','tools'=>['list_posts'=>['label'=>'List Posts','description'=>'List WordPress posts with filtering by status, author, category, tag, date range, search, and CPT.','badges'=>['read']],'get_post'=>['label'=>'Get Post','description'=>'Get full details of a post including content, meta, taxonomies, and featured image.','badges'=>['read']],'create_post'=>['label'=>'Create Post','description'=>'Create a new WordPress post.','badges'=>['write']],'update_post'=>['label'=>'Update Post','description'=>'Update an existing WordPress post.','badges'=>['write']],'delete_post'=>['label'=>'Delete Post','description'=>'Delete or trash a WordPress post.','badges'=>['write','destructive']],'get_post_meta'=>['label'=>'Get Post Meta','description'=>'Get post meta data.','badges'=>['read']],'update_post_meta'=>['label'=>'Update Post Meta','description'=>'Update post meta data.','badges'=>['write']],'set_featured_image'=>['label'=>'Set Featured Image','description'=>'Set or replace the featured image on a post or page.','badges'=>['write']],'count_posts'=>['label'=>'Count Posts','description'=>'Get post counts by status.','badges'=>['read']]]],
'pages'=>['label'=>'Pages','tools'=>['list_pages'=>['label'=>'List Pages','description'=>'List WordPress pages.','badges'=>['read']],'get_page'=>['label'=>'Get Page','description'=>'Get a single WordPress page.','badges'=>['read']],'create_page'=>['label'=>'Create Page','description'=>'Create a new WordPress page.','badges'=>['write']],'update_page'=>['label'=>'Update Page','description'=>'Update an existing WordPress page.','badges'=>['write']],'delete_page'=>['label'=>'Delete Page','description'=>'Delete or trash a WordPress page.','badges'=>['write','destructive']]]],
'media'=>['label'=>'Media','tools'=>['list_media'=>['label'=>'List Media','description'=>'List WordPress media library items.','badges'=>['read']],'get_media'=>['label'=>'Get Media','description'=>'Get a single media item by ID.','badges'=>['read']],'upload_media'=>['label'=>'Upload Media','description'=>'Upload a media file from a public URL.','badges'=>['write']],'update_media'=>['label'=>'Update Media','description'=>'Update media metadata.','badges'=>['write']],'delete_media'=>['label'=>'Delete Media','description'=>'Delete a media item permanently.','badges'=>['write','destructive']],'count_media'=>['label'=>'Count Media','description'=>'Get media counts by type.','badges'=>['read']]]],
'users'=>['label'=>'Users','tools'=>['list_users'=>['label'=>'List Users','description'=>'List WordPress users.','badges'=>['read']],'get_user'=>['label'=>'Get User','description'=>'Get a single user by ID.','badges'=>['read']],'create_user'=>['label'=>'Create User','description'=>'Create a new WordPress user.','badges'=>['admin']],'update_user'=>['label'=>'Update User','description'=>'Update an existing WordPress user.','badges'=>['admin']],'delete_user'=>['label'=>'Delete User','description'=>'Delete a WordPress user.','badges'=>['admin','destructive']]]],
'taxonomy'=>['label'=>'Taxonomy & Terms','tools'=>['list_terms'=>['label'=>'List Terms','description'=>'List terms in any taxonomy.','badges'=>['read']],'create_term'=>['label'=>'Create Term','description'=>'Create a new term in a taxonomy.','badges'=>['write']],'update_term'=>['label'=>'Update Term','description'=>'Update an existing term.','badges'=>['write']],'delete_term'=>['label'=>'Delete Term','description'=>'Delete a term from a taxonomy.','badges'=>['write','destructive']],'count_terms'=>['label'=>'Count Terms','description'=>'Get term counts in a taxonomy.','badges'=>['read']]]],
'options'=>['label'=>'Options','tools'=>['get_option'=>['label'=>'Get Option','description'=>'Get a WordPress option value by key.','badges'=>['read']],'update_option'=>['label'=>'Update Option','description'=>'Update a WordPress option.','badges'=>['admin']],'list_options'=>['label'=>'List Options','description'=>'List WordPress options matching a prefix.','badges'=>['read']]]],
'plugins'=>['label'=>'Plugins','tools'=>['list_plugins'=>['label'=>'List Plugins','description'=>'List all installed WordPress plugins with their status.','badges'=>['read']],'activate_plugin'=>['label'=>'Activate Plugin','description'=>'Activate a WordPress plugin by its slug/file path.','badges'=>['admin']],'deactivate_plugin'=>['label'=>'Deactivate Plugin','description'=>'Deactivate a WordPress plugin.','badges'=>['admin']]]],
'themes'=>['label'=>'Themes','tools'=>['list_themes'=>['label'=>'List Themes','description'=>'List all installed WordPress themes.','badges'=>['read']],'get_active_theme'=>['label'=>'Get Active Theme','description'=>'Get the currently active WordPress theme details.','badges'=>['read']],'switch_theme'=>['label'=>'Switch Theme','description'=>'Switch the active WordPress theme by stylesheet slug.','badges'=>['admin']]]],
'menus'=>['label'=>'Menus','tools'=>['list_menus'=>['label'=>'List Menus','description'=>'List all WordPress navigation menus.','badges'=>['read']],'get_menu'=>['label'=>'Get Menu','description'=>'Get all items in a navigation menu by ID or slug.','badges'=>['read']],'update_menu_item'=>['label'=>'Update Menu Item','description'=>'Update or create a navigation menu item.','badges'=>['write']]]],
'comments'=>['label'=>'Comments','tools'=>['list_comments'=>['label'=>'List Comments','description'=>'List WordPress comments with optional post and status filters.','badges'=>['read']],'approve_comment'=>['label'=>'Approve Comment','description'=>'Approve a pending comment.','badges'=>['write']],'spam_comment'=>['label'=>'Spam Comment','description'=>'Mark a comment as spam.','badges'=>['write']],'delete_comment'=>['label'=>'Delete Comment','description'=>'Delete or trash a comment.','badges'=>['write','destructive']]]],
'seo'=>['label'=>'SEO','tools'=>['get_seo_meta'=>['label'=>'Get SEO Meta','description'=>'Get SEO meta for a post/page (Yoast or RankMath).','badges'=>['read']],'update_seo_meta'=>['label'=>'Update SEO Meta','description'=>'Update SEO meta for a post/page (Yoast or RankMath).','badges'=>['write']]]],
'site'=>['label'=>'Site','tools'=>['get_site_info'=>['label'=>'Get Site Info','description'=>'Get WordPress site information.','badges'=>['read']],'get_health'=>['label'=>'Get Health','description'=>'Run WordPress site health checks.','badges'=>['read']],'run_cron'=>['label'=>'Run Cron','description'=>'Trigger WordPress cron manually.','badges'=>['admin']],'search_content'=>['label'=>'Search Content','description'=>'Search across posts, pages, and CPTs.','badges'=>['read']],'get_permalink_structure'=>['label'=>'Get Permalink Structure','description'=>'Get the WordPress permalink structure.','badges'=>['read']]]],
'woocommerce'=>['label'=>'WooCommerce','tools'=>['wc_list_products'=>['label'=>'List Products','description'=>'List WooCommerce products.','badges'=>['read','woocommerce']],'wc_get_product'=>['label'=>'Get Product','description'=>'Get a single WooCommerce product by ID.','badges'=>['read','woocommerce']],'wc_create_product'=>['label'=>'Create Product','description'=>'Create a WooCommerce product.','badges'=>['write','woocommerce']],'wc_update_product'=>['label'=>'Update Product','description'=>'Update a WooCommerce product.','badges'=>['write','woocommerce']],'wc_list_orders'=>['label'=>'List Orders','description'=>'List WooCommerce orders.','badges'=>['read','woocommerce']],'wc_get_order'=>['label'=>'Get Order','description'=>'Get a single WooCommerce order by ID.','badges'=>['read','woocommerce']],'wc_update_order_status'=>['label'=>'Update Order Status','description'=>'Update a WooCommerce order status.','badges'=>['write','woocommerce']],'wc_list_customers'=>['label'=>'List Customers','description'=>'List WooCommerce customers.','badges'=>['read','woocommerce']],'wc_get_store_stats'=>['label'=>'Get Store Stats','description'=>'Get WooCommerce store stats.','badges'=>['read','woocommerce']]]],
'elementor_pages'=>['label'=>'Elementor — Page Management','tools'=>['elementor_create_page'=>['label'=>'Create Page','description'=>'Creates a new WordPress page with Elementor enabled.','badges'=>['write','elementor']],'elementor_get_page'=>['label'=>'Get Page','description'=>'Returns the full Elementor element tree for a page.','badges'=>['read','elementor']],'elementor_update_page_settings'=>['label'=>'Update Page Settings','description'=>'Updates Elementor page-level settings.','badges'=>['write','elementor']],'elementor_delete_page_content'=>['label'=>'Delete Page Content','description'=>'Removes all Elementor content from a page.','badges'=>['write','destructive','elementor']],'elementor_import_template'=>['label'=>'Import Template','description'=>'Imports an Elementor template JSON into a page.','badges'=>['write','elementor']],'elementor_export_page'=>['label'=>'Export Page','description'=>"Exports a page's Elementor data as JSON.",'badges'=>['read','elementor']]]],
'elementor_layout'=>['label'=>'Elementor — Layout & Structure','tools'=>['elementor_add_container'=>['label'=>'Add Container','description'=>'Adds a new flexbox container to a page or inside another container.','badges'=>['write','elementor']],'elementor_update_container'=>['label'=>'Update Container','description'=>'Updates settings on an existing container element.','badges'=>['write','elementor']],'elementor_update_element'=>['label'=>'Update Element','description'=>'Updates settings on any element by ID.','badges'=>['write','elementor']],'elementor_remove_element'=>['label'=>'Remove Element','description'=>'Removes an element and all its children from the page.','badges'=>['write','destructive','elementor']],'elementor_duplicate_element'=>['label'=>'Duplicate Element','description'=>'Creates a deep copy of an element.','badges'=>['write','elementor']],'elementor_move_element'=>['label'=>'Move Element','description'=>'Moves an element to a new parent or position.','badges'=>['write','elementor']]]],
'elementor_widgets'=>['label'=>'Elementor — Widgets','tools'=>['elementor_add_widget'=>['label'=>'Add Widget','description'=>'Adds any widget type to a container with full settings control.','badges'=>['write','elementor']],'elementor_update_widget'=>['label'=>'Update Widget','description'=>'Updates settings on an existing widget element.','badges'=>['write','elementor']],'elementor_add_heading'=>['label'=>'Add Heading','description'=>'Adds a heading widget with simplified parameters.','badges'=>['write','elementor']],'elementor_add_text'=>['label'=>'Add Text','description'=>'Adds a text editor widget.','badges'=>['write','elementor']],'elementor_add_image'=>['label'=>'Add Image','description'=>'Adds an image widget by media library ID or URL.','badges'=>['write','elementor']],'elementor_add_button'=>['label'=>'Add Button','description'=>'Adds a button widget with text and link.','badges'=>['write','elementor']]]],
'elementor_templates'=>['label'=>'Elementor — Templates','tools'=>['elementor_save_as_template'=>['label'=>'Save as Template','description'=>'Saves the current page content as a reusable Elementor template.','badges'=>['write','elementor']],'elementor_apply_template'=>['label'=>'Apply Template','description'=>'Applies a saved template to a target page.','badges'=>['write','elementor']],'elementor_list_templates'=>['label'=>'List Templates','description'=>'Lists all saved Elementor templates.','badges'=>['read','elementor']]]],
'elementor_global'=>['label'=>'Elementor — Global Settings','tools'=>['elementor_get_global_colors'=>['label'=>'Get Global Colors','description'=>'Returns the site-wide Elementor color palette.','badges'=>['read','elementor']],'elementor_set_global_color'=>['label'=>'Set Global Color','description'=>'Updates a specific global color in the Elementor palette.','badges'=>['write','elementor']],'elementor_get_global_fonts'=>['label'=>'Get Global Fonts','description'=>'Returns the global typography presets.','badges'=>['read','elementor']],'elementor_get_kit_settings'=>['label'=>'Get Kit Settings','description'=>'Returns the full Elementor kit/site settings.','badges'=>['read','elementor']],'elementor_update_kit_settings'=>['label'=>'Update Kit Settings','description'=>'Updates Elementor kit/site settings.','badges'=>['write','elementor']]]],
'elementor_query'=>['label'=>'Elementor — Query & Discovery','tools'=>['elementor_list_widgets'=>['label'=>'List Widgets','description'=>'Lists all widget types and names on a page.','badges'=>['read','elementor']],'elementor_get_element'=>['label'=>'Get Element','description'=>'Returns settings of a specific element by ID.','badges'=>['read','elementor']],'elementor_clone_page'=>['label'=>'Clone Page','description'=>'Clones an Elementor page to a new post.','badges'=>['write','elementor']],'elementor_replace_text'=>['label'=>'Replace Text','description'=>'Bulk-replaces text content across all widgets on a page.','badges'=>['write','elementor']],'elementor_get_page_outline'=>['label'=>'Get Page Outline','description'=>'Returns a simplified structural outline of an Elementor page.','badges'=>['read','elementor']]]],
'file_manager'=>['label'=>'File Manager','tools'=>['fs_list_dir'=>['label'=>'List Directory','description'=>'List files and directories in the uploads folder. Sandboxed to /wp-content/uploads/.','badges'=>['read']],'fs_read_file'=>['label'=>'Read File','description'=>'Read a file from the uploads directory. Returns text or base64 for binary.','badges'=>['read']],'fs_delete_file'=>['label'=>'Delete File','description'=>'Delete a file from the uploads directory.','badges'=>['write','destructive']],'fs_delete_dir'=>['label'=>'Delete Directory','description'=>'Delete a directory and all its contents from uploads. Cannot delete uploads root.','badges'=>['admin','destructive']],'fs_uploads_info'=>['label'=>'Uploads Info','description'=>'Get uploads directory path, URL, and total disk usage.','badges'=>['read']]]],
'email'=>['label'=>'Email','tools'=>['email_get_config'=>['label'=>'Get Email Config','description'=>'Get WordPress email configuration: admin email, SMTP settings, active mail plugins.','badges'=>['read']],'email_send_test'=>['label'=>'Send Test Email','description'=>'Send a test email to verify WordPress mail is working.','badges'=>['admin']],'email_list_log'=>['label'=>'List Email Log','description'=>'List recent emails from WP Mail SMTP log (if plugin is active).','badges'=>['read']]]],
'transients'=>['label'=>'Transients','tools'=>['transient_list'=>['label'=>'List Transients','description'=>'List WordPress transients, optionally filtered by key prefix or search term.','badges'=>['read']],'transient_get'=>['label'=>'Get Transient','description'=>'Get a WordPress transient value by key.','badges'=>['read']],'transient_set'=>['label'=>'Set Transient','description'=>'Set a WordPress transient with optional expiration (seconds).','badges'=>['admin']],'transient_delete'=>['label'=>'Delete Transient','description'=>'Delete a WordPress transient by key.','badges'=>['admin']],'transient_flush_expired'=>['label'=>'Flush Expired Transients','description'=>'Delete all expired transients from the database.','badges'=>['admin']]]],
'widget_areas'=>['label'=>'Widget Areas','tools'=>['widget_list_areas'=>['label'=>'List Widget Areas','description'=>'List all registered WordPress widget areas (sidebars).','badges'=>['read']],'widget_get_area'=>['label'=>'Get Widget Area','description'=>'Get all widgets in a specific widget area with their settings.','badges'=>['read']],'widget_list_registered'=>['label'=>'List Registered Widgets','description'=>'List all registered WordPress widget types.','badges'=>['read']],'widget_update_area'=>['label'=>'Update Widget Area','description'=>'Reorder or set the widget list for a widget area.','badges'=>['admin']],'widget_remove'=>['label'=>'Remove Widget','description'=>'Remove a specific widget from a widget area.','badges'=>['admin','destructive']]]],
'cli'=>['label'=>'WP-CLI','tools'=>['cli_available'=>['label'=>'CLI Available','description'=>'Check if WP-CLI is available on the server and list allowed commands.','badges'=>['read']],'cli_run'=>['label'=>'Run CLI Command','description'=>'Run a WP-CLI command (admin scope). Allowed: cache, cron, db, media, option, plugin, post, rewrite, theme, transient, user, widget, elementor, wc, yoast.','badges'=>['admin']]]],
'raw_sql'=>['label'=>'Raw SQL','tools'=>['sql_query'=>['label'=>'SQL Query','description'=>'Run a SELECT query against the WordPress database. Must be enabled in Settings.','badges'=>['admin']],'sql_list_tables'=>['label'=>'List Tables','description'=>'List WordPress database tables with row count and size.','badges'=>['admin']],'sql_describe_table'=>['label'=>'Describe Table','description'=>'Describe the columns of a WordPress database table.','badges'=>['admin']]]],
'theme_discovery'=>['label'=>'Theme Discovery','tools'=>['theme_elementor_support'=>['label'=>'Elementor Support','description'=>'Check what Elementor features the active theme supports: full-width, canvas, templates, Pro theme builder.','badges'=>['read']],'theme_widget_areas'=>['label'=>'Theme Widget Areas','description'=>'List all widget areas registered by the active theme.','badges'=>['read']],'theme_custom_post_types'=>['label'=>'Custom Post Types','description'=>'List all custom post types registered by the theme and active plugins.','badges'=>['read']],'theme_taxonomies'=>['label'=>'Theme Taxonomies','description'=>'List all public taxonomies registered by the theme and active plugins.','badges'=>['read']],'theme_elementor_tokens'=>['label'=>'Elementor Kit Tokens','description'=>'Get Elementor kit design tokens: global colors (with CSS vars), typography presets.','badges'=>['read']],'theme_woodmart_elements'=>['label'=>'WoodMart Elements','description'=>'Discover WoodMart custom Elementor widgets, shortcodes, and widget areas. Returns nothing if WoodMart is not active.','badges'=>['read']],'theme_page_templates'=>['label'=>'Page Templates','description'=>'List all page templates in the active theme, including Elementor canvas and full-width.','badges'=>['read']]]],
];
}
public function getAllToolSlugs(): array {
$slugs = [];
foreach ($this->getAllToolsGrouped() as $cat)
foreach ($cat['tools'] as $slug => $_) $slugs[] = $slug;
return $slugs;
}
}