@mangerik/wordpress-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.env.example +32 -0
  2. package/CHANGELOG.md +23 -0
  3. package/LICENSE +21 -0
  4. package/README.md +256 -0
  5. package/dist/config.d.ts +80 -0
  6. package/dist/config.js +84 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +145 -0
  9. package/dist/prompts.d.ts +7 -0
  10. package/dist/prompts.js +104 -0
  11. package/dist/resources.d.ts +13 -0
  12. package/dist/resources.js +64 -0
  13. package/dist/tools/batch.d.ts +14 -0
  14. package/dist/tools/batch.js +49 -0
  15. package/dist/tools/blocks.d.ts +4 -0
  16. package/dist/tools/blocks.js +202 -0
  17. package/dist/tools/comments.d.ts +4 -0
  18. package/dist/tools/comments.js +80 -0
  19. package/dist/tools/cpt.d.ts +16 -0
  20. package/dist/tools/cpt.js +97 -0
  21. package/dist/tools/jwt.d.ts +9 -0
  22. package/dist/tools/jwt.js +17 -0
  23. package/dist/tools/media.d.ts +4 -0
  24. package/dist/tools/media.js +101 -0
  25. package/dist/tools/multisite.d.ts +17 -0
  26. package/dist/tools/multisite.js +111 -0
  27. package/dist/tools/pages.d.ts +4 -0
  28. package/dist/tools/pages.js +101 -0
  29. package/dist/tools/posts.d.ts +4 -0
  30. package/dist/tools/posts.js +160 -0
  31. package/dist/tools/seo.d.ts +4 -0
  32. package/dist/tools/seo.js +269 -0
  33. package/dist/tools/site.d.ts +4 -0
  34. package/dist/tools/site.js +96 -0
  35. package/dist/tools/taxonomy.d.ts +4 -0
  36. package/dist/tools/taxonomy.js +147 -0
  37. package/dist/tools/users.d.ts +4 -0
  38. package/dist/tools/users.js +99 -0
  39. package/dist/tools/woocommerce.d.ts +4 -0
  40. package/dist/tools/woocommerce.js +400 -0
  41. package/dist/types.d.ts +26 -0
  42. package/dist/types.js +2 -0
  43. package/dist/wordpress-client.d.ts +223 -0
  44. package/dist/wordpress-client.js +519 -0
  45. package/package.json +67 -0
