├── 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 |
--------------------------------------------------------------------------------
/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(/