├── wpmcp-plugin ├── wpmcp.zip ├── wpmcp-plugin.php ├── includes │ ├── shortcodes.php │ ├── performance.php │ ├── database.php │ ├── cron.php │ ├── security.php │ └── filesystem.php └── README.md ├── .github └── FUNDING.yml ├── .env.example ├── tsconfig.json ├── .gitignore ├── src ├── config │ └── wordpress.ts ├── tools │ ├── shortcodes.ts │ ├── pages.ts │ ├── cron.ts │ ├── database.ts │ ├── widgets.ts │ ├── security.ts │ ├── user-roles.ts │ ├── performance.ts │ ├── media.ts │ ├── backup.ts │ ├── menus.ts │ ├── plugins.ts │ ├── custom-post-types.ts │ ├── seo.ts │ ├── blocks.ts │ ├── posts.ts │ └── filesystem.ts ├── utils │ ├── helpers.ts │ └── api.ts ├── types │ └── wordpress.ts └── index.ts ├── logo.svg ├── LICENSE ├── package.json ├── RELEASE_SUMMARY.md ├── WPMCP_TOOLS.MD └── README.md /wpmcp-plugin/wpmcp.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaheesAhmed/wordpress-mcp-server/HEAD/wpmcp-plugin/wpmcp.zip -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # These are supported funding model platforms 4 | 5 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 6 | patreon: raheesahmed 7 | custom: https://www.upwork.com/freelancers/raheesahmed 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # WordPress MCP Server Configuration 2 | 3 | # Your WordPress site URL (without trailing slash) 4 | WORDPRESS_URL=https://yourblog.com 5 | 6 | # WordPress admin username 7 | WORDPRESS_USERNAME=admin 8 | 9 | # WordPress Application Password (generate in WordPress admin: Users → Profile → Application Passwords) 10 | # Format: xxxx xxxx xxxx xxxx xxxx xxxx 11 | WORDPRESS_PASSWORD=your-application-password-here -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "node", 6 | "lib": ["ES2022"], 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "declaration": true, 15 | "declarationMap": true, 16 | "sourceMap": true 17 | }, 18 | "include": ["src/**/*"], 19 | "exclude": ["node_modules", "dist"] 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | dist/ 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | .env.*.local 11 | 12 | # Logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # OS files 19 | .DS_Store 20 | Thumbs.db 21 | 22 | # IDE 23 | .vscode/ 24 | .idea/ 25 | *.swp 26 | *.swo 27 | *~ 28 | 29 | # TypeScript 30 | *.tsbuildinfo 31 | 32 | ARCHITECTURE_DESIGN.md 33 | FEATURE_GAP_ANALYSIS.md 34 | IMPLEMENTATION_SUMMARY.md 35 | RECOMMENDATIONS.md 36 | QuickMCP.md 37 | NEXT_FEATURES_PRIORITY.md 38 | -------------------------------------------------------------------------------- /src/config/wordpress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Configuration 3 | * Loads environment variables and provides configuration 4 | */ 5 | 6 | export const config = { 7 | url: process.env.WORDPRESS_URL || '', 8 | username: process.env.WORDPRESS_USERNAME || '', 9 | password: process.env.WORDPRESS_PASSWORD || '', 10 | 11 | // Create Basic Auth token 12 | getAuthToken: (): string => { 13 | const { username, password } = config; 14 | return Buffer.from(`${username}:${password}`).toString('base64'); 15 | }, 16 | 17 | // Validate configuration 18 | validate: (): void => { 19 | if (!config.url || !config.username || !config.password) { 20 | console.error('❌ Error: Missing required environment variables'); 21 | console.error('Please set: WORDPRESS_URL, WORDPRESS_USERNAME, WORDPRESS_PASSWORD'); 22 | process.exit(1); 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | WPMCP 15 | 16 | 17 | WordPress MCP Server 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Rahees Ahmed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /wpmcp-plugin/wpmcp-plugin.php: -------------------------------------------------------------------------------- 1 | 'GET', 19 | 'callback' => [$this, 'list_shortcodes'], 20 | 'permission_callback' => [$this, 'check_permissions'] 21 | ]); 22 | 23 | register_rest_route($ns, '/shortcodes/execute', [ 24 | 'methods' => 'POST', 25 | 'callback' => [$this, 'execute_shortcode'], 26 | 'permission_callback' => [$this, 'check_permissions'] 27 | ]); 28 | } 29 | 30 | public function check_permissions() { 31 | return current_user_can('manage_options'); 32 | } 33 | 34 | public function list_shortcodes() { 35 | global $shortcode_tags; 36 | 37 | $shortcodes = []; 38 | foreach ($shortcode_tags as $tag => $callback) { 39 | $shortcodes[] = [ 40 | 'tag' => $tag, 41 | 'callback' => is_string($callback) ? $callback : 'Closure' 42 | ]; 43 | } 44 | 45 | return ['shortcodes' => $shortcodes, 'total' => count($shortcodes)]; 46 | } 47 | 48 | public function execute_shortcode($request) { 49 | $content = $request->get_param('content'); 50 | if (empty($content)) return new WP_Error('missing_content', 'Content required'); 51 | 52 | return ['input' => $content, 'output' => do_shortcode($content)]; 53 | } 54 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wpmcp", 3 | "version": "3.0.0", 4 | "description": "MCP server for WordPress with 190+ tools - complete WordPress control including WooCommerce, Gutenberg, SEO, Security, Performance, Backup, and User Roles - 99% developer task coverage", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "type": "module", 8 | "bin": { 9 | "wordpress-mcp-server": "dist/index.js" 10 | }, 11 | "files": [ 12 | "dist", 13 | "README.md", 14 | "LICENSE" 15 | ], 16 | "scripts": { 17 | "build": "tsc", 18 | "start": "node dist/index.js", 19 | "dev": "tsx src/index.ts", 20 | "prepublishOnly": "npm run build", 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "keywords": [ 24 | "mcp", 25 | "wordpress", 26 | "ai", 27 | "claude", 28 | "wpagent", 29 | "model-context-protocol", 30 | "wordpress-api", 31 | "wordpress-rest-api", 32 | "wordpress-automation", 33 | "ai-agent", 34 | "langgraph", 35 | "anthropic", 36 | "cms", 37 | "content-management", 38 | "wordpress-developer", 39 | "theme-development", 40 | "plugin-management", 41 | "wordpress-database", 42 | "file-system", 43 | "child-themes" 44 | ], 45 | "author": { 46 | "name": "Rahees Ahmed", 47 | "url": "https://github.com/RaheesAhmed" 48 | }, 49 | "license": "MIT", 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/RaheesAhmed/wordpress-mcp-server.git" 53 | }, 54 | "homepage": "https://github.com/RaheesAhmed/wordpress-mcp-server#readme", 55 | "bugs": { 56 | "url": "https://github.com/RaheesAhmed/wordpress-mcp-server/issues" 57 | }, 58 | "engines": { 59 | "node": ">=18.0.0" 60 | }, 61 | "dependencies": { 62 | "node-fetch": "^3.3.2", 63 | "quickmcp-sdk": "^1.0.2" 64 | }, 65 | "devDependencies": { 66 | "@types/node": "^20.11.5", 67 | "tsx": "^4.7.0", 68 | "typescript": "^5.3.3" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RELEASE_SUMMARY.md: -------------------------------------------------------------------------------- 1 | # WordPress MCP Server v3.0 - Release Notes 2 | 3 | **Version:** 3.0.0 4 | **Release Date:** October 25, 2025 5 | **Type:** Major Update 6 | 7 | --- 8 | 9 | ## What's New 10 | 11 | WordPress MCP Server v3.0 expands from 49 to **190 tools**, enabling AI to handle 99% of WordPress development tasks. 12 | 13 | --- 14 | 15 | ## New Features 16 | 17 | ### **Developer Tools (66 tools)** 18 | - File System Operations (8) - Read/write files with automatic backups 19 | - Theme Management (13) - Create child themes, customize theme.json 20 | - Plugin Management (10) - Activate/deactivate, modify code 21 | - Menu Management (8) - Build navigation menus 22 | - Custom Post Types (7) - Manage content structures 23 | - Shortcodes, Cron, Widgets, Database (20) - Advanced WordPress APIs 24 | 25 | ### **E-commerce & Modern WordPress (27 tools)** 26 | - WooCommerce (15) - Products, orders, inventory, coupons, analytics 27 | - Gutenberg (12) - 95 block types, 156 patterns, reusable blocks 28 | 29 | ### **Production Features (48 tools)** 30 | - Advanced SEO (10) - Sitemaps, redirects, schema markup, Open Graph 31 | - Security (7) - Site health, updates, integrity checks 32 | - Performance (8) - Cache management, database optimization 33 | - Advanced Media (5) - Image optimization, WebP conversion, cleanup 34 | - Backup & Migration (10) - Full/partial backups, restore, clone 35 | - User Roles (8) - Custom roles, capabilities, permissions 36 | 37 | --- 38 | 39 | ## Installation 40 | 41 | ```bash 42 | npm install -g wpmcp@3.0.0 43 | ``` 44 | 45 | Upload `wpmcp-plugin` folder to WordPress and activate. 46 | 47 | --- 48 | 49 | ## What AI Can Do 50 | 51 | - Build complete WordPress + WooCommerce sites 52 | - Develop custom themes with Gutenberg 53 | - Manage plugins and code 54 | - Database operations 55 | - Advanced SEO optimization 56 | - Backup and disaster recovery 57 | - Performance optimization 58 | - User role management 59 | 60 | --- 61 | 62 | ## Breaking Changes 63 | 64 | None. All v2.x tools remain compatible. 65 | 66 | --- 67 | 68 | ## Requirements 69 | 70 | - Node.js 18+ 71 | - WordPress 5.0+ 72 | - PHP 7.2+ 73 | 74 | --- 75 | 76 | ## Documentation 77 | 78 | - [README.md](./README.md) - Quick start 79 | - [WPMCP_TOOLS.MD](./WPMCP_TOOLS.MD) - All 190 tools 80 | - [wpmcp-plugin/README.md](./wpmcp-plugin/README.md) - Plugin setup 81 | 82 | --- 83 | 84 | **Total:** 190 tools | 99% coverage | Production ready 85 | -------------------------------------------------------------------------------- /src/tools/shortcodes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Shortcode System Tools 3 | * List and execute WordPress shortcodes 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerShortcodeTools(server: any) { 10 | 11 | // ========== SHORTCODE SYSTEM ========== 12 | 13 | /** 14 | * List all registered shortcodes 15 | */ 16 | server.tool('wordpress_list_shortcodes', async () => { 17 | try { 18 | const result = await callCustomAPI('wp-json/wpmcp/v1/shortcodes/list', 'GET'); 19 | 20 | return Responses.success( 21 | { 22 | shortcodes: result.shortcodes.map((sc: any) => ({ 23 | tag: sc.tag, 24 | callback: sc.callback 25 | })), 26 | total: result.total 27 | }, 28 | `📝 Retrieved ${result.total} registered shortcodes` 29 | ); 30 | } catch (error) { 31 | return Responses.error(`Failed to list shortcodes: ${(error as Error).message}`); 32 | } 33 | }, { 34 | description: 'Get all registered WordPress shortcodes', 35 | schema: {} 36 | }); 37 | 38 | /** 39 | * Execute a shortcode 40 | */ 41 | server.tool('wordpress_execute_shortcode', async (args: any) => { 42 | const { content } = args; 43 | 44 | try { 45 | const result = await callCustomAPI('wp-json/wpmcp/v1/shortcodes/execute', 'POST', { 46 | content 47 | }); 48 | 49 | return Responses.success( 50 | { 51 | input: result.input, 52 | output: result.output 53 | }, 54 | `✅ Executed shortcode` 55 | ); 56 | } catch (error) { 57 | return Responses.error(`Failed to execute shortcode: ${(error as Error).message}`); 58 | } 59 | }, { 60 | description: 'Process and execute a shortcode string', 61 | schema: { 62 | content: 'string' 63 | } 64 | }); 65 | 66 | /** 67 | * Check if shortcode exists 68 | */ 69 | server.tool('wordpress_shortcode_exists', async (args: any) => { 70 | const { tag } = args; 71 | 72 | try { 73 | const result = await callCustomAPI('wp-json/wpmcp/v1/shortcodes/list', 'GET'); 74 | const exists = result.shortcodes.some((sc: any) => sc.tag === tag); 75 | 76 | return Responses.success( 77 | { 78 | tag, 79 | exists 80 | }, 81 | exists ? `✅ Shortcode [${tag}] exists` : `❌ Shortcode [${tag}] not found` 82 | ); 83 | } catch (error) { 84 | return Responses.error(`Failed to check shortcode: ${(error as Error).message}`); 85 | } 86 | }, { 87 | description: 'Check if a shortcode is registered', 88 | schema: { 89 | tag: 'string' 90 | } 91 | }); 92 | } -------------------------------------------------------------------------------- /src/tools/pages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Pages Tools 3 | */ 4 | 5 | import { Responses } from 'quickmcp-sdk'; 6 | import { callWordPressAPI } from '../utils/api.js'; 7 | import { formatPage, buildQueryString } from '../utils/helpers.js'; 8 | 9 | export function registerPageTools(server: any) { 10 | 11 | server.tool('wordpress_create_page', async (args: any) => { 12 | const { title, content, status = 'draft', parent = 0, template = '' } = args; 13 | 14 | try { 15 | const page = await callWordPressAPI('/pages', 'POST', { title, content, status, parent, template }); 16 | return Responses.success(formatPage(page), `✅ Created page: "${title}"`); 17 | } catch (error) { 18 | return Responses.error(`Failed to create page: ${(error as Error).message}`); 19 | } 20 | }, { 21 | description: 'Create a new WordPress page with hierarchy support', 22 | schema: { title: 'string', content: 'string' } 23 | }); 24 | 25 | server.tool('wordpress_update_page', async (args: any) => { 26 | const { pageId, updates } = args; 27 | 28 | try { 29 | const page = await callWordPressAPI(`/pages/${pageId}`, 'PUT', updates); 30 | return Responses.success(formatPage(page), `✅ Updated page ID ${pageId}`); 31 | } catch (error) { 32 | return Responses.error(`Failed to update page: ${(error as Error).message}`); 33 | } 34 | }, { 35 | description: 'Update an existing page', 36 | schema: { pageId: 'number', updates: 'object' } 37 | }); 38 | 39 | server.tool('wordpress_get_pages', async (args: any) => { 40 | const { perPage = 10, page = 1, parent, orderby = 'menu_order' } = args; 41 | 42 | try { 43 | const params: any = { per_page: perPage, page, orderby }; 44 | if (parent !== undefined) params.parent = parent; 45 | 46 | const queryString = buildQueryString(params); 47 | const pages = await callWordPressAPI(`/pages?${queryString}`); 48 | 49 | return Responses.success( 50 | { pages: pages.map(formatPage), count: pages.length }, 51 | `📄 Retrieved ${pages.length} pages` 52 | ); 53 | } catch (error) { 54 | return Responses.error(`Failed to get pages: ${(error as Error).message}`); 55 | } 56 | }, { 57 | description: 'Get pages with hierarchy and ordering', 58 | schema: { perPage: 'number', page: 'number' } 59 | }); 60 | 61 | server.tool('wordpress_delete_page', async (args: any) => { 62 | const { pageId, force = false } = args; 63 | 64 | try { 65 | const endpoint = force ? `/pages/${pageId}?force=true` : `/pages/${pageId}`; 66 | await callWordPressAPI(endpoint, 'DELETE'); 67 | return Responses.success({ id: pageId, deleted: true }, `✅ Deleted page ID ${pageId}`); 68 | } catch (error) { 69 | return Responses.error(`Failed to delete page: ${(error as Error).message}`); 70 | } 71 | }, { 72 | description: 'Delete a page', 73 | schema: { pageId: 'number', force: 'boolean' } 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper Functions 3 | * Utility functions used across the application 4 | */ 5 | 6 | /** 7 | * Format WordPress object for response 8 | */ 9 | export function formatPost(post: any) { 10 | return { 11 | id: post.id, 12 | title: post.title?.rendered || post.title, 13 | content: post.content?.rendered || post.content, 14 | excerpt: post.excerpt?.rendered || post.excerpt, 15 | url: post.link, 16 | status: post.status, 17 | date: post.date, 18 | modified: post.modified, 19 | author: post.author, 20 | categories: post.categories || [], 21 | tags: post.tags || [], 22 | featured_media: post.featured_media || 0 23 | }; 24 | } 25 | 26 | /** 27 | * Format WordPress page for response 28 | */ 29 | export function formatPage(page: any) { 30 | return { 31 | id: page.id, 32 | title: page.title?.rendered || page.title, 33 | content: page.content?.rendered || page.content, 34 | url: page.link, 35 | status: page.status, 36 | date: page.date, 37 | modified: page.modified, 38 | parent: page.parent || 0, 39 | menu_order: page.menu_order || 0, 40 | template: page.template || '' 41 | }; 42 | } 43 | 44 | /** 45 | * Format media object for response 46 | */ 47 | export function formatMedia(media: any) { 48 | return { 49 | id: media.id, 50 | title: media.title?.rendered || media.title, 51 | url: media.source_url, 52 | alt_text: media.alt_text || '', 53 | caption: media.caption?.rendered || media.caption || '', 54 | media_type: media.media_type, 55 | mime_type: media.mime_type, 56 | date: media.date, 57 | width: media.media_details?.width || 0, 58 | height: media.media_details?.height || 0 59 | }; 60 | } 61 | 62 | /** 63 | * Format user object for response (safe - no passwords) 64 | */ 65 | export function formatUser(user: any) { 66 | return { 67 | id: user.id, 68 | username: user.username || user.slug, 69 | name: user.name, 70 | email: user.email, 71 | roles: user.roles || [], 72 | url: user.url || '', 73 | description: user.description || '', 74 | avatar_urls: user.avatar_urls || {} 75 | }; 76 | } 77 | 78 | /** 79 | * Format comment for response 80 | */ 81 | export function formatComment(comment: any) { 82 | return { 83 | id: comment.id, 84 | post: comment.post, 85 | author_name: comment.author_name, 86 | author_email: comment.author_email, 87 | content: comment.content?.rendered || comment.content, 88 | date: comment.date, 89 | status: comment.status, 90 | parent: comment.parent || 0 91 | }; 92 | } 93 | 94 | /** 95 | * Build query string from parameters 96 | */ 97 | export function buildQueryString(params: Record): string { 98 | const searchParams = new URLSearchParams(); 99 | Object.entries(params).forEach(([key, value]) => { 100 | if (value !== undefined && value !== null) { 101 | searchParams.append(key, String(value)); 102 | } 103 | }); 104 | return searchParams.toString(); 105 | } 106 | -------------------------------------------------------------------------------- /wpmcp-plugin/includes/performance.php: -------------------------------------------------------------------------------- 1 | 'POST', 'callback' => [$this, 'clear_cache'], 'permission_callback' => [$this, 'check_permissions']]); 18 | register_rest_route($ns, '/performance/optimize-db', ['methods' => 'POST', 'callback' => [$this, 'optimize_database'], 'permission_callback' => [$this, 'check_permissions']]); 19 | register_rest_route($ns, '/performance/cleanup', ['methods' => 'POST', 'callback' => [$this, 'cleanup_database'], 'permission_callback' => [$this, 'check_permissions']]); 20 | register_rest_route($ns, '/performance/flush-rewrites', ['methods' => 'POST', 'callback' => [$this, 'flush_rewrites'], 'permission_callback' => [$this, 'check_permissions']]); 21 | } 22 | 23 | public function check_permissions() { 24 | return current_user_can('manage_options'); 25 | } 26 | 27 | public function clear_cache() { 28 | global $wpdb; 29 | 30 | $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_%'"); 31 | 32 | if (function_exists('wp_cache_flush')) { 33 | wp_cache_flush(); 34 | } 35 | 36 | return ['cleared' => ['transients', 'object_cache'], 'success' => true]; 37 | } 38 | 39 | public function optimize_database() { 40 | global $wpdb; 41 | 42 | $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); 43 | $optimized = 0; 44 | 45 | foreach ($tables as $table) { 46 | $wpdb->query("OPTIMIZE TABLE `{$table[0]}`"); 47 | $optimized++; 48 | } 49 | 50 | return ['tables' => $optimized, 'success' => true]; 51 | } 52 | 53 | public function cleanup_database($request) { 54 | global $wpdb; 55 | 56 | $removed = ['revisions' => 0, 'auto_drafts' => 0, 'spam' => 0, 'trash' => 0]; 57 | 58 | if ($request->get_param('revisions')) { 59 | $removed['revisions'] = $wpdb->query("DELETE FROM {$wpdb->posts} WHERE post_type = 'revision'"); 60 | } 61 | 62 | if ($request->get_param('autodrafts')) { 63 | $removed['auto_drafts'] = $wpdb->query("DELETE FROM {$wpdb->posts} WHERE post_status = 'auto-draft'"); 64 | } 65 | 66 | if ($request->get_param('spam')) { 67 | $removed['spam'] = $wpdb->query("DELETE FROM {$wpdb->comments} WHERE comment_approved = 'spam'"); 68 | } 69 | 70 | if ($request->get_param('trash')) { 71 | $removed['trash'] = $wpdb->query("DELETE FROM {$wpdb->posts} WHERE post_status = 'trash'"); 72 | } 73 | 74 | return ['removed' => $removed, 'total' => array_sum($removed)]; 75 | } 76 | 77 | public function flush_rewrites() { 78 | flush_rewrite_rules(); 79 | return ['success' => true]; 80 | } 81 | } -------------------------------------------------------------------------------- /wpmcp-plugin/includes/database.php: -------------------------------------------------------------------------------- 1 | 'GET', 'callback' => [$this, 'list_tables'], 'permission_callback' => [$this, 'check_permissions']]); 18 | register_rest_route($ns, '/database/query', ['methods' => 'POST', 'callback' => [$this, 'execute_query'], 'permission_callback' => [$this, 'check_permissions']]); 19 | register_rest_route($ns, '/database/option', ['methods' => 'GET', 'callback' => [$this, 'get_option_value'], 'permission_callback' => [$this, 'check_permissions']]); 20 | register_rest_route($ns, '/database/option', ['methods' => 'POST', 'callback' => [$this, 'update_option_value'], 'permission_callback' => [$this, 'check_permissions']]); 21 | } 22 | 23 | public function check_permissions() { 24 | return current_user_can('manage_options'); 25 | } 26 | 27 | public function list_tables() { 28 | global $wpdb; 29 | 30 | $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); 31 | $table_list = []; 32 | 33 | foreach ($tables as $table) { 34 | $table_name = $table[0]; 35 | $table_list[] = [ 36 | 'name' => $table_name, 37 | 'rows' => (int)$wpdb->get_var("SELECT COUNT(*) FROM `$table_name`"), 38 | 'is_wp_table' => strpos($table_name, $wpdb->prefix) === 0 39 | ]; 40 | } 41 | 42 | return ['tables' => $table_list, 'total' => count($table_list), 'prefix' => $wpdb->prefix]; 43 | } 44 | 45 | public function execute_query($request) { 46 | global $wpdb; 47 | 48 | $query = $request->get_param('query'); 49 | if (empty($query)) return new WP_Error('missing_query', 'Query required'); 50 | 51 | if (!preg_match('/^SELECT|^SHOW|^DESCRIBE|^EXPLAIN/i', trim($query))) { 52 | return new WP_Error('unsafe_query', 'Only SELECT, SHOW, DESCRIBE, EXPLAIN allowed'); 53 | } 54 | 55 | $results = $wpdb->get_results($query, ARRAY_A); 56 | if ($wpdb->last_error) return new WP_Error('query_error', $wpdb->last_error); 57 | 58 | return ['results' => $results, 'rows' => count($results), 'query' => $query]; 59 | } 60 | 61 | public function get_option_value($request) { 62 | $name = $request->get_param('name'); 63 | if (empty($name)) return new WP_Error('missing_name', 'Name required'); 64 | 65 | $value = get_option($name, null); 66 | return ['name' => $name, 'value' => $value, 'exists' => $value !== null]; 67 | } 68 | 69 | public function update_option_value($request) { 70 | $name = $request->get_param('name'); 71 | if (empty($name)) return new WP_Error('missing_name', 'Name required'); 72 | 73 | $updated = update_option($name, $request->get_param('value')); 74 | return ['name' => $name, 'updated' => $updated, 'value' => $request->get_param('value')]; 75 | } 76 | } -------------------------------------------------------------------------------- /wpmcp-plugin/includes/cron.php: -------------------------------------------------------------------------------- 1 | 'GET', 'callback' => [$this, 'list_cron_jobs'], 'permission_callback' => [$this, 'check_permissions']]); 18 | register_rest_route($ns, '/cron/schedule', ['methods' => 'POST', 'callback' => [$this, 'schedule_event'], 'permission_callback' => [$this, 'check_permissions']]); 19 | register_rest_route($ns, '/cron/unschedule', ['methods' => 'POST', 'callback' => [$this, 'unschedule_event'], 'permission_callback' => [$this, 'check_permissions']]); 20 | register_rest_route($ns, '/cron/run', ['methods' => 'POST', 'callback' => [$this, 'run_cron'], 'permission_callback' => [$this, 'check_permissions']]); 21 | } 22 | 23 | public function check_permissions() { 24 | return current_user_can('manage_options'); 25 | } 26 | 27 | public function list_cron_jobs() { 28 | $cron = _get_cron_array(); 29 | $jobs = []; 30 | 31 | foreach ($cron as $timestamp => $hooks) { 32 | foreach ($hooks as $hook => $events) { 33 | foreach ($events as $event) { 34 | $jobs[] = [ 35 | 'hook' => $hook, 36 | 'timestamp' => $timestamp, 37 | 'schedule' => $event['schedule'] ?? 'once', 38 | 'args' => $event['args'] ?? [], 39 | 'next_run' => date('Y-m-d H:i:s', $timestamp) 40 | ]; 41 | } 42 | } 43 | } 44 | 45 | return ['jobs' => $jobs, 'total' => count($jobs)]; 46 | } 47 | 48 | public function schedule_event($request) { 49 | $hook = $request->get_param('hook'); 50 | if (!$hook) return new WP_Error('missing_hook', 'Hook required'); 51 | 52 | $time = $request->get_param('timestamp') ? strtotime($request->get_param('timestamp')) : time(); 53 | $recurrence = $request->get_param('recurrence'); 54 | $args = $request->get_param('args') ?? []; 55 | 56 | if ($recurrence && $recurrence !== 'once') { 57 | wp_schedule_event($time, $recurrence, $hook, $args); 58 | } else { 59 | wp_schedule_single_event($time, $hook, $args); 60 | } 61 | 62 | return ['success' => true, 'hook' => $hook, 'next_run' => date('Y-m-d H:i:s', $time), 'recurrence' => $recurrence ?? 'once']; 63 | } 64 | 65 | public function unschedule_event($request) { 66 | $hook = $request->get_param('hook'); 67 | if (!$hook) return new WP_Error('missing_hook', 'Hook required'); 68 | 69 | $timestamp = wp_next_scheduled($hook, $request->get_param('args') ?? []); 70 | if ($timestamp) { 71 | wp_unschedule_event($timestamp, $hook, $request->get_param('args') ?? []); 72 | return ['success' => true, 'hook' => $hook]; 73 | } 74 | 75 | return new WP_Error('not_scheduled', 'Event not found'); 76 | } 77 | 78 | public function run_cron() { 79 | spawn_cron(); 80 | return ['success' => true, 'message' => 'Cron triggered']; 81 | } 82 | } -------------------------------------------------------------------------------- /wpmcp-plugin/includes/security.php: -------------------------------------------------------------------------------- 1 | 'GET', 'callback' => [$this, 'check_updates'], 'permission_callback' => [$this, 'check_permissions']]); 18 | register_rest_route($ns, '/security/debug-log', ['methods' => 'GET', 'callback' => [$this, 'get_debug_log'], 'permission_callback' => [$this, 'check_permissions']]); 19 | register_rest_route($ns, '/security/verify-core', ['methods' => 'GET', 'callback' => [$this, 'verify_core_files'], 'permission_callback' => [$this, 'check_permissions']]); 20 | register_rest_route($ns, '/security/system-info', ['methods' => 'GET', 'callback' => [$this, 'get_system_info'], 'permission_callback' => [$this, 'check_permissions']]); 21 | } 22 | 23 | public function check_permissions() { 24 | return current_user_can('manage_options'); 25 | } 26 | 27 | public function check_updates() { 28 | require_once ABSPATH . 'wp-admin/includes/update.php'; 29 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 30 | 31 | wp_update_plugins(); 32 | wp_update_themes(); 33 | 34 | $core = get_core_updates(); 35 | $plugins = get_plugin_updates(); 36 | $themes = get_theme_updates(); 37 | 38 | return [ 39 | 'core' => $core, 40 | 'plugins' => $plugins, 41 | 'themes' => $themes, 42 | 'totalUpdates' => count($core) + count($plugins) + count($themes) 43 | ]; 44 | } 45 | 46 | public function get_debug_log() { 47 | $debug_file = WP_CONTENT_DIR . '/debug.log'; 48 | 49 | if (!file_exists($debug_file)) { 50 | return ['exists' => false, 'content' => '']; 51 | } 52 | 53 | return ['exists' => true, 'content' => file_get_contents($debug_file), 'size' => filesize($debug_file)]; 54 | } 55 | 56 | public function verify_core_files() { 57 | require_once ABSPATH . 'wp-admin/includes/file.php'; 58 | 59 | $checksums = get_core_checksums(get_bloginfo('version'), get_locale()); 60 | if (!$checksums) return ['verified' => false, 'message' => 'Could not get checksums']; 61 | 62 | $modified = []; 63 | foreach ($checksums as $file => $checksum) { 64 | $filepath = ABSPATH . $file; 65 | if (file_exists($filepath) && md5_file($filepath) !== $checksum) { 66 | $modified[] = $file; 67 | } 68 | } 69 | 70 | return ['verified' => count($modified) === 0, 'modified' => $modified, 'total' => count($checksums)]; 71 | } 72 | 73 | public function get_system_info() { 74 | global $wpdb; 75 | 76 | return [ 77 | 'wp_version' => get_bloginfo('version'), 78 | 'php_version' => PHP_VERSION, 79 | 'mysql_version' => $wpdb->db_version(), 80 | 'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown', 81 | 'max_upload' => ini_get('upload_max_filesize'), 82 | 'memory_limit' => ini_get('memory_limit'), 83 | 'timezone' => wp_timezone_string() 84 | ]; 85 | } 86 | } -------------------------------------------------------------------------------- /src/tools/cron.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Cron & Scheduled Tasks Tools 3 | * Manage WordPress scheduled events and cron jobs 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerCronTools(server: any) { 10 | 11 | // ========== CRON JOB MANAGEMENT ========== 12 | 13 | /** 14 | * List all scheduled cron jobs 15 | */ 16 | server.tool('wordpress_list_cron_jobs', async () => { 17 | try { 18 | const result = await callCustomAPI('wp-json/wpmcp/v1/cron/list', 'GET'); 19 | 20 | return Responses.success( 21 | { 22 | jobs: result.jobs, 23 | total: result.total 24 | }, 25 | `⏰ Retrieved ${result.total} scheduled jobs` 26 | ); 27 | } catch (error) { 28 | return Responses.error(`Failed to list cron jobs: ${(error as Error).message}`); 29 | } 30 | }, { 31 | description: 'Get all scheduled WordPress cron jobs', 32 | schema: {} 33 | }); 34 | 35 | /** 36 | * Schedule a cron event 37 | */ 38 | server.tool('wordpress_schedule_event', async (args: any) => { 39 | const { hook, timestamp, recurrence = 'once', args: eventArgs = [] } = args; 40 | 41 | try { 42 | const result = await callCustomAPI('wp-json/wpmcp/v1/cron/schedule', 'POST', { 43 | hook, 44 | timestamp, 45 | recurrence, 46 | args: eventArgs 47 | }); 48 | 49 | return Responses.success( 50 | { 51 | hook: result.hook, 52 | nextRun: result.next_run, 53 | recurrence: result.recurrence, 54 | success: true 55 | }, 56 | `✅ Scheduled event: ${hook} (${result.recurrence})` 57 | ); 58 | } catch (error) { 59 | return Responses.error(`Failed to schedule event: ${(error as Error).message}`); 60 | } 61 | }, { 62 | description: 'Schedule a new cron event (one-time or recurring)', 63 | schema: { 64 | hook: 'string' 65 | } 66 | }); 67 | 68 | /** 69 | * Unschedule a cron event 70 | */ 71 | server.tool('wordpress_unschedule_event', async (args: any) => { 72 | const { hook, args: eventArgs = [] } = args; 73 | 74 | try { 75 | const result = await callCustomAPI('wp-json/wpmcp/v1/cron/unschedule', 'POST', { 76 | hook, 77 | args: eventArgs 78 | }); 79 | 80 | return Responses.success( 81 | { 82 | hook, 83 | unscheduled: true 84 | }, 85 | `✅ Unscheduled event: ${hook}` 86 | ); 87 | } catch (error) { 88 | return Responses.error(`Failed to unschedule event: ${(error as Error).message}`); 89 | } 90 | }, { 91 | description: 'Remove a scheduled cron event', 92 | schema: { 93 | hook: 'string' 94 | } 95 | }); 96 | 97 | /** 98 | * Run WordPress cron manually 99 | */ 100 | server.tool('wordpress_run_cron', async () => { 101 | try { 102 | const result = await callCustomAPI('wp-json/wpmcp/v1/cron/run', 'POST'); 103 | 104 | return Responses.success( 105 | { 106 | success: true, 107 | message: result.message 108 | }, 109 | `✅ Manually triggered WordPress cron` 110 | ); 111 | } catch (error) { 112 | return Responses.error(`Failed to run cron: ${(error as Error).message}`); 113 | } 114 | }, { 115 | description: 'Manually trigger WordPress cron execution', 116 | schema: {} 117 | }); 118 | 119 | /** 120 | * Get cron schedules 121 | */ 122 | server.tool('wordpress_get_cron_schedules', async () => { 123 | try { 124 | // Get available cron schedules 125 | const result = await callCustomAPI('wp-json/wpmcp/v1/cron/list', 'GET'); 126 | 127 | // Extract unique schedules 128 | const schedules = new Set(); 129 | result.jobs.forEach((job: any) => { 130 | if (job.schedule) schedules.add(job.schedule); 131 | }); 132 | 133 | return Responses.success( 134 | { 135 | schedules: Array.from(schedules), 136 | available: ['once', 'hourly', 'twicedaily', 'daily', 'weekly'] 137 | }, 138 | `⏱️ Retrieved cron schedules` 139 | ); 140 | } catch (error) { 141 | return Responses.error(`Failed to get schedules: ${(error as Error).message}`); 142 | } 143 | }, { 144 | description: 'Get available cron schedule intervals', 145 | schema: {} 146 | }); 147 | } -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress REST API Wrapper 3 | * Handles all API calls to WordPress 4 | */ 5 | 6 | import fetch from 'node-fetch'; 7 | import { config } from '../config/wordpress.js'; 8 | 9 | /** 10 | * Call WordPress REST API 11 | * @param endpoint - API endpoint (e.g., '/posts', '/users/me') 12 | * @param method - HTTP method (GET, POST, PUT, DELETE) 13 | * @param body - Request body for POST/PUT requests 14 | * @returns API response data 15 | */ 16 | export async function callWordPressAPI( 17 | endpoint: string, 18 | method: string = 'GET', 19 | body?: any, 20 | namespace: string = 'wp/v2' 21 | ): Promise { 22 | const url = `${config.url}/wp-json/${namespace}${endpoint}`; 23 | 24 | try { 25 | const response = await fetch(url, { 26 | method, 27 | headers: { 28 | 'Authorization': `Basic ${config.getAuthToken()}`, 29 | 'Content-Type': 'application/json', 30 | }, 31 | body: body ? JSON.stringify(body) : undefined, 32 | }); 33 | 34 | if (!response.ok) { 35 | const errorText = await response.text(); 36 | throw new Error(`WordPress API error: ${response.status} ${response.statusText} - ${errorText}`); 37 | } 38 | 39 | return await response.json(); 40 | } catch (error) { 41 | throw new Error(`Failed to call WordPress API: ${(error as Error).message}`); 42 | } 43 | } 44 | 45 | /** 46 | * Call WordPress root API (for site info) 47 | * @returns Site information 48 | */ 49 | export async function callWordPressRootAPI(): Promise { 50 | const url = `${config.url}/wp-json/`; 51 | 52 | try { 53 | const response = await fetch(url, { 54 | headers: { 'Authorization': `Basic ${config.getAuthToken()}` } 55 | }); 56 | 57 | if (!response.ok) { 58 | throw new Error(`API call failed: ${response.statusText}`); 59 | } 60 | 61 | return await response.json(); 62 | } catch (error) { 63 | throw new Error(`Failed to call WordPress root API: ${(error as Error).message}`); 64 | } 65 | } 66 | 67 | /** 68 | * Call custom WordPress REST API endpoint (with custom namespace) 69 | * @param fullEndpoint - Complete endpoint path (e.g., '/wp-json/wpmcp/v1/file/read') 70 | * @param method - HTTP method (GET, POST, PUT, DELETE) 71 | * @param body - Request body for POST/PUT requests 72 | * @returns API response data 73 | */ 74 | export async function callCustomAPI( 75 | fullEndpoint: string, 76 | method: string = 'GET', 77 | body?: any 78 | ): Promise { 79 | // Remove leading slash if present 80 | const cleanEndpoint = fullEndpoint.startsWith('/') ? fullEndpoint.slice(1) : fullEndpoint; 81 | const url = `${config.url}/${cleanEndpoint}`; 82 | 83 | try { 84 | const response = await fetch(url, { 85 | method, 86 | headers: { 87 | 'Authorization': `Basic ${config.getAuthToken()}`, 88 | 'Content-Type': 'application/json', 89 | }, 90 | body: body ? JSON.stringify(body) : undefined, 91 | }); 92 | 93 | if (!response.ok) { 94 | const errorText = await response.text(); 95 | throw new Error(`WordPress API error: ${response.status} ${response.statusText} - ${errorText}`); 96 | } 97 | 98 | return await response.json(); 99 | } catch (error) { 100 | throw new Error(`Failed to call WordPress API: ${(error as Error).message}`); 101 | } 102 | } 103 | 104 | /** 105 | * Upload media file to WordPress 106 | * @param fileBase64 - Base64 encoded file 107 | * @param filename - File name 108 | * @returns Media object 109 | */ 110 | export async function uploadMediaFile(fileBase64: string, filename: string): Promise { 111 | const buffer = Buffer.from(fileBase64, 'base64'); 112 | const boundary = '----WebKitFormBoundary' + Math.random().toString(36); 113 | 114 | const body = [ 115 | `--${boundary}`, 116 | `Content-Disposition: form-data; name="file"; filename="${filename}"`, 117 | 'Content-Type: application/octet-stream', 118 | '', 119 | buffer.toString('binary'), 120 | `--${boundary}--` 121 | ].join('\r\n'); 122 | 123 | const url = `${config.url}/wp-json/wp/v2/media`; 124 | 125 | try { 126 | const response = await fetch(url, { 127 | method: 'POST', 128 | headers: { 129 | 'Authorization': `Basic ${config.getAuthToken()}`, 130 | 'Content-Type': `multipart/form-data; boundary=${boundary}` 131 | }, 132 | body 133 | }); 134 | 135 | if (!response.ok) { 136 | throw new Error(`Upload failed: ${response.statusText}`); 137 | } 138 | 139 | return await response.json(); 140 | } catch (error) { 141 | throw new Error(`Failed to upload media: ${(error as Error).message}`); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/tools/database.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Database Operations Tools 3 | * Safe database queries, wp_options management, table operations 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerDatabaseTools(server: any) { 10 | 11 | // ========== DATABASE OPERATIONS ========== 12 | 13 | /** 14 | * List all database tables 15 | */ 16 | server.tool('wordpress_list_tables', async () => { 17 | try { 18 | const result = await callCustomAPI('wp-json/wpmcp/v1/database/tables', 'GET'); 19 | 20 | return Responses.success( 21 | { 22 | tables: result.tables, 23 | total: result.total, 24 | prefix: result.prefix 25 | }, 26 | `🗄️ Retrieved ${result.total} database tables (prefix: ${result.prefix})` 27 | ); 28 | } catch (error) { 29 | return Responses.error(`Failed to list tables: ${(error as Error).message}`); 30 | } 31 | }, { 32 | description: 'Get all WordPress database tables with row counts', 33 | schema: {} 34 | }); 35 | 36 | /** 37 | * Execute SQL query (SELECT only for safety) 38 | */ 39 | server.tool('wordpress_execute_sql', async (args: any) => { 40 | const { query } = args; 41 | 42 | try { 43 | const result = await callCustomAPI('wp-json/wpmcp/v1/database/query', 'POST', { 44 | query 45 | }); 46 | 47 | return Responses.success( 48 | { 49 | results: result.results, 50 | rows: result.rows, 51 | query: result.query 52 | }, 53 | `✅ Query returned ${result.rows} rows` 54 | ); 55 | } catch (error) { 56 | return Responses.error(`Query failed: ${(error as Error).message}`); 57 | } 58 | }, { 59 | description: 'Execute SQL query (SELECT, SHOW, DESCRIBE, EXPLAIN only for safety)', 60 | schema: { 61 | query: 'string' 62 | } 63 | }); 64 | 65 | /** 66 | * Get WordPress option value 67 | */ 68 | server.tool('wordpress_get_option', async (args: any) => { 69 | const { name } = args; 70 | 71 | try { 72 | const result = await callCustomAPI(`wp-json/wpmcp/v1/database/option?name=${encodeURIComponent(name)}`, 'GET'); 73 | 74 | return Responses.success( 75 | { 76 | name: result.name, 77 | value: result.value, 78 | exists: result.exists 79 | }, 80 | result.exists ? `✅ Option "${name}": ${JSON.stringify(result.value)}` : `❌ Option "${name}" not found` 81 | ); 82 | } catch (error) { 83 | return Responses.error(`Failed to get option: ${(error as Error).message}`); 84 | } 85 | }, { 86 | description: 'Get WordPress option value from wp_options table', 87 | schema: { 88 | name: 'string' 89 | } 90 | }); 91 | 92 | /** 93 | * Update WordPress option value 94 | */ 95 | server.tool('wordpress_update_option', async (args: any) => { 96 | const { name, value } = args; 97 | 98 | try { 99 | const result = await callCustomAPI('wp-json/wpmcp/v1/database/option', 'POST', { 100 | name, 101 | value 102 | }); 103 | 104 | return Responses.success( 105 | { 106 | name: result.name, 107 | updated: result.updated, 108 | value: result.value 109 | }, 110 | `✅ Updated option "${name}"` 111 | ); 112 | } catch (error) { 113 | return Responses.error(`Failed to update option: ${(error as Error).message}`); 114 | } 115 | }, { 116 | description: 'Update WordPress option value in wp_options table', 117 | schema: { 118 | name: 'string', 119 | value: 'string' 120 | } 121 | }); 122 | 123 | /** 124 | * Get table structure 125 | */ 126 | server.tool('wordpress_get_table_structure', async (args: any) => { 127 | const { table } = args; 128 | 129 | try { 130 | const result = await callCustomAPI('wp-json/wpmcp/v1/database/query', 'POST', { 131 | query: `DESCRIBE ${table}` 132 | }); 133 | 134 | return Responses.success( 135 | { 136 | table, 137 | columns: result.results, 138 | count: result.rows 139 | }, 140 | `📋 Table "${table}" has ${result.rows} columns` 141 | ); 142 | } catch (error) { 143 | return Responses.error(`Failed to get table structure: ${(error as Error).message}`); 144 | } 145 | }, { 146 | description: 'Get database table structure (columns and types)', 147 | schema: { 148 | table: 'string' 149 | } 150 | }); 151 | 152 | /** 153 | * Get table preview (first 10 rows) 154 | */ 155 | server.tool('wordpress_get_table_preview', async (args: any) => { 156 | const { table, limit = 10 } = args; 157 | 158 | try { 159 | const result = await callCustomAPI('wp-json/wpmcp/v1/database/query', 'POST', { 160 | query: `SELECT * FROM ${table} LIMIT ${limit}` 161 | }); 162 | 163 | return Responses.success( 164 | { 165 | table, 166 | rows: result.results, 167 | count: result.rows 168 | }, 169 | `👀 Preview of table "${table}": ${result.rows} rows` 170 | ); 171 | } catch (error) { 172 | return Responses.error(`Failed to preview table: ${(error as Error).message}`); 173 | } 174 | }, { 175 | description: 'Get preview of table data (first 10 rows by default)', 176 | schema: { 177 | table: 'string' 178 | } 179 | }); 180 | } -------------------------------------------------------------------------------- /src/tools/widgets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Widget Management Tools 3 | * Manage WordPress widgets and sidebars 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callWordPressAPI, callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerWidgetTools(server: any) { 10 | 11 | // ========== WIDGET MANAGEMENT ========== 12 | 13 | /** 14 | * Get all sidebars (widget areas) 15 | */ 16 | server.tool('wordpress_get_sidebars', async () => { 17 | try { 18 | const sidebars = await callWordPressAPI('/sidebars'); 19 | 20 | return Responses.success( 21 | { 22 | sidebars: sidebars.map((sidebar: any) => ({ 23 | id: sidebar.id, 24 | name: sidebar.name, 25 | description: sidebar.description || '', 26 | class: sidebar.class || '', 27 | widgets: sidebar.widgets || [] 28 | })), 29 | total: sidebars.length 30 | }, 31 | `📦 Retrieved ${sidebars.length} widget areas` 32 | ); 33 | } catch (error) { 34 | return Responses.error(`Failed to get sidebars: ${(error as Error).message}`); 35 | } 36 | }, { 37 | description: 'Get all registered sidebar/widget areas', 38 | schema: {} 39 | }); 40 | 41 | /** 42 | * Get specific sidebar details 43 | */ 44 | server.tool('wordpress_get_sidebar', async (args: any) => { 45 | const { id } = args; 46 | 47 | try { 48 | const sidebar = await callWordPressAPI(`/sidebars/${id}`); 49 | 50 | return Responses.success( 51 | { 52 | id: sidebar.id, 53 | name: sidebar.name, 54 | description: sidebar.description || '', 55 | widgets: sidebar.widgets || [] 56 | }, 57 | `📦 Retrieved sidebar: ${sidebar.name}` 58 | ); 59 | } catch (error) { 60 | return Responses.error(`Failed to get sidebar: ${(error as Error).message}`); 61 | } 62 | }, { 63 | description: 'Get details for a specific sidebar', 64 | schema: { 65 | id: 'string' 66 | } 67 | }); 68 | 69 | /** 70 | * Get all widgets 71 | */ 72 | server.tool('wordpress_get_widgets', async (args: any) => { 73 | const { sidebarId } = args || {}; 74 | 75 | try { 76 | const widgets = await callWordPressAPI('/widgets'); 77 | 78 | const filteredWidgets = sidebarId 79 | ? widgets.filter((w: any) => w.sidebar === sidebarId) 80 | : widgets; 81 | 82 | return Responses.success( 83 | { 84 | widgets: filteredWidgets.map((widget: any) => ({ 85 | id: widget.id, 86 | sidebar: widget.sidebar, 87 | instance: widget.instance || {}, 88 | idBase: widget.id_base || '' 89 | })), 90 | total: filteredWidgets.length 91 | }, 92 | `🔧 Retrieved ${filteredWidgets.length} widgets${sidebarId ? ` from ${sidebarId}` : ''}` 93 | ); 94 | } catch (error) { 95 | return Responses.error(`Failed to get widgets: ${(error as Error).message}`); 96 | } 97 | }, { 98 | description: 'Get all widgets or widgets in a specific sidebar', 99 | schema: {} 100 | }); 101 | 102 | /** 103 | * Update widget 104 | */ 105 | server.tool('wordpress_update_widget', async (args: any) => { 106 | const { widgetId, instance } = args; 107 | 108 | try { 109 | const widget = await callWordPressAPI(`/widgets/${widgetId}`, 'PUT', { 110 | instance 111 | }); 112 | 113 | return Responses.success( 114 | { 115 | id: widget.id, 116 | sidebar: widget.sidebar, 117 | updated: true 118 | }, 119 | `✅ Updated widget ID ${widgetId}` 120 | ); 121 | } catch (error) { 122 | return Responses.error(`Failed to update widget: ${(error as Error).message}`); 123 | } 124 | }, { 125 | description: 'Update widget configuration', 126 | schema: { 127 | widgetId: 'string', 128 | instance: 'object' 129 | } 130 | }); 131 | 132 | /** 133 | * Delete widget 134 | */ 135 | server.tool('wordpress_delete_widget', async (args: any) => { 136 | const { widgetId, force = false } = args; 137 | 138 | try { 139 | const endpoint = force ? `/widgets/${widgetId}?force=true` : `/widgets/${widgetId}`; 140 | await callWordPressAPI(endpoint, 'DELETE'); 141 | 142 | return Responses.success( 143 | { 144 | id: widgetId, 145 | deleted: true 146 | }, 147 | `✅ Deleted widget ID ${widgetId}` 148 | ); 149 | } catch (error) { 150 | return Responses.error(`Failed to delete widget: ${(error as Error).message}`); 151 | } 152 | }, { 153 | description: 'Delete a widget from a sidebar', 154 | schema: { 155 | widgetId: 'string', 156 | force: 'boolean' 157 | } 158 | }); 159 | 160 | /** 161 | * Get widget types 162 | */ 163 | server.tool('wordpress_get_widget_types', async () => { 164 | try { 165 | const widgetTypes = await callWordPressAPI('/widget-types'); 166 | 167 | return Responses.success( 168 | { 169 | widgetTypes: widgetTypes.map((type: any) => ({ 170 | id: type.id, 171 | name: type.name || type.id, 172 | description: type.description || '', 173 | is_multi: type.is_multi || false 174 | })), 175 | total: widgetTypes.length 176 | }, 177 | `🔧 Retrieved ${widgetTypes.length} widget types` 178 | ); 179 | } catch (error) { 180 | return Responses.error(`Failed to get widget types: ${(error as Error).message}`); 181 | } 182 | }, { 183 | description: 'Get all available widget types', 184 | schema: {} 185 | }); 186 | } -------------------------------------------------------------------------------- /src/types/wordpress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Type Definitions 3 | * Comprehensive types for WordPress REST API 4 | */ 5 | 6 | export interface WordPressSiteInfo { 7 | name: string; 8 | description: string; 9 | url: string; 10 | home: string; 11 | gmt_offset: number; 12 | timezone_string: string; 13 | namespaces: string[]; 14 | authentication: Record; 15 | routes: Record; 16 | } 17 | 18 | export interface WordPressPost { 19 | id: number; 20 | date: string; 21 | date_gmt: string; 22 | modified: string; 23 | modified_gmt: string; 24 | slug: string; 25 | status: 'publish' | 'future' | 'draft' | 'pending' | 'private'; 26 | type: string; 27 | link: string; 28 | title: { rendered: string }; 29 | content: { rendered: string; protected: boolean }; 30 | excerpt: { rendered: string; protected: boolean }; 31 | author: number; 32 | featured_media: number; 33 | comment_status: 'open' | 'closed'; 34 | ping_status: 'open' | 'closed'; 35 | sticky: boolean; 36 | template: string; 37 | format: string; 38 | meta: Record; 39 | categories: number[]; 40 | tags: number[]; 41 | } 42 | 43 | export interface WordPressPage { 44 | id: number; 45 | date: string; 46 | modified: string; 47 | slug: string; 48 | status: string; 49 | type: string; 50 | link: string; 51 | title: { rendered: string }; 52 | content: { rendered: string }; 53 | author: number; 54 | featured_media: number; 55 | parent: number; 56 | menu_order: number; 57 | template: string; 58 | } 59 | 60 | export interface WordPressMedia { 61 | id: number; 62 | date: string; 63 | slug: string; 64 | type: string; 65 | link: string; 66 | title: { rendered: string }; 67 | author: number; 68 | caption: { rendered: string }; 69 | alt_text: string; 70 | media_type: string; 71 | mime_type: string; 72 | media_details: { 73 | width: number; 74 | height: number; 75 | file: string; 76 | sizes: Record; 77 | }; 78 | source_url: string; 79 | } 80 | 81 | export interface WordPressCategory { 82 | id: number; 83 | count: number; 84 | description: string; 85 | link: string; 86 | name: string; 87 | slug: string; 88 | taxonomy: string; 89 | parent: number; 90 | } 91 | 92 | export interface WordPressTag { 93 | id: number; 94 | count: number; 95 | description: string; 96 | link: string; 97 | name: string; 98 | slug: string; 99 | taxonomy: string; 100 | } 101 | 102 | export interface WordPressUser { 103 | id: number; 104 | username: string; 105 | name: string; 106 | first_name: string; 107 | last_name: string; 108 | email: string; 109 | url: string; 110 | description: string; 111 | link: string; 112 | locale: string; 113 | nickname: string; 114 | slug: string; 115 | roles: string[]; 116 | capabilities: Record; 117 | avatar_urls: Record; 118 | } 119 | 120 | export interface WordPressComment { 121 | id: number; 122 | post: number; 123 | parent: number; 124 | author: number; 125 | author_name: string; 126 | author_email: string; 127 | author_url: string; 128 | date: string; 129 | content: { rendered: string }; 130 | link: string; 131 | status: string; 132 | type: string; 133 | } 134 | 135 | export interface WordPressPlugin { 136 | plugin: string; 137 | status: 'inactive' | 'active'; 138 | name: string; 139 | plugin_uri: string; 140 | author: string; 141 | author_uri: string; 142 | description: { raw: string; rendered: string }; 143 | version: string; 144 | network_only: boolean; 145 | requires_wp: string; 146 | requires_php: string; 147 | textdomain: string; 148 | } 149 | 150 | export interface WordPressTheme { 151 | stylesheet: string; 152 | template: string; 153 | author: string; 154 | author_uri: string; 155 | description: { raw: string; rendered: string }; 156 | version: string; 157 | name: { raw: string; rendered: string }; 158 | tags: { raw: string[]; rendered: string }; 159 | theme_uri: string; 160 | status: 'inactive' | 'active'; 161 | } 162 | 163 | export interface WordPressMenu { 164 | term_id: number; 165 | name: string; 166 | slug: string; 167 | term_group: number; 168 | term_taxonomy_id: number; 169 | taxonomy: string; 170 | description: string; 171 | parent: number; 172 | count: number; 173 | } 174 | 175 | export interface WordPressMenuItem { 176 | id: number; 177 | title: { rendered: string }; 178 | status: string; 179 | url: string; 180 | attr_title: string; 181 | description: string; 182 | type: string; 183 | type_label: string; 184 | object: string; 185 | object_id: number; 186 | parent: number; 187 | menu_order: number; 188 | target: string; 189 | classes: string[]; 190 | xfn: string[]; 191 | invalid: boolean; 192 | menus: number[]; 193 | } 194 | 195 | export interface WordPressSettings { 196 | title: string; 197 | description: string; 198 | url: string; 199 | email: string; 200 | timezone: string; 201 | date_format: string; 202 | time_format: string; 203 | start_of_week: number; 204 | language: string; 205 | use_smilies: boolean; 206 | default_category: number; 207 | default_post_format: string; 208 | posts_per_page: number; 209 | default_ping_status: 'open' | 'closed'; 210 | default_comment_status: 'open' | 'closed'; 211 | } 212 | 213 | export interface CreatePostParams { 214 | title: string; 215 | content: string; 216 | status?: 'publish' | 'future' | 'draft' | 'pending' | 'private'; 217 | date?: string; 218 | categories?: number[]; 219 | tags?: number[]; 220 | excerpt?: string; 221 | featured_media?: number; 222 | comment_status?: 'open' | 'closed'; 223 | ping_status?: 'open' | 'closed'; 224 | meta?: Record; 225 | } 226 | 227 | export interface UpdatePostParams { 228 | title?: string; 229 | content?: string; 230 | status?: string; 231 | categories?: number[]; 232 | tags?: number[]; 233 | excerpt?: string; 234 | featured_media?: number; 235 | comment_status?: string; 236 | ping_status?: string; 237 | meta?: Record; 238 | } 239 | -------------------------------------------------------------------------------- /src/tools/security.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Security & Site Health Tools 3 | * Monitor security, check for updates, verify integrity 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callWordPressAPI, callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerSecurityTools(server: any) { 10 | 11 | // ========== SITE HEALTH & SECURITY ========== 12 | 13 | /** 14 | * Get site health status 15 | */ 16 | server.tool('wordpress_get_site_health', async () => { 17 | try { 18 | // Get site health tests 19 | const tests = await callWordPressAPI('/site-health/v1/tests/background-updates', 'GET', undefined, 'wp-site-health'); 20 | 21 | return Responses.success( 22 | { 23 | tests, 24 | message: 'Site health check completed' 25 | }, 26 | `🏥 Site health status retrieved` 27 | ); 28 | } catch (error) { 29 | return Responses.error(`Failed to get site health: ${(error as Error).message}`); 30 | } 31 | }, { 32 | description: 'Get WordPress site health and status checks', 33 | schema: {} 34 | }); 35 | 36 | /** 37 | * Check for WordPress updates 38 | */ 39 | server.tool('wordpress_check_updates', async () => { 40 | try { 41 | // Trigger update check 42 | const result = await callCustomAPI('wp-json/wpmcp/v1/security/check-updates', 'GET'); 43 | 44 | return Responses.success( 45 | { 46 | coreUpdates: result.core || [], 47 | pluginUpdates: result.plugins || [], 48 | themeUpdates: result.themes || [], 49 | totalUpdates: (result.core?.length || 0) + (result.plugins?.length || 0) + (result.themes?.length || 0) 50 | }, 51 | `🔄 Found ${result.totalUpdates || 0} available updates` 52 | ); 53 | } catch (error) { 54 | return Responses.error(`Failed to check updates: ${(error as Error).message}`); 55 | } 56 | }, { 57 | description: 'Check for available WordPress, plugin, and theme updates', 58 | schema: {} 59 | }); 60 | 61 | /** 62 | * Get debug log 63 | */ 64 | server.tool('wordpress_get_debug_log', async (args: any) => { 65 | const { lines = 100 } = args || {}; 66 | 67 | try { 68 | const result = await callCustomAPI('wp-json/wpmcp/v1/security/debug-log', 'GET'); 69 | 70 | // Get last N lines 71 | const logLines = result.content ? result.content.split('\n').slice(-lines) : []; 72 | 73 | return Responses.success( 74 | { 75 | lines: logLines, 76 | totalLines: logLines.length, 77 | exists: result.exists 78 | }, 79 | result.exists ? `📝 Retrieved last ${logLines.length} debug log entries` : `❌ Debug log not found` 80 | ); 81 | } catch (error) { 82 | return Responses.error(`Failed to get debug log: ${(error as Error).message}`); 83 | } 84 | }, { 85 | description: 'Read WordPress debug.log file', 86 | schema: {} 87 | }); 88 | 89 | /** 90 | * Verify core file integrity 91 | */ 92 | server.tool('wordpress_verify_core_files', async () => { 93 | try { 94 | const result = await callCustomAPI('wp-json/wpmcp/v1/security/verify-core', 'GET'); 95 | 96 | return Responses.success( 97 | { 98 | verified: result.verified, 99 | modifiedFiles: result.modified || [], 100 | totalChecked: result.total || 0 101 | }, 102 | result.verified ? `✅ Core files verified` : `⚠️ ${result.modified?.length} modified files found` 103 | ); 104 | } catch (error) { 105 | return Responses.error(`Failed to verify files: ${(error as Error).message}`); 106 | } 107 | }, { 108 | description: 'Verify WordPress core file integrity (checksums)', 109 | schema: {} 110 | }); 111 | 112 | /** 113 | * Get failed login attempts 114 | */ 115 | server.tool('wordpress_get_failed_logins', async (args: any) => { 116 | const { limit = 50 } = args || {}; 117 | 118 | try { 119 | const result = await callCustomAPI('wp-json/wpmcp/v1/security/failed-logins', 'GET'); 120 | 121 | const attempts = result.attempts ? result.attempts.slice(0, limit) : []; 122 | 123 | return Responses.success( 124 | { 125 | attempts, 126 | total: attempts.length 127 | }, 128 | `🔒 Retrieved ${attempts.length} failed login attempts` 129 | ); 130 | } catch (error) { 131 | return Responses.error(`Failed to get login attempts: ${(error as Error).message}`); 132 | } 133 | }, { 134 | description: 'Get failed login attempts for security monitoring', 135 | schema: {} 136 | }); 137 | 138 | /** 139 | * Scan file permissions 140 | */ 141 | server.tool('wordpress_scan_permissions', async () => { 142 | try { 143 | const result = await callCustomAPI('wp-json/wpmcp/v1/security/scan-permissions', 'GET'); 144 | 145 | return Responses.success( 146 | { 147 | issues: result.issues || [], 148 | totalScanned: result.total || 0, 149 | hasIssues: (result.issues?.length || 0) > 0 150 | }, 151 | (result.issues?.length || 0) > 0 152 | ? `⚠️ Found ${result.issues.length} permission issues` 153 | : `✅ File permissions OK` 154 | ); 155 | } catch (error) { 156 | return Responses.error(`Failed to scan permissions: ${(error as Error).message}`); 157 | } 158 | }, { 159 | description: 'Scan file and directory permissions for security issues', 160 | schema: {} 161 | }); 162 | 163 | /** 164 | * Get WordPress version info 165 | */ 166 | server.tool('wordpress_get_version_info', async () => { 167 | try { 168 | const siteInfo = await callWordPressAPI('/'); 169 | 170 | return Responses.success( 171 | { 172 | wpVersion: siteInfo.description || 'Unknown', 173 | phpVersion: siteInfo.php_version || 'Unknown', 174 | mysqlVersion: siteInfo.mysql_version || 'Unknown' 175 | }, 176 | `ℹ️ WordPress version information` 177 | ); 178 | } catch (error) { 179 | return Responses.error(`Failed to get version info: ${(error as Error).message}`); 180 | } 181 | }, { 182 | description: 'Get WordPress, PHP, and MySQL version information', 183 | schema: {} 184 | }); 185 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * WordPress MCP Server - Main Entry Point 4 | * Comprehensive WordPress management through MCP 5 | * 6 | * Organization: 7 | * - config/: Configuration and environment 8 | * - types/: TypeScript type definitions 9 | * - utils/: API wrapper and helper functions 10 | * - tools/: Feature-organized tool modules 11 | */ 12 | 13 | import { createServer } from 'quickmcp-sdk'; 14 | import { config } from './config/wordpress.js'; 15 | import { registerPostTools } from './tools/posts.js'; 16 | import { registerPageTools } from './tools/pages.js'; 17 | import { registerMediaTools } from './tools/media.js'; 18 | import { registerAllFeatureTools } from './tools/all-features.js'; 19 | import { registerFileSystemTools } from './tools/filesystem.js'; 20 | import { registerThemeTools } from './tools/themes.js'; 21 | import { registerPluginTools } from './tools/plugins.js'; 22 | import { registerMenuTools } from './tools/menus.js'; 23 | import { registerCustomPostTypeTools } from './tools/custom-post-types.js'; 24 | import { registerShortcodeTools } from './tools/shortcodes.js'; 25 | import { registerCronTools } from './tools/cron.js'; 26 | import { registerWidgetTools } from './tools/widgets.js'; 27 | import { registerDatabaseTools } from './tools/database.js'; 28 | import { registerWooCommerceTools } from './tools/woocommerce.js'; 29 | import { registerBlockTools } from './tools/blocks.js'; 30 | import { registerSecurityTools } from './tools/security.js'; 31 | import { registerPerformanceTools } from './tools/performance.js'; 32 | import { registerSEOTools } from './tools/seo.js'; 33 | import { registerBackupTools } from './tools/backup.js'; 34 | import { registerUserRoleTools } from './tools/user-roles.js'; 35 | 36 | // Validate configuration 37 | config.validate(); 38 | 39 | // Create MCP server 40 | const server = createServer({ 41 | name: 'wpagent-wordpress', 42 | debug: true, 43 | }); 44 | 45 | console.log('🚀 wpAgent WordPress MCP Server starting...'); 46 | console.log(`📡 Connected to: ${config.url}`); 47 | console.log(''); 48 | 49 | // Register all tool modules 50 | console.log('📦 Loading tool modules...'); 51 | 52 | registerPostTools(server); 53 | console.log(' ✅ Posts (15 tools)'); 54 | 55 | registerPageTools(server); 56 | console.log(' ✅ Pages (4 tools)'); 57 | 58 | registerMediaTools(server); 59 | console.log(' ✅ Media (5 tools)'); 60 | 61 | registerAllFeatureTools(server); 62 | console.log(' ✅ Users, Taxonomy, Comments, Site, SEO (25+ tools)'); 63 | 64 | registerFileSystemTools(server); 65 | console.log(' ✅ File System (8 tools)'); 66 | 67 | registerThemeTools(server); 68 | console.log(' ✅ Theme Management (13 tools)'); 69 | 70 | registerPluginTools(server); 71 | console.log(' ✅ Plugin Management (10 tools)'); 72 | 73 | registerMenuTools(server); 74 | console.log(' ✅ Menu Management (8 tools)'); 75 | 76 | registerCustomPostTypeTools(server); 77 | console.log(' ✅ Custom Post Types & Taxonomies (7 tools)'); 78 | 79 | registerShortcodeTools(server); 80 | console.log(' ✅ Shortcode System (3 tools) - NEW!'); 81 | 82 | registerCronTools(server); 83 | console.log(' ✅ Cron & Scheduled Tasks (5 tools) - NEW!'); 84 | 85 | registerWidgetTools(server); 86 | console.log(' ✅ Widget Management (6 tools)'); 87 | 88 | registerDatabaseTools(server); 89 | console.log(' ✅ Database Operations (6 tools)'); 90 | 91 | registerWooCommerceTools(server); 92 | console.log(' ✅ WooCommerce Integration (15 tools)'); 93 | 94 | registerBlockTools(server); 95 | console.log(' ✅ Gutenberg/Block Editor (12 tools)'); 96 | 97 | registerSecurityTools(server); 98 | console.log(' ✅ Security & Site Health (7 tools) - NEW!'); 99 | 100 | registerPerformanceTools(server); 101 | console.log(' ✅ Performance Optimization (8 tools)'); 102 | 103 | registerSEOTools(server); 104 | console.log(' ✅ Advanced SEO (10 tools)'); 105 | 106 | registerBackupTools(server); 107 | console.log(' ✅ Backup & Migration (10 tools) - NEW!'); 108 | 109 | registerUserRoleTools(server); 110 | console.log(' ✅ User Roles & Capabilities (8 tools) - NEW!'); 111 | 112 | console.log(''); 113 | console.log('✅ WordPress MCP Server initialized'); 114 | console.log(`📋 Total: 195+ WordPress management tools loaded`); 115 | console.log(''); 116 | console.log('🔧 Available Feature Categories:'); 117 | console.log(' 📝 Posts: create, update, delete, publish, schedule, search, duplicate, revisions, bulk operations'); 118 | console.log(' 📄 Pages: create, update, delete, hierarchy management'); 119 | console.log(' 🖼️ Media: upload, get, update, delete, featured images'); 120 | console.log(' 👥 Users: create, get, update, delete, role management'); 121 | console.log(' 📁 Categories: create, get, update, delete, hierarchy'); 122 | console.log(' 🏷️ Tags: create, get, manage'); 123 | console.log(' 💬 Comments: create, get, update, delete, moderation'); 124 | console.log(' ⚙️ Settings: get, update site configuration'); 125 | console.log(' 🔌 Plugins: list installed plugins'); 126 | console.log(' 🎨 Themes: list installed themes'); 127 | console.log(' 🔍 SEO: Yoast, Rank Math, All-in-One SEO support'); 128 | console.log(' 🛠️ Site Management: info, connection test, custom meta'); 129 | console.log(' 📁 File System: read, write, delete, copy, move files - secure theme/plugin editing'); 130 | console.log(' 🎨 Theme Manager: activate, create child themes, modify theme.json, read/write theme files'); 131 | console.log(' 🔌 Plugin Manager: activate, deactivate, delete, read/write plugin files, status checks'); 132 | console.log(' 🧭 Menu Manager: create menus, add items, assign to locations, full navigation control'); 133 | console.log(' 📋 Custom Post Types: get post types, taxonomies, create/update terms'); 134 | console.log(' 📝 Shortcodes: list registered shortcodes, execute shortcode strings'); 135 | console.log(' ⏰ Cron Jobs: schedule events, manage tasks, manual cron execution'); 136 | console.log(' 🔧 Widgets: get sidebars, manage widgets, widget types'); 137 | console.log(' 🗄️ Database: execute queries, manage options, list tables, inspect data'); 138 | console.log(' 🛍️ WooCommerce: products, orders, customers, inventory, coupons, reports'); 139 | console.log(' 🧱 Gutenberg Blocks: block types, patterns, reusable blocks, templates'); 140 | console.log(' 🔒 Security: site health, updates, debug logs, file integrity, permissions'); 141 | console.log(' ⚡ Performance: cache clearing, database optimization, image regeneration'); 142 | console.log(' 🎯 SEO: sitemaps, redirects, schema markup, Open Graph, Twitter cards, analysis'); 143 | console.log(' 📦 Backup (NEW): full/partial backups, restore, export/import, clone to staging'); 144 | console.log(' 👤 User Roles (NEW): custom roles, capabilities, permissions, role assignment'); 145 | console.log(''); 146 | console.log('🔗 Listening for MCP requests...'); 147 | 148 | // Start server 149 | await server.start(); 150 | -------------------------------------------------------------------------------- /src/tools/user-roles.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress User Roles & Capabilities Tools 3 | * Manage user roles and permissions 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerUserRoleTools(server: any) { 10 | 11 | // ========== USER ROLES & CAPABILITIES ========== 12 | 13 | /** 14 | * Get all user roles 15 | */ 16 | server.tool('wordpress_get_roles', async () => { 17 | try { 18 | const result = await callCustomAPI('wp-json/wpmcp/v1/roles/list', 'GET'); 19 | 20 | return Responses.success( 21 | { 22 | roles: result.roles || {}, 23 | total: Object.keys(result.roles || {}).length 24 | }, 25 | `👥 Retrieved ${Object.keys(result.roles || {}).length} user roles` 26 | ); 27 | } catch (error) { 28 | return Responses.error(`Failed to get roles: ${(error as Error).message}`); 29 | } 30 | }, { 31 | description: 'Get all WordPress user roles', 32 | schema: {} 33 | }); 34 | 35 | /** 36 | * Get capabilities for a role 37 | */ 38 | server.tool('wordpress_get_capabilities', async (args: any) => { 39 | const { role } = args; 40 | 41 | try { 42 | const result = await callCustomAPI(`wp-json/wpmcp/v1/roles/${role}/capabilities`, 'GET'); 43 | 44 | return Responses.success( 45 | { 46 | role, 47 | capabilities: result.capabilities || {}, 48 | totalCaps: Object.keys(result.capabilities || {}).length 49 | }, 50 | `🔑 Role "${role}" has ${Object.keys(result.capabilities || {}).length} capabilities` 51 | ); 52 | } catch (error) { 53 | return Responses.error(`Failed to get capabilities: ${(error as Error).message}`); 54 | } 55 | }, { 56 | description: 'Get all capabilities for a user role', 57 | schema: { 58 | role: 'string' 59 | } 60 | }); 61 | 62 | /** 63 | * Create custom role 64 | */ 65 | server.tool('wordpress_create_role', async (args: any) => { 66 | const { role, displayName, capabilities = {} } = args; 67 | 68 | try { 69 | const result = await callCustomAPI('wp-json/wpmcp/v1/roles/create', 'POST', { 70 | role, 71 | displayName, 72 | capabilities 73 | }); 74 | 75 | return Responses.success( 76 | { 77 | role, 78 | displayName, 79 | created: true 80 | }, 81 | `✅ Created role: ${displayName}` 82 | ); 83 | } catch (error) { 84 | return Responses.error(`Failed to create role: ${(error as Error).message}`); 85 | } 86 | }, { 87 | description: 'Create a custom user role', 88 | schema: { 89 | role: 'string', 90 | displayName: 'string' 91 | } 92 | }); 93 | 94 | /** 95 | * Delete custom role 96 | */ 97 | server.tool('wordpress_delete_role', async (args: any) => { 98 | const { role } = args; 99 | 100 | try { 101 | await callCustomAPI(`wp-json/wpmcp/v1/roles/${role}`, 'DELETE'); 102 | 103 | return Responses.success( 104 | { 105 | role, 106 | deleted: true 107 | }, 108 | `✅ Deleted role: ${role}` 109 | ); 110 | } catch (error) { 111 | return Responses.error(`Failed to delete role: ${(error as Error).message}`); 112 | } 113 | }, { 114 | description: 'Delete a custom user role', 115 | schema: { 116 | role: 'string' 117 | } 118 | }); 119 | 120 | /** 121 | * Add capability to role 122 | */ 123 | server.tool('wordpress_add_capability', async (args: any) => { 124 | const { role, capability } = args; 125 | 126 | try { 127 | await callCustomAPI('wp-json/wpmcp/v1/roles/add-capability', 'POST', { 128 | role, 129 | capability 130 | }); 131 | 132 | return Responses.success( 133 | { 134 | role, 135 | capability, 136 | added: true 137 | }, 138 | `✅ Added "${capability}" to ${role}` 139 | ); 140 | } catch (error) { 141 | return Responses.error(`Failed to add capability: ${(error as Error).message}`); 142 | } 143 | }, { 144 | description: 'Add capability to a user role', 145 | schema: { 146 | role: 'string', 147 | capability: 'string' 148 | } 149 | }); 150 | 151 | /** 152 | * Remove capability from role 153 | */ 154 | server.tool('wordpress_remove_capability', async (args: any) => { 155 | const { role, capability } = args; 156 | 157 | try { 158 | await callCustomAPI('wp-json/wpmcp/v1/roles/remove-capability', 'POST', { 159 | role, 160 | capability 161 | }); 162 | 163 | return Responses.success( 164 | { 165 | role, 166 | capability, 167 | removed: true 168 | }, 169 | `✅ Removed "${capability}" from ${role}` 170 | ); 171 | } catch (error) { 172 | return Responses.error(`Failed to remove capability: ${(error as Error).message}`); 173 | } 174 | }, { 175 | description: 'Remove capability from a user role', 176 | schema: { 177 | role: 'string', 178 | capability: 'string' 179 | } 180 | }); 181 | 182 | /** 183 | * Assign role to user 184 | */ 185 | server.tool('wordpress_assign_role', async (args: any) => { 186 | const { userId, role } = args; 187 | 188 | try { 189 | await callCustomAPI('wp-json/wpmcp/v1/roles/assign', 'POST', { 190 | userId, 191 | role 192 | }); 193 | 194 | return Responses.success( 195 | { 196 | userId, 197 | role, 198 | assigned: true 199 | }, 200 | `✅ Assigned ${role} to user ${userId}` 201 | ); 202 | } catch (error) { 203 | return Responses.error(`Failed to assign role: ${(error as Error).message}`); 204 | } 205 | }, { 206 | description: 'Assign role to user', 207 | schema: { 208 | userId: 'number', 209 | role: 'string' 210 | } 211 | }); 212 | 213 | /** 214 | * Check user capability 215 | */ 216 | server.tool('wordpress_check_user_capability', async (args: any) => { 217 | const { userId, capability } = args; 218 | 219 | try { 220 | const result = await callCustomAPI('wp-json/wpmcp/v1/roles/check-capability', 'POST', { 221 | userId, 222 | capability 223 | }); 224 | 225 | return Responses.success( 226 | { 227 | userId, 228 | capability, 229 | hasCapability: result.has || false 230 | }, 231 | result.has ? `✅ User ${userId} has "${capability}"` : `❌ User ${userId} lacks "${capability}"` 232 | ); 233 | } catch (error) { 234 | return Responses.error(`Failed to check capability: ${(error as Error).message}`); 235 | } 236 | }, { 237 | description: 'Check if user has specific capability', 238 | schema: { 239 | userId: 'number', 240 | capability: 'string' 241 | } 242 | }); 243 | } -------------------------------------------------------------------------------- /wpmcp-plugin/README.md: -------------------------------------------------------------------------------- 1 | # WordPress MCP Server Plugin 2 | 3 | **Version:** 2.0.0 4 | **Requires WordPress:** 5.0+ 5 | **Requires PHP:** 7.2+ 6 | **License:** MIT 7 | 8 | ## Overview 9 | 10 | Single comprehensive plugin that enables all WordPress MCP Server features: 11 | - ✅ File System Operations (read, write, delete, copy, move) 12 | - ✅ Shortcode Management (list, execute) 13 | - ✅ Cron Job Scheduling (list, schedule, run) 14 | - ✅ Widget Management (via WordPress REST API) 15 | 16 | ## Quick Installation 17 | 18 | ### Method 1: Upload via WordPress Admin 19 | 20 | 1. Download `wpmcp-plugin.php` 21 | 2. Go to **WordPress Admin → Plugins → Add New → Upload Plugin** 22 | 3. Upload `wpmcp-plugin.php` 23 | 4. Click **Activate** 24 | 25 | ### Method 2: Manual Upload 26 | 27 | ```bash 28 | # SSH into your WordPress server 29 | cd /path/to/wordpress/wp-content/plugins 30 | 31 | # Create plugin directory 32 | mkdir wpmcp-plugin 33 | cd wpmcp-plugin 34 | 35 | # Copy the plugin file 36 | # Upload wpmcp-plugin.php to this directory 37 | 38 | # Set permissions 39 | chmod 644 wpmcp-plugin.php 40 | 41 | # Activate via WordPress Admin → Plugins 42 | ``` 43 | 44 | ### Method 3: WP-CLI 45 | 46 | ```bash 47 | wp plugin install /path/to/wpmcp-plugin.php 48 | wp plugin activate wpmcp-plugin 49 | ``` 50 | 51 | ## Features 52 | 53 | ### File System Operations 54 | 55 | **Endpoints:** 56 | - `/wp-json/wpmcp/v1/file/read` - Read file contents 57 | - `/wp-json/wpmcp/v1/file/write` - Create/modify files 58 | - `/wp-json/wpmcp/v1/file/delete` - Remove files 59 | - `/wp-json/wpmcp/v1/file/copy` - Duplicate files 60 | - `/wp-json/wpmcp/v1/file/move` - Relocate files 61 | - `/wp-json/wpmcp/v1/file/list` - List directory contents 62 | - `/wp-json/wpmcp/v1/file/info` - Get file metadata 63 | 64 | **Security:** 65 | - Allowed directories: `wp-content/themes/`, `wp-content/plugins/`, `wp-content/uploads/`, `wp-content/mu-plugins/` 66 | - Allowed extensions: `.php`, `.js`, `.css`, `.scss`, `.json`, `.html`, `.xml`, `.txt`, `.md`, images 67 | - Automatic backups before modifications 68 | - Path validation (prevents directory traversal) 69 | - File size limit: 10MB 70 | - Requires `edit_themes` + `edit_plugins` capabilities 71 | 72 | ### Shortcode Management 73 | 74 | **Endpoints:** 75 | - `/wp-json/wpmcp/v1/shortcodes/list` - Get all registered shortcodes 76 | - `/wp-json/wpmcp/v1/shortcodes/execute` - Process shortcode string 77 | 78 | **Use Cases:** 79 | - Discover available shortcodes 80 | - Test shortcode output 81 | - Execute shortcodes programmatically 82 | 83 | ### Cron Job Scheduling 84 | 85 | **Endpoints:** 86 | - `/wp-json/wpmcp/v1/cron/list` - Get all scheduled events 87 | - `/wp-json/wpmcp/v1/cron/schedule` - Create scheduled task 88 | - `/wp-json/wpmcp/v1/cron/unschedule` - Remove scheduled event 89 | - `/wp-json/wpmcp/v1/cron/run` - Trigger cron manually 90 | 91 | **Use Cases:** 92 | - Monitor scheduled tasks 93 | - Add custom recurring events 94 | - Debug cron issues 95 | - Run maintenance tasks 96 | 97 | ### Widget System 98 | 99 | **Access via WordPress REST API:** 100 | - `/wp/v2/sidebars` - Widget areas 101 | - `/wp/v2/widgets` - All widgets 102 | - `/wp/v2/widget-types` - Available widget types 103 | 104 | **Note:** Widgets use standard WordPress REST API endpoints. 105 | 106 | ## Configuration 107 | 108 | ### Permissions Required 109 | 110 | **File Operations:** 111 | - User must have `edit_themes` capability 112 | - User must have `edit_plugins` capability 113 | 114 | **Advanced Features:** 115 | - User must have `manage_options` capability (administrator) 116 | 117 | ### Backup System 118 | 119 | **Location:** `wp-content/wpmcp-backups/` 120 | 121 | **Features:** 122 | - Automatic backups before file modifications 123 | - Metadata stored (.meta files) 124 | - Protected with `.htaccess` (deny web access) 125 | - Manual restore capability 126 | 127 | **Backup Structure:** 128 | ``` 129 | wp-content/wpmcp-backups/ 130 | ├── backup_xyz123.bak # Backed up file 131 | ├── backup_xyz123.bak.meta # Metadata (JSON) 132 | └── .htaccess # Protection 133 | ``` 134 | 135 | ## Security 136 | 137 | ### File System Protection 138 | 139 | **Whitelist Approach:** 140 | - Only specified directories allowed 141 | - Only approved file extensions 142 | - Path traversal attempts blocked 143 | - Automatic backup before changes 144 | 145 | **Forbidden Directories:** 146 | - WordPress core files 147 | - System directories 148 | - User directories 149 | - Database directories 150 | 151 | ### Permission System 152 | 153 | All endpoints require proper WordPress capabilities: 154 | - File operations: `edit_themes` AND `edit_plugins` 155 | - Admin operations: `manage_options` 156 | 157 | ### Best Practices 158 | 159 | 1. ✅ Use strong admin passwords 160 | 2. ✅ Enable HTTPS 161 | 3. ✅ Keep WordPress updated 162 | 4. ✅ Regular backups 163 | 5. ✅ Monitor plugin changes 164 | 6. ✅ Limit admin access 165 | 166 | ## API Usage Examples 167 | 168 | ### Read Theme File 169 | 170 | ```http 171 | POST /wp-json/wpmcp/v1/file/read 172 | Content-Type: application/json 173 | Authorization: Basic {base64(username:password)} 174 | 175 | { 176 | "path": "wp-content/themes/mytheme/style.css" 177 | } 178 | ``` 179 | 180 | ### List Shortcodes 181 | 182 | ```http 183 | GET /wp-json/wpmcp/v1/shortcodes/list 184 | Authorization: Basic {base64(username:password)} 185 | ``` 186 | 187 | ### Schedule Cron Job 188 | 189 | ```http 190 | POST /wp-json/wpmcp/v1/cron/schedule 191 | Content-Type: application/json 192 | Authorization: Basic {base64(username:password)} 193 | 194 | { 195 | "hook": "my_custom_task", 196 | "recurrence": "daily", 197 | "args": [] 198 | } 199 | ``` 200 | 201 | ## Troubleshooting 202 | 203 | ### "Permission denied" Error 204 | - Verify user has required capabilities 205 | - Check file permissions on server 206 | - Ensure WordPress user can write to directories 207 | 208 | ### "File not found" Error 209 | - Check file path is correct 210 | - Verify file exists in WordPress 211 | - Ensure path uses forward slashes 212 | 213 | ### "Path not allowed" Error 214 | - File must be in allowed directories 215 | - Cannot access core WordPress files 216 | - Cannot access system files 217 | 218 | ### Plugin Not Working 219 | - Check plugin is activated 220 | - Verify WordPress version 5.0+ 221 | - Ensure PHP 7.2+ 222 | - Check REST API is enabled 223 | 224 | ## Changelog 225 | 226 | ### Version 2.0.0 (2024-10-25) 227 | - Complete rewrite combining all features 228 | - File system operations 229 | - Shortcode management 230 | - Cron job scheduling 231 | - Enhanced security 232 | - Automatic backups 233 | - Comprehensive error handling 234 | 235 | ### Version 1.0.0 (2024-10-24) 236 | - Initial release (file system only) 237 | 238 | ## Support 239 | 240 | - **GitHub:** https://github.com/RaheesAhmed/wordpress-mcp-server 241 | - **Issues:** Report bugs or request features 242 | - **Documentation:** See main project README 243 | 244 | ## License 245 | 246 | MIT License - See LICENSE in main project 247 | 248 | --- 249 | 250 | **⚠️ Important:** This plugin provides powerful WordPress control. Only install on trusted WordPress installations with proper admin security. -------------------------------------------------------------------------------- /src/tools/performance.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Performance & Optimization Tools 3 | * Cache management, database optimization, image processing 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerPerformanceTools(server: any) { 10 | 11 | // ========== CACHE MANAGEMENT ========== 12 | 13 | /** 14 | * Clear all caches 15 | */ 16 | server.tool('wordpress_clear_cache', async (args: any) => { 17 | const { type = 'all' } = args || {}; 18 | 19 | try { 20 | const result = await callCustomAPI('wp-json/wpmcp/v1/performance/clear-cache', 'POST', { 21 | type 22 | }); 23 | 24 | return Responses.success( 25 | { 26 | cleared: result.cleared || [], 27 | success: true 28 | }, 29 | `✅ Cleared ${type} cache` 30 | ); 31 | } catch (error) { 32 | return Responses.error(`Failed to clear cache: ${(error as Error).message}`); 33 | } 34 | }, { 35 | description: 'Clear WordPress caches (transients, object cache, page cache)', 36 | schema: {} 37 | }); 38 | 39 | /** 40 | * Optimize database 41 | */ 42 | server.tool('wordpress_optimize_database', async () => { 43 | try { 44 | const result = await callCustomAPI('wp-json/wpmcp/v1/performance/optimize-db', 'POST'); 45 | 46 | return Responses.success( 47 | { 48 | tablesOptimized: result.tables || 0, 49 | spaceReclaimed: result.space || '0 MB', 50 | success: true 51 | }, 52 | `✅ Optimized ${result.tables || 0} database tables` 53 | ); 54 | } catch (error) { 55 | return Responses.error(`Failed to optimize database: ${(error as Error).message}`); 56 | } 57 | }, { 58 | description: 'Optimize all database tables for better performance', 59 | schema: {} 60 | }); 61 | 62 | /** 63 | * Cleanup database (revisions, drafts, spam) 64 | */ 65 | server.tool('wordpress_cleanup_database', async (args: any) => { 66 | const { 67 | revisions = true, 68 | autodrafts = true, 69 | spam = true, 70 | trash = true 71 | } = args || {}; 72 | 73 | try { 74 | const result = await callCustomAPI('wp-json/wpmcp/v1/performance/cleanup', 'POST', { 75 | revisions, 76 | autodrafts, 77 | spam, 78 | trash 79 | }); 80 | 81 | return Responses.success( 82 | { 83 | removed: result.removed || {}, 84 | totalRemoved: result.total || 0 85 | }, 86 | `✅ Cleaned up ${result.total || 0} items from database` 87 | ); 88 | } catch (error) { 89 | return Responses.error(`Failed to cleanup database: ${(error as Error).message}`); 90 | } 91 | }, { 92 | description: 'Clean up database (revisions, auto-drafts, spam, trash)', 93 | schema: {} 94 | }); 95 | 96 | /** 97 | * Regenerate image thumbnails 98 | */ 99 | server.tool('wordpress_regenerate_thumbnails', async (args: any) => { 100 | const { attachmentId } = args || {}; 101 | 102 | try { 103 | const result = await callCustomAPI('wp-json/wpmcp/v1/performance/regenerate-thumbs', 'POST', { 104 | attachmentId 105 | }); 106 | 107 | return Responses.success( 108 | { 109 | regenerated: result.count || 0, 110 | success: true 111 | }, 112 | attachmentId 113 | ? `✅ Regenerated thumbnails for image ${attachmentId}` 114 | : `✅ Regenerated ${result.count || 0} image thumbnails` 115 | ); 116 | } catch (error) { 117 | return Responses.error(`Failed to regenerate thumbnails: ${(error as Error).message}`); 118 | } 119 | }, { 120 | description: 'Regenerate image thumbnails for all or specific images', 121 | schema: {} 122 | }); 123 | 124 | /** 125 | * Get performance metrics 126 | */ 127 | server.tool('wordpress_get_performance_metrics', async () => { 128 | try { 129 | const result = await callCustomAPI('wp-json/wpmcp/v1/performance/metrics', 'GET'); 130 | 131 | return Responses.success( 132 | { 133 | dbSize: result.db_size || 'Unknown', 134 | uploadsSize: result.uploads_size || 'Unknown', 135 | themesSize: result.themes_size || 'Unknown', 136 | pluginsSize: result.plugins_size || 'Unknown', 137 | totalSize: result.total_size || 'Unknown', 138 | cacheStatus: result.cache_enabled || false, 139 | phpMemoryLimit: result.php_memory || 'Unknown' 140 | }, 141 | `📊 Performance metrics retrieved` 142 | ); 143 | } catch (error) { 144 | return Responses.error(`Failed to get metrics: ${(error as Error).message}`); 145 | } 146 | }, { 147 | description: 'Get WordPress performance metrics and resource usage', 148 | schema: {} 149 | }); 150 | 151 | /** 152 | * Enable maintenance mode 153 | */ 154 | server.tool('wordpress_enable_maintenance_mode', async (args: any) => { 155 | const { enabled = true } = args; 156 | 157 | try { 158 | const result = await callCustomAPI('wp-json/wpmcp/v1/performance/maintenance', 'POST', { 159 | enabled 160 | }); 161 | 162 | return Responses.success( 163 | { 164 | enabled: result.enabled, 165 | success: true 166 | }, 167 | result.enabled ? `🚧 Maintenance mode enabled` : `✅ Maintenance mode disabled` 168 | ); 169 | } catch (error) { 170 | return Responses.error(`Failed to toggle maintenance: ${(error as Error).message}`); 171 | } 172 | }, { 173 | description: 'Enable or disable WordPress maintenance mode', 174 | schema: {} 175 | }); 176 | 177 | /** 178 | * Flush rewrite rules 179 | */ 180 | server.tool('wordpress_flush_rewrite_rules', async () => { 181 | try { 182 | const result = await callCustomAPI('wp-json/wpmcp/v1/performance/flush-rewrites', 'POST'); 183 | 184 | return Responses.success( 185 | { 186 | flushed: true, 187 | success: true 188 | }, 189 | `✅ Flushed rewrite rules` 190 | ); 191 | } catch (error) { 192 | return Responses.error(`Failed to flush rewrites: ${(error as Error).message}`); 193 | } 194 | }, { 195 | description: 'Flush WordPress rewrite rules (fixes permalink issues)', 196 | schema: {} 197 | }); 198 | 199 | /** 200 | * Get system information 201 | */ 202 | server.tool('wordpress_get_system_info', async () => { 203 | try { 204 | const result = await callCustomAPI('wp-json/wpmcp/v1/security/system-info', 'GET'); 205 | 206 | return Responses.success( 207 | { 208 | wordpress: result.wp_version || 'Unknown', 209 | php: result.php_version || 'Unknown', 210 | mysql: result.mysql_version || 'Unknown', 211 | server: result.server_software || 'Unknown', 212 | maxUploadSize: result.max_upload || 'Unknown', 213 | memoryLimit: result.memory_limit || 'Unknown', 214 | timezone: result.timezone || 'Unknown' 215 | }, 216 | `💻 System information retrieved` 217 | ); 218 | } catch (error) { 219 | return Responses.error(`Failed to get system info: ${(error as Error).message}`); 220 | } 221 | }, { 222 | description: 'Get complete system information (versions, limits, configuration)', 223 | schema: {} 224 | }); 225 | } -------------------------------------------------------------------------------- /src/tools/media.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Media Tools 3 | */ 4 | 5 | import { Responses } from 'quickmcp-sdk'; 6 | import { callWordPressAPI, uploadMediaFile, callCustomAPI } from '../utils/api.js'; 7 | import { formatMedia, buildQueryString } from '../utils/helpers.js'; 8 | 9 | export function registerMediaTools(server: any) { 10 | 11 | server.tool('wordpress_upload_media', async (args: any) => { 12 | const { fileBase64, filename, altText = '', caption = '' } = args; 13 | 14 | try { 15 | const media = await uploadMediaFile(fileBase64, filename); 16 | 17 | if (altText || caption) { 18 | const updateData: any = {}; 19 | if (altText) updateData.alt_text = altText; 20 | if (caption) updateData.caption = caption; 21 | await callWordPressAPI(`/media/${media.id}`, 'PUT', updateData); 22 | } 23 | 24 | return Responses.success(formatMedia(media), `✅ Uploaded: ${filename}`); 25 | } catch (error) { 26 | return Responses.error(`Failed to upload media: ${(error as Error).message}`); 27 | } 28 | }, { 29 | description: 'Upload image or file to WordPress media library (provide base64 encoded file)', 30 | schema: { fileBase64: 'string', filename: 'string' } 31 | }); 32 | 33 | server.tool('wordpress_get_media', async (args: any) => { 34 | const { perPage = 20, page = 1, mediaType } = args; 35 | 36 | try { 37 | const params: any = { per_page: perPage, page }; 38 | if (mediaType) params.media_type = mediaType; 39 | 40 | const queryString = buildQueryString(params); 41 | const media = await callWordPressAPI(`/media?${queryString}`); 42 | 43 | return Responses.success( 44 | { media: media.map(formatMedia), count: media.length }, 45 | `🖼️ Retrieved ${media.length} media files` 46 | ); 47 | } catch (error) { 48 | return Responses.error(`Failed to get media: ${(error as Error).message}`); 49 | } 50 | }, { 51 | description: 'Get media library files with filtering by type', 52 | schema: { perPage: 'number', page: 'number' } 53 | }); 54 | 55 | server.tool('wordpress_update_media', async (args: any) => { 56 | const { mediaId, altText, caption, title } = args; 57 | 58 | try { 59 | const updateData: any = {}; 60 | if (altText) updateData.alt_text = altText; 61 | if (caption) updateData.caption = caption; 62 | if (title) updateData.title = title; 63 | 64 | const media = await callWordPressAPI(`/media/${mediaId}`, 'PUT', updateData); 65 | return Responses.success(formatMedia(media), `✅ Updated media ID ${mediaId}`); 66 | } catch (error) { 67 | return Responses.error(`Failed to update media: ${(error as Error).message}`); 68 | } 69 | }, { 70 | description: 'Update media file metadata (alt text, caption, title)', 71 | schema: { mediaId: 'number' } 72 | }); 73 | 74 | server.tool('wordpress_delete_media', async (args: any) => { 75 | const { mediaId, force = false } = args; 76 | 77 | try { 78 | const endpoint = force ? `/media/${mediaId}?force=true` : `/media/${mediaId}`; 79 | await callWordPressAPI(endpoint, 'DELETE'); 80 | return Responses.success({ id: mediaId, deleted: true }, `✅ Deleted media ID ${mediaId}`); 81 | } catch (error) { 82 | return Responses.error(`Failed to delete media: ${(error as Error).message}`); 83 | } 84 | }, { 85 | description: 'Delete a media file from library', 86 | schema: { mediaId: 'number', force: 'boolean' } 87 | }); 88 | 89 | server.tool('wordpress_set_featured_image', async (args: any) => { 90 | const { postId, mediaId } = args; 91 | 92 | try { 93 | await callWordPressAPI(`/posts/${postId}`, 'PUT', { featured_media: mediaId }); 94 | return Responses.success({ postId, mediaId }, `✅ Set featured image for post ${postId}`); 95 | } catch (error) { 96 | return Responses.error(`Failed to set featured image: ${(error as Error).message}`); 97 | } 98 | }, { 99 | description: 'Set featured image (thumbnail) for a post', 100 | schema: { postId: 'number', mediaId: 'number' } 101 | }); 102 | 103 | // ========== ADVANCED MEDIA TOOLS ========== 104 | 105 | /** 106 | * Bulk optimize images 107 | */ 108 | server.tool('wordpress_bulk_optimize_images', async (args: any) => { 109 | const { limit = 10 } = args || {}; 110 | 111 | try { 112 | const result = await callCustomAPI('wp-json/wpmcp/v1/media/optimize-bulk', 'POST', { limit }); 113 | 114 | return Responses.success( 115 | { 116 | optimized: result.count || 0, 117 | savedSpace: result.saved || '0 MB' 118 | }, 119 | `✅ Optimized ${result.count || 0} images` 120 | ); 121 | } catch (error) { 122 | return Responses.error(`Failed to optimize images: ${(error as Error).message}`); 123 | } 124 | }, { 125 | description: 'Bulk optimize images (compress all images)', 126 | schema: {} 127 | }); 128 | 129 | /** 130 | * Convert images to WebP 131 | */ 132 | server.tool('wordpress_convert_to_webp', async (args: any) => { 133 | const { mediaId } = args; 134 | 135 | try { 136 | const result = await callCustomAPI('wp-json/wpmcp/v1/media/convert-webp', 'POST', { mediaId }); 137 | 138 | return Responses.success( 139 | { 140 | mediaId, 141 | webpUrl: result.url || '', 142 | converted: true 143 | }, 144 | `✅ Converted image ${mediaId} to WebP` 145 | ); 146 | } catch (error) { 147 | return Responses.error(`Failed to convert to WebP: ${(error as Error).message}`); 148 | } 149 | }, { 150 | description: 'Convert image to WebP format', 151 | schema: { 152 | mediaId: 'number' 153 | } 154 | }); 155 | 156 | /** 157 | * Find unused media 158 | */ 159 | server.tool('wordpress_get_unused_media', async (args: any) => { 160 | const { limit = 50 } = args || {}; 161 | 162 | try { 163 | const result = await callCustomAPI('wp-json/wpmcp/v1/media/unused', 'GET'); 164 | 165 | return Responses.success( 166 | { 167 | unusedMedia: (result.media || []).slice(0, limit), 168 | total: result.total || 0 169 | }, 170 | `📊 Found ${result.total || 0} unused media files` 171 | ); 172 | } catch (error) { 173 | return Responses.error(`Failed to find unused media: ${(error as Error).message}`); 174 | } 175 | }, { 176 | description: 'Find unused media files in library', 177 | schema: {} 178 | }); 179 | 180 | /** 181 | * Bulk delete media 182 | */ 183 | server.tool('wordpress_bulk_delete_media', async (args: any) => { 184 | const { mediaIds } = args; 185 | 186 | try { 187 | const results = await Promise.all( 188 | mediaIds.map((id: number) => callWordPressAPI(`/media/${id}?force=true`, 'DELETE')) 189 | ); 190 | 191 | return Responses.success( 192 | { 193 | deleted: results.length, 194 | mediaIds 195 | }, 196 | `✅ Deleted ${results.length} media files` 197 | ); 198 | } catch (error) { 199 | return Responses.error(`Failed to bulk delete: ${(error as Error).message}`); 200 | } 201 | }, { 202 | description: 'Delete multiple media files', 203 | schema: { 204 | mediaIds: 'array' 205 | } 206 | }); 207 | 208 | /** 209 | * Get media storage analytics 210 | */ 211 | server.tool('wordpress_get_media_analytics', async () => { 212 | try { 213 | const result = await callCustomAPI('wp-json/wpmcp/v1/media/analytics', 'GET'); 214 | 215 | return Responses.success( 216 | { 217 | totalFiles: result.total || 0, 218 | totalSize: result.size || '0 MB', 219 | byType: result.byType || {} 220 | }, 221 | `📊 Media analytics retrieved` 222 | ); 223 | } catch (error) { 224 | return Responses.error(`Failed to get analytics: ${(error as Error).message}`); 225 | } 226 | }, { 227 | description: 'Get media library storage usage statistics', 228 | schema: {} 229 | }); 230 | } 231 | -------------------------------------------------------------------------------- /src/tools/backup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Backup & Migration Tools 3 | * Complete backup, restore, and migration capabilities 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callCustomAPI } from '../utils/api.js'; 8 | 9 | export function registerBackupTools(server: any) { 10 | 11 | // ========== BACKUP OPERATIONS ========== 12 | 13 | /** 14 | * Full site backup (files + database) 15 | */ 16 | server.tool('wordpress_full_backup', async (args: any) => { 17 | const { name } = args || {}; 18 | 19 | try { 20 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/full', 'POST', { 21 | name: name || `backup-${Date.now()}` 22 | }); 23 | 24 | return Responses.success( 25 | { 26 | backupId: result.id, 27 | filename: result.filename, 28 | size: result.size || '0 MB', 29 | timestamp: result.timestamp 30 | }, 31 | `✅ Full backup created: ${result.filename}` 32 | ); 33 | } catch (error) { 34 | return Responses.error(`Failed to create backup: ${(error as Error).message}`); 35 | } 36 | }, { 37 | description: 'Create complete site backup (files + database)', 38 | schema: {} 39 | }); 40 | 41 | /** 42 | * Database backup only 43 | */ 44 | server.tool('wordpress_backup_database', async (args: any) => { 45 | const { name } = args || {}; 46 | 47 | try { 48 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/database', 'POST', { 49 | name: name || `db-backup-${Date.now()}` 50 | }); 51 | 52 | return Responses.success( 53 | { 54 | backupId: result.id, 55 | filename: result.filename, 56 | size: result.size || '0 MB' 57 | }, 58 | `✅ Database backup created` 59 | ); 60 | } catch (error) { 61 | return Responses.error(`Failed to backup database: ${(error as Error).message}`); 62 | } 63 | }, { 64 | description: 'Export database only', 65 | schema: {} 66 | }); 67 | 68 | /** 69 | * Files backup only 70 | */ 71 | server.tool('wordpress_backup_files', async (args: any) => { 72 | const { name } = args || {}; 73 | 74 | try { 75 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/files', 'POST', { 76 | name: name || `files-backup-${Date.now()}` 77 | }); 78 | 79 | return Responses.success( 80 | { 81 | backupId: result.id, 82 | filename: result.filename, 83 | size: result.size || '0 MB' 84 | }, 85 | `✅ Files backup created` 86 | ); 87 | } catch (error) { 88 | return Responses.error(`Failed to backup files: ${(error as Error).message}`); 89 | } 90 | }, { 91 | description: 'Backup files only (no database)', 92 | schema: {} 93 | }); 94 | 95 | /** 96 | * List available backups 97 | */ 98 | server.tool('wordpress_list_backups', async () => { 99 | try { 100 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/list', 'GET'); 101 | 102 | return Responses.success( 103 | { 104 | backups: result.backups || [], 105 | total: result.total || 0 106 | }, 107 | `📦 Retrieved ${result.total || 0} backups` 108 | ); 109 | } catch (error) { 110 | return Responses.error(`Failed to list backups: ${(error as Error).message}`); 111 | } 112 | }, { 113 | description: 'Get all available backups', 114 | schema: {} 115 | }); 116 | 117 | /** 118 | * Restore from backup 119 | */ 120 | server.tool('wordpress_restore_backup', async (args: any) => { 121 | const { backupId } = args; 122 | 123 | try { 124 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/restore', 'POST', { 125 | backupId 126 | }); 127 | 128 | return Responses.success( 129 | { 130 | backupId, 131 | restored: true, 132 | filesRestored: result.files || 0, 133 | databaseRestored: result.database || false 134 | }, 135 | `✅ Restored from backup ${backupId}` 136 | ); 137 | } catch (error) { 138 | return Responses.error(`Failed to restore backup: ${(error as Error).message}`); 139 | } 140 | }, { 141 | description: 'Restore WordPress from backup', 142 | schema: { 143 | backupId: 'string' 144 | } 145 | }); 146 | 147 | /** 148 | * Delete backup 149 | */ 150 | server.tool('wordpress_delete_backup', async (args: any) => { 151 | const { backupId } = args; 152 | 153 | try { 154 | await callCustomAPI(`wp-json/wpmcp/v1/backup/${backupId}`, 'DELETE'); 155 | 156 | return Responses.success( 157 | { 158 | backupId, 159 | deleted: true 160 | }, 161 | `✅ Deleted backup ${backupId}` 162 | ); 163 | } catch (error) { 164 | return Responses.error(`Failed to delete backup: ${(error as Error).message}`); 165 | } 166 | }, { 167 | description: 'Delete a backup file', 168 | schema: { 169 | backupId: 'string' 170 | } 171 | }); 172 | 173 | // ========== MIGRATION OPERATIONS ========== 174 | 175 | /** 176 | * Export content (posts/pages as XML) 177 | */ 178 | server.tool('wordpress_export_content', async (args: any) => { 179 | const { postType = 'all', status = 'all' } = args || {}; 180 | 181 | try { 182 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/export-content', 'POST', { 183 | postType, 184 | status 185 | }); 186 | 187 | return Responses.success( 188 | { 189 | exportUrl: result.url || '', 190 | itemCount: result.count || 0, 191 | fileSize: result.size || '0 MB' 192 | }, 193 | `✅ Exported ${result.count || 0} items` 194 | ); 195 | } catch (error) { 196 | return Responses.error(`Failed to export content: ${(error as Error).message}`); 197 | } 198 | }, { 199 | description: 'Export posts/pages as WordPress XML', 200 | schema: {} 201 | }); 202 | 203 | /** 204 | * Import content (from XML) 205 | */ 206 | server.tool('wordpress_import_content', async (args: any) => { 207 | const { fileUrl, importAttachments = true } = args; 208 | 209 | try { 210 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/import-content', 'POST', { 211 | fileUrl, 212 | importAttachments 213 | }); 214 | 215 | return Responses.success( 216 | { 217 | imported: result.count || 0, 218 | skipped: result.skipped || 0 219 | }, 220 | `✅ Imported ${result.count || 0} items` 221 | ); 222 | } catch (error) { 223 | return Responses.error(`Failed to import content: ${(error as Error).message}`); 224 | } 225 | }, { 226 | description: 'Import content from WordPress XML', 227 | schema: { 228 | fileUrl: 'string' 229 | } 230 | }); 231 | 232 | /** 233 | * Clone to staging 234 | */ 235 | server.tool('wordpress_clone_to_staging', async (args: any) => { 236 | const { stagingUrl } = args; 237 | 238 | try { 239 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/clone-staging', 'POST', { 240 | stagingUrl 241 | }); 242 | 243 | return Responses.success( 244 | { 245 | stagingUrl, 246 | cloned: true, 247 | backupId: result.backupId 248 | }, 249 | `✅ Cloned to staging: ${stagingUrl}` 250 | ); 251 | } catch (error) { 252 | return Responses.error(`Failed to clone to staging: ${(error as Error).message}`); 253 | } 254 | }, { 255 | description: 'Clone WordPress to staging environment', 256 | schema: { 257 | stagingUrl: 'string' 258 | } 259 | }); 260 | 261 | /** 262 | * Schedule automatic backups 263 | */ 264 | server.tool('wordpress_schedule_backups', async (args: any) => { 265 | const { frequency = 'daily', time = '03:00', keepCount = 7 } = args || {}; 266 | 267 | try { 268 | const result = await callCustomAPI('wp-json/wpmcp/v1/backup/schedule', 'POST', { 269 | frequency, 270 | time, 271 | keepCount 272 | }); 273 | 274 | return Responses.success( 275 | { 276 | scheduled: true, 277 | frequency, 278 | nextRun: result.nextRun 279 | }, 280 | `✅ Scheduled ${frequency} backups at ${time}` 281 | ); 282 | } catch (error) { 283 | return Responses.error(`Failed to schedule backups: ${(error as Error).message}`); 284 | } 285 | }, { 286 | description: 'Schedule automatic backups', 287 | schema: {} 288 | }); 289 | } -------------------------------------------------------------------------------- /wpmcp-plugin/includes/filesystem.php: -------------------------------------------------------------------------------- 1 | ensure_backup_dir(); 18 | } 19 | 20 | private function ensure_backup_dir() { 21 | $backup_path = ABSPATH . $this->backup_dir; 22 | if (!file_exists($backup_path)) { 23 | wp_mkdir_p($backup_path); 24 | file_put_contents($backup_path . '/.htaccess', "Deny from all\n"); 25 | } 26 | } 27 | 28 | public function register_routes() { 29 | $ns = 'wpmcp/v1'; 30 | 31 | register_rest_route($ns, '/file/read', ['methods' => 'POST', 'callback' => [$this, 'read_file'], 'permission_callback' => [$this, 'check_permissions']]); 32 | register_rest_route($ns, '/file/list', ['methods' => 'POST', 'callback' => [$this, 'list_files'], 'permission_callback' => [$this, 'check_permissions']]); 33 | register_rest_route($ns, '/file/info', ['methods' => 'POST', 'callback' => [$this, 'file_info'], 'permission_callback' => [$this, 'check_permissions']]); 34 | register_rest_route($ns, '/file/write', ['methods' => 'POST', 'callback' => [$this, 'write_file'], 'permission_callback' => [$this, 'check_permissions']]); 35 | register_rest_route($ns, '/file/delete', ['methods' => 'POST', 'callback' => [$this, 'delete_file'], 'permission_callback' => [$this, 'check_permissions']]); 36 | register_rest_route($ns, '/file/copy', ['methods' => 'POST', 'callback' => [$this, 'copy_file'], 'permission_callback' => [$this, 'check_permissions']]); 37 | register_rest_route($ns, '/file/move', ['methods' => 'POST', 'callback' => [$this, 'move_file'], 'permission_callback' => [$this, 'check_permissions']]); 38 | } 39 | 40 | public function check_permissions() { 41 | return current_user_can('edit_themes') && current_user_can('edit_plugins'); 42 | } 43 | 44 | private function validate_path($path) { 45 | $path = str_replace(['../', '..\\'], '', $path); 46 | $path = ltrim($path, '/\\'); 47 | 48 | foreach ($this->allowed_dirs as $dir) { 49 | if (strpos($path, $dir) === 0) { 50 | $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); 51 | if ($ext && !in_array($ext, $this->allowed_extensions)) { 52 | return new WP_Error('invalid_extension', 'Extension not allowed'); 53 | } 54 | return ABSPATH . $path; 55 | } 56 | } 57 | 58 | return new WP_Error('invalid_path', 'Path not allowed'); 59 | } 60 | 61 | private function create_backup($file_path) { 62 | if (!file_exists($file_path)) return null; 63 | 64 | $backup_id = uniqid('backup_', true); 65 | $backup_file = ABSPATH . $this->backup_dir . '/' . $backup_id . '.bak'; 66 | 67 | if (copy($file_path, $backup_file)) { 68 | $meta = ['original_path' => str_replace(ABSPATH, '', $file_path), 'timestamp' => current_time('mysql'), 'user_id' => get_current_user_id()]; 69 | file_put_contents($backup_file . '.meta', json_encode($meta)); 70 | return $backup_id; 71 | } 72 | 73 | return null; 74 | } 75 | 76 | public function read_file($request) { 77 | $file_path = $this->validate_path($request->get_param('path')); 78 | if (is_wp_error($file_path)) return $file_path; 79 | if (!file_exists($file_path)) return new WP_Error('file_not_found', 'File not found'); 80 | 81 | return ['content' => file_get_contents($file_path), 'size' => filesize($file_path), 'modified' => date('Y-m-d H:i:s', filemtime($file_path))]; 82 | } 83 | 84 | public function list_files($request) { 85 | $dir_path = $this->validate_path($request->get_param('path')); 86 | if (is_wp_error($dir_path)) return $dir_path; 87 | if (!is_dir($dir_path)) return new WP_Error('not_directory', 'Not a directory'); 88 | 89 | $files = []; 90 | $iterator = $request->get_param('recursive') 91 | ? new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_path, RecursiveDirectoryIterator::SKIP_DOTS)) 92 | : new DirectoryIterator($dir_path); 93 | 94 | foreach ($iterator as $file) { 95 | if ($file->isDot()) continue; 96 | $files[] = [ 97 | 'path' => str_replace(ABSPATH, '', $file->getPathname()), 98 | 'name' => $file->getFilename(), 99 | 'type' => $file->isDir() ? 'directory' : 'file', 100 | 'size' => $file->isFile() ? $file->getSize() : 0, 101 | 'modified' => date('Y-m-d H:i:s', $file->getMTime()) 102 | ]; 103 | } 104 | 105 | return ['files' => $files]; 106 | } 107 | 108 | public function file_info($request) { 109 | $file_path = $this->validate_path($request->get_param('path')); 110 | if (is_wp_error($file_path)) return $file_path; 111 | if (!file_exists($file_path)) return new WP_Error('file_not_found', 'File not found'); 112 | 113 | return [ 114 | 'size' => filesize($file_path), 115 | 'modified' => date('Y-m-d H:i:s', filemtime($file_path)), 116 | 'permissions' => substr(sprintf('%o', fileperms($file_path)), -4), 117 | 'type' => is_dir($file_path) ? 'directory' : 'file' 118 | ]; 119 | } 120 | 121 | public function write_file($request) { 122 | $file_path = $this->validate_path($request->get_param('path')); 123 | if (is_wp_error($file_path)) return $file_path; 124 | 125 | $content = $request->get_param('content'); 126 | if (strlen($content) > $this->max_file_size) return new WP_Error('file_too_large', 'File too large'); 127 | 128 | $backup_id = null; 129 | if ($request->get_param('createBackup') && file_exists($file_path)) { 130 | $backup_id = $this->create_backup($file_path); 131 | } 132 | 133 | $dir = dirname($file_path); 134 | if (!is_dir($dir)) wp_mkdir_p($dir); 135 | 136 | $result = file_put_contents($file_path, $content); 137 | return $result !== false ? ['success' => true, 'backup' => $backup_id, 'bytes_written' => $result] : new WP_Error('write_failed', 'Write failed'); 138 | } 139 | 140 | public function delete_file($request) { 141 | $file_path = $this->validate_path($request->get_param('path')); 142 | if (is_wp_error($file_path)) return $file_path; 143 | if (!file_exists($file_path)) return new WP_Error('file_not_found', 'File not found'); 144 | 145 | $backup_id = $request->get_param('createBackup') ? $this->create_backup($file_path) : null; 146 | return unlink($file_path) ? ['success' => true, 'backup' => $backup_id] : new WP_Error('delete_failed', 'Delete failed'); 147 | } 148 | 149 | public function copy_file($request) { 150 | $source_path = $this->validate_path($request->get_param('source')); 151 | if (is_wp_error($source_path)) return $source_path; 152 | 153 | $dest_path = $this->validate_path($request->get_param('destination')); 154 | if (is_wp_error($dest_path)) return $dest_path; 155 | 156 | if (!file_exists($source_path)) return new WP_Error('source_not_found', 'Source not found'); 157 | 158 | $dir = dirname($dest_path); 159 | if (!is_dir($dir)) wp_mkdir_p($dir); 160 | 161 | return copy($source_path, $dest_path) ? ['success' => true] : new WP_Error('copy_failed', 'Copy failed'); 162 | } 163 | 164 | public function move_file($request) { 165 | $source_path = $this->validate_path($request->get_param('source')); 166 | if (is_wp_error($source_path)) return $source_path; 167 | 168 | $dest_path = $this->validate_path($request->get_param('destination')); 169 | if (is_wp_error($dest_path)) return $dest_path; 170 | 171 | if (!file_exists($source_path)) return new WP_Error('source_not_found', 'Source not found'); 172 | 173 | $dir = dirname($dest_path); 174 | if (!is_dir($dir)) wp_mkdir_p($dir); 175 | 176 | return rename($source_path, $dest_path) ? ['success' => true] : new WP_Error('move_failed', 'Move failed'); 177 | } 178 | } -------------------------------------------------------------------------------- /src/tools/menus.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Menu Management Tools 3 | * Create, update, and manage WordPress navigation menus 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callWordPressAPI } from '../utils/api.js'; 8 | import { buildQueryString } from '../utils/helpers.js'; 9 | 10 | export function registerMenuTools(server: any) { 11 | 12 | // ========== MENU MANAGEMENT ========== 13 | 14 | /** 15 | * Get all menus 16 | */ 17 | server.tool('wordpress_get_menus', async (args: any) => { 18 | const { perPage = 100 } = args || {}; 19 | 20 | try { 21 | const params = { per_page: perPage }; 22 | const queryString = buildQueryString(params); 23 | const menus = await callWordPressAPI(`/menus?${queryString}`); 24 | 25 | return Responses.success( 26 | { 27 | menus: menus.map((menu: any) => ({ 28 | id: menu.id, 29 | name: menu.name, 30 | slug: menu.slug, 31 | description: menu.description || '', 32 | count: menu.count || 0, 33 | locations: menu.locations || [] 34 | })), 35 | total: menus.length 36 | }, 37 | `🧭 Retrieved ${menus.length} menus` 38 | ); 39 | } catch (error) { 40 | return Responses.error(`Failed to get menus: ${(error as Error).message}`); 41 | } 42 | }, { 43 | description: 'Get all navigation menus', 44 | schema: {} 45 | }); 46 | 47 | /** 48 | * Create menu 49 | */ 50 | server.tool('wordpress_create_menu', async (args: any) => { 51 | const { name, description = '', slug } = args; 52 | 53 | try { 54 | const menuData: any = { name, description }; 55 | if (slug) menuData.slug = slug; 56 | 57 | const menu = await callWordPressAPI('/menus', 'POST', menuData); 58 | 59 | return Responses.success( 60 | { 61 | id: menu.id, 62 | name: menu.name, 63 | slug: menu.slug 64 | }, 65 | `✅ Created menu: "${name}"` 66 | ); 67 | } catch (error) { 68 | return Responses.error(`Failed to create menu: ${(error as Error).message}`); 69 | } 70 | }, { 71 | description: 'Create a new navigation menu', 72 | schema: { 73 | name: 'string' 74 | } 75 | }); 76 | 77 | /** 78 | * Delete menu 79 | */ 80 | server.tool('wordpress_delete_menu', async (args: any) => { 81 | const { menuId, force = true } = args; 82 | 83 | try { 84 | const endpoint = force ? `/menus/${menuId}?force=true` : `/menus/${menuId}`; 85 | await callWordPressAPI(endpoint, 'DELETE'); 86 | 87 | return Responses.success( 88 | { 89 | id: menuId, 90 | deleted: true 91 | }, 92 | `✅ Deleted menu ID ${menuId}` 93 | ); 94 | } catch (error) { 95 | return Responses.error(`Failed to delete menu: ${(error as Error).message}`); 96 | } 97 | }, { 98 | description: 'Delete a navigation menu', 99 | schema: { 100 | menuId: 'number', 101 | force: 'boolean' 102 | } 103 | }); 104 | 105 | /** 106 | * Get menu items 107 | */ 108 | server.tool('wordpress_get_menu_items', async (args: any) => { 109 | const { menuId, perPage = 100 } = args || {}; 110 | 111 | try { 112 | const params: any = { per_page: perPage }; 113 | if (menuId) params.menus = menuId; 114 | 115 | const queryString = buildQueryString(params); 116 | const items = await callWordPressAPI(`/menu-items?${queryString}`); 117 | 118 | return Responses.success( 119 | { 120 | items: items.map((item: any) => ({ 121 | id: item.id, 122 | title: item.title?.rendered || '', 123 | url: item.url || '', 124 | type: item.type || '', 125 | objectId: item.object_id || 0, 126 | parent: item.parent || 0, 127 | menuOrder: item.menu_order || 0, 128 | target: item.target || '', 129 | classes: item.classes || [] 130 | })), 131 | total: items.length 132 | }, 133 | `📋 Retrieved ${items.length} menu items${menuId ? ` for menu ${menuId}` : ''}` 134 | ); 135 | } catch (error) { 136 | return Responses.error(`Failed to get menu items: ${(error as Error).message}`); 137 | } 138 | }, { 139 | description: 'Get menu items from a specific menu or all menus', 140 | schema: {} 141 | }); 142 | 143 | /** 144 | * Create menu item 145 | */ 146 | server.tool('wordpress_create_menu_item', async (args: any) => { 147 | const { 148 | title, 149 | url, 150 | menus, 151 | parent = 0, 152 | type = 'custom', 153 | objectId = 0, 154 | menuOrder, 155 | target = '', 156 | classes = [] 157 | } = args; 158 | 159 | try { 160 | const itemData: any = { 161 | title, 162 | menus, 163 | type, 164 | parent, 165 | url: url || '#' 166 | }; 167 | 168 | if (objectId) itemData.object_id = objectId; 169 | if (menuOrder && menuOrder > 0) itemData.menu_order = menuOrder; 170 | if (target) itemData.target = target; 171 | if (classes.length > 0) itemData.classes = classes; 172 | 173 | const item = await callWordPressAPI('/menu-items', 'POST', itemData); 174 | 175 | return Responses.success( 176 | { 177 | id: item.id, 178 | title: item.title?.rendered || title, 179 | menuOrder: item.menu_order 180 | }, 181 | `✅ Created menu item: "${title}"` 182 | ); 183 | } catch (error) { 184 | return Responses.error(`Failed to create menu item: ${(error as Error).message}`); 185 | } 186 | }, { 187 | description: 'Add an item to a navigation menu', 188 | schema: { 189 | title: 'string', 190 | menus: 'number' 191 | } 192 | }); 193 | 194 | /** 195 | * Update menu item 196 | */ 197 | server.tool('wordpress_update_menu_item', async (args: any) => { 198 | const { itemId, updates } = args; 199 | 200 | try { 201 | const item = await callWordPressAPI(`/menu-items/${itemId}`, 'PUT', updates); 202 | 203 | return Responses.success( 204 | { 205 | id: item.id, 206 | title: item.title?.rendered || '', 207 | updated: true 208 | }, 209 | `✅ Updated menu item ID ${itemId}` 210 | ); 211 | } catch (error) { 212 | return Responses.error(`Failed to update menu item: ${(error as Error).message}`); 213 | } 214 | }, { 215 | description: 'Update a menu item (title, URL, order, etc.)', 216 | schema: { 217 | itemId: 'number', 218 | updates: 'object' 219 | } 220 | }); 221 | 222 | /** 223 | * Delete menu item 224 | */ 225 | server.tool('wordpress_delete_menu_item', async (args: any) => { 226 | const { itemId, force = true } = args; 227 | 228 | try { 229 | const endpoint = force ? `/menu-items/${itemId}?force=true` : `/menu-items/${itemId}`; 230 | await callWordPressAPI(endpoint, 'DELETE'); 231 | 232 | return Responses.success( 233 | { 234 | id: itemId, 235 | deleted: true 236 | }, 237 | `✅ Deleted menu item ID ${itemId}` 238 | ); 239 | } catch (error) { 240 | return Responses.error(`Failed to delete menu item: ${(error as Error).message}`); 241 | } 242 | }, { 243 | description: 'Delete a menu item', 244 | schema: { 245 | itemId: 'number', 246 | force: 'boolean' 247 | } 248 | }); 249 | 250 | /** 251 | * Get menu locations 252 | */ 253 | server.tool('wordpress_get_menu_locations', async () => { 254 | try { 255 | const locations = await callWordPressAPI('/menu-locations'); 256 | 257 | return Responses.success( 258 | { 259 | locations: Object.entries(locations).map(([key, value]: [string, any]) => ({ 260 | location: key, 261 | name: value.name || key, 262 | description: value.description || '', 263 | menu: value.menu || null 264 | })), 265 | total: Object.keys(locations).length 266 | }, 267 | `📍 Retrieved ${Object.keys(locations).length} menu locations` 268 | ); 269 | } catch (error) { 270 | return Responses.error(`Failed to get menu locations: ${(error as Error).message}`); 271 | } 272 | }, { 273 | description: 'Get all registered menu locations in the active theme', 274 | schema: {} 275 | }); 276 | 277 | /** 278 | * Assign menu to location 279 | */ 280 | server.tool('wordpress_assign_menu_to_location', async (args: any) => { 281 | const { location, menuId } = args; 282 | 283 | try { 284 | await callWordPressAPI(`/menu-locations/${location}`, 'PUT', { 285 | menu: menuId 286 | }); 287 | 288 | return Responses.success( 289 | { 290 | location, 291 | menuId, 292 | assigned: true 293 | }, 294 | `✅ Assigned menu ${menuId} to location "${location}"` 295 | ); 296 | } catch (error) { 297 | return Responses.error(`Failed to assign menu: ${(error as Error).message}`); 298 | } 299 | }, { 300 | description: 'Assign a menu to a theme location', 301 | schema: { 302 | location: 'string', 303 | menuId: 'number' 304 | } 305 | }); 306 | } -------------------------------------------------------------------------------- /src/tools/plugins.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Plugin Management Tools 3 | * Activate, deactivate, and manage plugins 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callWordPressAPI, callCustomAPI } from '../utils/api.js'; 8 | import { buildQueryString } from '../utils/helpers.js'; 9 | 10 | export function registerPluginTools(server: any) { 11 | 12 | // ========== PLUGIN MANAGEMENT ========== 13 | 14 | /** 15 | * Get detailed plugin information 16 | */ 17 | server.tool('wordpress_get_plugins_detailed', async (args: any) => { 18 | const { status, search } = args || {}; 19 | 20 | try { 21 | const params: any = {}; 22 | if (status) params.status = status; 23 | if (search) params.search = search; 24 | 25 | const queryString = buildQueryString(params); 26 | const plugins = await callWordPressAPI(`/plugins${queryString ? '?' + queryString : ''}`); 27 | 28 | return Responses.success( 29 | { 30 | plugins: plugins.map((plugin: any) => ({ 31 | plugin: plugin.plugin, 32 | status: plugin.status, 33 | name: plugin.name, 34 | pluginUri: plugin.plugin_uri || '', 35 | version: plugin.version, 36 | description: plugin.description?.rendered || '', 37 | author: plugin.author, 38 | authorUri: plugin.author_uri || '', 39 | textDomain: plugin.textdomain || '', 40 | requiresWP: plugin.requires_wp || '', 41 | requiresPHP: plugin.requires_php || '', 42 | networkOnly: plugin.network_only || false 43 | })), 44 | total: plugins.length 45 | }, 46 | `🔌 Retrieved ${plugins.length} plugins` 47 | ); 48 | } catch (error) { 49 | return Responses.error(`Failed to get plugins: ${(error as Error).message}`); 50 | } 51 | }, { 52 | description: 'Get detailed information about all installed plugins', 53 | schema: {} 54 | }); 55 | 56 | /** 57 | * Activate plugin 58 | */ 59 | server.tool('wordpress_activate_plugin', async (args: any) => { 60 | const { plugin } = args; 61 | 62 | try { 63 | const result = await callWordPressAPI(`/plugins/${plugin}`, 'PUT', { 64 | status: 'active' 65 | }); 66 | 67 | return Responses.success( 68 | { 69 | plugin: result.plugin, 70 | name: result.name, 71 | status: 'active' 72 | }, 73 | `✅ Activated plugin: ${result.name}` 74 | ); 75 | } catch (error) { 76 | return Responses.error(`Failed to activate plugin: ${(error as Error).message}`); 77 | } 78 | }, { 79 | description: 'Activate a plugin', 80 | schema: { 81 | plugin: 'string' 82 | } 83 | }); 84 | 85 | /** 86 | * Deactivate plugin 87 | */ 88 | server.tool('wordpress_deactivate_plugin', async (args: any) => { 89 | const { plugin } = args; 90 | 91 | try { 92 | const result = await callWordPressAPI(`/plugins/${plugin}`, 'PUT', { 93 | status: 'inactive' 94 | }); 95 | 96 | return Responses.success( 97 | { 98 | plugin: result.plugin, 99 | name: result.name, 100 | status: 'inactive' 101 | }, 102 | `⏸️ Deactivated plugin: ${result.name}` 103 | ); 104 | } catch (error) { 105 | return Responses.error(`Failed to deactivate plugin: ${(error as Error).message}`); 106 | } 107 | }, { 108 | description: 'Deactivate a plugin', 109 | schema: { 110 | plugin: 'string' 111 | } 112 | }); 113 | 114 | /** 115 | * Delete plugin 116 | */ 117 | server.tool('wordpress_delete_plugin', async (args: any) => { 118 | const { plugin } = args; 119 | 120 | try { 121 | await callWordPressAPI(`/plugins/${plugin}`, 'DELETE'); 122 | 123 | return Responses.success( 124 | { 125 | plugin, 126 | deleted: true 127 | }, 128 | `✅ Deleted plugin: ${plugin}` 129 | ); 130 | } catch (error) { 131 | return Responses.error(`Failed to delete plugin: ${(error as Error).message}`); 132 | } 133 | }, { 134 | description: 'Delete a plugin (must be deactivated first)', 135 | schema: { 136 | plugin: 'string' 137 | } 138 | }); 139 | 140 | /** 141 | * Read plugin file 142 | */ 143 | server.tool('wordpress_read_plugin_file', async (args: any) => { 144 | const { plugin, filePath } = args; 145 | 146 | try { 147 | const fullPath = `wp-content/plugins/${plugin}/${filePath}`; 148 | const result = await callCustomAPI('wp-json/wpmcp/v1/file/read', 'POST', { 149 | path: fullPath 150 | }); 151 | 152 | return Responses.success( 153 | { 154 | plugin, 155 | filePath, 156 | content: result.content, 157 | size: result.size, 158 | modified: result.modified 159 | }, 160 | `📄 Read ${filePath} from ${plugin}` 161 | ); 162 | } catch (error) { 163 | return Responses.error(`Failed to read plugin file: ${(error as Error).message}`); 164 | } 165 | }, { 166 | description: 'Read a specific file from a plugin', 167 | schema: { 168 | plugin: 'string', 169 | filePath: 'string' 170 | } 171 | }); 172 | 173 | /** 174 | * Write plugin file 175 | */ 176 | server.tool('wordpress_write_plugin_file', async (args: any) => { 177 | const { plugin, filePath, content, createBackup = true } = args; 178 | 179 | try { 180 | const fullPath = `wp-content/plugins/${plugin}/${filePath}`; 181 | const result = await callCustomAPI('wp-json/wpmcp/v1/file/write', 'POST', { 182 | path: fullPath, 183 | content, 184 | createBackup 185 | }); 186 | 187 | return Responses.success( 188 | { 189 | plugin, 190 | filePath, 191 | success: true, 192 | backup: result.backup 193 | }, 194 | `✅ Wrote ${filePath} to ${plugin}${result.backup ? ' (backup created)' : ''}` 195 | ); 196 | } catch (error) { 197 | return Responses.error(`Failed to write plugin file: ${(error as Error).message}`); 198 | } 199 | }, { 200 | description: 'Write or modify a file in a plugin (automatically creates backup)', 201 | schema: { 202 | plugin: 'string', 203 | filePath: 'string', 204 | content: 'string' 205 | } 206 | }); 207 | 208 | /** 209 | * List plugin files 210 | */ 211 | server.tool('wordpress_list_plugin_files', async (args: any) => { 212 | const { plugin, recursive = false } = args; 213 | 214 | try { 215 | const pluginPath = `wp-content/plugins/${plugin}/`; 216 | const result = await callCustomAPI('wp-json/wpmcp/v1/file/list', 'POST', { 217 | path: pluginPath, 218 | recursive 219 | }); 220 | 221 | return Responses.success( 222 | { 223 | plugin, 224 | files: result.files, 225 | count: result.files.length 226 | }, 227 | `📁 Listed ${result.files.length} files in ${plugin}` 228 | ); 229 | } catch (error) { 230 | return Responses.error(`Failed to list plugin files: ${(error as Error).message}`); 231 | } 232 | }, { 233 | description: 'List all files in a plugin directory', 234 | schema: { 235 | plugin: 'string', 236 | recursive: 'boolean' 237 | } 238 | }); 239 | 240 | /** 241 | * Get active plugins 242 | */ 243 | server.tool('wordpress_get_active_plugins', async () => { 244 | try { 245 | const plugins = await callWordPressAPI('/plugins?status=active'); 246 | 247 | return Responses.success( 248 | { 249 | plugins: plugins.map((p: any) => ({ 250 | plugin: p.plugin, 251 | name: p.name, 252 | version: p.version 253 | })), 254 | total: plugins.length 255 | }, 256 | `🔌 ${plugins.length} active plugins` 257 | ); 258 | } catch (error) { 259 | return Responses.error(`Failed to get active plugins: ${(error as Error).message}`); 260 | } 261 | }, { 262 | description: 'Get all currently active plugins', 263 | schema: {} 264 | }); 265 | 266 | /** 267 | * Check if plugin exists 268 | */ 269 | server.tool('wordpress_plugin_exists', async (args: any) => { 270 | const { plugin } = args; 271 | 272 | try { 273 | const plugins = await callWordPressAPI('/plugins'); 274 | const exists = plugins.some((p: any) => p.plugin === plugin); 275 | 276 | return Responses.success( 277 | { 278 | plugin, 279 | exists 280 | }, 281 | exists ? `✅ Plugin "${plugin}" exists` : `❌ Plugin "${plugin}" not found` 282 | ); 283 | } catch (error) { 284 | return Responses.error(`Failed to check plugin: ${(error as Error).message}`); 285 | } 286 | }, { 287 | description: 'Check if a plugin is installed', 288 | schema: { 289 | plugin: 'string' 290 | } 291 | }); 292 | 293 | /** 294 | * Get plugin status 295 | */ 296 | server.tool('wordpress_get_plugin_status', async (args: any) => { 297 | const { plugin } = args; 298 | 299 | try { 300 | const pluginData = await callWordPressAPI(`/plugins/${plugin}`); 301 | 302 | return Responses.success( 303 | { 304 | plugin: pluginData.plugin, 305 | name: pluginData.name, 306 | status: pluginData.status, 307 | version: pluginData.version, 308 | active: pluginData.status === 'active' 309 | }, 310 | `ℹ️ Plugin "${pluginData.name}" is ${pluginData.status}` 311 | ); 312 | } catch (error) { 313 | return Responses.error(`Failed to get plugin status: ${(error as Error).message}`); 314 | } 315 | }, { 316 | description: 'Get status and details of a specific plugin', 317 | schema: { 318 | plugin: 'string' 319 | } 320 | }); 321 | } -------------------------------------------------------------------------------- /src/tools/custom-post-types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Custom Post Types & Taxonomies Tools 3 | * Register and manage custom content types 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callWordPressAPI } from '../utils/api.js'; 8 | 9 | export function registerCustomPostTypeTools(server: any) { 10 | 11 | // ========== POST TYPES ========== 12 | 13 | /** 14 | * Get all post types 15 | */ 16 | server.tool('wordpress_get_post_types', async (args: any) => { 17 | const { context = 'view' } = args || {}; 18 | 19 | try { 20 | const postTypes = await callWordPressAPI(`/types?context=${context}`); 21 | 22 | const types = Object.entries(postTypes).map(([slug, data]: [string, any]) => ({ 23 | slug, 24 | name: data.name || slug, 25 | description: data.description || '', 26 | hierarchical: data.hierarchical || false, 27 | hasArchive: data.has_archive || false, 28 | public: data.public !== undefined ? data.public : true, 29 | showInRest: data.show_in_rest !== undefined ? data.show_in_rest : true, 30 | supports: data.supports || [], 31 | taxonomies: data.taxonomies || [] 32 | })); 33 | 34 | return Responses.success( 35 | { 36 | postTypes: types, 37 | total: types.length 38 | }, 39 | `📝 Retrieved ${types.length} post types` 40 | ); 41 | } catch (error) { 42 | return Responses.error(`Failed to get post types: ${(error as Error).message}`); 43 | } 44 | }, { 45 | description: 'Get all registered post types', 46 | schema: {} 47 | }); 48 | 49 | /** 50 | * Get specific post type details 51 | */ 52 | server.tool('wordpress_get_post_type', async (args: any) => { 53 | const { postType } = args; 54 | 55 | try { 56 | const data = await callWordPressAPI(`/types/${postType}`); 57 | 58 | return Responses.success( 59 | { 60 | slug: data.slug || postType, 61 | name: data.name || '', 62 | description: data.description || '', 63 | hierarchical: data.hierarchical || false, 64 | hasArchive: data.has_archive || false, 65 | restBase: data.rest_base || postType, 66 | supports: data.supports || [], 67 | taxonomies: data.taxonomies || [], 68 | labels: data.labels || {} 69 | }, 70 | `ℹ️ Post type: ${data.name}` 71 | ); 72 | } catch (error) { 73 | return Responses.error(`Failed to get post type: ${(error as Error).message}`); 74 | } 75 | }, { 76 | description: 'Get details for a specific post type', 77 | schema: { 78 | postType: 'string' 79 | } 80 | }); 81 | 82 | // ========== TAXONOMIES ========== 83 | 84 | /** 85 | * Get all taxonomies 86 | */ 87 | server.tool('wordpress_get_taxonomies', async (args: any) => { 88 | const { context = 'view', type } = args || {}; 89 | 90 | try { 91 | let endpoint = `/taxonomies?context=${context}`; 92 | if (type) endpoint += `&type=${type}`; 93 | 94 | const taxonomies = await callWordPressAPI(endpoint); 95 | 96 | const taxs = Object.entries(taxonomies).map(([slug, data]: [string, any]) => ({ 97 | slug, 98 | name: data.name || slug, 99 | description: data.description || '', 100 | hierarchical: data.hierarchical || false, 101 | public: data.public !== undefined ? data.public : true, 102 | showInRest: data.show_in_rest !== undefined ? data.show_in_rest : true, 103 | types: data.types || [] 104 | })); 105 | 106 | return Responses.success( 107 | { 108 | taxonomies: taxs, 109 | total: taxs.length 110 | }, 111 | `🏷️ Retrieved ${taxs.length} taxonomies` 112 | ); 113 | } catch (error) { 114 | return Responses.error(`Failed to get taxonomies: ${(error as Error).message}`); 115 | } 116 | }, { 117 | description: 'Get all registered taxonomies', 118 | schema: {} 119 | }); 120 | 121 | /** 122 | * Get specific taxonomy details 123 | */ 124 | server.tool('wordpress_get_taxonomy', async (args: any) => { 125 | const { taxonomy } = args; 126 | 127 | try { 128 | const data = await callWordPressAPI(`/taxonomies/${taxonomy}`); 129 | 130 | return Responses.success( 131 | { 132 | slug: data.slug || taxonomy, 133 | name: data.name || '', 134 | description: data.description || '', 135 | hierarchical: data.hierarchical || false, 136 | restBase: data.rest_base || taxonomy, 137 | types: data.types || [], 138 | labels: data.labels || {} 139 | }, 140 | `ℹ️ Taxonomy: ${data.name}` 141 | ); 142 | } catch (error) { 143 | return Responses.error(`Failed to get taxonomy: ${(error as Error).message}`); 144 | } 145 | }, { 146 | description: 'Get details for a specific taxonomy', 147 | schema: { 148 | taxonomy: 'string' 149 | } 150 | }); 151 | 152 | /** 153 | * Get terms from a taxonomy 154 | */ 155 | server.tool('wordpress_get_terms', async (args: any) => { 156 | const { taxonomy, perPage = 100, hideEmpty = false, parent, search } = args; 157 | 158 | try { 159 | const params: any = { 160 | per_page: perPage, 161 | hide_empty: hideEmpty 162 | }; 163 | 164 | if (parent !== undefined) params.parent = parent; 165 | if (search) params.search = search; 166 | 167 | // Map taxonomy to correct endpoint (WordPress uses plural forms) 168 | const endpointMap: Record = { 169 | 'category': 'categories', 170 | 'post_tag': 'tags', 171 | 'nav_menu': 'menus' 172 | }; 173 | 174 | const endpoint = endpointMap[taxonomy] || taxonomy; 175 | const queryParams = new URLSearchParams(params).toString(); 176 | const terms = await callWordPressAPI(`/${endpoint}?${queryParams}`); 177 | 178 | return Responses.success( 179 | { 180 | taxonomy, 181 | terms: terms.map((term: any) => ({ 182 | id: term.id, 183 | name: term.name, 184 | slug: term.slug, 185 | description: term.description || '', 186 | count: term.count || 0, 187 | parent: term.parent || 0, 188 | link: term.link || '' 189 | })), 190 | total: terms.length 191 | }, 192 | `🏷️ Retrieved ${terms.length} terms from ${taxonomy}` 193 | ); 194 | } catch (error) { 195 | return Responses.error(`Failed to get terms: ${(error as Error).message}`); 196 | } 197 | }, { 198 | description: 'Get terms from a taxonomy (categories, tags, or custom)', 199 | schema: { 200 | taxonomy: 'string' 201 | } 202 | }); 203 | 204 | /** 205 | * Create term in taxonomy 206 | */ 207 | server.tool('wordpress_create_term', async (args: any) => { 208 | const { taxonomy, name, description = '', slug, parent } = args; 209 | 210 | try { 211 | // Map taxonomy to correct endpoint 212 | const endpointMap: Record = { 213 | 'category': 'categories', 214 | 'post_tag': 'tags', 215 | 'nav_menu': 'menus' 216 | }; 217 | 218 | const endpoint = endpointMap[taxonomy] || taxonomy; 219 | 220 | const termData: any = { name, description }; 221 | if (slug) termData.slug = slug; 222 | if (parent) termData.parent = parent; 223 | 224 | const term = await callWordPressAPI(`/${endpoint}`, 'POST', termData); 225 | 226 | return Responses.success( 227 | { 228 | id: term.id, 229 | taxonomy, 230 | name: term.name, 231 | slug: term.slug 232 | }, 233 | `✅ Created ${taxonomy} term: "${name}"` 234 | ); 235 | } catch (error) { 236 | return Responses.error(`Failed to create term: ${(error as Error).message}`); 237 | } 238 | }, { 239 | description: 'Create a new term in a taxonomy', 240 | schema: { 241 | taxonomy: 'string', 242 | name: 'string' 243 | } 244 | }); 245 | 246 | /** 247 | * Update term 248 | */ 249 | server.tool('wordpress_update_term', async (args: any) => { 250 | const { taxonomy, termId, updates } = args; 251 | 252 | try { 253 | const endpointMap: Record = { 254 | 'category': 'categories', 255 | 'post_tag': 'tags', 256 | 'nav_menu': 'menus' 257 | }; 258 | 259 | const endpoint = endpointMap[taxonomy] || taxonomy; 260 | const term = await callWordPressAPI(`/${endpoint}/${termId}`, 'PUT', updates); 261 | 262 | return Responses.success( 263 | { 264 | id: term.id, 265 | name: term.name, 266 | slug: term.slug 267 | }, 268 | `✅ Updated ${taxonomy} term ID ${termId}` 269 | ); 270 | } catch (error) { 271 | return Responses.error(`Failed to update term: ${(error as Error).message}`); 272 | } 273 | }, { 274 | description: 'Update a term in a taxonomy', 275 | schema: { 276 | taxonomy: 'string', 277 | termId: 'number', 278 | updates: 'object' 279 | } 280 | }); 281 | 282 | /** 283 | * Delete term 284 | */ 285 | server.tool('wordpress_delete_term', async (args: any) => { 286 | const { taxonomy, termId, force = false } = args; 287 | 288 | try { 289 | const endpointMap: Record = { 290 | 'category': 'categories', 291 | 'post_tag': 'tags', 292 | 'nav_menu': 'menus' 293 | }; 294 | 295 | const endpointName = endpointMap[taxonomy] || taxonomy; 296 | const endpoint = force ? `/${endpointName}/${termId}?force=true` : `/${endpointName}/${termId}`; 297 | await callWordPressAPI(endpoint, 'DELETE'); 298 | 299 | return Responses.success( 300 | { 301 | taxonomy, 302 | id: termId, 303 | deleted: true 304 | }, 305 | `✅ Deleted ${taxonomy} term ID ${termId}` 306 | ); 307 | } catch (error) { 308 | return Responses.error(`Failed to delete term: ${(error as Error).message}`); 309 | } 310 | }, { 311 | description: 'Delete a term from a taxonomy', 312 | schema: { 313 | taxonomy: 'string', 314 | termId: 'number', 315 | force: 'boolean' 316 | } 317 | }); 318 | } -------------------------------------------------------------------------------- /src/tools/seo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Advanced SEO Tools 3 | * Comprehensive SEO management for WordPress sites 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callWordPressAPI, callCustomAPI } from '../utils/api.js'; 8 | 9 | function calculateSEOScore(analysis: any): number { 10 | let score = 0; 11 | if (analysis.hasTitle) score += 15; 12 | if (analysis.hasContent) score += 15; 13 | if (analysis.hasExcerpt) score += 10; 14 | if (analysis.hasFeaturedImage) score += 10; 15 | if (analysis.hasCategories) score += 10; 16 | if (analysis.hasTags) score += 10; 17 | if (analysis.titleLength >= 30 && analysis.titleLength <= 60) score += 15; 18 | if (analysis.contentLength >= 300) score += 15; 19 | return score; 20 | } 21 | 22 | export function registerSEOTools(server: any) { 23 | 24 | // ========== SITEMAP MANAGEMENT ========== 25 | 26 | /** 27 | * Generate XML sitemap 28 | */ 29 | server.tool('wordpress_generate_sitemap', async () => { 30 | try { 31 | // WordPress 5.5+ has built-in sitemaps 32 | const sitemapUrl = await callCustomAPI('wp-json/wpmcp/v1/seo/generate-sitemap', 'POST'); 33 | 34 | return Responses.success( 35 | { 36 | url: sitemapUrl.url || '/wp-sitemap.xml', 37 | generated: true 38 | }, 39 | `✅ Sitemap generated` 40 | ); 41 | } catch (error) { 42 | return Responses.error(`Failed to generate sitemap: ${(error as Error).message}`); 43 | } 44 | }, { 45 | description: 'Generate XML sitemap for search engines', 46 | schema: {} 47 | }); 48 | 49 | /** 50 | * Get robots.txt content 51 | */ 52 | server.tool('wordpress_get_robots_txt', async () => { 53 | try { 54 | const result = await callCustomAPI('wp-json/wpmcp/v1/seo/robots-txt', 'GET'); 55 | 56 | return Responses.success( 57 | { 58 | content: result.content || '', 59 | exists: result.exists || false 60 | }, 61 | result.exists ? `📄 Robots.txt content retrieved` : `❌ Robots.txt not found` 62 | ); 63 | } catch (error) { 64 | return Responses.error(`Failed to get robots.txt: ${(error as Error).message}`); 65 | } 66 | }, { 67 | description: 'Get robots.txt file content', 68 | schema: {} 69 | }); 70 | 71 | /** 72 | * Update robots.txt content 73 | */ 74 | server.tool('wordpress_update_robots_txt', async (args: any) => { 75 | const { content } = args; 76 | 77 | try { 78 | await callCustomAPI('wp-json/wpmcp/v1/seo/robots-txt', 'POST', { content }); 79 | 80 | return Responses.success( 81 | { 82 | updated: true 83 | }, 84 | `✅ Robots.txt updated` 85 | ); 86 | } catch (error) { 87 | return Responses.error(`Failed to update robots.txt: ${(error as Error).message}`); 88 | } 89 | }, { 90 | description: 'Update robots.txt file content', 91 | schema: { 92 | content: 'string' 93 | } 94 | }); 95 | 96 | // ========== REDIRECTS ========== 97 | 98 | /** 99 | * Create redirect 100 | */ 101 | server.tool('wordpress_create_redirect', async (args: any) => { 102 | const { source, destination, type = 301 } = args; 103 | 104 | try { 105 | await callCustomAPI('wp-json/wpmcp/v1/seo/redirect', 'POST', { 106 | source, 107 | destination, 108 | type 109 | }); 110 | 111 | return Responses.success( 112 | { 113 | source, 114 | destination, 115 | type, 116 | created: true 117 | }, 118 | `✅ Created ${type} redirect: ${source} → ${destination}` 119 | ); 120 | } catch (error) { 121 | return Responses.error(`Failed to create redirect: ${(error as Error).message}`); 122 | } 123 | }, { 124 | description: 'Create URL redirect (301, 302, 307)', 125 | schema: { 126 | source: 'string', 127 | destination: 'string' 128 | } 129 | }); 130 | 131 | /** 132 | * Get all redirects 133 | */ 134 | server.tool('wordpress_get_redirects', async () => { 135 | try { 136 | const result = await callCustomAPI('wp-json/wpmcp/v1/seo/redirects', 'GET'); 137 | 138 | return Responses.success( 139 | { 140 | redirects: result.redirects || [], 141 | total: result.total || 0 142 | }, 143 | `🔀 Retrieved ${result.total || 0} redirects` 144 | ); 145 | } catch (error) { 146 | return Responses.error(`Failed to get redirects: ${(error as Error).message}`); 147 | } 148 | }, { 149 | description: 'Get all URL redirects', 150 | schema: {} 151 | }); 152 | 153 | /** 154 | * Delete redirect 155 | */ 156 | server.tool('wordpress_delete_redirect', async (args: any) => { 157 | const { redirectId } = args; 158 | 159 | try { 160 | await callCustomAPI(`wp-json/wpmcp/v1/seo/redirect/${redirectId}`, 'DELETE'); 161 | 162 | return Responses.success( 163 | { 164 | id: redirectId, 165 | deleted: true 166 | }, 167 | `✅ Deleted redirect ID ${redirectId}` 168 | ); 169 | } catch (error) { 170 | return Responses.error(`Failed to delete redirect: ${(error as Error).message}`); 171 | } 172 | }, { 173 | description: 'Delete URL redirect', 174 | schema: { 175 | redirectId: 'number' 176 | } 177 | }); 178 | 179 | // ========== META TAGS & SCHEMA ========== 180 | 181 | /** 182 | * Set Open Graph tags 183 | */ 184 | server.tool('wordpress_set_og_tags', async (args: any) => { 185 | const { postId, ogTitle, ogDescription, ogImage, ogType = 'article' } = args; 186 | 187 | try { 188 | const meta: Record = {}; 189 | 190 | if (ogTitle) meta._yoast_wpseo_opengraph_title = ogTitle; 191 | if (ogDescription) meta._yoast_wpseo_opengraph_description = ogDescription; 192 | if (ogImage) meta._yoast_wpseo_opengraph_image = ogImage; 193 | if (ogType) meta._yoast_wpseo_opengraph_type = ogType; 194 | 195 | await callWordPressAPI(`/posts/${postId}`, 'PUT', { meta }); 196 | 197 | return Responses.success( 198 | { 199 | postId, 200 | fields: Object.keys(meta) 201 | }, 202 | `✅ Set Open Graph tags for post ${postId}` 203 | ); 204 | } catch (error) { 205 | return Responses.error(`Failed to set OG tags: ${(error as Error).message}`); 206 | } 207 | }, { 208 | description: 'Set Open Graph meta tags for social sharing', 209 | schema: { 210 | postId: 'number' 211 | } 212 | }); 213 | 214 | /** 215 | * Set Twitter Card tags 216 | */ 217 | server.tool('wordpress_set_twitter_cards', async (args: any) => { 218 | const { postId, twitterTitle, twitterDescription, twitterImage, cardType = 'summary_large_image' } = args; 219 | 220 | try { 221 | const meta: Record = {}; 222 | 223 | if (twitterTitle) meta._yoast_wpseo_twitter_title = twitterTitle; 224 | if (twitterDescription) meta._yoast_wpseo_twitter_description = twitterDescription; 225 | if (twitterImage) meta._yoast_wpseo_twitter_image = twitterImage; 226 | if (cardType) meta._yoast_wpseo_twitter_card_type = cardType; 227 | 228 | await callWordPressAPI(`/posts/${postId}`, 'PUT', { meta }); 229 | 230 | return Responses.success( 231 | { 232 | postId, 233 | fields: Object.keys(meta) 234 | }, 235 | `✅ Set Twitter Card tags for post ${postId}` 236 | ); 237 | } catch (error) { 238 | return Responses.error(`Failed to set Twitter cards: ${(error as Error).message}`); 239 | } 240 | }, { 241 | description: 'Set Twitter Card meta tags for social sharing', 242 | schema: { 243 | postId: 'number' 244 | } 245 | }); 246 | 247 | /** 248 | * Set canonical URL 249 | */ 250 | server.tool('wordpress_set_canonical_url', async (args: any) => { 251 | const { postId, canonicalUrl } = args; 252 | 253 | try { 254 | await callWordPressAPI(`/posts/${postId}`, 'PUT', { 255 | meta: { 256 | _yoast_wpseo_canonical: canonicalUrl 257 | } 258 | }); 259 | 260 | return Responses.success( 261 | { 262 | postId, 263 | canonicalUrl 264 | }, 265 | `✅ Set canonical URL for post ${postId}` 266 | ); 267 | } catch (error) { 268 | return Responses.error(`Failed to set canonical: ${(error as Error).message}`); 269 | } 270 | }, { 271 | description: 'Set canonical URL for post', 272 | schema: { 273 | postId: 'number', 274 | canonicalUrl: 'string' 275 | } 276 | }); 277 | 278 | /** 279 | * Set schema markup 280 | */ 281 | server.tool('wordpress_set_schema_markup', async (args: any) => { 282 | const { postId, schemaType, schemaData } = args; 283 | 284 | try { 285 | const schema = { 286 | '@context': 'https://schema.org', 287 | '@type': schemaType, 288 | ...schemaData 289 | }; 290 | 291 | await callWordPressAPI(`/posts/${postId}`, 'PUT', { 292 | meta: { 293 | _wpmcp_schema_markup: JSON.stringify(schema) 294 | } 295 | }); 296 | 297 | return Responses.success( 298 | { 299 | postId, 300 | schemaType, 301 | schema 302 | }, 303 | `✅ Set ${schemaType} schema for post ${postId}` 304 | ); 305 | } catch (error) { 306 | return Responses.error(`Failed to set schema: ${(error as Error).message}`); 307 | } 308 | }, { 309 | description: 'Add JSON-LD schema markup to post', 310 | schema: { 311 | postId: 'number', 312 | schemaType: 'string', 313 | schemaData: 'object' 314 | } 315 | }); 316 | 317 | /** 318 | * Analyze SEO for post 319 | */ 320 | server.tool('wordpress_analyze_seo', async (args: any) => { 321 | const { postId } = args; 322 | 323 | try { 324 | const post = await callWordPressAPI(`/posts/${postId}`); 325 | 326 | const analysis = { 327 | hasTitle: !!post.title?.rendered, 328 | hasContent: !!post.content?.rendered, 329 | hasExcerpt: !!post.excerpt?.rendered, 330 | hasFeaturedImage: !!post.featured_media, 331 | hasCategories: post.categories?.length > 0, 332 | hasTags: post.tags?.length > 0, 333 | titleLength: post.title?.rendered?.length || 0, 334 | contentLength: post.content?.rendered?.length || 0 335 | }; 336 | 337 | return Responses.success( 338 | { 339 | postId, 340 | analysis, 341 | score: calculateSEOScore(analysis) 342 | }, 343 | `📊 SEO analysis for post ${postId}` 344 | ); 345 | } catch (error) { 346 | return Responses.error(`Failed to analyze SEO: ${(error as Error).message}`); 347 | } 348 | }, { 349 | description: 'Analyze SEO elements for a post', 350 | schema: { 351 | postId: 'number' 352 | } 353 | }); 354 | } -------------------------------------------------------------------------------- /WPMCP_TOOLS.MD: -------------------------------------------------------------------------------- 1 | # WordPress MCP Server - Tool Reference 2 | 3 | **Version:** 3.0.0 | **Total Tools:** 190 4 | 5 | --- 6 | 7 | ## Posts (15 tools) 8 | 9 | 1. `wordpress_create_post` - Create new post 10 | 2. `wordpress_update_post` - Update existing post 11 | 3. `wordpress_delete_post` - Delete post 12 | 4. `wordpress_get_posts` - Get posts with filtering 13 | 5. `wordpress_get_post` - Get single post by ID 14 | 6. `wordpress_search_posts` - Search posts by keyword 15 | 7. `wordpress_schedule_post` - Schedule post for future publication 16 | 8. `wordpress_publish_post` - Publish draft post 17 | 9. `wordpress_duplicate_post` - Duplicate existing post 18 | 10. `wordpress_get_post_revisions` - Get post edit history 19 | 11. `wordpress_bulk_create_posts` - Create multiple posts 20 | 12. `wordpress_bulk_update_posts` - Update multiple posts 21 | 13. `wordpress_bulk_delete_posts` - Delete multiple posts 22 | 23 | ## Pages (4 tools) 24 | 25 | 1. `wordpress_create_page` - Create new page 26 | 2. `wordpress_update_page` - Update existing page 27 | 3. `wordpress_delete_page` - Delete page 28 | 4. `wordpress_get_pages` - Get pages with hierarchy 29 | 30 | ## Media (5 tools) 31 | 32 | 1. `wordpress_upload_media` - Upload image or file 33 | 2. `wordpress_get_media` - Get media library files 34 | 3. `wordpress_update_media` - Update media metadata 35 | 4. `wordpress_delete_media` - Delete media file 36 | 5. `wordpress_set_featured_image` - Set post featured image 37 | 38 | ## Users (4 tools) 39 | 40 | 1. `wordpress_create_user` - Create new user 41 | 2. `wordpress_get_users` - Get users with role filtering 42 | 3. `wordpress_update_user` - Update user information 43 | 4. `wordpress_delete_user` - Delete user 44 | 45 | ## Categories (4 tools) 46 | 47 | 1. `wordpress_create_category` - Create new category 48 | 2. `wordpress_get_categories` - Get all categories 49 | 3. `wordpress_update_category` - Update category 50 | 4. `wordpress_delete_category` - Delete category 51 | 52 | ## Tags (2 tools) 53 | 54 | 1. `wordpress_create_tag` - Create new tag 55 | 2. `wordpress_get_tags` - Get all tags 56 | 57 | ## Comments (4 tools) 58 | 59 | 1. `wordpress_create_comment` - Create comment on post 60 | 2. `wordpress_get_comments` - Get comments with filtering 61 | 3. `wordpress_update_comment` - Update comment status/content 62 | 4. `wordpress_delete_comment` - Delete comment 63 | 64 | ## Settings (4 tools) 65 | 66 | 1. `wordpress_get_site_info` - Get complete site information 67 | 2. `wordpress_test_connection` - Test WordPress connection 68 | 3. `wordpress_get_settings` - Get site settings 69 | 4. `wordpress_update_settings` - Update site settings 70 | 71 | ## SEO (2 tools) 72 | 73 | 1. `wordpress_set_seo_meta` - Set SEO metadata (Yoast, Rank Math, AIOSEO) 74 | 2. `wordpress_set_custom_meta` - Set custom post metadata 75 | 76 | ## File System (8 tools) 77 | 78 | 1. `wordpress_read_file` - Read file contents 79 | 2. `wordpress_write_file` - Write or create file with backup 80 | 3. `wordpress_delete_file` - Delete file with optional backup 81 | 4. `wordpress_copy_file` - Copy file to another location 82 | 5. `wordpress_move_file` - Move or rename file 83 | 6. `wordpress_list_files` - List files in directory 84 | 7. `wordpress_file_info` - Get file information 85 | 86 | ## Themes (13 tools) 87 | 88 | 1. `wordpress_get_themes_detailed` - Get all installed themes 89 | 2. `wordpress_activate_theme` - Activate theme 90 | 3. `wordpress_get_active_theme` - Get currently active theme 91 | 4. `wordpress_create_child_theme` - Create child theme automatically 92 | 5. `wordpress_get_theme_mods` - Get theme customizer settings 93 | 6. `wordpress_delete_theme` - Delete theme 94 | 7. `wordpress_get_theme_json` - Get theme.json for block themes 95 | 8. `wordpress_update_theme_json` - Update theme.json 96 | 9. `wordpress_read_theme_file` - Read theme file 97 | 10. `wordpress_write_theme_file` - Write theme file 98 | 11. `wordpress_list_theme_files` - List theme files 99 | 12. `wordpress_get_theme_templates` - Get theme templates 100 | 13. `wordpress_get_template_parts` - Get template parts 101 | 14. `wordpress_get_global_styles` - Get global styles 102 | 15. `wordpress_theme_exists` - Check if theme is installed 103 | 104 | ## Plugins (10 tools) 105 | 106 | 1. `wordpress_get_plugins_detailed` - Get all installed plugins 107 | 2. `wordpress_activate_plugin` - Activate plugin 108 | 3. `wordpress_deactivate_plugin` - Deactivate plugin 109 | 4. `wordpress_delete_plugin` - Delete plugin 110 | 5. `wordpress_read_plugin_file` - Read plugin file 111 | 6. `wordpress_write_plugin_file` - Write plugin file 112 | 7. `wordpress_list_plugin_files` - List plugin files 113 | 8. `wordpress_get_active_plugins` - Get active plugins 114 | 9. `wordpress_plugin_exists` - Check if plugin installed 115 | 10. `wordpress_get_plugin_status` - Get plugin status 116 | 117 | ## Menus (8 tools) 118 | 119 | 1. `wordpress_get_menus` - Get all navigation menus 120 | 2. `wordpress_create_menu` - Create new menu 121 | 3. `wordpress_delete_menu` - Delete menu 122 | 4. `wordpress_get_menu_items` - Get menu items 123 | 5. `wordpress_create_menu_item` - Add menu item 124 | 6. `wordpress_update_menu_item` - Update menu item 125 | 7. `wordpress_delete_menu_item` - Delete menu item 126 | 8. `wordpress_get_menu_locations` - Get menu locations 127 | 9. `wordpress_assign_menu_to_location` - Assign menu to location 128 | 129 | ## Custom Post Types & Taxonomies (7 tools) 130 | 131 | 1. `wordpress_get_post_types` - Get all post types 132 | 2. `wordpress_get_post_type` - Get post type details 133 | 3. `wordpress_get_taxonomies` - Get all taxonomies 134 | 4. `wordpress_get_taxonomy` - Get taxonomy details 135 | 5. `wordpress_get_terms` - Get terms from taxonomy 136 | 6. `wordpress_create_term` - Create new term 137 | 7. `wordpress_update_term` - Update term 138 | 8. `wordpress_delete_term` - Delete term 139 | 140 | ## Shortcodes (3 tools) 141 | 142 | 1. `wordpress_list_shortcodes` - Get all registered shortcodes 143 | 2. `wordpress_execute_shortcode` - Execute shortcode string 144 | 3. `wordpress_shortcode_exists` - Check if shortcode registered 145 | 146 | ## Cron Jobs (5 tools) 147 | 148 | 1. `wordpress_list_cron_jobs` - Get all scheduled cron jobs 149 | 2. `wordpress_schedule_event` - Schedule new cron event 150 | 3. `wordpress_unschedule_event` - Remove scheduled event 151 | 4. `wordpress_run_cron` - Manually trigger cron 152 | 5. `wordpress_get_cron_schedules` - Get cron schedules 153 | 154 | ## Widgets (6 tools) 155 | 156 | 1. `wordpress_get_sidebars` - Get all widget areas 157 | 2. `wordpress_get_sidebar` - Get sidebar details 158 | 3. `wordpress_get_widgets` - Get all widgets 159 | 4. `wordpress_update_widget` - Update widget configuration 160 | 5. `wordpress_delete_widget` - Delete widget 161 | 6. `wordpress_get_widget_types` - Get widget types 162 | 163 | ## Database (6 tools) 164 | 165 | 1. `wordpress_list_tables` - Get all database tables 166 | 2. `wordpress_execute_sql` - Execute SQL query (SELECT only) 167 | 3. `wordpress_get_option` - Get WordPress option value 168 | 4. `wordpress_update_option` - Update WordPress option 169 | 5. `wordpress_get_table_structure` - Get table structure 170 | 6. `wordpress_get_table_preview` - Preview table data 171 | 172 | ## WooCommerce (15 tools) 173 | 174 | 1. `wordpress_wc_create_product` - Create product 175 | 2. `wordpress_wc_update_product` - Update product 176 | 3. `wordpress_wc_get_products` - Get products 177 | 4. `wordpress_wc_delete_product` - Delete product 178 | 5. `wordpress_wc_get_orders` - Get orders 179 | 6. `wordpress_wc_update_order_status` - Update order status 180 | 7. `wordpress_wc_get_customers` - Get customers 181 | 8. `wordpress_wc_update_stock` - Update inventory 182 | 9. `wordpress_wc_create_coupon` - Create coupon 183 | 10. `wordpress_wc_get_coupons` - Get coupons 184 | 11. `wordpress_wc_get_payment_gateways` - Get payment methods 185 | 12. `wordpress_wc_get_shipping_zones` - Get shipping zones 186 | 13. `wordpress_wc_get_sales_report` - Get sales report 187 | 14. `wordpress_wc_get_top_sellers` - Get top products 188 | 15. `wordpress_wc_get_product_categories` - Get product categories 189 | 16. `wordpress_wc_is_active` - Check if WooCommerce active 190 | 191 | ## Gutenberg Blocks (12 tools) 192 | 193 | 1. `wordpress_get_block_types` - Get all block types 194 | 2. `wordpress_get_block_patterns` - Get all block patterns 195 | 3. `wordpress_get_block_categories` - Get pattern categories 196 | 4. `wordpress_get_reusable_blocks` - Get reusable blocks 197 | 5. `wordpress_create_reusable_block` - Create reusable block 198 | 6. `wordpress_update_reusable_block` - Update reusable block 199 | 7. `wordpress_delete_reusable_block` - Delete reusable block 200 | 8. `wordpress_parse_blocks` - Parse block content 201 | 9. `wordpress_search_block_directory` - Search block directory 202 | 10. `wordpress_get_block_editor_settings` - Get editor settings 203 | 11. `wordpress_get_block_template` - Get block template 204 | 12. `wordpress_get_style_variations` - Get style variations 205 | 206 | ## Security (7 tools) 207 | 208 | 1. `wordpress_get_site_health` - Get site health status 209 | 2. `wordpress_check_updates` - Check for updates 210 | 3. `wordpress_get_debug_log` - Read debug.log 211 | 4. `wordpress_verify_core_files` - Verify core file integrity 212 | 5. `wordpress_get_failed_logins` - Get failed login attempts 213 | 6. `wordpress_scan_permissions` - Scan file permissions 214 | 7. `wordpress_get_version_info` - Get version information 215 | 216 | ## Performance (8 tools) 217 | 218 | 1. `wordpress_clear_cache` - Clear all caches 219 | 2. `wordpress_optimize_database` - Optimize database tables 220 | 3. `wordpress_cleanup_database` - Clean up revisions, spam, trash 221 | 4. `wordpress_regenerate_thumbnails` - Regenerate image thumbnails 222 | 5. `wordpress_get_performance_metrics` - Get performance metrics 223 | 6. `wordpress_enable_maintenance_mode` - Enable/disable maintenance mode 224 | 7. `wordpress_flush_rewrite_rules` - Flush rewrite rules 225 | 8. `wordpress_get_system_info` - Get system information 226 | 227 | 228 | ## Advanced SEO (10 tools) 229 | 230 | 1. `wordpress_generate_sitemap` - Generate XML sitemap 231 | 2. `wordpress_get_robots_txt` - Get robots.txt content 232 | 3. `wordpress_update_robots_txt` - Update robots.txt 233 | 4. `wordpress_create_redirect` - Create URL redirect 234 | 5. `wordpress_get_redirects` - Get all redirects 235 | 6. `wordpress_delete_redirect` - Delete redirect 236 | 7. `wordpress_set_og_tags` - Set Open Graph tags 237 | 8. `wordpress_set_twitter_cards` - Set Twitter cards 238 | 9. `wordpress_set_canonical_url` - Set canonical URL 239 | 10. `wordpress_set_schema_markup` - Add schema markup 240 | 11. `wordpress_analyze_seo` - Analyze SEO for post 241 | 242 | ## Advanced Media (5 tools) 243 | 244 | 1. `wordpress_bulk_optimize_images` - Bulk compress images 245 | 2. `wordpress_convert_to_webp` - Convert to WebP 246 | 3. `wordpress_get_unused_media` - Find unused media 247 | 4. `wordpress_bulk_delete_media` - Delete multiple files 248 | 5. `wordpress_get_media_analytics` - Storage statistics 249 | 250 | ## Backup & Migration (10 tools) 251 | 252 | 1. `wordpress_full_backup` - Complete site backup 253 | 2. `wordpress_backup_database` - Database backup 254 | 3. `wordpress_backup_files` - Files backup 255 | 4. `wordpress_list_backups` - List backups 256 | 5. `wordpress_restore_backup` - Restore backup 257 | 6. `wordpress_delete_backup` - Delete backup 258 | 7. `wordpress_export_content` - Export as XML 259 | 8. `wordpress_import_content` - Import from XML 260 | 9. `wordpress_clone_to_staging` - Clone to staging 261 | 10. `wordpress_schedule_backups` - Schedule auto backups 262 | 263 | ## User Roles & Capabilities (8 tools) 264 | 265 | 1. `wordpress_get_roles` - Get all roles 266 | 2. `wordpress_get_capabilities` - Get role capabilities 267 | 3. `wordpress_create_role` - Create custom role 268 | 4. `wordpress_delete_role` - Delete role 269 | 5. `wordpress_add_capability` - Add capability 270 | 6. `wordpress_remove_capability` - Remove capability 271 | 7. `wordpress_assign_role` - Assign role to user 272 | 8. `wordpress_check_user_capability` - Check user capability 273 | 274 | --- 275 | 276 | **Total:** 190 tools for complete WordPress control 277 | **Version:** 3.0.0 278 | **Coverage:** 99% of WordPress developer tasks 279 | -------------------------------------------------------------------------------- /src/tools/blocks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Gutenberg/Block Editor Tools 3 | * Manage blocks, block patterns, and block-based content 4 | */ 5 | 6 | import { Responses } from 'quickmcp-sdk'; 7 | import { callWordPressAPI } from '../utils/api.js'; 8 | import { buildQueryString } from '../utils/helpers.js'; 9 | 10 | export function registerBlockTools(server: any) { 11 | 12 | // ========== BLOCK MANAGEMENT ========== 13 | 14 | /** 15 | * Get all block types 16 | */ 17 | server.tool('wordpress_get_block_types', async (args: any) => { 18 | const { namespace, perPage = 100 } = args || {}; 19 | 20 | try { 21 | let endpoint = '/block-types'; 22 | if (namespace) endpoint += `/${namespace}`; 23 | 24 | const params = { per_page: perPage }; 25 | const queryString = buildQueryString(params); 26 | 27 | const blocks = await callWordPressAPI(`${endpoint}?${queryString}`); 28 | 29 | const blockList = Array.isArray(blocks) ? blocks : [blocks]; 30 | 31 | return Responses.success( 32 | { 33 | blocks: blockList.map((block: any) => ({ 34 | name: block.name, 35 | title: block.title, 36 | description: block.description || '', 37 | category: block.category, 38 | icon: block.icon, 39 | supports: block.supports || {} 40 | })), 41 | total: blockList.length 42 | }, 43 | `🧱 Retrieved ${blockList.length} block types` 44 | ); 45 | } catch (error) { 46 | return Responses.error(`Failed to get block types: ${(error as Error).message}`); 47 | } 48 | }, { 49 | description: 'Get all registered block types', 50 | schema: {} 51 | }); 52 | 53 | /** 54 | * Get block patterns 55 | */ 56 | server.tool('wordpress_get_block_patterns', async (args: any) => { 57 | const { perPage = 100 } = args || {}; 58 | 59 | try { 60 | const params = { per_page: perPage }; 61 | const queryString = buildQueryString(params); 62 | 63 | const patterns = await callWordPressAPI(`/block-patterns/patterns?${queryString}`); 64 | 65 | return Responses.success( 66 | { 67 | patterns: patterns.map((p: any) => ({ 68 | name: p.name, 69 | title: p.title, 70 | description: p.description || '', 71 | categories: p.categories || [], 72 | keywords: p.keywords || [] 73 | })), 74 | total: patterns.length 75 | }, 76 | `🎨 Retrieved ${patterns.length} block patterns` 77 | ); 78 | } catch (error) { 79 | return Responses.error(`Failed to get patterns: ${(error as Error).message}`); 80 | } 81 | }, { 82 | description: 'Get all available block patterns', 83 | schema: {} 84 | }); 85 | 86 | /** 87 | * Get block pattern categories 88 | */ 89 | server.tool('wordpress_get_block_categories', async () => { 90 | try { 91 | const categories = await callWordPressAPI('/block-patterns/categories'); 92 | 93 | return Responses.success( 94 | { 95 | categories: categories.map((c: any) => ({ 96 | name: c.name, 97 | label: c.label 98 | })), 99 | total: categories.length 100 | }, 101 | `📁 Retrieved ${categories.length} pattern categories` 102 | ); 103 | } catch (error) { 104 | return Responses.error(`Failed to get categories: ${(error as Error).message}`); 105 | } 106 | }, { 107 | description: 'Get block pattern categories', 108 | schema: {} 109 | }); 110 | 111 | /** 112 | * Get reusable blocks 113 | */ 114 | server.tool('wordpress_get_reusable_blocks', async (args: any) => { 115 | const { perPage = 100, search } = args || {}; 116 | 117 | try { 118 | const params: any = { per_page: perPage }; 119 | if (search) params.search = search; 120 | 121 | const queryString = buildQueryString(params); 122 | const blocks = await callWordPressAPI(`/blocks?${queryString}`); 123 | 124 | return Responses.success( 125 | { 126 | blocks: blocks.map((b: any) => ({ 127 | id: b.id, 128 | title: b.title?.rendered || '', 129 | content: b.content?.rendered || '', 130 | status: b.status 131 | })), 132 | total: blocks.length 133 | }, 134 | `🔄 Retrieved ${blocks.length} reusable blocks` 135 | ); 136 | } catch (error) { 137 | return Responses.error(`Failed to get reusable blocks: ${(error as Error).message}`); 138 | } 139 | }, { 140 | description: 'Get reusable blocks (saved blocks)', 141 | schema: {} 142 | }); 143 | 144 | /** 145 | * Create reusable block 146 | */ 147 | server.tool('wordpress_create_reusable_block', async (args: any) => { 148 | const { title, content, status = 'publish' } = args; 149 | 150 | try { 151 | const block = await callWordPressAPI('/blocks', 'POST', { 152 | title, 153 | content, 154 | status 155 | }); 156 | 157 | return Responses.success( 158 | { 159 | id: block.id, 160 | title: block.title?.rendered || title, 161 | status: block.status 162 | }, 163 | `✅ Created reusable block: "${title}"` 164 | ); 165 | } catch (error) { 166 | return Responses.error(`Failed to create block: ${(error as Error).message}`); 167 | } 168 | }, { 169 | description: 'Create a reusable block', 170 | schema: { 171 | title: 'string', 172 | content: 'string' 173 | } 174 | }); 175 | 176 | /** 177 | * Update reusable block 178 | */ 179 | server.tool('wordpress_update_reusable_block', async (args: any) => { 180 | const { blockId, updates } = args; 181 | 182 | try { 183 | const block = await callWordPressAPI(`/blocks/${blockId}`, 'PUT', updates); 184 | 185 | return Responses.success( 186 | { 187 | id: block.id, 188 | title: block.title?.rendered || '', 189 | updated: true 190 | }, 191 | `✅ Updated block ID ${blockId}` 192 | ); 193 | } catch (error) { 194 | return Responses.error(`Failed to update block: ${(error as Error).message}`); 195 | } 196 | }, { 197 | description: 'Update a reusable block', 198 | schema: { 199 | blockId: 'number', 200 | updates: 'object' 201 | } 202 | }); 203 | 204 | /** 205 | * Delete reusable block 206 | */ 207 | server.tool('wordpress_delete_reusable_block', async (args: any) => { 208 | const { blockId, force = false } = args; 209 | 210 | try { 211 | const params = force ? '?force=true' : ''; 212 | await callWordPressAPI(`/blocks/${blockId}${params}`, 'DELETE'); 213 | 214 | return Responses.success( 215 | { 216 | id: blockId, 217 | deleted: true 218 | }, 219 | `✅ Deleted block ID ${blockId}` 220 | ); 221 | } catch (error) { 222 | return Responses.error(`Failed to delete block: ${(error as Error).message}`); 223 | } 224 | }, { 225 | description: 'Delete a reusable block', 226 | schema: { 227 | blockId: 'number', 228 | force: 'boolean' 229 | } 230 | }); 231 | 232 | /** 233 | * Parse block content 234 | */ 235 | server.tool('wordpress_parse_blocks', async (args: any) => { 236 | const { content } = args; 237 | 238 | try { 239 | // This would require server-side block parsing 240 | // For now, return the content as-is with basic parsing info 241 | const blockCount = (content.match(/