MCPcopy Index your code
hub / github.com/InstaWP/mcp-wp

github.com/InstaWP/mcp-wp @v0.1.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.1.0 ↗ · + Follow
88 symbols 268 edges 21 files 14 documented · 16%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

WordPress MCP Server

This is a Model Context Protocol (MCP) server for WordPress, allowing you to interact with your WordPress site using natural language via an MCP-compatible client like Claude for Desktop. This server exposes various WordPress data and functionality as MCP tools.

Usage

Claude Desktop

  1. Download and install Claude Desktop.
  2. Open Claude Desktop settings and navigate to the "Developer" tab.
  3. Copy the contents of the claude_desktop_config.json.example file.
  4. Click "Edit Config" to open the claude_desktop_config.json file.
  5. Copy paste the contents of the example file into the config file. Make sure to replace the placeholder values with your actual values for the WordPress site. To generate the application keys, follow this guide - Application Passwords.
  6. Save the configuration.
  7. Restart Claude Desktop.

Features

This server provides tools to interact with core WordPress data and supports multi-site management - manage multiple WordPress sites from a single MCP server instance.

Multi-Site Management (3 tools)

Manage multiple WordPress sites from a single MCP server:

  • list_sites: List all configured WordPress sites
  • get_site: Get details about a specific site configuration
  • test_site: Test connection to a specific WordPress site

All content and taxonomy tools support an optional site_id parameter to target specific sites.

Unified Content Management (9 tools)

Handles ALL content types (posts, pages, custom post types) with a single set of intelligent tools:

  • list_content: List any content type with filtering and pagination
  • get_content: Get specific content by ID and type
  • create_content: Create new content of any type
  • update_content: Update existing content of any type, including targeted partial edits
  • delete_content: Delete content of any type
  • discover_content_types: Find all available content types on your site
  • find_content_by_url: Smart URL resolver that can find and optionally update content from any WordPress URL, including targeted partial edits
  • get_content_by_slug: Search by slug across all content types
  • get_content_summary: Return a minimal summary (id, title, slug, status, excerpt, taxonomies, word count, Yoast SEO fields) for audit and lookup workflows. Look up by id or url.

Unified Taxonomy Management (8 tools)

Handles ALL taxonomies (categories, tags, custom taxonomies) with a single set of tools:

  • discover_taxonomies: Find all available taxonomies on your site
  • list_terms: List terms in any taxonomy
  • get_term: Get specific term by ID
  • create_term: Create new term in any taxonomy
  • update_term: Update existing term
  • delete_term: Delete term from any taxonomy
  • assign_terms_to_content: Assign terms to any content type
  • get_content_terms: Get all terms for any content

Specialized Tools

  • Media:
  • list_media: List all media items (supports pagination and searching).
  • get_media: Retrieve a specific media item by ID.
  • create_media: Create a new media item from a URL or local file path.
  • update_media: Update an existing media item.
  • delete_media: Delete a media item.
  • edit_media: Legacy alias for update_media kept for backward compatibility.
  • Users:
  • list_users: List all users with filtering, sorting, and pagination options.
  • get_user: Retrieve a specific user by ID.
  • create_user: Create a new user.
  • update_user: Update an existing user.
  • delete_user: Delete a user.
  • Comments:
  • list_comments: List all comments with filtering, sorting, and pagination options.
  • get_comment: Retrieve a specific comment by ID.
  • create_comment: Create a new comment.
  • update_comment: Update an existing comment.
  • delete_comment: Delete a comment.
  • Plugins:
  • list_plugins: List all plugins installed on the site.
  • get_plugin: Retrieve details about a specific plugin.
  • activate_plugin: Activate a plugin.
  • deactivate_plugin: Deactivate a plugin.
  • create_plugin: Create a new plugin.
  • Plugin Repository:
  • search_plugins: Search for plugins in the WordPress.org repository.
  • get_plugin_info: Get detailed information about a plugin from the repository.
  • Database Queries:
  • execute_sql_query: Execute read-only SQL queries against the WordPress database (requires custom endpoint setup).

Key Advantages

Media Upload Workflows

Upload a local screenshot from the same machine running the MCP server:

{
  "file_path": "./screenshots/homepage.png",
  "title": "Homepage Screenshot",
  "alt_text": "Homepage screenshot showing the hero section"
}

Upload media from a remote URL:

{
  "source_url": "https://example.com/assets/hero-image.png",
  "title": "Hero Image",
  "caption": "Imported from the design system"
}

Use the returned media ID as featured media on new content:

{
  "content_type": "post",
  "title": "Release Notes",
  "content": "

Launch summary...

",
  "featured_media": 123
}

