Complete Guide: Connect Claude to WordPress with MCP Server

Complete Guide: Connect Claude to WordPress with MCP Server

Manage your entire WordPress site through conversation with AI. This complete guide includes all the code, step-by-step instructions, and troubleshooting you need. Expected setup time: 30 minutes.

🤖What is an MCP Server?

MCP (Model Context Protocol) is an open standard that allows AI assistants like Claude to connect directly to external systems.

Simple Explanation:

  • Without MCP: You ask Claude to write content, then YOU manually copy/paste it into WordPress
  • With MCP: You ask Claude to write and publish content, and Claude does EVERYTHING automatically

Think of it like giving Claude a key to your WordPress admin panel – but in a secure, controlled way.

🎯What You’ll Build

By the end of this guide, you’ll be able to tell Claude things like:

“Create a blog post titled ‘Top 10 AI Tools’ with an introduction, 10 tools with descriptions, and publish it”

And Claude will:

  1. Write the entire post
  2. Format it properly
  3. Publish it to your WordPress site
  4. Give you the link

📝Create Content

Write and publish blog posts and pages through conversation

✏️Edit Posts

Update existing content, change titles, or fix typos instantly

📊Manage Content

List posts, check status, view publication dates

🗑️Delete Content

Remove outdated posts or clean up drafts

📋Prerequisites Checklist

Before you begin, make sure you have everything you need. Check each item:

Python 3.8 or higher installed
Test by running: python --version in your terminal
Download from python.org if needed
Claude Desktop installed
Download from claude.ai/download
WordPress site (version 5.6 or higher)
Most WordPress sites support this by default
WordPress admin access
You need to be able to log into wp-admin
30 minutes of time
To complete the setup without rushing
Basic comfort with command line/terminal
You’ll need to run a few simple commands

Verify Your Python Installation

Open your terminal (Command Prompt on Windows, Terminal on Mac) and run:

python --version

You should see something like: Python 3.8.0 or higher

💡 Tip: If python --version doesn’t work, try python3 --version on Mac

⚙️Complete Setup Guide

Step 1: Create Your Project Folder

First, create a folder where all your MCP server files will live.

Create a folder at:

C:\Users\YourName\wordpress-mcp\

Replace YourName with your actual Windows username

Create a folder at:

/Users/YourName/wordpress-mcp/

Or use the terminal:

mkdir -p ~/wordpress-mcp
cd ~/wordpress-mcp

Step 2: Create the Server Files

You need to create 2 files in your wordpress-mcp folder. I’ll provide the complete code for both.

File 1: requirements.txt

This file lists the Python libraries we need.

📄 requirements.txt
mcp>=1.0.0
httpx>=0.27.0
python-dotenv>=1.0.0

💡 How to create this file:

  1. Open Notepad (Windows) or TextEdit (Mac) or any text editor
  2. Copy the 3 lines above
  3. Paste them into the editor
  4. Save as requirements.txt in your wordpress-mcp folder

File 2: wordpress_mcp_server.py

This is the main server code. Copy ALL of this code:

📄 wordpress_mcp_server.py (Complete Code – Copy Everything)
#!/usr/bin/env python3
"""
WordPress MCP Server
Connects to WordPress REST API to manage content
"""

import asyncio
import os
import httpx
from typing import Any
from mcp.server import Server
from mcp.types import Tool, TextContent
import mcp.server.stdio
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

# WordPress configuration
WP_SITE_URL = os.getenv("WP_SITE_URL", "")
WP_USERNAME = os.getenv("WP_USERNAME", "")
WP_APP_PASSWORD = os.getenv("WP_APP_PASSWORD", "")

server = Server("wordpress-mcp")

async def wp_request(method: str, endpoint: str, data: dict = None) -> dict:
    """Make authenticated request to WordPress REST API"""
    url = f"{WP_SITE_URL}/wp-json/wp/v2/{endpoint}"
    
    auth = (WP_USERNAME, WP_APP_PASSWORD)
    
    async with httpx.AsyncClient(timeout=30.0) as client:
        if method == "GET":
            response = await client.get(url, auth=auth)
        elif method == "POST":
            response = await client.post(url, auth=auth, json=data)
        elif method == "PUT":
            response = await client.put(url, auth=auth, json=data)
        elif method == "DELETE":
            response = await client.delete(url, auth=auth)
        
        response.raise_for_status()
        return response.json()

