This file provides guidance to coding agents (Claude Code, Cursor, Codex, and others) when working with code in this repository. CLAUDE.md is a one-line @AGENTS.md import so Claude Code picks up this same content.
This is a WordPress MCP (Model Context Protocol) server that allows interaction with WordPress sites through natural language via MCP-compatible clients like Claude Desktop. The server exposes WordPress REST API functionality as MCP tools.
# Install dependencies
npm install
# Build TypeScript to JavaScript (tsc, outputs to build/)
npm run build
# Run in development mode with hot reload (tsx watch)
npm run dev
# Run the built server
npm start
# Clean build artifacts
npm run cleanThere is no test script in package.json; the repo currently ships no automated test suite. npm run prepare runs the build automatically (e.g. on install/publish).
Create a .env file in the project root with:
WORDPRESS_API_URL=https://your-wordpress-site.com
WORDPRESS_USERNAME=wp_username
WORDPRESS_PASSWORD=wp_app_passwordFor managing multiple WordPress sites (numbered config, read in src/config/site-manager.ts:48):
# Site 1 (Production)
WORDPRESS_1_URL=https://production-site.com
WORDPRESS_1_USERNAME=admin
WORDPRESS_1_PASSWORD=app_password_1
WORDPRESS_1_ID=production
WORDPRESS_1_DEFAULT=true
WORDPRESS_1_ALIASES=prod,main
# Site 2 (Staging)
WORDPRESS_2_URL=https://staging-site.com
WORDPRESS_2_USERNAME=admin
WORDPRESS_2_PASSWORD=app_password_2
WORDPRESS_2_ID=staging
WORDPRESS_2_ALIASES=stage,devIf no numbered sites are found, the server falls back to the legacy single-site WORDPRESS_API_URL/WORDPRESS_USERNAME/WORDPRESS_PASSWORD variables. The first configured site is the default unless a WORDPRESS_N_DEFAULT=true is set.
The app password can be generated from WordPress admin panel following the Application Passwords guide.
WORDPRESS_LOG_LEVEL—debug|info|error(defaulterror). Controls log verbosity (logs go to stderr, not a file).DISABLE_LOGGING=true— silences all logging.WORDPRESS_SQL_ENDPOINT— override the SQL-query endpoint (default/mcp/v1/query); seesrc/tools/sql-query.ts:95.WORDPRESS_CACHE_DURATION— cache TTL for WordPress lookups.WORDPRESS_PARALLEL_SEARCH— toggle parallel content-type search.UNIFIED_CONTENT_CACHE_DIR— directory for the unified-content cache.
-
MCP Server (
src/server.ts):- Entry point that initializes the server using the
McpServerclass from the ModelContextProtocol SDK - Registers every tool from
allToolswith its handler in a loop (src/server.ts:27) and logs the registered count - Uses
StdioServerTransportfor communication with Claude Desktop - Validates environment variables and establishes WordPress connection on startup
- Entry point that initializes the server using the
-
Site Manager (
src/config/site-manager.ts):- Manages multiple WordPress site configurations
- Lazy loads site configurations from environment variables
- Maintains separate authenticated Axios clients for each site
- Provides site detection from context (domain mentions, aliases, site IDs)
- Supports both numbered multi-site config and legacy single-site config
-
WordPress Client (
src/wordpress.ts):- Manages authenticated Axios instance for WordPress REST API calls
- Integrates with SiteManager for multi-site support
- Handles authentication using Basic Auth with application passwords
- Provides
makeWordPressRequest()wrapper for all API calls with optionalsiteIdparameter - Logs to stderr via
logToFile()(src/wordpress.ts:20), gated byWORDPRESS_LOG_LEVEL/DISABLE_LOGGING— stdout is reserved for the MCP protocol - Special handler
searchWordPressPluginRepository()(src/wordpress.ts:130) for WordPress.org plugin search
-
Tool System (
src/tools/):- Each WordPress entity (posts, pages, media, etc.) has its own module
- Each module exports a tools array and a handlers object
- Tools use Zod schemas for input validation and type safety
- The unified content tools (and the
get_site/test_sitesite-management tools) accept an optionalsite_idparameter for multi-site targeting; other tool modules operate on the default site - All tools are aggregated in
src/tools/index.ts(allTools/toolHandlers)
-
CLI Launcher (
src/cli.ts):- A thin alternate launcher that checks env vars and spawns
server.js. Note: the packagebinentry points atbuild/server.jsdirectly, not at this file.
- A thin alternate launcher that checks env vars and spawns
Each tool module follows this pattern:
// Define Zod schemas for input validation
const listSchema = z.object({...});
const getSchema = z.object({...});
const createSchema = z.object({...});
const updateSchema = z.object({...});
const deleteSchema = z.object({...});
// Export tools array with MCP tool definitions
export const entityTools: Tool[] = [
{ name: "list_entity", description: "...", inputSchema: {...} },
{ name: "get_entity", description: "...", inputSchema: {...} },
{ name: "create_entity", description: "...", inputSchema: {...} },
{ name: "update_entity", description: "...", inputSchema: {...} },
{ name: "delete_entity", description: "...", inputSchema: {...} }
];
// Export handlers object with async functions
export const entityHandlers = {
list_entity: async (params) => {...},
get_entity: async (params) => {...},
create_entity: async (params) => {...},
update_entity: async (params) => {...},
delete_entity: async (params) => {...}
};The MCP server uses a unified tool approach to reduce complexity and tool count (down from ~65 separate per-entity tools). Instead of separate tools for posts, pages, and custom post types, there are unified tools that handle all content types. The server currently registers 41 tools, aggregated in src/tools/index.ts:14.
Handles ALL content types (posts, pages, custom post types) with a single set of tools:
list_content— List any content type with filtering and paginationget_content— Get specific content by ID and typecreate_content— Create new content of any typeupdate_content— Update existing content of any typedelete_content— Delete content of any typediscover_content_types— Find all available content typesfind_content_by_url— Smart URL resolver with optional updateget_content_by_slug— Search by slug across content types
Handles ALL taxonomies (categories, tags, custom taxonomies) with a single set of tools:
discover_taxonomies— Find all available taxonomieslist_terms— List terms in any taxonomyget_term— Get specific term by IDcreate_term— Create new term in any taxonomyupdate_term— Update existing termdelete_term— Delete term from any taxonomyassign_terms_to_content— Assign terms to any content typeget_content_terms— Get all terms for any content
list_plugins,get_plugin,activate_plugin,deactivate_plugin,create_plugin
list_media,create_media,edit_media,delete_media
list_users,get_user,create_user,update_user,delete_user
list_comments,get_comment,create_comment,update_comment,delete_comment
search_plugin_repository— Search WordPress.org for pluginsget_plugin_details— Get details for a WordPress.org plugin
execute_sql_query— Execute read-only database queries. Requires a custom endpoint on the WordPress side; uses/mcp/v1/queryby default, overridable viaWORDPRESS_SQL_ENDPOINT.
list_sites— List all configured WordPress sitesget_site— Get details about a specific sitetest_site— Test connection to a WordPress site
The find_content_by_url tool can:
- Take any WordPress URL and automatically find the corresponding content
- Detect the content type from URL patterns (e.g.,
/documentation/→ documentation CPT) - Optionally update the content in a single operation
- Cache content type information to minimize API calls
Example: Given https://site.com/documentation/api-guide/, it will:
- Extract the slug
api-guide - Detect hints suggesting a documentation content type
- Search efficiently across relevant content types
- Return or update the found content
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 custom post types
"content_type": "documentation" // for custom post types
}All taxonomy operations use a single taxonomy parameter:
{
"taxonomy": "category", // for categories
"taxonomy": "post_tag", // for tags
"taxonomy": "product_category", // for custom taxonomies
"taxonomy": "skill" // for custom taxonomies
}The unified content tools (and the get_site/test_site site-management tools) accept an optional site_id parameter to target a specific site:
{
"content_type": "post",
"site_id": "production" // Optional - targets specific site
}If site_id is not provided, the default site is used. Sites can be managed via:
list_sites- See all configured sitesget_site- Get details about a sitetest_site- Test connection to a site
- Target: ES2022 with ESNext modules (
moduleResolution: node) - Strict mode enabled
- Source in
src/, builds tobuild/(outDir) - Declaration files generated
The server integrates with Claude Desktop via the configuration in claude_desktop_config.json:
{
"mcpServers": {
"wordpress": {
"command": "npx",
"args": ["-y", "@instawp/mcp-wp"],
"env": {
"WORDPRESS_API_URL": "https://your-site.com",
"WORDPRESS_USERNAME": "username",
"WORDPRESS_PASSWORD": "app_password"
}
}
}
}- All API requests are wrapped in try-catch blocks
- Errors are logged to stderr via
logToFile()(levelerror) with request/response details - Process signals (SIGTERM, SIGINT) are handled gracefully
- Uncaught exceptions and rejections trigger proper shutdown
@modelcontextprotocol/sdk: MCP protocol implementationaxios: HTTP client for WordPress REST APIzod+zod-to-json-schema: Runtime type validation and JSON-schema generation for tool inputsdotenv: Environment variable managementfs-extra: Filesystem helpers (e.g. content cache)marked: Markdown parsing for content handlingtsx: TypeScript execution for development