Smart URL Resolution

The find_content_by_url tool can:

  • Take any WordPress URL and automatically find the corresponding content
  • Detect content types from URL patterns (e.g., /documentation/ → documentation custom post type)
  • Optionally update the content in a single operation
  • Works with posts, pages, and any custom post types

Audit & Lookup Summaries

The get_content_summary tool returns a minimal, fixed-shape representation of a single piece of content. Designed for audit and lookup workflows where the full WP REST response — which can exceed 50KB on recipe posts because of the rendered Recipe Maker card HTML — is overkill.

Look up by ID (with optional content_type, defaulting to post):

{
  "id": 4274,
  "content_type": "post"
}

Look up by URL (content type is detected from the URL):

{
  "url": "https://example.com/blog/easy-smoked-asparagus/"
}

id and url are mutually exclusive — provide exactly one.

The response shape is fixed:

{
  "id": 4274,
  "title": "Easy Smoked Asparagus & Hot Honey",
  "slug": "easy-smoked-asparagus",
  "status": "publish",
  "link": "https://example.com/blog/easy-smoked-asparagus/",
  "excerpt": "Smoky asparagus with hot honey.",
  "date_modified": "2026-04-30T10:14:00",
  "categories": [12, 7],
  "tags": [33],
  "featured_media": 9012,
  "word_count": 875,
  "yoast_focus_keyword": "smoked asparagus",
  "yoast_meta_title": "Easy Smoked Asparagus | Example",
  "yoast_meta_description": "Smoky charred asparagus finished with chili-lime hot honey."
}