@server.list_tools()
async def list_tools() -> list[Tool]:
    """List available WordPress management tools"""
    return [
        Tool(
            name="list_posts",
            description="List WordPress posts with optional filters",
            inputSchema={
                "type": "object",
                "properties": {
                    "per_page": {
                        "type": "number",
                        "description": "Number of posts to retrieve (max 100)",
                        "default": 10
                    },
                    "status": {
                        "type": "string",
                        "description": "Post status (publish, draft, pending, etc.)",
                        "default": "publish"
                    }
                }
            }
        ),
        Tool(
            name="get_post",
            description="Get a specific WordPress post by ID",
            inputSchema={
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "number",
                        "description": "The ID of the post to retrieve"
                    }
                },
                "required": ["post_id"]
            }
        ),
        Tool(
            name="create_post",
            description="Create a new WordPress post",
            inputSchema={
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "Post title"
                    },
                    "content": {
                        "type": "string",
                        "description": "Post content (HTML allowed)"
                    },
                    "status": {
                        "type": "string",
                        "description": "Post status: draft, publish, pending",
                        "default": "draft"
                    },
                    "excerpt": {
                        "type": "string",
                        "description": "Post excerpt (optional)"
                    }
                },
                "required": ["title", "content"]
            }
        ),
        Tool(
            name="update_post",
            description="Update an existing WordPress post",
            inputSchema={
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "number",
                        "description": "The ID of the post to update"
                    },
                    "title": {
                        "type": "string",
                        "description": "New post title"
                    },
                    "content": {
                        "type": "string",
                        "description": "New post content"
                    },
                    "status": {
                        "type": "string",
                        "description": "New post status"
                    }
                },
                "required": ["post_id"]
            }
        ),
        Tool(
            name="delete_post",
            description="Delete a WordPress post",
            inputSchema={
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "number",
                        "description": "The ID of the post to delete"
                    },
                    "force": {
                        "type": "boolean",
                        "description": "Whether to bypass trash and force deletion",
                        "default": False
                    }
                },
                "required": ["post_id"]
            }
        ),
        Tool(
            name="list_pages",
            description="List WordPress pages",
            inputSchema={
                "type": "object",
                "properties": {
                    "per_page": {
                        "type": "number",
                        "description": "Number of pages to retrieve",
                        "default": 10
                    }
                }
            }
        ),
        Tool(
            name="create_page",
            description="Create a new WordPress page",
            inputSchema={
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "Page title"
                    },
                    "content": {
                        "type": "string",
                        "description": "Page content (HTML allowed)"
                    },
                    "status": {
                        "type": "string",
                        "description": "Page status: draft, publish, pending",
                        "default": "draft"
                    }
                },
                "required": ["title", "content"]
            }
        ),
        Tool(
            name="list_media",
            description="List media files in WordPress library",
            inputSchema={
                "type": "object",
                "properties": {
                    "per_page": {
                        "type": "number",
                        "description": "Number of media items to retrieve",
                        "default": 20
                    }
                }
            }
        ),
        Tool(
            name="get_site_info",
            description="Get WordPress site information and settings",
            inputSchema={
                "type": "object",
                "properties": {}
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: Any) -> list[TextContent]:
    """Execute WordPress management tools"""
    
    try:
        if name == "list_posts":
            per_page = arguments.get("per_page", 10)
            status = arguments.get("status", "publish")
            posts = await wp_request("GET", f"posts?per_page={per_page}&status={status}")
            
            result = f"Found {len(posts)} posts:\\n\\n"
            for post in posts:
                result += f"ID: {post['id']}\\n"
                result += f"Title: {post['title']['rendered']}\\n"
                result += f"Status: {post['status']}\\n"
                result += f"Date: {post['date']}\\n"
                result += f"Link: {post['link']}\\n\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "get_post":
            post_id = arguments["post_id"]
            post = await wp_request("GET", f"posts/{post_id}")
            
            result = f"Post ID: {post['id']}\\n"
            result += f"Title: {post['title']['rendered']}\\n"
            result += f"Status: {post['status']}\\n"
            result += f"Date: {post['date']}\\n"
            result += f"Modified: {post['modified']}\\n"
            result += f"Link: {post['link']}\\n\\n"
            result += f"Content:\\n{post['content']['rendered']}\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "create_post":
            data = {
                "title": arguments["title"],
                "content": arguments["content"],
                "status": arguments.get("status", "draft")
            }
            if "excerpt" in arguments:
                data["excerpt"] = arguments["excerpt"]
            
            post = await wp_request("POST", "posts", data)
            
            result = f"Post created successfully!\\n\\n"
            result += f"ID: {post['id']}\\n"
            result += f"Title: {post['title']['rendered']}\\n"
            result += f"Status: {post['status']}\\n"
            result += f"Link: {post['link']}\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "update_post":
            post_id = arguments["post_id"]
            data = {}
            
            if "title" in arguments:
                data["title"] = arguments["title"]
            if "content" in arguments:
                data["content"] = arguments["content"]
            if "status" in arguments:
                data["status"] = arguments["status"]
            
            post = await wp_request("POST", f"posts/{post_id}", data)
            
            result = f"Post updated successfully!\\n\\n"
            result += f"ID: {post['id']}\\n"
            result += f"Title: {post['title']['rendered']}\\n"
            result += f"Status: {post['status']}\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "delete_post":
            post_id = arguments["post_id"]
            force = arguments.get("force", False)
            
            endpoint = f"posts/{post_id}"
            if force:
                endpoint += "?force=true"
            
            result_data = await wp_request("DELETE", endpoint)
            
            result = f"Post deleted successfully!\\n"
            result += f"ID: {post_id}\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "list_pages":
            per_page = arguments.get("per_page", 10)
            pages = await wp_request("GET", f"pages?per_page={per_page}")
            
            result = f"Found {len(pages)} pages:\\n\\n"
            for page in pages:
                result += f"ID: {page['id']}\\n"
                result += f"Title: {page['title']['rendered']}\\n"
                result += f"Status: {page['status']}\\n"
                result += f"Link: {page['link']}\\n\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "create_page":
            data = {
                "title": arguments["title"],
                "content": arguments["content"],
                "status": arguments.get("status", "draft")
            }
            
            page = await wp_request("POST", "pages", data)
            
            result = f"Page created successfully!\\n\\n"
            result += f"ID: {page['id']}\\n"
            result += f"Title: {page['title']['rendered']}\\n"
            result += f"Status: {page['status']}\\n"
            result += f"Link: {page['link']}\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "list_media":
            per_page = arguments.get("per_page", 20)
            media = await wp_request("GET", f"media?per_page={per_page}")
            
            result = f"Found {len(media)} media items:\\n\\n"
            for item in media:
                result += f"ID: {item['id']}\\n"
                result += f"Title: {item['title']['rendered']}\\n"
                result += f"Type: {item['mime_type']}\\n"
                result += f"URL: {item['source_url']}\\n\\n"
            
            return [TextContent(type="text", text=result)]
        
        elif name == "get_site_info":
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.get(f"{WP_SITE_URL}/wp-json/")
                site_info = response.json()
            
            result = f"WordPress Site Information:\\n\\n"
            result += f"Name: {site_info.get('name', 'N/A')}\\n"
            result += f"Description: {site_info.get('description', 'N/A')}\\n"
            result += f"URL: {site_info.get('url', 'N/A')}\\n"
            result += f"Home: {site_info.get('home', 'N/A')}\\n"
            
            return [TextContent(type="text", text=result)]
        
        else:
            return [TextContent(type="text", text=f"Unknown tool: {name}")]
    
    except httpx.HTTPStatusError as e:
        return [TextContent(type="text", text=f"HTTP Error: {e.response.status_code} - {e.response.text}")]
    except Exception as e:
        return [TextContent(type="text", text=f"Error: {str(e)}")]

async def main():
    """Run the MCP server"""
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options()
        )

