Skip to main content
Glama
server.js10.8 kB
#!/usr/bin/env node import { z } from 'zod'; import { WordPressRestClient, WPCLIClient, logger, createMcpServer, startServer } from '@akungapaul/wp-mcp-shared'; // Initialize clients const restClient = new WordPressRestClient({ url: process.env.WORDPRESS_URL, username: process.env.WORDPRESS_USERNAME, appPassword: process.env.WORDPRESS_APP_PASSWORD }); const cliClient = new WPCLIClient({ enabled: process.env.ENABLE_WP_CLI === 'true', wpCliPath: process.env.WP_CLI_PATH, wordPressPath: process.env.WORDPRESS_PATH, sshHost: process.env.SSH_HOST, sshPort: process.env.SSH_PORT, sshUser: process.env.SSH_USER, sshKeyPath: process.env.SSH_KEY_PATH }); // Define tools const tools = [ // Posts { name: 'create_post', description: 'Create a new WordPress post with title, content, and metadata', inputSchema: z.object({ title: z.string().describe('Post title'), content: z.string().optional().describe('Post content (HTML or blocks)'), status: z.enum(['publish', 'draft', 'pending', 'private', 'future']).default('draft').describe('Post status'), excerpt: z.string().optional().describe('Post excerpt'), slug: z.string().optional().describe('Post slug (URL-friendly name)'), author: z.number().optional().describe('Author user ID'), featured_media: z.number().optional().describe('Featured image media ID'), categories: z.array(z.number()).optional().describe('Category IDs'), tags: z.array(z.number()).optional().describe('Tag IDs'), meta: z.record(z.any()).optional().describe('Custom meta fields') }), handler: async (args) => { const post = await restClient.createPost(args); return { content: [{ type: 'text', text: JSON.stringify({ success: true, post }, null, 2) }] }; } }, { name: 'update_post', description: 'Update an existing WordPress post', inputSchema: z.object({ id: z.number().describe('Post ID to update'), title: z.string().optional().describe('New post title'), content: z.string().optional().describe('New post content'), status: z.enum(['publish', 'draft', 'pending', 'private']).optional().describe('Post status'), excerpt: z.string().optional().describe('Post excerpt'), slug: z.string().optional().describe('Post slug'), featured_media: z.number().optional().describe('Featured image media ID'), categories: z.array(z.number()).optional().describe('Category IDs'), tags: z.array(z.number()).optional().describe('Tag IDs') }), handler: async (args) => { const { id, ...data } = args; const post = await restClient.updatePost(id, data); return { content: [{ type: 'text', text: JSON.stringify({ success: true, post }, null, 2) }] }; } }, { name: 'delete_post', description: 'Delete or trash a WordPress post', inputSchema: z.object({ id: z.number().describe('Post ID to delete'), force: z.boolean().default(false).describe('Permanently delete (true) or move to trash (false)') }), handler: async (args) => { const result = await restClient.deletePost(args.id, args.force); return { content: [{ type: 'text', text: JSON.stringify({ success: true, deleted: args.id, result }, null, 2) }] }; } }, { name: 'get_post', description: 'Retrieve a WordPress post by ID', inputSchema: z.object({ id: z.number().describe('Post ID to retrieve') }), handler: async (args) => { const post = await restClient.getPost(args.id); return { content: [{ type: 'text', text: JSON.stringify(post, null, 2) }] }; } }, { name: 'list_posts', description: 'List WordPress posts with filtering and pagination', inputSchema: z.object({ status: z.enum(['publish', 'draft', 'pending', 'private', 'any']).default('publish').describe('Post status filter'), per_page: z.number().default(10).describe('Number of posts per page'), page: z.number().default(1).describe('Page number'), search: z.string().optional().describe('Search query'), author: z.number().optional().describe('Filter by author ID'), categories: z.array(z.number()).optional().describe('Filter by category IDs'), tags: z.array(z.number()).optional().describe('Filter by tag IDs'), orderby: z.enum(['date', 'title', 'modified', 'id']).default('date').describe('Order by field'), order: z.enum(['asc', 'desc']).default('desc').describe('Order direction') }), handler: async (args) => { const posts = await restClient.getPosts(args); return { content: [{ type: 'text', text: JSON.stringify({ count: posts.length, posts }, null, 2) }] }; } }, // Pages { name: 'create_page', description: 'Create a new WordPress page', inputSchema: z.object({ title: z.string().describe('Page title'), content: z.string().optional().describe('Page content (HTML or blocks)'), status: z.enum(['publish', 'draft', 'pending', 'private']).default('draft').describe('Page status'), slug: z.string().optional().describe('Page slug (URL-friendly name)'), parent: z.number().optional().describe('Parent page ID for hierarchical structure'), template: z.string().optional().describe('Page template'), author: z.number().optional().describe('Author user ID') }), handler: async (args) => { const page = await restClient.createPage(args); return { content: [{ type: 'text', text: JSON.stringify({ success: true, page }, null, 2) }] }; } }, { name: 'update_page', description: 'Update an existing WordPress page', inputSchema: z.object({ id: z.number().describe('Page ID to update'), title: z.string().optional().describe('New page title'), content: z.string().optional().describe('New page content'), status: z.enum(['publish', 'draft', 'pending', 'private']).optional().describe('Page status'), slug: z.string().optional().describe('Page slug'), parent: z.number().optional().describe('Parent page ID'), template: z.string().optional().describe('Page template') }), handler: async (args) => { const { id, ...data } = args; const page = await restClient.updatePage(id, data); return { content: [{ type: 'text', text: JSON.stringify({ success: true, page }, null, 2) }] }; } }, // Advanced content operations { name: 'duplicate_post', description: 'Create a duplicate copy of an existing post or page', inputSchema: z.object({ id: z.number().describe('ID of post/page to duplicate'), status: z.enum(['publish', 'draft', 'pending', 'private']).default('draft').describe('Status for duplicated content'), title_suffix: z.string().default('(Copy)').describe('Suffix to add to duplicated title') }), handler: async (args) => { const original = await restClient.getPost(args.id); const newPost = await restClient.createPost({ title: `${original.title.rendered} ${args.title_suffix}`, content: original.content.rendered, status: args.status, excerpt: original.excerpt?.rendered, categories: original.categories, tags: original.tags }); return { content: [{ type: 'text', text: JSON.stringify({ success: true, original_id: args.id, new_post: newPost }, null, 2) }] }; } }, { name: 'schedule_post', description: 'Schedule a post to be published at a future date and time', inputSchema: z.object({ id: z.number().describe('Post ID to schedule'), date: z.string().describe('Publication date and time (ISO 8601 format: YYYY-MM-DDTHH:MM:SS)') }), handler: async (args) => { const post = await restClient.updatePost(args.id, { status: 'future', date: args.date }); return { content: [{ type: 'text', text: JSON.stringify({ success: true, scheduled: post }, null, 2) }] }; } }, { name: 'bulk_edit_posts', description: 'Perform bulk updates on multiple posts at once', inputSchema: z.object({ post_ids: z.array(z.number()).describe('Array of post IDs to update'), updates: z.object({ status: z.enum(['publish', 'draft', 'pending', 'private']).optional().describe('Change status'), author: z.number().optional().describe('Change author'), categories: z.array(z.number()).optional().describe('Set categories'), tags: z.array(z.number()).optional().describe('Set tags') }).describe('Fields to update on all posts') }), handler: async (args) => { const results = await Promise.all( args.post_ids.map(id => restClient.updatePost(id, args.updates)) ); return { content: [{ type: 'text', text: JSON.stringify({ success: true, updated: results.length, posts: results }, null, 2) }] }; } }, { name: 'get_revisions', description: 'Get revision history for a post or page', inputSchema: z.object({ id: z.number().describe('Post ID to get revisions for') }), handler: async (args) => { const revisions = await restClient.get(`/wp/v2/posts/${args.id}/revisions`); return { content: [{ type: 'text', text: JSON.stringify({ post_id: args.id, revisions }, null, 2) }] }; } }, { name: 'trash_restore_post', description: 'Move a post to trash or restore it from trash', inputSchema: z.object({ id: z.number().describe('Post ID'), action: z.enum(['trash', 'restore']).describe('Action to perform') }), handler: async (args) => { if (args.action === 'trash') { const result = await restClient.deletePost(args.id, false); return { content: [{ type: 'text', text: JSON.stringify({ success: true, action: 'trashed', post: result }, null, 2) }] }; } else { const post = await restClient.updatePost(args.id, { status: 'draft' }); return { content: [{ type: 'text', text: JSON.stringify({ success: true, action: 'restored', post }, null, 2) }] }; } } } ]; // Create and start server const server = createMcpServer('wp-content-mcp', '1.0.0', tools); startServer(server); logger.info('wp-content-mcp server started');

Latest Blog Posts

MCP directory API

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

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

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