package/.env.example ADDED
@@ -0,0 +1,32 @@
1
+ # ── Required: where to connect ───────────────────────────────────────────
2
+ WP_URL=https://example.com
3
+
4
+ # ── Auth mode: 'application_password' (default) or 'jwt' ─────────────────
5
+ WP_AUTH_MODE=application_password
6
+
7
+ # Application Password mode (recommended; built into WP core)
8
+ WP_USERNAME=your-wp-username
9
+ # Application Password from: WP Admin → Users → Profile → Application Passwords
10
+ # Spaces are fine and should be preserved.
11
+ WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
12
+
13
+ # JWT mode (requires the "JWT Authentication for WP REST API" plugin or compatible)
14
+ # Either supply WP_USERNAME + WP_PASSWORD (server fetches a token at startup),
15
+ # or supply WP_JWT_TOKEN directly.
16
+ # WP_PASSWORD=
17
+ # WP_JWT_TOKEN=
18
+ # WP_JWT_NAMESPACE=jwt-auth/v1
19
+
20
+ # ── Optional: tuning ─────────────────────────────────────────────────────
21
+ WP_TIMEOUT_MS=30000
22
+ WP_MAX_RETRIES=3
23
+ # Set to "false" only for local dev with self-signed certificates
24
+ WP_VERIFY_SSL=true
25
+ WP_USER_AGENT=WordPress-MCP-Server/1.0
26
+
27
+ # ── Optional: WooCommerce dedicated credentials ──────────────────────────
28
+ # Generate at: WC Admin → Settings → Advanced → REST API → Add key.
29
+ # When set, /wc/* and /wc-analytics/* requests use these instead of the
30
+ # Application Password (the WP user no longer needs `manage_woocommerce`).
31
+ WC_CONSUMER_KEY=
32
+ WC_CONSUMER_SECRET=
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-05-17
9
+
10
+ Initial public preview. Tagged `beta` on npm while we collect feedback from
11
+ real WordPress installations. API surface may evolve before 1.0.0.
12
+
13
+ ### Added
14
+ - Initial release.
15
+ - Application Password and JWT auth modes.
16
+ - 95+ MCP tools spanning posts, pages, media, comments, taxonomies, users,
17
+ settings, search, custom post types, WooCommerce, Yoast / Rank Math SEO,
18
+ block themes (templates, parts, patterns, global styles, menus), multisite,
19
+ the WP 5.6+ batch endpoint, and JWT diagnostics.
20
+ - Resources: `wp://site`, `wp://post/{id}`, `wp://page/{id}`, `wp://media/{id}`.
21
+ - Prompts: `summarize_post`, `seo_rewrite`, `translate_page`, `draft_post`.
22
+ - Auto-retry on 408/425/429/5xx with `Retry-After` honored.
23
+ - WooCommerce consumer key/secret auth path for `/wc/*` routes.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mangerik
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.
package/README.md ADDED
@@ -0,0 +1,256 @@
1
+ # WordPress MCP Server
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that lets any MCP-compatible AI client (Claude Desktop, Kiro, Cursor, Continue, etc.) read and manage a WordPress site through the official REST API.
4
+
5
+ ## Features
6
+
7
+ - **30+ tools** covering posts, pages, media, comments, categories, tags, users, settings, search, and revisions.
8
+ - **Generic CPT tools** (`wp_list_items`, `wp_get_item`, `wp_create_item`, `wp_update_item`, `wp_delete_item`) for any custom post type, custom taxonomy, or third-party namespace (WooCommerce, ACF, Yoast, etc.).
9
+ - **Resources** so the LLM can attach a post/page as context: `wp://post/{id}`, `wp://page/{id}`, `wp://media/{id}`, `wp://site`.
10
+ - **Prompts** for common workflows: summarize, SEO rewrite, translate, draft.
11
+ - **Application Password** auth (recommended by WordPress core).
12
+ - **Auto-retry** on 429/5xx with `Retry-After` honored.
13
+ - **Token-saving knobs**: `_fields`, `_embed`, `context=view|edit`.
14
+ - **Safety**: destructive tool annotations, drafts default to `status=draft`.
15
+
16
+ ## Requirements
17
+
18
+ - Node.js ≥ 18
19
+ - A WordPress site with the REST API enabled (default since WP 4.7).
20
+ - A user with an Application Password (WP Admin → Users → Profile → Application Passwords).
21
+
22
+ ## Install
23
+
24
+ ### From npm (when published)
25
+
26
+ ```bash
27
+ npm install -g @mangerik/wordpress-mcp
28
+ # the `wordpress-mcp` binary is now on your PATH
29
+ ```
30
+
31
+ Or run on demand without installing:
32
+
33
+ ```bash
34
+ npx -y @mangerik/wordpress-mcp
35
+ ```
36
+
37
+ ### From source
38
+
39
+ ```bash
40
+ git clone https://github.com/mangerik/wordpress-mcp.git
41
+ cd wordpress-mcp
42
+ npm install
43
+ npm run build
44
+ ```
45
+
46
+ ## Configure
47
+
48
+ Two auth modes are supported.
49
+
50
+ ### A. Application Password (default, recommended)
51
+
52
+ Copy `.env.example` to `.env` and fill in:
53
+
54
+ ```env
55
+ WP_URL=https://example.com
56
+ WP_USERNAME=your-wp-username
57
+ WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
58
+ ```
59
+
60
+ ### B. JWT (when Application Passwords are blocked or you need short-lived tokens)
61
+
62
+ Install the "JWT Authentication for WP REST API" plugin (Tmeister) or compatible. Add to `wp-config.php`:
63
+
64
+ ```php
65
+ define('JWT_AUTH_SECRET_KEY', 'a long random string');
66
+ define('JWT_AUTH_CORS_ENABLE', true);
67
+ ```
68
+
69
+ Then in `.env`:
70
+
71
+ ```env
72
+ WP_AUTH_MODE=jwt
73
+ WP_URL=https://example.com
74
+ WP_USERNAME=your-wp-username
75
+ WP_PASSWORD=your-wp-password
76
+ # Optional override if your plugin uses a different namespace:
77
+ # WP_JWT_NAMESPACE=jwt-auth/v1
78
+
79
+ # Or skip credentials and supply a pre-issued token:
80
+ # WP_JWT_TOKEN=eyJhbGciOiJIUzI1NiIs...
81
+ ```
82
+
83
+ The server fetches a token at startup, attaches it as `Authorization: Bearer …`, and you can validate it any time via the `wp_jwt_validate` tool.
84
+
85
+ Optional:
86
+
87
+ | Variable | Default | Purpose |
88
+ |----------|---------|---------|
89
+ | `WP_TIMEOUT_MS` | `30000` | Per-request timeout |
90
+ | `WP_MAX_RETRIES` | `3` | Retries on 408/425/429/5xx |
91
+ | `WP_VERIFY_SSL` | `true` | Set `false` only for local self-signed certs |
92
+ | `WP_USER_AGENT` | `WordPress-MCP-Server/1.0` | UA header |
93
+
94
+ ## Use it from an MCP client
95
+
96
+ ### Claude Desktop / Kiro
97
+
98
+ Add to your MCP config (`~/.kiro/settings/mcp.json` or `~/Library/Application Support/Claude/claude_desktop_config.json`):
99
+
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "wordpress": {
104
+ "command": "npx",
105
+ "args": ["-y", "@mangerik/wordpress-mcp"],
106
+ "env": {
107
+ "WP_URL": "https://example.com",
108
+ "WP_USERNAME": "your-wp-username",
109
+ "WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ For local development (running from source):
117
+
118
+ ```json
119
+ {
120
+ "command": "node",
121
+ "args": ["/absolute/path/to/wordpress-mcp/dist/index.js"],
122
+ "env": { "WP_URL": "...", "WP_USERNAME": "...", "WP_APP_PASSWORD": "..." }
123
+ }
124
+ ```
125
+
126
+ ### Test from the terminal
127
+
128
+ ```bash
129
+ WP_URL=https://example.com \
130
+ WP_USERNAME=admin \
131
+ WP_APP_PASSWORD="xxxx xxxx xxxx xxxx xxxx xxxx" \
132
+ npm run dev
133
+ ```
134
+
135
+ The server speaks JSON-RPC over stdio. Use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to drive it interactively:
136
+
137
+ ```bash
138
+ npx @modelcontextprotocol/inspector node dist/index.js
139
+ ```
140
+
141
+ ## Tool catalogue
142
+
143
+ ### Site / discovery
144
+ - `wp_site_info` · `wp_get_settings` · `wp_update_settings`
145
+ - `wp_get_post_types` · `wp_get_taxonomies` · `wp_search`
146
+
147
+ ### Posts
148
+ - `wp_get_posts` · `wp_get_post` · `wp_create_post` · `wp_update_post` · `wp_delete_post` · `wp_get_post_revisions`
149
+
150
+ ### Pages
151
+ - `wp_get_pages` · `wp_get_page` · `wp_create_page` · `wp_update_page` · `wp_delete_page`
152
+
153
+ ### Media
154
+ - `wp_get_media` · `wp_get_media_item` · `wp_upload_media_file` · `wp_upload_media_url` · `wp_update_media` · `wp_delete_media`
155
+
156
+ ### Comments
157
+ - `wp_get_comments` · `wp_get_comment` · `wp_create_comment` · `wp_update_comment` · `wp_delete_comment`
158
+
159
+ ### Taxonomies
160
+ - `wp_get_categories` · `wp_get_category` · `wp_create_category` · `wp_update_category` · `wp_delete_category`
161
+ - `wp_get_tags` · `wp_get_tag` · `wp_create_tag` · `wp_update_tag` · `wp_delete_tag`
162
+
163
+ ### Users
164
+ - `wp_get_users` · `wp_get_user` · `wp_get_current_user` · `wp_create_user` · `wp_update_user` · `wp_delete_user`
165
+
166
+ ### Generic (CPT, custom taxonomies, plugin namespaces)
167
+ - `wp_list_items` · `wp_get_item` · `wp_create_item` · `wp_update_item` · `wp_delete_item`
168
+
169
+ Use these with any REST route, e.g. `wc/v3/products`, `wp/v2/movie`, `acf/v3/posts/123`.
170
+
171
+ ### WooCommerce (`wc/v3`)
172
+ Auth: set `WC_CONSUMER_KEY` + `WC_CONSUMER_SECRET` (recommended) or rely on the WP user having `manage_woocommerce`.
173
+
174
+ - Products: `wc_list_products` · `wc_get_product` · `wc_create_product` · `wc_update_product` · `wc_delete_product`
175
+ - Variations: `wc_list_variations` · `wc_create_variation`
176
+ - Categories: `wc_list_product_categories`
177
+ - Orders: `wc_list_orders` · `wc_get_order` · `wc_update_order` · `wc_create_order_note` · `wc_create_refund`
178
+ - Customers: `wc_list_customers` · `wc_get_customer`
179
+ - Coupons: `wc_list_coupons` · `wc_create_coupon`
180
+ - Reports: `wc_get_sales_report` · `wc_get_top_sellers`
181
+
182
+ ### SEO (Yoast & Rank Math)
183
+ Both plugins store SEO data in post meta with different keys; one uniform tool reads/writes either.
184
+
185
+ - `seo_get_meta` — read SEO meta (`plugin: 'yoast' | 'rank_math'`)
186
+ - `seo_set_meta` — write SEO meta
187
+ - `yoast_get_head` — render Yoast `<head>` for a URL (SERP preview)
188
+ - `yoast_indexable_for_post` — Yoast computed indexable record
189
+ - `rankmath_list_redirections` · `rankmath_create_redirection` (Rank Math Pro)
190
+
191
+ > Yoast exposes its meta via REST automatically. **Rank Math** may require enabling "REST API" in the plugin settings, and meta keys must be marked `show_in_rest`.
192
+
193
+ ### Block themes (WP 5.9+)
194
+ - Templates: `wp_list_templates` · `wp_get_template` · `wp_update_template`
195
+ - Template parts: `wp_list_template_parts` · `wp_get_template_part` · `wp_update_template_part`
196
+ - Patterns: `wp_list_block_patterns` · `wp_list_block_pattern_categories`
197
+ - Block types: `wp_list_block_types`
198
+ - Global styles: `wp_get_global_styles`
199
+ - Menus: `wp_list_menus` · `wp_list_menu_items`
200
+
201
+ > Template IDs are strings shaped like `theme//slug`, e.g. `twentytwentyfour//single`.
202
+
203
+ ### Multisite
204
+ - `ms_list_sites` · `ms_get_site` · `ms_create_site` · `ms_update_site` · `ms_delete_site`
205
+ - `ms_list_networks` · `ms_get_network`
206
+
207
+ > Core WP does **not yet** ship `/wp/v2/sites`. These tools assume a Multisite REST plugin is installed (e.g. `brettkrueger/multisite-rest-api`). Without one you will get `rest_no_route`.
208
+
209
+ ### Batch operations (WP 5.6+, `/batch/v1`)
210
+ - `wp_batch_options` — discover the per-batch limit (default 25, filterable).
211
+ - `wp_batch` — execute up to N writes in one request. GET is **not** supported by core; only `POST/PUT/PATCH/DELETE`.
212
+
213
+ ```jsonc
214
+ // Example call to wp_batch
215
+ {
216
+ "validation": "require-all-validate",
217
+ "requests": [
218
+ { "method": "POST", "path": "/wp/v2/posts", "body": { "title": "First", "content": "...", "status": "draft" } },
219
+ { "method": "POST", "path": "/wp/v2/posts", "body": { "title": "Second", "content": "...", "status": "draft" } },
220
+ { "method": "POST", "path": "/wp/v2/posts/123", "body": { "status": "publish" } }
221
+ ]
222
+ }
223
+ ```
224
+
225
+ ### JWT diagnostics (only in JWT mode)
226
+ - `wp_jwt_validate` — validate the active token via `/jwt-auth/v1/token/validate`.
227
+
228
+ ## Resources
229
+
230
+ | URI | What |
231
+ |-----|------|
232
+ | `wp://site` | REST root summary (name, namespaces, etc.) |
233
+ | `wp://post/{id}` | Single post (rendered + embedded relations) |
234
+ | `wp://page/{id}` | Single page |
235
+ | `wp://media/{id}` | Single media item |
236
+
237
+ ## Prompts
238
+
239
+ | Name | Args |
240
+ |------|------|
241
+ | `summarize_post` | `post_id`, `length` |
242
+ | `seo_rewrite` | `post_id`, `primary_keyword`, `target_audience?` |
243
+ | `translate_page` | `page_id`, `target_language`, `create_new?` |
244
+ | `draft_post` | `topic`, `tone?`, `word_count?` |
245
+
246
+ ## Notes & gotchas
247
+
248
+ - **`status` defaults to `draft`** for `wp_create_post` / `wp_create_page`. Override explicitly if you really want to publish.
249
+ - **Custom fields (`meta`)** must be registered server-side with `register_post_meta(..., 'show_in_rest' => true)` to be writable through REST.
250
+ - **Use `_fields`** to slim responses — e.g. `_fields: "id,title,slug"` cuts response size by ~80% for list calls.
251
+ - **`context=edit`** is required to receive raw (unfiltered) content for round-tripping through `wp_update_post`.
252
+ - The server logs to **stderr only**. Stdout is reserved for JSON-RPC framing.
253
+
254
+ ## License
255
+
256
+ MIT
@@ -0,0 +1,80 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Server configuration loaded from environment variables.
4
+ * Validated with zod so we fail fast at startup.
5
+ */
6
+ declare const ConfigSchema: z.ZodEffects<z.ZodObject<{
7
+ WP_URL: z.ZodString;
8
+ WP_AUTH_MODE: z.ZodDefault<z.ZodEnum<["application_password", "jwt"]>>;
9
+ WP_USERNAME: z.ZodOptional<z.ZodString>;
10
+ WP_APP_PASSWORD: z.ZodOptional<z.ZodString>;
11
+ WP_PASSWORD: z.ZodOptional<z.ZodString>;
12
+ WP_JWT_TOKEN: z.ZodOptional<z.ZodString>;
13
+ WP_JWT_NAMESPACE: z.ZodDefault<z.ZodString>;
14
+ WP_TIMEOUT_MS: z.ZodDefault<z.ZodNumber>;
15
+ WP_MAX_RETRIES: z.ZodDefault<z.ZodNumber>;
16
+ WP_VERIFY_SSL: z.ZodEffects<z.ZodDefault<z.ZodUnion<[z.ZodLiteral<"true">, z.ZodLiteral<"false">]>>, boolean, "true" | "false" | undefined>;
17
+ WP_USER_AGENT: z.ZodDefault<z.ZodString>;
18
+ WC_CONSUMER_KEY: z.ZodOptional<z.ZodString>;
19
+ WC_CONSUMER_SECRET: z.ZodOptional<z.ZodString>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ WP_URL: string;
22
+ WP_AUTH_MODE: "application_password" | "jwt";
23
+ WP_JWT_NAMESPACE: string;
24
+ WP_TIMEOUT_MS: number;
25
+ WP_MAX_RETRIES: number;
26
+ WP_VERIFY_SSL: boolean;
27
+ WP_USER_AGENT: string;
28
+ WP_USERNAME?: string | undefined;
29
+ WP_APP_PASSWORD?: string | undefined;
30
+ WP_PASSWORD?: string | undefined;
31
+ WP_JWT_TOKEN?: string | undefined;
32
+ WC_CONSUMER_KEY?: string | undefined;
33
+ WC_CONSUMER_SECRET?: string | undefined;
34
+ }, {
35
+ WP_URL: string;
36
+ WP_AUTH_MODE?: "application_password" | "jwt" | undefined;
37
+ WP_USERNAME?: string | undefined;
38
+ WP_APP_PASSWORD?: string | undefined;
39
+ WP_PASSWORD?: string | undefined;
40
+ WP_JWT_TOKEN?: string | undefined;
41
+ WP_JWT_NAMESPACE?: string | undefined;
42
+ WP_TIMEOUT_MS?: number | undefined;
43
+ WP_MAX_RETRIES?: number | undefined;
44
+ WP_VERIFY_SSL?: "true" | "false" | undefined;
45
+ WP_USER_AGENT?: string | undefined;
46
+ WC_CONSUMER_KEY?: string | undefined;
47
+ WC_CONSUMER_SECRET?: string | undefined;
48
+ }>, {
49
+ WP_URL: string;
50
+ WP_AUTH_MODE: "application_password" | "jwt";
51
+ WP_JWT_NAMESPACE: string;
52
+ WP_TIMEOUT_MS: number;
53
+ WP_MAX_RETRIES: number;
54
+ WP_VERIFY_SSL: boolean;
55
+ WP_USER_AGENT: string;
56
+ WP_USERNAME?: string | undefined;
57
+ WP_APP_PASSWORD?: string | undefined;
58
+ WP_PASSWORD?: string | undefined;
59
+ WP_JWT_TOKEN?: string | undefined;
60
+ WC_CONSUMER_KEY?: string | undefined;
61
+ WC_CONSUMER_SECRET?: string | undefined;
62
+ }, {
63
+ WP_URL: string;
64
+ WP_AUTH_MODE?: "application_password" | "jwt" | undefined;
65
+ WP_USERNAME?: string | undefined;
66
+ WP_APP_PASSWORD?: string | undefined;
67
+ WP_PASSWORD?: string | undefined;
68
+ WP_JWT_TOKEN?: string | undefined;
69
+ WP_JWT_NAMESPACE?: string | undefined;
70
+ WP_TIMEOUT_MS?: number | undefined;
71
+ WP_MAX_RETRIES?: number | undefined;
72
+ WP_VERIFY_SSL?: "true" | "false" | undefined;
73
+ WP_USER_AGENT?: string | undefined;
74
+ WC_CONSUMER_KEY?: string | undefined;
75
+ WC_CONSUMER_SECRET?: string | undefined;
76
+ }>;
77
+ export type ServerConfig = z.infer<typeof ConfigSchema>;
78
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): ServerConfig;
79
+ export {};
80
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Server configuration loaded from environment variables.
4
+ * Validated with zod so we fail fast at startup.
5
+ */
6
+ const ConfigSchema = z
7
+ .object({
8
+ WP_URL: z
9
+ .string()
10
+ .url()
11
+ .describe("Base URL of the WordPress site, e.g. https://example.com"),
12
+ WP_AUTH_MODE: z
13
+ .enum(["application_password", "jwt"])
14
+ .default("application_password"),
15
+ // ── Application Password mode ───────────────────────────────────────
16
+ WP_USERNAME: z.string().min(1).optional(),
17
+ WP_APP_PASSWORD: z.string().min(1).optional(),
18
+ // ── JWT mode (Tmeister "JWT Authentication for WP REST API") ────────
19
+ // Either supply username + password and we'll fetch a token at startup,
20
+ // or supply WP_JWT_TOKEN directly (e.g. issued by another auth plugin).
21
+ WP_PASSWORD: z.string().min(1).optional(),
22
+ WP_JWT_TOKEN: z.string().min(10).optional(),
23
+ WP_JWT_NAMESPACE: z.string().default("jwt-auth/v1"),
24
+ // ── Common ──────────────────────────────────────────────────────────
25
+ WP_TIMEOUT_MS: z.coerce.number().int().positive().default(30_000),
26
+ WP_MAX_RETRIES: z.coerce.number().int().min(0).max(10).default(3),
27
+ WP_VERIFY_SSL: z
28
+ .union([z.literal("true"), z.literal("false")])
29
+ .default("true")
30
+ .transform((v) => v === "true"),
31
+ WP_USER_AGENT: z.string().default("WordPress-MCP-Server/1.0"),
32
+ // ── WooCommerce (optional) ──────────────────────────────────────────
33
+ WC_CONSUMER_KEY: z.string().optional(),
34
+ WC_CONSUMER_SECRET: z.string().optional(),
35
+ })
36
+ .superRefine((c, ctx) => {
37
+ if (c.WP_AUTH_MODE === "application_password") {
38
+ if (!c.WP_USERNAME)
39
+ ctx.addIssue({ code: "custom", path: ["WP_USERNAME"], message: "Required for application_password mode" });
40
+ if (!c.WP_APP_PASSWORD)
41
+ ctx.addIssue({ code: "custom", path: ["WP_APP_PASSWORD"], message: "Required for application_password mode" });
42
+ }
43
+ if (c.WP_AUTH_MODE === "jwt") {
44
+ const hasToken = !!c.WP_JWT_TOKEN;
45
+ const hasUserPass = !!c.WP_USERNAME && !!c.WP_PASSWORD;
46
+ if (!hasToken && !hasUserPass) {
47
+ ctx.addIssue({
48
+ code: "custom",
49
+ path: ["WP_JWT_TOKEN"],
50
+ message: "JWT mode requires either WP_JWT_TOKEN, or WP_USERNAME + WP_PASSWORD to obtain a token",
51
+ });
52
+ }
53
+ }
54
+ });
55
+ export function loadConfig(env = process.env) {
56
+ const parsed = ConfigSchema.safeParse(env);
57
+ if (!parsed.success) {
58
+ const issues = parsed.error.issues
59
+ .map((i) => ` • ${i.path.join(".")}: ${i.message}`)
60
+ .join("\n");
61
+ throw new Error(`Invalid configuration. Check your environment variables:\n${issues}\n\n` +
62
+ `Required (application_password mode, default):\n` +
63
+ ` WP_URL (e.g. https://example.com)\n` +
64
+ ` WP_USERNAME (your WordPress username)\n` +
65
+ ` WP_APP_PASSWORD (Application Password from WP profile)\n\n` +
66
+ `Required (jwt mode):\n` +
67
+ ` WP_AUTH_MODE=jwt\n` +
68
+ ` WP_URL\n` +
69
+ ` Either:\n` +
70
+ ` WP_USERNAME + WP_PASSWORD (server fetches token)\n` +
71
+ ` or:\n` +
72
+ ` WP_JWT_TOKEN (pre-issued token)\n` +
73
+ ` WP_JWT_NAMESPACE (default jwt-auth/v1; change if your plugin differs)\n\n` +
74
+ `Optional:\n` +
75
+ ` WP_TIMEOUT_MS (default 30000)\n` +
76
+ ` WP_MAX_RETRIES (default 3)\n` +
77
+ ` WP_VERIFY_SSL (default true)\n` +
78
+ ` WP_USER_AGENT (default WordPress-MCP-Server/1.0)\n` +
79
+ ` WC_CONSUMER_KEY (optional, WooCommerce consumer key)\n` +
80
+ ` WC_CONSUMER_SECRET(optional, WooCommerce consumer secret)`);
81
+ }
82
+ return parsed.data;
83
+ }
84
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { WordPressClient, WPError } from "./wordpress-client.js";
6
+ import { postTools } from "./tools/posts.js";
7
+ import { pageTools } from "./tools/pages.js";
8
+ import { mediaTools } from "./tools/media.js";
9
+ import { commentTools } from "./tools/comments.js";
10
+ import { taxonomyTools } from "./tools/taxonomy.js";
11
+ import { userTools } from "./tools/users.js";
12
+ import { siteTools } from "./tools/site.js";
13
+ import { cptTools } from "./tools/cpt.js";
14
+ import { woocommerceTools } from "./tools/woocommerce.js";
15
+ import { seoTools } from "./tools/seo.js";
16
+ import { blockTools } from "./tools/blocks.js";
17
+ import { multisiteTools } from "./tools/multisite.js";
18
+ import { batchTools } from "./tools/batch.js";
19
+ import { jwtTools } from "./tools/jwt.js";
20
+ import { registerResources } from "./resources.js";
21
+ import { registerPrompts } from "./prompts.js";
22
+ const NAME = "wordpress-mcp";
23
+ const VERSION = "1.0.0";
24
+ /**
25
+ * Wrap a tool handler so its return value becomes a proper `CallToolResult`
26
+ * and any thrown error becomes an `isError: true` result the LLM can see.
27
+ *
28
+ * Important for stdio: never write to stdout outside the MCP framing — all
29
+ * logs go to stderr.
30
+ */
31
+ function wrap(tool) {
32
+ return async (input) => {
33
+ try {
34
+ const data = await tool.handler(input);
35
+ const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
36
+ const result = {
37
+ content: [{ type: "text", text }],
38
+ };
39
+ // Attach structured output when it's a JSON-compatible object.
40
+ if (data && typeof data === "object") {
41
+ result.structuredContent = data;
42
+ }
43
+ return result;
44
+ }
45
+ catch (err) {
46
+ const message = formatError(err);
47
+ // Log full error to stderr for debugging without breaking stdio framing.
48
+ console.error(`[${tool.name}]`, err);
49
+ return {
50
+ isError: true,
51
+ content: [{ type: "text", text: message }],
52
+ };
53
+ }
54
+ };
55
+ }
56
+ function formatError(err) {
57
+ if (err instanceof WPError) {
58
+ const parts = [
59
+ `WordPress error: ${err.message}`,
60
+ `code: ${err.code}`,
61
+ `status: ${err.status}`,
62
+ ];
63
+ if (err.details && typeof err.details === "object") {
64
+ parts.push(`details: ${JSON.stringify(err.details)}`);
65
+ }
66
+ return parts.join(" | ");
67
+ }
68
+ if (err instanceof Error)
69
+ return `${err.name}: ${err.message}`;
70
+ return `Unknown error: ${String(err)}`;
71
+ }
72
+ async function main() {
73
+ const config = loadConfig();
74
+ const wp = new WordPressClient({
75
+ baseUrl: config.WP_URL,
76
+ authMode: config.WP_AUTH_MODE,
77
+ username: config.WP_USERNAME,
78
+ appPassword: config.WP_APP_PASSWORD,
79
+ password: config.WP_PASSWORD,
80
+ jwtToken: config.WP_JWT_TOKEN,
81
+ jwtNamespace: config.WP_JWT_NAMESPACE,
82
+ timeoutMs: config.WP_TIMEOUT_MS,
83
+ maxRetries: config.WP_MAX_RETRIES,
84
+ verifySsl: config.WP_VERIFY_SSL,
85
+ userAgent: config.WP_USER_AGENT,
86
+ wcConsumerKey: config.WC_CONSUMER_KEY,
87
+ wcConsumerSecret: config.WC_CONSUMER_SECRET,
88
+ });
89
+ // Bootstrap JWT if needed (no-op for application_password mode).
90
+ await wp.ensureJwt();
91
+ const server = new McpServer({ name: NAME, version: VERSION }, {
92
+ capabilities: {
93
+ tools: {},
94
+ resources: {},
95
+ prompts: {},
96
+ logging: {},
97
+ },
98
+ instructions: "WordPress MCP server. Authenticated via Application Password. " +
99
+ "Discover the site shape with wp_site_info / wp_get_post_types / wp_get_taxonomies " +
100
+ "before performing CRUD on custom content. Destructive tools (delete, force=true) " +
101
+ "are irreversible — confirm with the user first.",
102
+ });
103
+ // Collect all tools from feature modules.
104
+ const tools = [
105
+ ...siteTools(wp),
106
+ ...postTools(wp),
107
+ ...pageTools(wp),
108
+ ...mediaTools(wp),
109
+ ...commentTools(wp),
110
+ ...taxonomyTools(wp),
111
+ ...userTools(wp),
112
+ ...cptTools(wp),
113
+ ...woocommerceTools(wp),
114
+ ...seoTools(wp),
115
+ ...blockTools(wp),
116
+ ...multisiteTools(wp),
117
+ ...batchTools(wp),
118
+ ...(config.WP_AUTH_MODE === "jwt" ? jwtTools(wp) : []),
119
+ ];
120
+ // Sanity check: tool names must be unique.
121
+ const seen = new Set();
122
+ for (const t of tools) {
123
+ if (seen.has(t.name))
124
+ throw new Error(`Duplicate tool name: ${t.name}`);
125
+ seen.add(t.name);
126
+ }
127
+ for (const tool of tools) {
128
+ server.registerTool(tool.name, {
129
+ title: tool.title,
130
+ description: tool.description,
131
+ inputSchema: tool.inputSchema,
132
+ annotations: tool.annotations,
133
+ }, wrap(tool));
134
+ }
135
+ registerResources(server, wp);
136
+ registerPrompts(server);
137
+ const transport = new StdioServerTransport();
138
+ await server.connect(transport);
139
+ console.error(`[${NAME}] connected · ${tools.length} tools · auth: ${config.WP_AUTH_MODE} · target: ${config.WP_URL}`);
140
+ }
141
+ main().catch((err) => {
142
+ console.error(`[${NAME}] fatal:`, err instanceof Error ? err.message : err);
143
+ process.exit(1);
144
+ });
145
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Reusable prompt templates that turn common WordPress workflows into one-click
4
+ * actions for the LLM client.
5
+ */
6
+ export declare function registerPrompts(server: McpServer): void;
7
+ //# sourceMappingURL=prompts.d.ts.map