if __name__ == "__main__":
    asyncio.run(main())

💡 How to create this file:

  1. Open Notepad (Windows) or TextEdit (Mac) or VS Code
  2. Copy ALL the code above (scroll through the entire code block)
  3. Paste it into the editor
  4. Save as wordpress_mcp_server.py in your wordpress-mcp folder
  5. Important: Make sure it’s saved as .py, not .txt!

Your Folder Should Now Look Like This:

📁 wordpress-mcp/ 📄 wordpress_mcp_server.py (the main server – 350 lines) 📄 requirements.txt (Python dependencies – 3 lines)

Step 3: Install Python Dependencies

Now we need to install the Python libraries that the server needs.

  1. Open your terminal/command prompt
  2. Navigate to your wordpress-mcp folder:
    cd C:\Users\YourName\wordpress-mcp
    (Replace with your actual path)
  3. Run this command:
    pip install -r requirements.txt
  4. Wait for installation to complete (about 30 seconds)

✅ Success looks like:

Successfully installed mcp-1.0.0 httpx-0.27.0 python-dotenv-1.0.0

⚠️ If you see errors:

  • Try using pip3 instead of pip
  • Make sure you’re in the correct folder
  • Check that Python is properly installed

Step 4: Generate WordPress Application Password

Now we need a special password from WordPress that the MCP server will use to connect.

