@wpgaurav/wp-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 (118) hide show
  1. package/README.md +215 -0
  2. package/dist/bin/wp-mcp.d.ts +3 -0
  3. package/dist/bin/wp-mcp.d.ts.map +1 -0
  4. package/dist/bin/wp-mcp.js +44 -0
  5. package/dist/bin/wp-mcp.js.map +1 -0
  6. package/dist/src/client/auth.d.ts +2 -0
  7. package/dist/src/client/auth.d.ts.map +1 -0
  8. package/dist/src/client/auth.js +5 -0
  9. package/dist/src/client/auth.js.map +1 -0
  10. package/dist/src/client/errors.d.ts +14 -0
  11. package/dist/src/client/errors.d.ts.map +1 -0
  12. package/dist/src/client/errors.js +27 -0
  13. package/dist/src/client/errors.js.map +1 -0
  14. package/dist/src/client/wp-client.d.ts +24 -0
  15. package/dist/src/client/wp-client.d.ts.map +1 -0
  16. package/dist/src/client/wp-client.js +101 -0
  17. package/dist/src/client/wp-client.js.map +1 -0
  18. package/dist/src/config.d.ts +11 -0
  19. package/dist/src/config.d.ts.map +1 -0
  20. package/dist/src/config.js +20 -0
  21. package/dist/src/config.js.map +1 -0
  22. package/dist/src/discovery/engine.d.ts +5 -0
  23. package/dist/src/discovery/engine.d.ts.map +1 -0
  24. package/dist/src/discovery/engine.js +116 -0
  25. package/dist/src/discovery/engine.js.map +1 -0
  26. package/dist/src/discovery/naming.d.ts +4 -0
  27. package/dist/src/discovery/naming.d.ts.map +1 -0
  28. package/dist/src/discovery/naming.js +42 -0
  29. package/dist/src/discovery/naming.js.map +1 -0
  30. package/dist/src/discovery/schema-mapper.d.ts +18 -0
  31. package/dist/src/discovery/schema-mapper.d.ts.map +1 -0
  32. package/dist/src/discovery/schema-mapper.js +103 -0
  33. package/dist/src/discovery/schema-mapper.js.map +1 -0
  34. package/dist/src/discovery/tool-generator.d.ts +13 -0
  35. package/dist/src/discovery/tool-generator.d.ts.map +1 -0
  36. package/dist/src/discovery/tool-generator.js +58 -0
  37. package/dist/src/discovery/tool-generator.js.map +1 -0
  38. package/dist/src/index.d.ts +6 -0
  39. package/dist/src/index.d.ts.map +1 -0
  40. package/dist/src/index.js +5 -0
  41. package/dist/src/index.js.map +1 -0
  42. package/dist/src/server.d.ts +11 -0
  43. package/dist/src/server.d.ts.map +1 -0
  44. package/dist/src/server.js +21 -0
  45. package/dist/src/server.js.map +1 -0
  46. package/dist/src/tools/advanced/menus.d.ts +5 -0
  47. package/dist/src/tools/advanced/menus.d.ts.map +1 -0
  48. package/dist/src/tools/advanced/menus.js +33 -0
  49. package/dist/src/tools/advanced/menus.js.map +1 -0
  50. package/dist/src/tools/advanced/search.d.ts +5 -0
  51. package/dist/src/tools/advanced/search.d.ts.map +1 -0
  52. package/dist/src/tools/advanced/search.js +24 -0
  53. package/dist/src/tools/advanced/search.js.map +1 -0
  54. package/dist/src/tools/advanced/site-health.d.ts +5 -0
  55. package/dist/src/tools/advanced/site-health.d.ts.map +1 -0
  56. package/dist/src/tools/advanced/site-health.js +16 -0
  57. package/dist/src/tools/advanced/site-health.js.map +1 -0
  58. package/dist/src/tools/content/categories.d.ts +5 -0
  59. package/dist/src/tools/content/categories.d.ts.map +1 -0
  60. package/dist/src/tools/content/categories.js +43 -0
  61. package/dist/src/tools/content/categories.js.map +1 -0
  62. package/dist/src/tools/content/comments.d.ts +5 -0
  63. package/dist/src/tools/content/comments.d.ts.map +1 -0
  64. package/dist/src/tools/content/comments.js +45 -0
  65. package/dist/src/tools/content/comments.js.map +1 -0
  66. package/dist/src/tools/content/media.d.ts +5 -0
  67. package/dist/src/tools/content/media.d.ts.map +1 -0
  68. package/dist/src/tools/content/media.js +97 -0
  69. package/dist/src/tools/content/media.js.map +1 -0
  70. package/dist/src/tools/content/pages.d.ts +5 -0
  71. package/dist/src/tools/content/pages.d.ts.map +1 -0
  72. package/dist/src/tools/content/pages.js +98 -0
  73. package/dist/src/tools/content/pages.js.map +1 -0
  74. package/dist/src/tools/content/posts.d.ts +5 -0
  75. package/dist/src/tools/content/posts.d.ts.map +1 -0
  76. package/dist/src/tools/content/posts.js +107 -0
  77. package/dist/src/tools/content/posts.js.map +1 -0
  78. package/dist/src/tools/content/tags.d.ts +5 -0
  79. package/dist/src/tools/content/tags.d.ts.map +1 -0
  80. package/dist/src/tools/content/tags.js +41 -0
  81. package/dist/src/tools/content/tags.js.map +1 -0
  82. package/dist/src/tools/index.d.ts +5 -0
  83. package/dist/src/tools/index.d.ts.map +1 -0
  84. package/dist/src/tools/index.js +29 -0
  85. package/dist/src/tools/index.js.map +1 -0
  86. package/dist/src/tools/management/plugins.d.ts +5 -0
  87. package/dist/src/tools/management/plugins.d.ts.map +1 -0
  88. package/dist/src/tools/management/plugins.js +67 -0
  89. package/dist/src/tools/management/plugins.js.map +1 -0
  90. package/dist/src/tools/management/settings.d.ts +5 -0
  91. package/dist/src/tools/management/settings.d.ts.map +1 -0
  92. package/dist/src/tools/management/settings.js +37 -0
  93. package/dist/src/tools/management/settings.js.map +1 -0
  94. package/dist/src/tools/management/themes.d.ts +5 -0
  95. package/dist/src/tools/management/themes.d.ts.map +1 -0
  96. package/dist/src/tools/management/themes.js +34 -0
  97. package/dist/src/tools/management/themes.js.map +1 -0
  98. package/dist/src/tools/management/users.d.ts +5 -0
  99. package/dist/src/tools/management/users.d.ts.map +1 -0
  100. package/dist/src/tools/management/users.js +69 -0
  101. package/dist/src/tools/management/users.js.map +1 -0
  102. package/dist/src/tools/registry.d.ts +7 -0
  103. package/dist/src/tools/registry.d.ts.map +1 -0
  104. package/dist/src/tools/registry.js +13 -0
  105. package/dist/src/tools/registry.js.map +1 -0
  106. package/dist/src/tools/shared.d.ts +20 -0
  107. package/dist/src/tools/shared.d.ts.map +1 -0
  108. package/dist/src/tools/shared.js +15 -0
  109. package/dist/src/tools/shared.js.map +1 -0
  110. package/dist/src/transports/http.d.ts +3 -0
  111. package/dist/src/transports/http.d.ts.map +1 -0
  112. package/dist/src/transports/http.js +53 -0
  113. package/dist/src/transports/http.js.map +1 -0
  114. package/dist/src/transports/stdio.d.ts +3 -0
  115. package/dist/src/transports/stdio.d.ts.map +1 -0
  116. package/dist/src/transports/stdio.js +5 -0
  117. package/dist/src/transports/stdio.js.map +1 -0
  118. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # wp-mcp
