Skip to main content
Glama
index.ts12.6 kB
/** * Themes Tools Registration * * Handles: Theme management (install, activate, update, delete, revert) * * Uses custom WP Navigator REST endpoints (/wpnav/v1/themes/*) that bypass * WordPress core REST API limitations. * * @package WP_Navigator_Pro * @since 1.3.0 */ import { toolRegistry, ToolCategory } from '../../tool-registry/index.js'; import { validateRequired } from '../../tool-registry/utils.js'; /** * Register theme management tools */ export function registerThemeTools() { // ============================================================================ // LIST THEMES // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_list_themes', description: 'List all installed WordPress themes. Returns theme identifier in "stylesheet" field (use this exact value for get/activate/delete operations), name, version, and status (active/inactive). Stylesheet is typically a lowercase hyphenated name (e.g., "twentytwentyfour", "developer-starter").', inputSchema: { type: 'object', properties: { status: { type: 'string', description: 'Optional filter by status (e.g., "active" or "inactive"). If omitted or set to "all", returns all themes.' }, }, required: [], }, }, handler: async (args, context) => { const params = new URLSearchParams(); if (args.status && args.status !== 'all') { params.append('status', args.status); } const qs = params.toString(); const endpoint = qs ? `/wp/v2/themes?${qs}` : '/wp/v2/themes'; const themes = await context.wpRequest(endpoint); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(themes, null, 2)) }], }; }, category: ToolCategory.THEMES, }); // ============================================================================ // GET THEME // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_get_theme', description: 'Get details about a specific theme by slug. Returns full metadata including description, author, and version.', inputSchema: { type: 'object', properties: { stylesheet: { type: 'string', description: 'Theme stylesheet from wpnav_list_themes "stylesheet" field (e.g., "twentytwentyfour")' }, }, required: ['stylesheet'], }, }, handler: async (args, context) => { validateRequired(args, ['stylesheet']); const theme = await context.wpRequest(`/wp/v2/themes/${args.stylesheet}`); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(theme, null, 2)) }], }; }, category: ToolCategory.THEMES, }); // ============================================================================ // INSTALL THEME // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_install_theme', description: 'Install a WordPress theme from WordPress.org by slug. Optionally activate after installation. Uses WP Navigator custom endpoint.', inputSchema: { type: 'object', properties: { slug: { type: 'string', description: 'Theme slug from WordPress.org (e.g., "flavor", "flavor-developer")' }, activate: { type: 'boolean', description: 'Activate theme after installation (default: false)', default: false }, }, required: ['slug'], }, }, handler: async (args, context) => { try { validateRequired(args, ['slug']); const result = await context.wpRequest('/wpnav/v1/themes/install', { method: 'POST', body: JSON.stringify({ slug: args.slug, activate: args.activate || false, }), }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(result, null, 2)) }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'INSTALL_FAILED', message: errorMessage, context: { resource_type: 'theme', slug: args.slug, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check theme slug exists on WordPress.org', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.THEMES, }); // ============================================================================ // ACTIVATE THEME // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_activate_theme', description: 'Activate a WordPress theme by stylesheet. The theme must already be installed. Use wpnav_list_themes to see available themes.', inputSchema: { type: 'object', properties: { stylesheet: { type: 'string', description: 'Theme stylesheet from wpnav_list_themes "stylesheet" field (e.g., "twentytwentyfour")' }, }, required: ['stylesheet'], }, }, handler: async (args, context) => { try { validateRequired(args, ['stylesheet']); const result = await context.wpRequest('/wpnav/v1/themes/activate', { method: 'POST', body: JSON.stringify({ stylesheet: args.stylesheet, }), }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(result, null, 2)) }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'ACTIVATE_FAILED', message: errorMessage, context: { resource_type: 'theme', stylesheet: args.stylesheet, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check theme is installed with wpnav_list_themes', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.THEMES, }); // ============================================================================ // UPDATE THEME // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_update_theme', description: 'Update an installed WordPress theme to the latest version. The theme must be installed from WordPress.org.', inputSchema: { type: 'object', properties: { stylesheet: { type: 'string', description: 'Theme stylesheet from wpnav_list_themes "stylesheet" field (e.g., "flavor")' }, }, required: ['stylesheet'], }, }, handler: async (args, context) => { try { validateRequired(args, ['stylesheet']); const result = await context.wpRequest('/wpnav/v1/themes/update', { method: 'POST', body: JSON.stringify({ stylesheet: args.stylesheet, }), }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(result, null, 2)) }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'UPDATE_FAILED', message: errorMessage, context: { resource_type: 'theme', stylesheet: args.stylesheet, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check theme is installed with wpnav_list_themes', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.THEMES, }); // ============================================================================ // DELETE THEME // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_delete_theme', description: 'Delete an installed WordPress theme. The theme must not be active - switch to a different theme first. Cannot delete parent theme while child theme is active.', inputSchema: { type: 'object', properties: { stylesheet: { type: 'string', description: 'Theme stylesheet from wpnav_list_themes "stylesheet" field (e.g., "flavor")' }, }, required: ['stylesheet'], }, }, handler: async (args, context) => { try { validateRequired(args, ['stylesheet']); const result = await context.wpRequest(`/wpnav/v1/themes/${args.stylesheet}`, { method: 'DELETE', }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(result, null, 2)) }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'DELETE_FAILED', message: errorMessage, context: { resource_type: 'theme', stylesheet: args.stylesheet, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check theme is not active and exists with wpnav_list_themes', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.THEMES, }); // ============================================================================ // REVERT THEME // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_revert_theme', description: 'Revert to the previously active theme. WordPress stores the previous theme when switching. Use this to quickly undo a theme change.', inputSchema: { type: 'object', properties: {}, required: [], }, }, handler: async (args, context) => { try { const result = await context.wpRequest('/wpnav/v1/themes/revert', { method: 'POST', }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(result, null, 2)) }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'REVERT_FAILED', message: errorMessage, context: { resource_type: 'theme', suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Ensure a previous theme exists to revert to', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.THEMES, }); }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server