├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .DS_Store 4 | *.log 5 | .env 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 stefans71 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress MCP Server 2 | 3 | A Model Context Protocol (MCP) server for WordPress integration, compatible with Windows, macOS, and Linux. 4 | 5 | ## Overview 6 | 7 | This MCP server enables interaction with WordPress sites through the WordPress REST API. It provides tools for creating, retrieving, and updating posts using JSON-RPC 2.0 protocol. 8 | 9 | ## Installation 10 | 11 | 1. Clone the repository 12 | 2. Install dependencies: 13 | ```bash 14 | npm install 15 | ``` 16 | 3. Build the project: 17 | ```bash 18 | npm run build 19 | ``` 20 | 21 | ## Configuration 22 | 23 | Add the server to your MCP settings file with environment variables for WordPress credentials: 24 | 25 | ```json 26 | { 27 | "mcpServers": { 28 | "wordpress": { 29 | "command": "node", 30 | "args": ["path/to/build/index.js"], 31 | "env": { 32 | "WORDPRESS_SITE_URL": "https://your-wordpress-site.com", 33 | "WORDPRESS_USERNAME": "your-username", 34 | "WORDPRESS_PASSWORD": "your-app-password" 35 | } 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | The environment variables are: 42 | - WORDPRESS_SITE_URL: Your WordPress site URL 43 | - WORDPRESS_USERNAME: WordPress username 44 | - WORDPRESS_PASSWORD: WordPress application password 45 | 46 | You can also provide these credentials in the request parameters if you prefer not to use environment variables. 47 | 48 | ## Available Methods 49 | 50 | ### create_post 51 | Creates a new WordPress post. 52 | 53 | Parameters: 54 | - siteUrl: (optional if set in env) WordPress site URL 55 | - username: (optional if set in env) WordPress username 56 | - password: (optional if set in env) WordPress application password 57 | - title: Post title 58 | - content: Post content 59 | - status: (optional) 'draft' | 'publish' | 'private' (default: 'draft') 60 | 61 | ### get_posts 62 | Retrieves WordPress posts. 63 | 64 | Parameters: 65 | - siteUrl: (optional if set in env) WordPress site URL 66 | - username: (optional if set in env) WordPress username 67 | - password: (optional if set in env) WordPress application password 68 | - perPage: (optional) Number of posts per page (default: 10) 69 | - page: (optional) Page number (default: 1) 70 | 71 | ### update_post 72 | Updates an existing WordPress post. 73 | 74 | Parameters: 75 | - siteUrl: (optional if set in env) WordPress site URL 76 | - username: (optional if set in env) WordPress username 77 | - password: (optional if set in env) WordPress application password 78 | - postId: ID of the post to update 79 | - title: (optional) New post title 80 | - content: (optional) New post content 81 | - status: (optional) 'draft' | 'publish' | 'private' 82 | 83 | ## Security Note 84 | 85 | For security, it's recommended to use WordPress application passwords instead of your main account password. You can generate an application password in your WordPress dashboard under Users → Security → Application Passwords. 86 | 87 | ## Example Usage 88 | 89 | Using environment variables: 90 | ```json 91 | { 92 | "jsonrpc": "2.0", 93 | "id": 1, 94 | "method": "create_post", 95 | "params": { 96 | "title": "My New Post", 97 | "content": "Hello World!", 98 | "status": "draft" 99 | } 100 | } 101 | ``` 102 | 103 | Without environment variables: 104 | ```json 105 | { 106 | "jsonrpc": "2.0", 107 | "id": 1, 108 | "method": "create_post", 109 | "params": { 110 | "siteUrl": "https://your-wordpress-site.com", 111 | "username": "your-username", 112 | "password": "your-app-password", 113 | "title": "My New Post", 114 | "content": "Hello World!", 115 | "status": "draft" 116 | } 117 | } 118 | ``` 119 | 120 | ## Requirements 121 | 122 | - Node.js 20.0.0 or higher 123 | - WordPress site with REST API enabled 124 | - WordPress application password for authentication 125 | 126 | ## License 127 | 128 | MIT License - See LICENSE file for details 129 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "wordpress-server", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^1.7.9" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^20.0.0", 16 | "typescript": "^5.0.0" 17 | } 18 | }, 19 | "node_modules/@types/node": { 20 | "version": "20.17.10", 21 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", 22 | "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", 23 | "dev": true, 24 | "license": "MIT", 25 | "dependencies": { 26 | "undici-types": "~6.19.2" 27 | } 28 | }, 29 | "node_modules/asynckit": { 30 | "version": "0.4.0", 31 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 32 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 33 | "license": "MIT" 34 | }, 35 | "node_modules/axios": { 36 | "version": "1.7.9", 37 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", 38 | "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", 39 | "license": "MIT", 40 | "dependencies": { 41 | "follow-redirects": "^1.15.6", 42 | "form-data": "^4.0.0", 43 | "proxy-from-env": "^1.1.0" 44 | } 45 | }, 46 | "node_modules/combined-stream": { 47 | "version": "1.0.8", 48 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 49 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 50 | "license": "MIT", 51 | "dependencies": { 52 | "delayed-stream": "~1.0.0" 53 | }, 54 | "engines": { 55 | "node": ">= 0.8" 56 | } 57 | }, 58 | "node_modules/delayed-stream": { 59 | "version": "1.0.0", 60 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 61 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 62 | "license": "MIT", 63 | "engines": { 64 | "node": ">=0.4.0" 65 | } 66 | }, 67 | "node_modules/follow-redirects": { 68 | "version": "1.15.9", 69 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 70 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 71 | "funding": [ 72 | { 73 | "type": "individual", 74 | "url": "https://github.com/sponsors/RubenVerborgh" 75 | } 76 | ], 77 | "license": "MIT", 78 | "engines": { 79 | "node": ">=4.0" 80 | }, 81 | "peerDependenciesMeta": { 82 | "debug": { 83 | "optional": true 84 | } 85 | } 86 | }, 87 | "node_modules/form-data": { 88 | "version": "4.0.1", 89 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 90 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 91 | "license": "MIT", 92 | "dependencies": { 93 | "asynckit": "^0.4.0", 94 | "combined-stream": "^1.0.8", 95 | "mime-types": "^2.1.12" 96 | }, 97 | "engines": { 98 | "node": ">= 6" 99 | } 100 | }, 101 | "node_modules/mime-db": { 102 | "version": "1.52.0", 103 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 104 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 105 | "license": "MIT", 106 | "engines": { 107 | "node": ">= 0.6" 108 | } 109 | }, 110 | "node_modules/mime-types": { 111 | "version": "2.1.35", 112 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 113 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 114 | "license": "MIT", 115 | "dependencies": { 116 | "mime-db": "1.52.0" 117 | }, 118 | "engines": { 119 | "node": ">= 0.6" 120 | } 121 | }, 122 | "node_modules/proxy-from-env": { 123 | "version": "1.1.0", 124 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 125 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 126 | "license": "MIT" 127 | }, 128 | "node_modules/typescript": { 129 | "version": "5.7.2", 130 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 131 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 132 | "dev": true, 133 | "license": "Apache-2.0", 134 | "bin": { 135 | "tsc": "bin/tsc", 136 | "tsserver": "bin/tsserver" 137 | }, 138 | "engines": { 139 | "node": ">=14.17" 140 | } 141 | }, 142 | "node_modules/undici-types": { 143 | "version": "6.19.8", 144 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 145 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 146 | "dev": true, 147 | "license": "MIT" 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress-server", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "node build/index.js" 9 | }, 10 | "dependencies": { 11 | "axios": "^1.7.9" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^20.0.0", 15 | "typescript": "^5.0.0" 16 | }, 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/stefans71/wordpress-mcp-server.git" 21 | }, 22 | "author": "stefans71", 23 | "description": "MCP server for WordPress integration" 24 | } 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import axios from 'axios'; 3 | import { createInterface } from 'readline'; 4 | 5 | interface JsonRpcRequest { 6 | jsonrpc: '2.0'; 7 | id: string | number; 8 | method: string; 9 | params?: any; 10 | } 11 | 12 | interface JsonRpcResponse { 13 | jsonrpc: '2.0'; 14 | id: string | number; 15 | result?: any; 16 | error?: { 17 | code: number; 18 | message: string; 19 | data?: any; 20 | }; 21 | } 22 | 23 | interface WordPressError { 24 | message: string; 25 | code?: string; 26 | } 27 | 28 | type AxiosError = { 29 | response?: { 30 | data?: WordPressError; 31 | }; 32 | message: string; 33 | }; 34 | 35 | const isAxiosError = (error: unknown): error is AxiosError => { 36 | return error !== null && 37 | typeof error === 'object' && 38 | 'message' in error && 39 | (error as any).response !== undefined; 40 | }; 41 | 42 | // Get WordPress credentials from environment variables 43 | const DEFAULT_SITE_URL = process.env.WORDPRESS_SITE_URL || ''; 44 | const DEFAULT_USERNAME = process.env.WORDPRESS_USERNAME || ''; 45 | const DEFAULT_PASSWORD = process.env.WORDPRESS_PASSWORD || ''; 46 | 47 | async function handleWordPressRequest(method: string, params: any): Promise { 48 | try { 49 | const siteUrl = params.siteUrl || DEFAULT_SITE_URL; 50 | const username = params.username || DEFAULT_USERNAME; 51 | const password = params.password || DEFAULT_PASSWORD; 52 | 53 | if (!siteUrl || !username || !password) { 54 | throw new Error('WordPress credentials not provided in environment variables or request parameters'); 55 | } 56 | 57 | const auth = Buffer.from(`${username}:${password}`).toString('base64'); 58 | const client = axios.create({ 59 | baseURL: `${siteUrl}/wp-json/wp/v2`, 60 | headers: { 61 | Authorization: `Basic ${auth}`, 62 | 'Content-Type': 'application/json', 63 | }, 64 | }); 65 | 66 | switch (method) { 67 | case 'create_post': 68 | if (!params.title || !params.content) { 69 | throw new Error('Title and content are required for creating a post'); 70 | } 71 | const createResponse = await client.post('/posts', { 72 | title: params.title, 73 | content: params.content, 74 | status: params.status || 'draft', 75 | }); 76 | return createResponse.data; 77 | 78 | case 'get_posts': 79 | const getResponse = await client.get('/posts', { 80 | params: { 81 | per_page: params.perPage || 10, 82 | page: params.page || 1, 83 | }, 84 | }); 85 | return getResponse.data; 86 | 87 | case 'update_post': 88 | if (!params.postId) { 89 | throw new Error('Post ID is required for updating a post'); 90 | } 91 | const updateData: Record = {}; 92 | if (params.title) updateData.title = params.title; 93 | if (params.content) updateData.content = params.content; 94 | if (params.status) updateData.status = params.status; 95 | 96 | const updateResponse = await client.post( 97 | `/posts/${params.postId}`, 98 | updateData 99 | ); 100 | return updateResponse.data; 101 | 102 | default: 103 | throw new Error(`Unknown method: ${method}`); 104 | } 105 | } catch (error: unknown) { 106 | if (isAxiosError(error)) { 107 | throw new Error(`WordPress API error: ${ 108 | error.response?.data?.message || error.message 109 | }`); 110 | } 111 | throw error; 112 | } 113 | } 114 | 115 | const rl = createInterface({ 116 | input: process.stdin, 117 | output: process.stdout, 118 | terminal: false, 119 | }); 120 | 121 | rl.on('line', async (line) => { 122 | let request: JsonRpcRequest; 123 | try { 124 | request = JSON.parse(line); 125 | if (request.jsonrpc !== '2.0') { 126 | throw new Error('Invalid JSON-RPC version'); 127 | } 128 | } catch (error) { 129 | console.log(JSON.stringify({ 130 | jsonrpc: '2.0', 131 | id: null, 132 | error: { 133 | code: -32700, 134 | message: 'Parse error', 135 | data: error instanceof Error ? error.message : String(error) 136 | } 137 | })); 138 | return; 139 | } 140 | 141 | try { 142 | const result = await handleWordPressRequest(request.method, request.params); 143 | console.log(JSON.stringify({ 144 | jsonrpc: '2.0', 145 | id: request.id, 146 | result 147 | })); 148 | } catch (error) { 149 | console.log(JSON.stringify({ 150 | jsonrpc: '2.0', 151 | id: request.id, 152 | error: { 153 | code: -32000, 154 | message: error instanceof Error ? error.message : String(error) 155 | } 156 | })); 157 | } 158 | }); 159 | 160 | process.on('SIGINT', () => { 161 | rl.close(); 162 | process.exit(0); 163 | }); 164 | 165 | console.error('WordPress MCP server running on stdin/stdout'); 166 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "outDir": "build", 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true 11 | }, 12 | "include": ["src/**/*"], 13 | "exclude": ["node_modules", "build"] 14 | } 15 | --------------------------------------------------------------------------------