Important: This is NOT your normal WordPress login password. It’s a special “Application Password” that’s more secure.

  1. Log into WordPress Admin
    • Go to yoursite.com/wp-admin
    • Enter your username and password
  2. Go to Your Profile
    • Click Users in the left sidebar
    • Click on your username
    • Or click Edit Profile at the top
  3. Scroll Down to Application Passwords
    • Look for a section called Application Passwords
    • It’s usually near the bottom of the page
  4. Create New Password
    • In the “New Application Password Name” field, type: Claude MCP Server
    • Click Add New Application Password
  5. COPY THE PASSWORD IMMEDIATELY
    • WordPress will show a password like: xxxx xxxx xxxx xxxx xxxx xxxx
    • THIS IS VERY IMPORTANT: Copy it right away! You won’t see it again!
    • Save it in a text file temporarily – you’ll need it in the next step

⚠️ Can’t find Application Passwords?

  • Make sure you’re running WordPress 5.6 or higher
  • Some hosting providers disable this – contact their support
  • Alternative: Install the “Application Passwords” plugin from WordPress.org

Step 5: Configure Claude Desktop

Now we’ll tell Claude Desktop about your MCP server.

1
Find your Claude config file
  • Windows: Press Win + R, type %APPDATA%\Claude, press Enter
    Then open claude_desktop_config.json
  • Mac: In Finder, press Cmd + Shift + G, paste:
    ~/Library/Application Support/Claude/
    Then open claude_desktop_config.json
2
Edit the config file

Open it with Notepad (Windows) or TextEdit (Mac). You’ll see JSON code.

Add this configuration (replace the placeholder values with YOUR actual information):

📄 claude_desktop_config.json
{
  "mcpServers": {
    "wordpress": {
      "command": "python",
      "args": ["C:\\Users\\YourName\\wordpress-mcp\\wordpress_mcp_server.py"],
      "env": {
        "WP_SITE_URL": "https://yoursite.com",
        "WP_USERNAME": "your_wordpress_username",
        "WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
      }
    }
  }
}

⚠️ IMPORTANT – Replace These Values:

  • C:\\Users\\YourName\\wordpress-mcp\\wordpress_mcp_server.py → Your ACTUAL full path
    Note: On Windows, use double backslashes (\\)
    On Mac: Use forward slashes: /Users/YourName/wordpress-mcp/wordpress_mcp_server.py
  • https://yoursite.com → Your WordPress site URL (include https://)
  • your_wordpress_username → Your WordPress username (NOT email)
  • xxxx xxxx xxxx xxxx xxxx xxxx → The Application Password you copied

💡 If you already have other MCP servers configured:

Add the wordpress section inside the existing mcpServers object, like this:

{
  "mcpServers": {
    "existing-server": {
      ...existing config...
    },
    "wordpress": {
      ...new wordpress config...
    }
  }
}
3
Save the file

Make sure to save your changes!

Step 6: Restart Claude Desktop

  1. Completely quit Claude Desktop (don’t just close the window)
  2. On Windows: Right-click the system tray icon and choose “Quit”
  3. On Mac: Press Cmd + Q
  4. Wait 5 seconds
  5. Restart Claude Desktop

🧪Testing Your Setup

Let’s make sure everything is working!

Test 1: Check Site Connection

Open a new conversation with Claude and type:

What’s my WordPress site information?

✅ If it works, Claude will respond with:

  • Your site name
  • Your site description
  • Your site URL

Congrats! Your MCP server is working! 🎉

Test 2: List Posts