2
+
3
+ A TypeScript MCP server that exposes any WordPress site's REST API as MCP tools. Hand-crafted tools for core endpoints plus **auto-discovery of custom plugin endpoints** (WooCommerce, ACF, Yoast, any plugin).
4
+
5
+ ## Features
6
+
7
+ - **37 core tools** for posts, pages, media, categories, tags, comments, plugins, themes, users, settings, menus, search, and site health
8
+ - **Auto-discovery engine** that scans `/wp-json/` and generates tools for any plugin's REST API endpoints
9
+ - **Both transports**: stdio (default for Claude Code/Desktop) and HTTP (StreamableHTTP)
10
+ - **Re-scan on demand** via `wp_discover_routes` after installing new plugins
11
+ - Single site, Basic Auth via Application Passwords
12
+
13
+ ## Prerequisites
14
+
15
+ - Node.js 18+
16
+ - A WordPress site with REST API enabled
17
+ - An [Application Password](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/) for authentication
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ # Run directly with npx
23
+ npx wp-mcp
24
+
25
+ # Or install globally
26
+ npm install -g wp-mcp
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ Set environment variables:
32
+
33
+ | Variable | Required | Default | Description |
34
+ |----------|----------|---------|-------------|
35
+ | `WP_URL` | Yes | — | WordPress site URL (no trailing slash) |
36
+ | `WP_USERNAME` | Yes | — | WordPress username |
37
+ | `WP_APP_PASSWORD` | Yes | — | Application Password |
38
+ | `WP_MCP_TRANSPORT` | No | `stdio` | `stdio` or `http` |
39
+ | `WP_MCP_PORT` | No | `3000` | HTTP transport port |
40
+ | `WP_MCP_HOST` | No | `127.0.0.1` | HTTP transport bind address |
41
+ | `WP_MCP_DISCOVER` | No | `true` | Auto-discover custom endpoints |
42
+
43
+ ### Claude Desktop
44
+
45
+ Add to your `claude_desktop_config.json`:
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "wordpress": {
51
+ "command": "npx",
52
+ "args": ["wp-mcp"],
53
+ "env": {
54
+ "WP_URL": "https://example.com",
55
+ "WP_USERNAME": "admin",
56
+ "WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ ### Claude Code
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "wordpress": {
69
+ "command": "npx",
70
+ "args": ["wp-mcp"],
71
+ "env": {
72
+ "WP_URL": "https://example.com",
73
+ "WP_USERNAME": "admin",
74
+ "WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### HTTP Transport
82
+
83
+ ```bash
84
+ WP_URL=https://example.com \
85
+ WP_USERNAME=admin \
86
+ WP_APP_PASSWORD="xxxx xxxx xxxx xxxx" \
87
+ WP_MCP_TRANSPORT=http \
88
+ npx wp-mcp
89
+ ```
90
+
91
+ The server listens on `http://127.0.0.1:3000/mcp` with StreamableHTTP.
92
+
93
+ ## Core Tools
94
+
95
+ ### Content (Tier 1)
96
+
97
+ | Tool | Description |
98
+ |------|-------------|
99
+ | `wp_posts_list` | List posts with filtering and pagination |
100
+ | `wp_posts_get` | Get a single post by ID |
101
+ | `wp_posts_create` | Create a new post |
102
+ | `wp_posts_update` | Update an existing post |
103
+ | `wp_posts_delete` | Delete a post |
104
+ | `wp_pages_list` | List pages |
105
+ | `wp_pages_get` | Get a single page by ID |
106
+ | `wp_pages_create` | Create a new page |
107
+ | `wp_pages_update` | Update an existing page |
108
+ | `wp_pages_delete` | Delete a page |
109
+ | `wp_media_list` | List media library items |
110
+ | `wp_media_get` | Get a media item by ID |
111
+ | `wp_media_upload` | Upload media from a URL |
112
+ | `wp_media_delete` | Delete a media item |
113
+ | `wp_categories_list` | List categories |
114
+ | `wp_categories_create` | Create a category |
115
+ | `wp_tags_list` | List tags |
116
+ | `wp_tags_create` | Create a tag |
117
+ | `wp_comments_list` | List comments |
118
+ | `wp_comments_create` | Create a comment |
119
+
120
+ ### Management (Tier 2)
121
+
122
+ | Tool | Description |
123
+ |------|-------------|
124
+ | `wp_plugins_list` | List installed plugins |
125
+ | `wp_plugins_get` | Get plugin details |
126
+ | `wp_plugins_activate` | Activate a plugin |
127
+ | `wp_plugins_deactivate` | Deactivate a plugin |
128
+ | `wp_themes_list` | List installed themes |
129
+ | `wp_themes_activate` | Activate a theme |
130
+ | `wp_users_list` | List users |
131
+ | `wp_users_get` | Get a user by ID |
132
+ | `wp_users_create` | Create a new user |
133
+ | `wp_users_me` | Get the authenticated user |
134
+ | `wp_settings_get` | Get site settings |
135
+ | `wp_settings_update` | Update site settings |
136
+
137
+ ### Advanced (Tier 3)
138
+
139
+ | Tool | Description |
140
+ |------|-------------|
141
+ | `wp_menus_list` | List navigation menus |
142
+ | `wp_menu_items_list` | List menu items |
143
+ | `wp_search` | Search across all content |
144
+ | `wp_site_health` | Get site health status |
145
+
146
+ ### Meta
147
+
148
+ | Tool | Description |
149
+ |------|-------------|
150
+ | `wp_discover_routes` | Re-scan the site for new REST API endpoints |
151
+
152
+ ## Auto-Discovery
153
+
154
+ On startup (when `WP_MCP_DISCOVER=true`), the server fetches `/wp-json/` and automatically generates MCP tools for every custom plugin endpoint it finds. Core `wp/v2` routes are skipped since they're already covered by hand-crafted tools.
155
+
156
+ ### Example: WooCommerce
157
+
158
+ If WooCommerce is installed, these tools appear automatically:
159
+
160
+ | Tool | Endpoint |
161
+ |------|----------|
162
+ | `wc_products_list` | GET `/wc/v3/products` |
163
+ | `wc_products_get` | GET `/wc/v3/products/{id}` |
164
+ | `wc_products_create` | POST `/wc/v3/products` |
165
+ | `wc_orders_list` | GET `/wc/v3/orders` |
166
+ | `wc_orders_notes_list` | GET `/wc/v3/orders/{id}/notes` |
167
+
168
+ ### Naming Convention
169
+
170
+ ```
171
+ {namespace_prefix}_{resource}_{action}
172
+
173
+ GET /wc/v3/products -> wc_products_list
174
+ GET /wc/v3/products/{id} -> wc_products_get
175
+ POST /wc/v3/products -> wc_products_create
176
+ GET /acf/v1/posts/{id} -> acf_posts_get
177
+ GET /yoast/v1/indexables -> yoast_indexables_list
178
+ ```
179
+
180
+ ### Re-scanning After Plugin Install
181
+
182
+ Use the `wp_discover_routes` tool to re-scan the site after installing or activating a new plugin:
183
+
184
+ ```
185
+ wp_discover_routes({ namespace_filter: "wc/v3" })
186
+ ```
187
+
188
+ ## Development
189
+
190
+ ```bash
191
+ git clone https://github.com/wpgaurav/wp-mcp.git
192
+ cd wp-mcp
193
+ npm install
194
+ npm run typecheck
195
+ npm test
196
+ npm run build
197
+ ```
198
+
199
+ ## Library Usage
200
+
201
+ ```typescript
202
+ import { createWpMcpServer, loadConfig } from "wp-mcp";
203
+
204
+ const config = loadConfig({
205
+ wpUrl: "https://example.com",
206
+ wpUsername: "admin",
207
+ wpAppPassword: "xxxx xxxx xxxx xxxx",
208
+ });
209
+
210
+ const { server, client, registry } = await createWpMcpServer(config);
211
+ ```
212
+
213
+ ## License
214
+
215
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=wp-mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-mcp.d.ts","sourceRoot":"","sources":["../../bin/wp-mcp.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig } from "../src/config.js";
3
+ import { createWpMcpServer } from "../src/server.js";
4
+ import { createStdioTransport } from "../src/transports/stdio.js";
5
+ import { startHttpTransport } from "../src/transports/http.js";
6
+ async function main() {
7
+ const args = process.argv.slice(2);
8
+ const overrides = {};
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ if (arg === "--transport" && args[i + 1]) {
12
+ overrides["transport"] = args[++i];
13
+ }
14
+ else if (arg === "--port" && args[i + 1]) {
15
+ overrides["port"] = args[++i];
16
+ }
17
+ else if (arg === "--host" && args[i + 1]) {
18
+ overrides["host"] = args[++i];
19
+ }
20
+ else if (arg === "--no-discover") {
21
+ overrides["discover"] = "false";
22
+ }
23
+ }
24
+ const config = loadConfig({
25
+ transport: overrides["transport"],
26
+ port: overrides["port"] ? parseInt(overrides["port"], 10) : undefined,
27
+ host: overrides["host"],
28
+ discover: overrides["discover"] === "false" ? false : undefined,
29
+ });
30
+ const { server } = await createWpMcpServer(config);
31
+ if (config.transport === "http") {
32
+ await startHttpTransport(server, config.host, config.port);
33
+ }
34
+ else {
35
+ const transport = createStdioTransport();
36
+ await server.connect(transport);
37
+ console.error("wp-mcp server running on stdio");
38
+ }
39
+ }
40
+ main().catch((err) => {
41
+ console.error("Fatal:", err);
42
+ process.exit(1);
43
+ });
44
+ //# sourceMappingURL=wp-mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-mcp.js","sourceRoot":"","sources":["../../bin/wp-mcp.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,SAAS,GAA2B,EAAE,CAAC;IAE7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,aAAa,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACzC,SAAS,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAE,CAAC;QACtC,CAAC;aAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC3C,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAE,CAAC;QACjC,CAAC;aAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC3C,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAE,CAAC;QACjC,CAAC;aAAM,IAAI,GAAG,KAAK,eAAe,EAAE,CAAC;YACnC,SAAS,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC;QAClC,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC;QACxB,SAAS,EAAE,SAAS,CAAC,WAAW,CAAiC;QACjE,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS;QACrE,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC;QACvB,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;KAChE,CAAC,CAAC;IAEH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAEnD,IAAI,MAAM,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAChC,MAAM,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAC;QACzC,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function basicAuthHeader(username: string, password: string): string;
2
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/client/auth.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1E"}
@@ -0,0 +1,5 @@
1
+ export function basicAuthHeader(username, password) {
2
+ const encoded = Buffer.from(`${username}:${password}`).toString("base64");
3
+ return `Basic ${encoded}`;
4
+ }
5
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/client/auth.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,QAAgB;IAChE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,IAAI,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1E,OAAO,SAAS,OAAO,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,14 @@
1
+ export declare class WpApiError extends Error {
2
+ readonly statusCode: number;
3
+ readonly wpCode: string;
4
+ constructor(statusCode: number, wpCode: string, message: string);
5
+ toMcpError(): {
6
+ content: Array<{
7
+ type: "text";
8
+ text: string;
9
+ }>;
10
+ isError: true;
11
+ };
12
+ }
13
+ export declare function parseWpError(response: Response): Promise<WpApiError>;
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/client/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAW,SAAQ,KAAK;aAEjB,UAAU,EAAE,MAAM;aAClB,MAAM,EAAE,MAAM;gBADd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;IAMjB,UAAU,IAAI;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,OAAO,EAAE,IAAI,CAAA;KAAE;CAMhF;AAQD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAa1E"}
@@ -0,0 +1,27 @@
1
+ export class WpApiError extends Error {
2
+ statusCode;
3
+ wpCode;
4
+ constructor(statusCode, wpCode, message) {
5
+ super(message);
6
+ this.statusCode = statusCode;
7
+ this.wpCode = wpCode;
8
+ this.name = "WpApiError";
9
+ }
10
+ toMcpError() {
11
+ return {
12
+ content: [{ type: "text", text: `WordPress API error (${this.statusCode}): [${this.wpCode}] ${this.message}` }],
13
+ isError: true,
14
+ };
15
+ }
16
+ }
17
+ export async function parseWpError(response) {
18
+ let body = {};
19
+ try {
20
+ body = (await response.json());
21
+ }
22
+ catch {
23
+ // non-JSON error response
24
+ }
25
+ return new WpApiError(response.status, body.code ?? "unknown_error", body.message ?? response.statusText);
26
+ }
27
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/client/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,UAAW,SAAQ,KAAK;IAEjB;IACA;IAFlB,YACkB,UAAkB,EAClB,MAAc,EAC9B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,eAAU,GAAV,UAAU,CAAQ;QAClB,WAAM,GAAN,MAAM,CAAQ;QAI9B,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;IAED,UAAU;QACR,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,IAAI,CAAC,UAAU,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YAC/G,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;CACF;AAQD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAkB;IACnD,IAAI,IAAI,GAAgB,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAgB,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,0BAA0B;IAC5B,CAAC;IAED,OAAO,IAAI,UAAU,CACnB,QAAQ,CAAC,MAAM,EACf,IAAI,CAAC,IAAI,IAAI,eAAe,EAC5B,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,UAAU,CACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { WpApiError } from "./errors.js";
2
+ import type { WpMcpConfig } from "../config.js";
3
+ export interface WpRequestOptions {
4
+ method?: string;
5
+ params?: Record<string, unknown>;
6
+ body?: Record<string, unknown> | FormData;
7
+ rawBody?: Buffer;
8
+ headers?: Record<string, string>;
9
+ }
10
+ export interface WpPaginatedResponse<T> {
11
+ data: T;
12
+ total: number;
13
+ totalPages: number;
14
+ }
15
+ export declare class WpClient {
16
+ private readonly baseUrl;
17
+ private readonly authHeader;
18
+ constructor(config: Pick<WpMcpConfig, "wpUrl" | "wpUsername" | "wpAppPassword">);
19
+ request<T = unknown>(endpoint: string, options?: WpRequestOptions): Promise<T>;
20
+ requestPaginated<T = unknown>(endpoint: string, options?: WpRequestOptions): Promise<WpPaginatedResponse<T>>;
21
+ fetchRawJson<T = unknown>(path: string): Promise<T>;
22
+ get wpApiError(): typeof WpApiError;
23
+ }
24
+ //# sourceMappingURL=wp-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-client.d.ts","sourceRoot":"","sources":["../../../src/client/wp-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,YAAY,GAAG,eAAe,CAAC;IAKzE,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,CAAC,CAAC;IAkDlF,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IA0C5B,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAazD,IAAI,UAAU,IAAI,OAAO,UAAU,CAElC;CACF"}
@@ -0,0 +1,101 @@
1
+ import { basicAuthHeader } from "./auth.js";
2
+ import { parseWpError, WpApiError } from "./errors.js";
3
+ export class WpClient {
4
+ baseUrl;
5
+ authHeader;
6
+ constructor(config) {
7
+ this.baseUrl = `${config.wpUrl.replace(/\/+$/, "")}/wp-json`;
8
+ this.authHeader = basicAuthHeader(config.wpUsername, config.wpAppPassword);
9
+ }
10
+ async request(endpoint, options = {}) {
11
+ const { method = "GET", params, body, rawBody, headers = {} } = options;
12
+ let url = `${this.baseUrl}${endpoint}`;
13
+ if (params) {
14
+ const searchParams = new URLSearchParams();
15
+ for (const [key, value] of Object.entries(params)) {
16
+ if (value !== undefined && value !== null) {
17
+ searchParams.set(key, String(value));
18
+ }
19
+ }
20
+ const qs = searchParams.toString();
21
+ if (qs)
22
+ url += `?${qs}`;
23
+ }
24
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
25
+ const fetchHeaders = {
26
+ Authorization: this.authHeader,
27
+ ...headers,
28
+ };
29
+ let fetchBody;
30
+ if (rawBody) {
31
+ fetchBody = rawBody;
32
+ }
33
+ else if (isFormData) {
34
+ fetchBody = body;
35
+ }
36
+ else if (body) {
37
+ fetchHeaders["Content-Type"] ??= "application/json";
38
+ fetchBody = JSON.stringify(body);
39
+ }
40
+ const response = await fetch(url, {
41
+ method,
42
+ headers: fetchHeaders,
43
+ body: fetchBody,
44
+ });
45
+ if (!response.ok) {
46
+ throw await parseWpError(response);
47
+ }
48
+ if (response.status === 204) {
49
+ return {};
50
+ }
51
+ return (await response.json());
52
+ }
53
+ async requestPaginated(endpoint, options = {}) {
54
+ const { method = "GET", params, body, headers = {} } = options;
55
+ let url = `${this.baseUrl}${endpoint}`;
56
+ if (params) {
57
+ const searchParams = new URLSearchParams();
58
+ for (const [key, value] of Object.entries(params)) {
59
+ if (value !== undefined && value !== null) {
60
+ searchParams.set(key, String(value));
61
+ }
62
+ }
63
+ const qs = searchParams.toString();
64
+ if (qs)
65
+ url += `?${qs}`;
66
+ }
67
+ const fetchHeaders = {
68
+ Authorization: this.authHeader,
69
+ ...headers,
70
+ };
71
+ if (body) {
72
+ fetchHeaders["Content-Type"] = "application/json";
73
+ }
74
+ const response = await fetch(url, {
75
+ method,
76
+ headers: fetchHeaders,
77
+ body: body ? JSON.stringify(body) : undefined,
78
+ });
79
+ if (!response.ok) {
80
+ throw await parseWpError(response);
81
+ }
82
+ const data = (await response.json());
83
+ const total = parseInt(response.headers.get("X-WP-Total") ?? "0", 10);
84
+ const totalPages = parseInt(response.headers.get("X-WP-TotalPages") ?? "0", 10);
85
+ return { data, total, totalPages };
86
+ }
87
+ async fetchRawJson(path) {
88
+ const url = `${this.baseUrl}${path}`;
89
+ const response = await fetch(url, {
90
+ headers: { Authorization: this.authHeader },
91
+ });
92
+ if (!response.ok) {
93
+ throw await parseWpError(response);
94
+ }
95
+ return (await response.json());
96
+ }
97
+ get wpApiError() {
98
+ return WpApiError;
99
+ }
100
+ }
101
+ //# sourceMappingURL=wp-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-client.js","sourceRoot":"","sources":["../../../src/client/wp-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface WpMcpConfig {
2
+ wpUrl: string;
3
+ wpUsername: string;
4
+ wpAppPassword: string;
5
+ transport: "stdio" | "http";
6
+ port: number;
7
+ host: string;
8
+ discover: boolean;
9
+ }
10
+ export declare function loadConfig(overrides?: Partial<WpMcpConfig>): WpMcpConfig;
11
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,UAAU,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAmB5E"}
@@ -0,0 +1,20 @@
1
+ export function loadConfig(overrides = {}) {
2
+ const config = {
3
+ wpUrl: overrides.wpUrl ?? process.env["WP_URL"] ?? "",
4
+ wpUsername: overrides.wpUsername ?? process.env["WP_USERNAME"] ?? "",
5
+ wpAppPassword: overrides.wpAppPassword ?? process.env["WP_APP_PASSWORD"] ?? "",
6
+ transport: (overrides.transport ?? process.env["WP_MCP_TRANSPORT"] ?? "stdio"),
7
+ port: overrides.port ?? parseInt(process.env["WP_MCP_PORT"] ?? "3000", 10),
8
+ host: overrides.host ?? process.env["WP_MCP_HOST"] ?? "127.0.0.1",
9
+ discover: overrides.discover ?? (process.env["WP_MCP_DISCOVER"] !== "false"),
10
+ };
11
+ config.wpUrl = config.wpUrl.replace(/\/+$/, "");
12
+ if (!config.wpUrl)
13
+ throw new Error("WP_URL is required");
14
+ if (!config.wpUsername)
15
+ throw new Error("WP_USERNAME is required");
16
+ if (!config.wpAppPassword)
17
+ throw new Error("WP_APP_PASSWORD is required");
18
+ return config;
19
+ }
20
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,UAAU,CAAC,YAAkC,EAAE;IAC7D,MAAM,MAAM,GAAgB;QAC1B,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;QACrD,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE;QACpE,aAAa,EAAE,SAAS,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,EAAE;QAC9E,SAAS,EACP,CAAC,SAAS,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAA6B;QACjG,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,MAAM,EAAE,EAAE,CAAC;QAC1E,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,WAAW;QACjE,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,OAAO,CAAC;KAC7E,CAAC;IAEF,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEhD,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACzD,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACnE,IAAI,CAAC,MAAM,CAAC,aAAa;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAE1E,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { WpClient } from "../client/wp-client.js";
3
+ import type { ToolRegistry } from "../tools/registry.js";
4
+ export declare function runDiscovery(server: McpServer, client: WpClient, registry: ToolRegistry): Promise<number>;
5
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/discovery/engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAoBzD,wBAAsB,YAAY,CAChC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,MAAM,CAAC,CA4CjB"}
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+ import { handleWpError, jsonResult } from "../tools/shared.js";
3
+ import { shouldSkipNamespace } from "./naming.js";
4
+ import { registerDiscoveredTool } from "./tool-generator.js";
5
+ export async function runDiscovery(server, client, registry) {
6
+ const discovery = await client.fetchRawJson("/");
7
+ const routes = discovery.routes ?? {};
8
+ let count = 0;
9
+ for (const [routePath, routeInfo] of Object.entries(routes)) {
10
+ const namespace = routeInfo.namespace ?? inferNamespace(routePath);
11
+ if (!namespace || shouldSkipNamespace(namespace))
12
+ continue;
13
+ const endpoints = routeInfo.endpoints ?? [];
14
+ if (endpoints.length === 0 && routeInfo.methods) {
15
+ for (const method of routeInfo.methods) {
16
+ const discovered = {
17
+ namespace,
18
+ path: stripNamespaceFromPath(routePath, namespace),
19
+ method: method.toUpperCase(),
20
+ args: {},
21
+ };
22
+ if (registerDiscoveredTool(server, client, registry, discovered)) {
23
+ count++;
24
+ }
25
+ }
26
+ continue;
27
+ }
28
+ for (const endpoint of endpoints) {
29
+ for (const method of endpoint.methods) {
30
+ const discovered = {
31
+ namespace,
32
+ path: stripNamespaceFromPath(routePath, namespace),
33
+ method: method.toUpperCase(),
34
+ args: endpoint.args ?? {},
35
+ };
36
+ if (registerDiscoveredTool(server, client, registry, discovered)) {
37
+ count++;
38
+ }
39
+ }
40
+ }
41
+ }
42
+ registerDiscoverRoutesTool(server, client, registry);
43
+ console.error(`Auto-discovery: registered ${count} tools from custom endpoints`);
44
+ return count;
45
+ }
46
+ function registerDiscoverRoutesTool(server, client, registry) {
47
+ if (registry.has("wp_discover_routes"))
48
+ return;
49
+ server.registerTool("wp_discover_routes", {
50
+ description: "Re-scan the WordPress site for REST API endpoints. Useful after installing new plugins.",
51
+ inputSchema: {
52
+ namespace_filter: z.string().optional().describe("Only discover routes in this namespace (e.g. 'wc/v3')"),
53
+ },
54
+ }, async ({ namespace_filter }) => {
55
+ try {
56
+ const discovery = await client.fetchRawJson("/");
57
+ const namespaces = discovery.namespaces ?? [];
58
+ const routes = discovery.routes ?? {};
59
+ if (namespace_filter) {
60
+ let count = 0;
61
+ for (const [routePath, routeInfo] of Object.entries(routes)) {
62
+ const ns = routeInfo.namespace ?? inferNamespace(routePath);
63
+ if (ns !== namespace_filter)
64
+ continue;
65
+ const endpoints = routeInfo.endpoints ?? [];
66
+ for (const endpoint of endpoints) {
67
+ for (const method of endpoint.methods) {
68
+ const discovered = {
69
+ namespace: ns,
70
+ path: stripNamespaceFromPath(routePath, ns),
71
+ method: method.toUpperCase(),
72
+ args: endpoint.args ?? {},
73
+ };
74
+ if (registerDiscoveredTool(server, client, registry, discovered)) {
75
+ count++;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ return jsonResult({ message: `Discovered ${count} new tools in namespace ${namespace_filter}`, namespaces });
81
+ }
82
+ return jsonResult({
83
+ namespaces,
84
+ route_count: Object.keys(routes).length,
85
+ registered_tools: registry.all(),
86
+ });
87
+ }
88
+ catch (err) {
89
+ return handleWpError(err);
90
+ }
91
+ });
92
+ registry.register("wp_discover_routes");
93
+ }
94
+ function inferNamespace(routePath) {
95
+ const clean = routePath.replace(/^\//, "");
96
+ const parts = clean.split("/");
97
+ if (parts.length >= 2 && /^v\d+/.test(parts[1])) {
98
+ return `${parts[0]}/${parts[1]}`;
99
+ }
100
+ if (parts.length >= 1) {
101
+ return parts[0];
102
+ }
103
+ return undefined;
104
+ }
105
+ function stripNamespaceFromPath(routePath, namespace) {
106
+ const prefix = `/${namespace}/`;
107
+ if (routePath.startsWith(prefix)) {
108
+ return routePath.slice(prefix.length);
109
+ }
110
+ const prefixNoSlash = `/${namespace}`;
111
+ if (routePath === prefixNoSlash) {
112
+ return "";
113
+ }
114
+ return routePath.replace(/^\//, "");
115
+ }
116
+ //# sourceMappingURL=engine.js.map