|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +### Build and Run |
| 12 | +```bash |
| 13 | +# Install dependencies |
| 14 | +npm install |
| 15 | + |
| 16 | +# Build TypeScript to JavaScript (tsc, outputs to build/) |
| 17 | +npm run build |
| 18 | + |
| 19 | +# Run in development mode with hot reload (tsx watch) |
| 20 | +npm run dev |
| 21 | + |
| 22 | +# Run the built server |
| 23 | +npm start |
| 24 | + |
| 25 | +# Clean build artifacts |
| 26 | +npm run clean |
| 27 | +``` |
| 28 | + |
| 29 | +There 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). |
| 30 | + |
| 31 | +### Environment Setup |
| 32 | + |
| 33 | +#### Single Site Configuration |
| 34 | +Create a `.env` file in the project root with: |
| 35 | +```env |
| 36 | +WORDPRESS_API_URL=https://your-wordpress-site.com |
| 37 | +WORDPRESS_USERNAME=wp_username |
| 38 | +WORDPRESS_PASSWORD=wp_app_password |
| 39 | +``` |
| 40 | + |
| 41 | +#### Multi-Site Configuration |
| 42 | +For managing multiple WordPress sites (numbered config, read in `src/config/site-manager.ts:48`): |
| 43 | +```env |
| 44 | +# Site 1 (Production) |
| 45 | +WORDPRESS_1_URL=https://production-site.com |
| 46 | +WORDPRESS_1_USERNAME=admin |
| 47 | +WORDPRESS_1_PASSWORD=app_password_1 |
| 48 | +WORDPRESS_1_ID=production |
| 49 | +WORDPRESS_1_DEFAULT=true |
| 50 | +WORDPRESS_1_ALIASES=prod,main |
| 51 | +
|
| 52 | +# Site 2 (Staging) |
| 53 | +WORDPRESS_2_URL=https://staging-site.com |
| 54 | +WORDPRESS_2_USERNAME=admin |
| 55 | +WORDPRESS_2_PASSWORD=app_password_2 |
| 56 | +WORDPRESS_2_ID=staging |
| 57 | +WORDPRESS_2_ALIASES=stage,dev |
| 58 | +``` |
| 59 | + |
| 60 | +If 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. |
| 61 | + |
| 62 | +The app password can be generated from WordPress admin panel following the [Application Passwords guide](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide#Getting-Credentials). |
| 63 | + |
| 64 | +#### Optional Environment Variables |
| 65 | +- `WORDPRESS_LOG_LEVEL` — `debug` | `info` | `error` (default `error`). Controls log verbosity (logs go to **stderr**, not a file). |
| 66 | +- `DISABLE_LOGGING=true` — silences all logging. |
| 67 | +- `WORDPRESS_SQL_ENDPOINT` — override the SQL-query endpoint (default `/mcp/v1/query`); see `src/tools/sql-query.ts:95`. |
| 68 | +- `WORDPRESS_CACHE_DURATION` — cache TTL for WordPress lookups. |
| 69 | +- `WORDPRESS_PARALLEL_SEARCH` — toggle parallel content-type search. |
| 70 | +- `UNIFIED_CONTENT_CACHE_DIR` — directory for the unified-content cache. |
| 71 | + |
| 72 | +## Architecture |
| 73 | + |
| 74 | +### Core Components |
| 75 | + |
| 76 | +1. **MCP Server (`src/server.ts`)**: |
| 77 | + - Entry point that initializes the server using the `McpServer` class from the ModelContextProtocol SDK |
| 78 | + - Registers every tool from `allTools` with its handler in a loop (`src/server.ts:27`) and logs the registered count |
| 79 | + - Uses `StdioServerTransport` for communication with Claude Desktop |
| 80 | + - Validates environment variables and establishes WordPress connection on startup |
| 81 | + |
| 82 | +2. **Site Manager (`src/config/site-manager.ts`)**: |
| 83 | + - Manages multiple WordPress site configurations |
| 84 | + - Lazy loads site configurations from environment variables |
| 85 | + - Maintains separate authenticated Axios clients for each site |
| 86 | + - Provides site detection from context (domain mentions, aliases, site IDs) |
| 87 | + - Supports both numbered multi-site config and legacy single-site config |
| 88 | + |
| 89 | +3. **WordPress Client (`src/wordpress.ts`)**: |
| 90 | + - Manages authenticated Axios instance for WordPress REST API calls |
| 91 | + - Integrates with SiteManager for multi-site support |
| 92 | + - Handles authentication using Basic Auth with application passwords |
| 93 | + - Provides `makeWordPressRequest()` wrapper for all API calls with optional `siteId` parameter |
| 94 | + - Logs to **stderr** via `logToFile()` (`src/wordpress.ts:20`), gated by `WORDPRESS_LOG_LEVEL` / `DISABLE_LOGGING` — stdout is reserved for the MCP protocol |
| 95 | + - Special handler `searchWordPressPluginRepository()` (`src/wordpress.ts:130`) for WordPress.org plugin search |
| 96 | + |
| 97 | +4. **Tool System (`src/tools/`)**: |
| 98 | + - Each WordPress entity (posts, pages, media, etc.) has its own module |
| 99 | + - Each module exports a tools array and a handlers object |
| 100 | + - Tools use Zod schemas for input validation and type safety |
| 101 | + - The unified content tools (and the `get_site`/`test_site` site-management tools) accept an optional `site_id` parameter for multi-site targeting; other tool modules operate on the default site |
| 102 | + - All tools are aggregated in `src/tools/index.ts` (`allTools` / `toolHandlers`) |
| 103 | + |
| 104 | +5. **CLI Launcher (`src/cli.ts`)**: |
| 105 | + - A thin alternate launcher that checks env vars and spawns `server.js`. Note: the package `bin` entry points at `build/server.js` directly, not at this file. |
| 106 | + |
| 107 | +### Tool Pattern |
| 108 | + |
| 109 | +Each tool module follows this pattern: |
| 110 | +```typescript |
| 111 | +// Define Zod schemas for input validation |
| 112 | +const listSchema = z.object({...}); |
| 113 | +const getSchema = z.object({...}); |
| 114 | +const createSchema = z.object({...}); |
| 115 | +const updateSchema = z.object({...}); |
| 116 | +const deleteSchema = z.object({...}); |
| 117 | + |
| 118 | +// Export tools array with MCP tool definitions |
| 119 | +export const entityTools: Tool[] = [ |
| 120 | + { name: "list_entity", description: "...", inputSchema: {...} }, |
| 121 | + { name: "get_entity", description: "...", inputSchema: {...} }, |
| 122 | + { name: "create_entity", description: "...", inputSchema: {...} }, |
| 123 | + { name: "update_entity", description: "...", inputSchema: {...} }, |
| 124 | + { name: "delete_entity", description: "...", inputSchema: {...} } |
| 125 | +]; |
| 126 | + |
| 127 | +// Export handlers object with async functions |
| 128 | +export const entityHandlers = { |
| 129 | + list_entity: async (params) => {...}, |
| 130 | + get_entity: async (params) => {...}, |
| 131 | + create_entity: async (params) => {...}, |
| 132 | + update_entity: async (params) => {...}, |
| 133 | + delete_entity: async (params) => {...} |
| 134 | +}; |
| 135 | +``` |
| 136 | + |
| 137 | +### Unified Tool Architecture |
| 138 | + |
| 139 | +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`. |
| 140 | + |
| 141 | +#### Unified Content Tools (`unified-content.ts`) — 8 tools |
| 142 | +Handles ALL content types (posts, pages, custom post types) with a single set of tools: |
| 143 | +- `list_content` — List any content type with filtering and pagination |
| 144 | +- `get_content` — Get specific content by ID and type |
| 145 | +- `create_content` — Create new content of any type |
| 146 | +- `update_content` — Update existing content of any type |
| 147 | +- `delete_content` — Delete content of any type |
| 148 | +- `discover_content_types` — Find all available content types |
| 149 | +- `find_content_by_url` — Smart URL resolver with optional update |
| 150 | +- `get_content_by_slug` — Search by slug across content types |
| 151 | + |
| 152 | +#### Unified Taxonomy Tools (`unified-taxonomies.ts`) — 8 tools |
| 153 | +Handles ALL taxonomies (categories, tags, custom taxonomies) with a single set of tools: |
| 154 | +- `discover_taxonomies` — Find all available taxonomies |
| 155 | +- `list_terms` — List terms in any taxonomy |
| 156 | +- `get_term` — Get specific term by ID |
| 157 | +- `create_term` — Create new term in any taxonomy |
| 158 | +- `update_term` — Update existing term |
| 159 | +- `delete_term` — Delete term from any taxonomy |
| 160 | +- `assign_terms_to_content` — Assign terms to any content type |
| 161 | +- `get_content_terms` — Get all terms for any content |
| 162 | + |
| 163 | +#### Plugin Tools (`plugins.ts`) — 5 tools |
| 164 | +- `list_plugins`, `get_plugin`, `activate_plugin`, `deactivate_plugin`, `create_plugin` |
| 165 | + |
| 166 | +#### Media Tools (`media.ts`) — 4 tools |
| 167 | +- `list_media`, `create_media`, `edit_media`, `delete_media` |
| 168 | + |
| 169 | +#### User Tools (`users.ts`) — 5 tools |
| 170 | +- `list_users`, `get_user`, `create_user`, `update_user`, `delete_user` |
| 171 | + |
| 172 | +#### Comment Tools (`comments.ts`) — 5 tools |
| 173 | +- `list_comments`, `get_comment`, `create_comment`, `update_comment`, `delete_comment` |
| 174 | + |
| 175 | +#### Plugin Repository Tools (`plugin-repository.ts`) — 2 tools |
| 176 | +- `search_plugin_repository` — Search WordPress.org for plugins |
| 177 | +- `get_plugin_details` — Get details for a WordPress.org plugin |
| 178 | + |
| 179 | +#### SQL Query Tool (`sql-query.ts`) — 1 tool |
| 180 | +- `execute_sql_query` — Execute read-only database queries. Requires a custom endpoint on the WordPress side; uses `/mcp/v1/query` by default, overridable via `WORDPRESS_SQL_ENDPOINT`. |
| 181 | + |
| 182 | +#### Site Management Tools (`site-management.ts`) — 3 tools |
| 183 | +- `list_sites` — List all configured WordPress sites |
| 184 | +- `get_site` — Get details about a specific site |
| 185 | +- `test_site` — Test connection to a WordPress site |
| 186 | + |
| 187 | +### Key Features |
| 188 | + |
| 189 | +#### Smart URL Resolution |
| 190 | +The `find_content_by_url` tool can: |
| 191 | +- Take any WordPress URL and automatically find the corresponding content |
| 192 | +- Detect the content type from URL patterns (e.g., `/documentation/` → documentation CPT) |
| 193 | +- Optionally update the content in a single operation |
| 194 | +- Cache content type information to minimize API calls |
| 195 | + |
| 196 | +Example: Given `https://site.com/documentation/api-guide/`, it will: |
| 197 | +1. Extract the slug `api-guide` |
| 198 | +2. Detect hints suggesting a documentation content type |
| 199 | +3. Search efficiently across relevant content types |
| 200 | +4. Return or update the found content |
| 201 | + |
| 202 | +#### Unified Content Management |
| 203 | +All content operations use a single `content_type` parameter: |
| 204 | +```json |
| 205 | +{ |
| 206 | + "content_type": "post", // for blog posts |
| 207 | + "content_type": "page", // for static pages |
| 208 | + "content_type": "product", // for custom post types |
| 209 | + "content_type": "documentation" // for custom post types |
| 210 | +} |
| 211 | +``` |
| 212 | + |
| 213 | +#### Unified Taxonomy Management |
| 214 | +All taxonomy operations use a single `taxonomy` parameter: |
| 215 | +```json |
| 216 | +{ |
| 217 | + "taxonomy": "category", // for categories |
| 218 | + "taxonomy": "post_tag", // for tags |
| 219 | + "taxonomy": "product_category", // for custom taxonomies |
| 220 | + "taxonomy": "skill" // for custom taxonomies |
| 221 | +} |
| 222 | +``` |
| 223 | + |
| 224 | +#### Multi-Site Support |
| 225 | +The unified content tools (and the `get_site`/`test_site` site-management tools) accept an optional `site_id` parameter to target a specific site: |
| 226 | +```json |
| 227 | +{ |
| 228 | + "content_type": "post", |
| 229 | + "site_id": "production" // Optional - targets specific site |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +If `site_id` is not provided, the default site is used. Sites can be managed via: |
| 234 | +- `list_sites` - See all configured sites |
| 235 | +- `get_site` - Get details about a site |
| 236 | +- `test_site` - Test connection to a site |
| 237 | + |
| 238 | +## TypeScript Configuration |
| 239 | + |
| 240 | +- Target: ES2022 with ESNext modules (`moduleResolution: node`) |
| 241 | +- Strict mode enabled |
| 242 | +- Source in `src/`, builds to `build/` (`outDir`) |
| 243 | +- Declaration files generated |
| 244 | + |
| 245 | +## Claude Desktop Integration |
| 246 | + |
| 247 | +The server integrates with Claude Desktop via the configuration in `claude_desktop_config.json`: |
| 248 | +```json |
| 249 | +{ |
| 250 | + "mcpServers": { |
| 251 | + "wordpress": { |
| 252 | + "command": "npx", |
| 253 | + "args": ["-y", "@instawp/mcp-wp"], |
| 254 | + "env": { |
| 255 | + "WORDPRESS_API_URL": "https://your-site.com", |
| 256 | + "WORDPRESS_USERNAME": "username", |
| 257 | + "WORDPRESS_PASSWORD": "app_password" |
| 258 | + } |
| 259 | + } |
| 260 | + } |
| 261 | +} |
| 262 | +``` |
| 263 | + |
| 264 | +## Error Handling |
| 265 | + |
| 266 | +- All API requests are wrapped in try-catch blocks |
| 267 | +- Errors are logged to **stderr** via `logToFile()` (level `error`) with request/response details |
| 268 | +- Process signals (SIGTERM, SIGINT) are handled gracefully |
| 269 | +- Uncaught exceptions and rejections trigger proper shutdown |
| 270 | + |
| 271 | +## Key Dependencies |
| 272 | + |
| 273 | +- `@modelcontextprotocol/sdk`: MCP protocol implementation |
| 274 | +- `axios`: HTTP client for WordPress REST API |
| 275 | +- `zod` + `zod-to-json-schema`: Runtime type validation and JSON-schema generation for tool inputs |
| 276 | +- `dotenv`: Environment variable management |
| 277 | +- `fs-extra`: Filesystem helpers (e.g. content cache) |
| 278 | +- `marked`: Markdown parsing for content handling |
| 279 | +- `tsx`: TypeScript execution for development |
0 commit comments