Try this command:

List my recent WordPress posts

Claude should show you a list of your posts with titles, dates, and links.

Test 3: Create a Draft Post

Try creating a test post:

Create a draft post titled “Test Post” with the content “This is a test from Claude”

Then check your WordPress admin to see if the draft appears!

✅ All three tests passed? You’re ready to use your MCP server!

🔧Troubleshooting Common Issues

Problem: “HTTP Error: 401”

This means authentication failed.

Solutions:

  • Double-check your Application Password – make sure you copied it correctly (no extra spaces)
  • Verify your WordPress username is correct (it’s NOT your email)
  • Make sure you’re using the Application Password, not your regular password
  • Try generating a new Application Password

Problem: “Connection Error” or “Cannot connect to site”

The server can’t reach your WordPress site.

Solutions:

  • Make sure your site URL includes https:// (not just www)
  • Verify your WordPress site is online and accessible
  • Check if your hosting has a firewall blocking API requests
  • Try accessing yoursite.com/wp-json/ in your browser – you should see JSON data

Problem: Claude doesn’t seem to have the WordPress tools

The MCP server didn’t load properly.

Solutions:

  • Make sure you completely quit and restarted Claude Desktop (not just minimized)
  • Check the file path in your config – it must be the FULL path to wordpress_mcp_server.py
  • Verify Python is installed: run python --version in terminal
  • Check for JSON syntax errors in claude_desktop_config.json (missing commas, brackets)
  • Look at Claude Desktop logs for error messages

Problem: “Module not found” or Import Errors

Python dependencies aren’t installed.

Solutions:

  • Navigate to your wordpress-mcp folder in terminal
  • Run pip install -r requirements.txt again
  • Try using pip3 instead of pip
  • Make sure you’re in the correct folder when installing

Problem: Can’t find the Claude config file

Windows:

  1. Press Win + R
  2. Type: %APPDATA%
  3. Press Enter
  4. Look for a “Claude” folder

Mac:

  1. In Finder, click “Go” menu
  2. Hold Option key – “Library” appears
  3. Click Library
  4. Go to Application Support → Claude

Common Beginner Mistakes

Watch out for these common errors:

  • ❌ Using login password instead of Application Password
  • ❌ Forgetting https:// in site URL
  • ❌ Using relative paths instead of absolute paths in config
  • ❌ Not restarting Claude Desktop after config changes
  • ❌ JSON syntax errors (missing commas, mismatched brackets)
  • ❌ Saving wordpress_mcp_server.py as a .txt file instead of .py
  • ❌ Not copying the entire server code (missing the bottom part)

🚀Using Your WordPress MCP Server

Now that it’s working, here are powerful ways to use it:

Content Creation

“Create a blog post titled ‘Top 10 AI Tools for Developers’ with an introduction, 10 tools with descriptions, and a conclusion. Publish it.”

Content Management

“List all my posts from the last month and tell me which ones need updating”

Quick Edits

“Update post #42 – change the title to ‘Complete Guide to Python’ and add a note at the beginning saying it was updated today”

Content Audits

“Show me all my draft posts and help me decide which ones to publish first”

Batch Operations

“Create 5 draft posts about machine learning topics, each with an outline”

🎓Next Steps

Master Your New Workflow

Now that your MCP server is set up, explore what’s possible:

  • Content Strategy: Use Claude to plan your editorial calendar
  • SEO Optimization: Let Claude analyze and improve your posts
  • Batch Creation: Generate multiple posts at once
  • Content Updates: Keep old posts fresh and relevant

Advanced Tips

  • Combine with other MCP servers for powerful workflows
  • Use Claude to migrate content from other platforms
  • Automate content quality checks before publishing
  • Generate documentation from your code and publish it

🎉Conclusion

Congratulations! You’ve successfully connected Claude to your WordPress site through an MCP server. You can now manage your entire site through natural conversation.

This workflow saves hours of manual work and makes content management feel effortless. Instead of context-switching between tools, you can do everything in one place through conversation.

Join the Community

Share what you’ve built! Connect with other developers using MCP servers and discover new use cases.


Having trouble? Questions about the setup? Drop a comment below and I’ll help you troubleshoot!

WordPress MCP Server Claude AI Tools Content Management Python REST API Tutorial Developer Tools

About the Author

Sumbul Ali