Field notes:

  • title and excerpt are stripped to plain text (HTML tags removed, basic entities decoded).
  • word_count prefers yoast_head_json.schema.@graph[].wordCount when Yoast SEO is active; otherwise it is computed from the rendered post content with HTML stripped.
  • yoast_meta_title and yoast_meta_description are read from yoast_head_json on the post. They are null when Yoast SEO is not active.
  • yoast_focus_keyword is read from meta._yoast_wpseo_focuskw. WordPress core only exposes meta keys that are registered with show_in_rest, and Yoast SEO does not register this key by default — so this field will typically be null unless a companion plugin registers it (see PR #17 for context on the broader meta-key REST exposure issue).
  • This tool internally bypasses the response trimming added in PR #16 so it can read yoast_head_json. The trim still applies to all other tools.

Universal Content Operations

All content operations use a single content_type parameter:

{
  "content_type": "post", // for blog posts
  "content_type": "page", // for static pages
  "content_type": "product", // for WooCommerce products
  "content_type": "documentation" // for custom post types
}

Targeted Content Edits

update_content and find_content_by_url.update_fields can patch the existing raw WordPress content without resending the full document.

To make exact matching easier, get_content and find_content_by_url both accept include_raw_content: true. When enabled, the response is fetched with WordPress edit context and includes a top-level content_raw field that matches what content_edit.target_text needs.

{
  "content_type": "page",
  "id": 7,
  "include_raw_content": true
}

Append a short release note to the end of a post:

{
  "content_type": "post",
  "id": 42,
  "content_edit": {
    "operation": "append",
    "value": "\n

Update: Early access is now open.

",
    "content_format": "html"
  }
}

Replace a unique HTML fragment or marker comment in place:

{
  "content_type": "page",
  "id": 7,
  "content_edit": {
    "operation": "replace",
    "target_text": "\n

Old price

\n",
    "value": "\n

New price

\n",
    "content_format": "html"
  }
}

Notes:

  • Rendered WordPress HTML can differ from content.raw because entities may be escaped and markup may be expanded, so use include_raw_content when you need an exact target_text.
  • target_text matches the stored raw WordPress content exactly.
  • If the same target_text appears multiple times, pass occurrence to choose the 1-based match.
  • For posts stored as Gutenberg blocks, set content_edit.convert_to_blocks when inserting Markdown or HTML that should become blocks.

Universal Taxonomy Operations

All taxonomy operations use a single taxonomy parameter:

{
  "taxonomy": "category", // for categories
  "taxonomy": "post_tag", // for tags
  "taxonomy": "product_category", // for WooCommerce
  "taxonomy": "skill" // for custom taxonomies
}

The taxonomy parameter accepts either the taxonomy slug or its rest_base (they can differ for custom taxonomies, e.g. slug documentation_category with rest_base documentation-categories). Tools resolve the identifier via /wp/v2/taxonomies and error on unknown taxonomies instead of guessing. assign_terms_to_content verifies the write against the WordPress response and reports an error if the terms were not actually saved.

Recipe Cards (WP Recipe Maker)

Sites running WP Recipe Maker (WPRM) store recipe cards in a separate wprm_recipe custom post type referenced by shortcode from the surrounding blog post. The unified content tools handle these recipes directly — no recipe-specific tool family is needed.

Reading recipesget_content, list_content, find_content_by_url, and get_content_by_slug all work with content_type: "wprm_recipe". WPRM exposes the full structured recipe payload as a recipe field on the REST response, including ingredients, instructions, times, equipment, nutrition, notes, and rating.

Writing recipes — pass the recipe payload via custom_fields.recipe on create_content or update_content. WPRM hooks into the WordPress REST insert action (rest_insert_wprm_recipe) and reads recipe from the request body root, so any field documented by WPRM's data model is accepted.

The recipe payload must be passed via custom_fields (which spreads at the request body root). The meta parameter nests its values under a meta key, which never reaches WPRM's REST hook.

Example update:

{
  "content_type": "wprm_recipe",
  "id": 4274,
  "custom_fields": {
    "recipe": {
      "name": "Easy Smoked Asparagus",
      "summary": "Smoky asparagus with hot honey.",
      "servings": "4",
      "servings_unit": "people",
      "prep_time": "5",
      "cook_time": "60",
      "total_time": "65",
      "ingredients": [
        {
          "name": "",
          "ingredients": [
            { "uid": 0, "amount": "1", "unit": "Bunch", "name": "Asparagus Spears", "notes": "" },
            { "uid": 1, "amount": "1", "unit": "tbsp", "name": "Olive Oil", "notes": "" }
          ]
        }
      ],
      "instructions": [
        {
          "name": "",
          "instructions": [
            { "uid": 0, "name": "", "text": "Preheat smoker to 225°F.", "ingredients": [] },
            { "uid": 1, "name": "", "text": "Drizzle with oil, season, smoke 1 hour.", "ingredients": [] }
          ]
        }
      ],
      "notes": "Thicker spears need more time."
    }
  }
}

Grouped ingredients and instructions — recipes can split items into named groups like "For the sauce" / "For the chicken". Each entry in the outer ingredients (or instructions) array is one group with its own name and inner array:

{
  "ingredients": [
    { "name": "For the sauce",   "ingredients": [ /* items */ ] },
    { "name": "For the chicken", "ingredients": [ /* items */ ] }
  ]
}

Commonly used recipe fields:

Field Type Notes
name string Recipe card title
summary string Short blurb (HTML allowed)
servings string e.g. "4"
servings_unit string e.g. "people", "servings"
prep_time string minutes, e.g. "15"
cook_time string minutes
total_time string minutes
ingredients array of groups nested structure shown above
instructions arra

Extension points exported contracts — how you extend this code

TaxonomyCacheEntry (Interface)
(no doc)
src/tools/unified-taxonomies.ts
WPContent (Interface)
(no doc)
src/types/wordpress-types.ts
SiteConfig (Interface)
(no doc)
src/config/site-manager.ts
ContentSummary (Interface)
(no doc)
src/tools/content-summary.ts
WPPost (Interface)
(no doc)
src/types/wordpress-types.ts
WPPage (Interface)
(no doc)
src/types/wordpress-types.ts
WPCategory (Interface)
(no doc)
src/types/wordpress-types.ts
WPUser (Interface)
(no doc)
src/types/wordpress-types.ts

Core symbols most depended-on inside this repo

logToFile
called by 50
src/wordpress.ts
makeWordPressRequest
called by 43
src/wordpress.ts
getContentEndpoint
called by 12
src/tools/unified-content.ts
getSite
called by 11
src/config/site-manager.ts
resolveTaxonomy
called by 7
src/tools/unified-taxonomies.ts
ensureInitialized
called by 6
src/config/site-manager.ts
getAllSites
called by 6
src/config/site-manager.ts
getDefaultSiteId
called by 6
src/config/site-manager.ts

Shape

Function 61
Interface 15
Method 10
Class 2

Languages

TypeScript100%

Modules by API surface

src/tools/unified-content.ts21 symbols
src/tools/media.ts18 symbols
src/config/site-manager.ts13 symbols
src/types/wordpress-types.ts12 symbols
src/wordpress.ts6 symbols
src/tools/content-summary.ts6 symbols
src/tools/unified-taxonomies.ts5 symbols
src/server.ts2 symbols
src/cli.ts2 symbols
scripts/manual-test-meta-warning.mjs2 symbols
tests/tools/wprm-recipe.integration.test.ts1 symbols

For agents

$ claude mcp add mcp-wp \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page