@wickedevolutions/abilities-mcp 1.3.1
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.
- package/CHANGELOG.md +81 -0
- package/LICENSE +12 -0
- package/README.md +321 -0
- package/abilities-mcp.js +169 -0
- package/lib/bridge-tools.js +67 -0
- package/lib/config.js +210 -0
- package/lib/connection-pool.js +272 -0
- package/lib/logger.js +43 -0
- package/lib/register.js +65 -0
- package/lib/router.js +436 -0
- package/lib/sanitizer.js +111 -0
- package/lib/tool-catalog.js +157 -0
- package/lib/tool-injector.js +51 -0
- package/lib/transports/http-transport.js +558 -0
- package/lib/transports/ssh-transport.js +595 -0
- package/package.json +23 -0
- package/wp-sites.example.json +49 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Abilities MCP are documented here.
|
|
4
|
+
|
|
5
|
+
## [1.3.1] - 2026-03-19
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Convert execute-ability error payloads to `isError` format — fold `input_schema` into error text for AI self-correction (#2)
|
|
9
|
+
- Convert JSON-RPC error objects to `isError` format for client visibility (#4)
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Remove protocol version rewriting from both transports — Adapter now handles MCP version negotiation natively
|
|
13
|
+
|
|
14
|
+
## [1.3.0] - 2026-03-11
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Schema validation warnings in sanitizer — logs when WordPress responses contain non-standard fields
|
|
18
|
+
- Architecture documentation (`docs/architecture.md`)
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Renamed from WP Abilities MCP to **Abilities MCP**
|
|
22
|
+
- Package name: `@wicked-evolutions/abilities-mcp`
|
|
23
|
+
- Entry point: `abilities-mcp.js`
|
|
24
|
+
- GPL-2.0 compliance headers on all source files
|
|
25
|
+
- Repo cleanup: removed ROADMAP (GitHub project board is the authority), fixed stale references
|
|
26
|
+
|
|
27
|
+
## [1.2.0] - 2026-03-09
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- JSON-RPC batch coalescing in `HttpTransport._drainQueue()` — 10ms window accumulates concurrent messages and dispatches as a single JSON-RPC batch POST. Reduces N round-trips to 1 for concurrent multi-agent workloads. Sequential single-agent sessions are unaffected.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- Fix integer overflow in synthetic handshake IDs — replace `Date.now()` with incrementing counter; 13-digit ms timestamps exceed 32-bit int max, causing `TypeError` in PHP `strict_types=1` environments
|
|
34
|
+
- Fix missing cookie support in HTTP transport — add per-host cookie jar; parse `Set-Cookie` response headers and send `Cookie` on subsequent requests so PHP native sessions aren't dropped between requests
|
|
35
|
+
- Fix incomplete session recovery — extend re-handshake trigger to include HTTP 401/403 when an active session exists; some WordPress configs return these for stale session tokens rather than 404/410
|
|
36
|
+
|
|
37
|
+
## [1.1.0] - 2026-03-08
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- Permission metadata passthrough — sanitizer preserves `permission` and `enabled` annotation fields from MCP Adapter
|
|
41
|
+
- `[DISABLED]` label injection — tools with `enabled: false` get description suffix showing required permission level
|
|
42
|
+
- Annotation whitelisting — keeps MCP-compliant fields (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title`, `permission`, `enabled`), strips non-standard fields
|
|
43
|
+
- Automated test suite using `node:test` (18 sanitizer tests)
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- Fix multisite blog_id not switching for subsite queries — build per-subsite endpoint URLs so WordPress boots into the correct blog context natively (#3)
|
|
47
|
+
- Fix tool registration failure in Claude Code — strip non-standard annotation fields while preserving MCP-compliant ones (#5)
|
|
48
|
+
- Fix HTTP multisite session loss — reuse existing transport for same-endpoint subsites instead of creating competing connections (#1, PR #2)
|
|
49
|
+
|
|
50
|
+
## [1.0.0] - 2026-02-26
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- Unified multi-site MCP bridge
|
|
54
|
+
- HTTP transport with session management (Mcp-Session-Id/Token headers)
|
|
55
|
+
- SSH transport with auto-reconnect, exponential backoff, and healthcheck pings
|
|
56
|
+
- Multi-site routing with `site` parameter injection on all tools
|
|
57
|
+
- Lazy connections — non-default sites connect on first tool call
|
|
58
|
+
- MCP handshake replay for mid-session connections
|
|
59
|
+
- WordPress multisite support via dot notation
|
|
60
|
+
- `wp-sites.json` configuration with config file search order
|
|
61
|
+
- `passwordCommand` and `passwordEnv` for secure credential storage
|
|
62
|
+
- Claude Desktop registration (`--register` flag)
|
|
63
|
+
- Debug logging (`--debug` flag)
|
|
64
|
+
- Zero dependencies — Node.js built-in modules only
|
|
65
|
+
- `wp_bridge_health` — check connectivity status of all configured sites
|
|
66
|
+
- `wp_browse_tools` / `wp_load_tools` — category-based lazy tool loading
|
|
67
|
+
- GitHub infrastructure: issue templates, PR template, Actions workflow
|
|
68
|
+
|
|
69
|
+
### Security
|
|
70
|
+
- Security audit — 9 findings, all fixed
|
|
71
|
+
- Session token forwarding (Mcp-Session-Token header)
|
|
72
|
+
- Schema sanitization (strips non-standard fields from tool definitions)
|
|
73
|
+
|
|
74
|
+
### Known Issues
|
|
75
|
+
- Session lock contention with concurrent bridge instances (#4) — server-side MySQL GET_LOCK fix deployed, bridge-side auto-recovery adds ~200ms latency
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
GPL-2.0-or-later
|
package/LICENSE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 2, June 1991
|
|
3
|
+
|
|
4
|
+
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
|
5
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
6
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
7
|
+
of this license document, but changing it is not allowed.
|
|
8
|
+
|
|
9
|
+
For the full license text, see: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
|
|
10
|
+
|
|
11
|
+
This software is distributed under the terms of the GNU General Public License
|
|
12
|
+
version 2 or later (GPL-2.0-or-later).
|
package/README.md
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Abilities MCP
|
|
2
|
+
|
|
3
|
+
> One MCP to Rule Your WordPress World.
|
|
4
|
+
|
|
5
|
+
Open-source MCP bridge that connects any AI client to your WordPress sites through the [WordPress Abilities API](https://developer.wordpress.org/reference/functions/wp_register_ability/). Single STDIO server, multi-site routing, zero dependencies.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Multi-site routing** — Single MCP server serves all your WordPress sites
|
|
10
|
+
- **Site parameter injection** — LLM sees a `site` enum on every tool, defaults to your primary site
|
|
11
|
+
- **Lazy connections** — Sites connect on first use, not at startup
|
|
12
|
+
- **HTTP transport** — Application Passwords with MCP session management
|
|
13
|
+
- **WordPress multisite** — Subdomain/subdirectory multisites via dot notation (`site.blog`)
|
|
14
|
+
- **Auto-reconnect** — Exponential backoff, healthcheck pings, session recovery
|
|
15
|
+
- **Zero dependencies** — Node.js built-in modules only
|
|
16
|
+
|
|
17
|
+
## What You Can Do
|
|
18
|
+
|
|
19
|
+
The abilities available to your AI agent depend on which ability plugins you install. With [Abilities for AI](https://wickedevolutions.com/abilities-for-ai) installed, your agent gets access to:
|
|
20
|
+
|
|
21
|
+
**Content & Publishing** — content, blocks, patterns, media, menus, taxonomies, comments, revisions
|
|
22
|
+
**Site Management** — plugins, themes, settings, users, site health, cache, cron, rewrite rules
|
|
23
|
+
**Infrastructure** — filesystem, meta, REST discovery, knowledge layer
|
|
24
|
+
**Third-party integrations** — auto-detected modules for supported plugins (Astra, Spectra, SureCart, Presto Player, and more)
|
|
25
|
+
|
|
26
|
+
Additional ability plugins extend coverage further. For example, [Abilities for Fluent Plugins](https://github.com/Wicked-Evolutions/abilities-for-fluent-plugins) adds modules for FluentCRM, Fluent Community, Fluent Forms, FluentBooking, Fluent Support, Fluent Boards, FluentSMTP, FluentAuth, Fluent Snippets, Fluent Messaging, FluentCart, and FluentAffiliate.
|
|
27
|
+
|
|
28
|
+
Every ability enforces `current_user_can()` at execution time — your WordPress role is the security boundary.
|
|
29
|
+
|
|
30
|
+
> **Sign up for the Abilities for AI alpha release:** https://wickedevolutions.com/abilities-for-ai
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Set up WordPress
|
|
35
|
+
|
|
36
|
+
Create a dedicated WordPress user for AI access and generate an Application Password.
|
|
37
|
+
|
|
38
|
+
**In WordPress Admin → Users → Add New:**
|
|
39
|
+
|
|
40
|
+
| Field | Value |
|
|
41
|
+
|-------|-------|
|
|
42
|
+
| Username | `mcp-agent` (or any name you prefer) |
|
|
43
|
+
| Role | **Administrator** for full access, or **Editor** for content-only access |
|
|
44
|
+
|
|
45
|
+
**Then generate an Application Password:**
|
|
46
|
+
|
|
47
|
+
Go to **Users → Edit (your mcp-agent user) → Application Passwords**, enter a name (e.g. "MCP Bridge"), and click **Add New Application Password**. Copy the generated password — it's shown only once.
|
|
48
|
+
|
|
49
|
+
#### Choosing a role
|
|
50
|
+
|
|
51
|
+
| Role | Access | Use case |
|
|
52
|
+
|------|--------|----------|
|
|
53
|
+
| **Administrator** | All modules — content, plugins, themes, settings, users, cache, cron, filesystem, and more | Full site management |
|
|
54
|
+
| **Editor** | Content, Blocks, Taxonomies, Patterns, Meta, Media | Content publishing workflows — safe for teams where AI should write but not configure |
|
|
55
|
+
|
|
56
|
+
> **Tip:** Start with Editor. Upgrade to Administrator when you need infrastructure abilities like plugin management, theme switching, or settings changes.
|
|
57
|
+
|
|
58
|
+
#### Required plugins
|
|
59
|
+
|
|
60
|
+
Install both on your WordPress site:
|
|
61
|
+
|
|
62
|
+
1. **[Abilities for AI](https://wickedevolutions.com/abilities-for-ai)** — registers WordPress abilities across content, site management, infrastructure, and third-party integration modules
|
|
63
|
+
2. **[Abilities MCP Adapter](https://github.com/Wicked-Evolutions/abilities-mcp-adapter)** — exposes abilities as MCP tools via REST API
|
|
64
|
+
|
|
65
|
+
### 2. Configure your sites
|
|
66
|
+
|
|
67
|
+
Copy the example config and edit:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cp wp-sites.example.json wp-sites.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Edit `wp-sites.json` with your site details.
|
|
74
|
+
|
|
75
|
+
### 3. Add to your MCP client
|
|
76
|
+
|
|
77
|
+
Works with any MCP-compatible client — Claude Code, Claude Desktop, Gemini CLI, Cursor, Windsurf, VS Code, and any other IDE or AI tool that supports the Model Context Protocol.
|
|
78
|
+
|
|
79
|
+
Add the server to your client's MCP config (usually `.mcp.json`, `settings.json`, or equivalent):
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"mcpServers": {
|
|
84
|
+
"wordpress": {
|
|
85
|
+
"command": "node",
|
|
86
|
+
"args": ["/path/to/abilities-mcp/abilities-mcp.js"]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Client | Config location |
|
|
93
|
+
|--------|----------------|
|
|
94
|
+
| Claude Code | `.mcp.json` in project root or `~/.claude/.mcp.json` |
|
|
95
|
+
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (or use `--register`) |
|
|
96
|
+
| Gemini CLI | `~/.gemini/settings.json` |
|
|
97
|
+
| Cursor | `.cursor/mcp.json` in project root |
|
|
98
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
99
|
+
| VS Code (Copilot) | `.vscode/mcp.json` in project root |
|
|
100
|
+
|
|
101
|
+
For Claude Desktop, you can also auto-register:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
node abilities-mcp.js --register
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Configuration
|
|
108
|
+
|
|
109
|
+
### `wp-sites.json`
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"defaultSite": "mysite",
|
|
114
|
+
"sites": {
|
|
115
|
+
"mysite": {
|
|
116
|
+
"label": "My WordPress Site",
|
|
117
|
+
"url": "https://example.com",
|
|
118
|
+
"transport": "http",
|
|
119
|
+
"http": {
|
|
120
|
+
"endpoint": "https://example.com/wp-json/mcp/mcp-adapter-default-server",
|
|
121
|
+
"username": "mcp-agent",
|
|
122
|
+
"passwordCommand": "security find-generic-password -a mcp-agent -s example.com -w"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Config file search order
|
|
130
|
+
|
|
131
|
+
1. `--config=/path/to/wp-sites.json` (explicit)
|
|
132
|
+
2. Same directory as `abilities-mcp.js`
|
|
133
|
+
3. `~/.abilities-mcp/wp-sites.json`
|
|
134
|
+
|
|
135
|
+
### WordPress Multisite
|
|
136
|
+
|
|
137
|
+
For WordPress multisites, add a `multisite` object mapping subsite keys to their URLs:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"network": {
|
|
142
|
+
"label": "My Network",
|
|
143
|
+
"transport": "http",
|
|
144
|
+
"http": {
|
|
145
|
+
"endpoint": "https://example.com/wp-json/mcp/mcp-adapter-default-server",
|
|
146
|
+
"username": "mcp-agent",
|
|
147
|
+
"passwordCommand": "security find-generic-password -a mcp-agent -s example.com -w"
|
|
148
|
+
},
|
|
149
|
+
"multisite": {
|
|
150
|
+
"main": "https://example.com/",
|
|
151
|
+
"blog": "https://blog.example.com/",
|
|
152
|
+
"shop": "https://shop.example.com/"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use dot notation to target subsites: `"site": "network.blog"`
|
|
159
|
+
|
|
160
|
+
### Secure password storage
|
|
161
|
+
|
|
162
|
+
Three options for providing Application Passwords, from most to least secure:
|
|
163
|
+
|
|
164
|
+
#### `passwordCommand` (recommended)
|
|
165
|
+
|
|
166
|
+
Runs a shell command at startup and uses stdout as the password. Works with any OS keychain or secrets manager:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"http": {
|
|
171
|
+
"endpoint": "https://example.com/wp-json/mcp/mcp-adapter-default-server",
|
|
172
|
+
"username": "mcp-agent",
|
|
173
|
+
"passwordCommand": "security find-generic-password -a mcp-agent -s example.com -w"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**macOS Keychain** — store the password first, then reference it:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Store (one-time)
|
|
182
|
+
security add-generic-password -a mcp-agent -s example.com -w 'YOUR_APP_PASSWORD'
|
|
183
|
+
|
|
184
|
+
# The passwordCommand retrieves it at runtime
|
|
185
|
+
"passwordCommand": "security find-generic-password -a mcp-agent -s example.com -w"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Linux (secret-tool / GNOME Keyring):**
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Store
|
|
192
|
+
secret-tool store --label="WP MCP" service example.com user mcp-agent <<< 'YOUR_APP_PASSWORD'
|
|
193
|
+
|
|
194
|
+
# Config
|
|
195
|
+
"passwordCommand": "secret-tool lookup service example.com user mcp-agent"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**1Password CLI:**
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
"passwordCommand": "op read 'op://Vault/WordPress MCP/password'"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### `passwordEnv`
|
|
205
|
+
|
|
206
|
+
Reads the password from an environment variable. Useful in CI/CD, Docker, or when you set secrets via `.env` files:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"http": {
|
|
211
|
+
"endpoint": "https://example.com/wp-json/mcp/mcp-adapter-default-server",
|
|
212
|
+
"username": "mcp-agent",
|
|
213
|
+
"passwordEnv": "WP_MCP_PASSWORD"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Set the variable before starting the bridge:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Shell export
|
|
222
|
+
export WP_MCP_PASSWORD="xxxx xxxx xxxx xxxx xxxx xxxx"
|
|
223
|
+
|
|
224
|
+
# Or in a .env file loaded by your shell/Docker
|
|
225
|
+
WP_MCP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The bridge reads `process.env.WP_MCP_PASSWORD` at connection time. If the variable is not set, it throws an error immediately.
|
|
229
|
+
|
|
230
|
+
#### `password` (not recommended)
|
|
231
|
+
|
|
232
|
+
Plaintext password directly in the config. Avoid this — config files end up in repos, backups, and logs:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"http": {
|
|
237
|
+
"password": "xxxx xxxx xxxx xxxx xxxx xxxx"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Priority order
|
|
243
|
+
|
|
244
|
+
If multiple are set: `passwordEnv` → `passwordCommand` → `password`.
|
|
245
|
+
|
|
246
|
+
## Bridge Tools
|
|
247
|
+
|
|
248
|
+
The bridge provides three built-in tools (not forwarded to WordPress):
|
|
249
|
+
|
|
250
|
+
| Tool | Description |
|
|
251
|
+
|------|-------------|
|
|
252
|
+
| `wp_bridge_health` | Check connectivity status of all configured WordPress sites |
|
|
253
|
+
| `wp_browse_tools` | List WordPress tool categories with counts (requires `toolFilter.enabled: true`) |
|
|
254
|
+
| `wp_load_tools` | Activate/deactivate tool categories for lazy loading |
|
|
255
|
+
|
|
256
|
+
## Usage
|
|
257
|
+
|
|
258
|
+
### Multi-site mode
|
|
259
|
+
|
|
260
|
+
When multiple sites are configured, every tool gets an optional `site` parameter:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"name": "content-list",
|
|
265
|
+
"arguments": {
|
|
266
|
+
"site": "staging",
|
|
267
|
+
"post_type": "post"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Omit `site` to use the default site.
|
|
273
|
+
|
|
274
|
+
## CLI Options
|
|
275
|
+
|
|
276
|
+
| Flag | Description |
|
|
277
|
+
|------|-------------|
|
|
278
|
+
| `--config=<path>` | Path to wp-sites.json |
|
|
279
|
+
| `--server=<name>` | MCP adapter server name |
|
|
280
|
+
| `--debug` | Enable debug logging to `/tmp/abilities-mcp.log` |
|
|
281
|
+
| `--register` | Register in Claude Desktop config |
|
|
282
|
+
| `--name=<name>` | Server name for `--register` (default: `wordpress`) |
|
|
283
|
+
|
|
284
|
+
## Architecture
|
|
285
|
+
|
|
286
|
+
```mermaid
|
|
287
|
+
graph TD
|
|
288
|
+
Client[AI Client<br/>Claude Code · Gemini CLI · Cursor · any MCP client] -->|STDIO| Bridge[Abilities MCP]
|
|
289
|
+
Bridge -->|HTTP POST| SiteA[Site A]
|
|
290
|
+
Bridge -->|HTTP POST| SiteB[Site B]
|
|
291
|
+
Bridge -->|SSH + WP-CLI| SiteC[Site C]
|
|
292
|
+
|
|
293
|
+
subgraph "Each WordPress Site"
|
|
294
|
+
Adapter[Abilities MCP Adapter] --> AbilitiesAPI[WordPress Abilities API]
|
|
295
|
+
AbilitiesAPI --> Plugins[Ability Plugins]
|
|
296
|
+
end
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
- One STDIO process handles all sites through a unified connection pool
|
|
300
|
+
- **HTTP transport** — Application Passwords with MCP session management, batch coalescing, auto-reconnect
|
|
301
|
+
- **SSH transport** — WP-CLI over SSH tunnel, healthcheck pings, handshake replay
|
|
302
|
+
- Lazy connections — non-default sites connect on first tool call
|
|
303
|
+
- Tool list comes from the default site with `site` enum injected
|
|
304
|
+
- Permission metadata (`permission`, `enabled`) flows through annotations to the LLM
|
|
305
|
+
- Error responses include `input_schema` for AI self-correction
|
|
306
|
+
|
|
307
|
+
See [docs/architecture.md](docs/architecture.md) for the full technical deep dive — transport comparison tables, session management, multi-site routing internals, and security model.
|
|
308
|
+
|
|
309
|
+
## Known Limitations
|
|
310
|
+
|
|
311
|
+
- **Session lock contention** ([#4](https://github.com/Wicked-Evolutions/abilities-mcp/issues/4)) — Concurrent bridge instances targeting the same site can cause session loss. Use a single bridge process per site.
|
|
312
|
+
|
|
313
|
+
## Requirements
|
|
314
|
+
|
|
315
|
+
- Node.js >= 18
|
|
316
|
+
- WordPress 6.9+ with [Abilities for AI](https://wickedevolutions.com/abilities-for-ai) and [Abilities MCP Adapter](https://github.com/Wicked-Evolutions/abilities-mcp-adapter) installed
|
|
317
|
+
- Application Passwords enabled (default in WordPress 5.6+)
|
|
318
|
+
|
|
319
|
+
## License
|
|
320
|
+
|
|
321
|
+
GPL-2.0-or-later
|
package/abilities-mcp.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* abilities-mcp v1.0.0
|
|
4
|
+
*
|
|
5
|
+
* One MCP to Rule Your WordPress World.
|
|
6
|
+
*
|
|
7
|
+
* Unified multi-site MCP bridge for WordPress Abilities API. Replaces
|
|
8
|
+
* mcp-ssh-bridge and mcp-http-bridge with a single STDIO server that
|
|
9
|
+
* routes tool calls to any configured WordPress site via SSH or HTTP.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node abilities-mcp.js (uses wp-sites.json)
|
|
13
|
+
* node abilities-mcp.js --config=/path/to/wp-sites.json (explicit config)
|
|
14
|
+
* node abilities-mcp.js --host=<ssh-host> --path=<wp-path> (legacy single-site)
|
|
15
|
+
* node abilities-mcp.js --register [--name=<name>] (Claude Desktop setup)
|
|
16
|
+
*
|
|
17
|
+
* Copyright (C) 2026 Influencentricity | Wicked Evolutions
|
|
18
|
+
* @package Wicked-Evolutions/abilities-mcp
|
|
19
|
+
* @version 1.0.0
|
|
20
|
+
* @license GPL-2.0-or-later
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const { createLogger } = require('./lib/logger');
|
|
26
|
+
const { loadConfig, buildSiteKeyEnum } = require('./lib/config');
|
|
27
|
+
const { ConnectionPool } = require('./lib/connection-pool');
|
|
28
|
+
const { ToolCatalog } = require('./lib/tool-catalog');
|
|
29
|
+
const { McpRouter } = require('./lib/router');
|
|
30
|
+
const { SshTransport } = require('./lib/transports/ssh-transport');
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// CLI argument parsing
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const args = {};
|
|
37
|
+
process.argv.slice(2).forEach(arg => {
|
|
38
|
+
if (arg.startsWith('--')) {
|
|
39
|
+
const [key, ...rest] = arg.slice(2).split('=');
|
|
40
|
+
args[key] = rest.length ? rest.join('=') : true;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const debug = !!args.debug;
|
|
45
|
+
const register = !!args.register;
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Registration mode (--register)
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
if (register) {
|
|
52
|
+
const { registerClaudeDesktop } = require('./lib/register');
|
|
53
|
+
registerClaudeDesktop({ name: args.name || 'wordpress', configPath: args.config });
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Initialize
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
const log = createLogger(debug);
|
|
62
|
+
log('abilities-mcp v1.0.0 starting');
|
|
63
|
+
|
|
64
|
+
// Ensure SSH agent is available (macOS launchd discovery)
|
|
65
|
+
SshTransport.ensureSshAuthSock();
|
|
66
|
+
|
|
67
|
+
let config;
|
|
68
|
+
try {
|
|
69
|
+
config = loadConfig(args);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
process.stderr.write(`abilities-mcp: ${err.message}\n`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isMultiSite = config._isMultiSite;
|
|
76
|
+
const siteKeys = buildSiteKeyEnum(config);
|
|
77
|
+
log(`Config loaded: ${siteKeys.length} site(s): ${siteKeys.join(', ')} (default: ${config.defaultSite})`);
|
|
78
|
+
log(`Multi-site mode: ${isMultiSite}`);
|
|
79
|
+
|
|
80
|
+
const pool = new ConnectionPool(config, log);
|
|
81
|
+
const catalog = new ToolCatalog(config, log);
|
|
82
|
+
|
|
83
|
+
if (catalog.isEnabled()) {
|
|
84
|
+
log('Tool filtering enabled');
|
|
85
|
+
} else {
|
|
86
|
+
log('Tool filtering disabled (no toolFilter in config or enabled: false)');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sendToClient(data) {
|
|
90
|
+
process.stdout.write(data + '\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const router = new McpRouter({
|
|
94
|
+
config,
|
|
95
|
+
siteKeys,
|
|
96
|
+
isMultiSite,
|
|
97
|
+
pool,
|
|
98
|
+
catalog,
|
|
99
|
+
sendToClient,
|
|
100
|
+
log,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Client STDIO processing
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
let inputBuffer = '';
|
|
108
|
+
|
|
109
|
+
process.stdin.on('data', (chunk) => {
|
|
110
|
+
inputBuffer += chunk.toString();
|
|
111
|
+
|
|
112
|
+
let newlineIdx;
|
|
113
|
+
while ((newlineIdx = inputBuffer.indexOf('\n')) !== -1) {
|
|
114
|
+
const line = inputBuffer.slice(0, newlineIdx);
|
|
115
|
+
inputBuffer = inputBuffer.slice(newlineIdx + 1);
|
|
116
|
+
if (line.trim()) {
|
|
117
|
+
let msg;
|
|
118
|
+
try {
|
|
119
|
+
msg = JSON.parse(line.trim());
|
|
120
|
+
} catch (e) {
|
|
121
|
+
log(`Non-JSON from client (dropped): ${line.substring(0, 200)}`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
router.handleClientMessage(msg, line.trim());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
process.stdin.on('end', () => {
|
|
130
|
+
log('Client stdin closed — shutting down');
|
|
131
|
+
shutdown();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Startup — connect to default site
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
(async function main() {
|
|
139
|
+
try {
|
|
140
|
+
const transport = await pool.connectDefault((parsedMsg, rawLine) => {
|
|
141
|
+
router.handleTransportMessage(parsedMsg, rawLine);
|
|
142
|
+
});
|
|
143
|
+
router.setDefaultTransport(transport);
|
|
144
|
+
log(`Default transport connected: ${config.defaultSite}`);
|
|
145
|
+
router.drainEarlyQueue();
|
|
146
|
+
} catch (err) {
|
|
147
|
+
process.stderr.write(`abilities-mcp: Failed to connect to default site: ${err.message}\n`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Signal handling
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
function shutdown() {
|
|
157
|
+
log('Shutting down');
|
|
158
|
+
pool.shutdownAll().then(() => {
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}).catch(() => {
|
|
161
|
+
process.exit(1);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
process.on('SIGTERM', shutdown);
|
|
166
|
+
process.on('SIGINT', shutdown);
|
|
167
|
+
process.on('unhandledRejection', (reason) => {
|
|
168
|
+
log(`Unhandled rejection: ${reason}`);
|
|
169
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bridge tools — synthetic tools handled locally, never forwarded to WordPress.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (C) 2026 Influencentricity | Wicked Evolutions
|
|
7
|
+
* @license GPL-2.0-or-later
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const BRIDGE_TOOLS = [
|
|
11
|
+
{
|
|
12
|
+
name: 'wp_bridge_health',
|
|
13
|
+
description: 'Check connectivity status of all configured WordPress sites. Returns status (connected/reachable/unreachable) and latency for each site.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
site: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Optional: check only this site. Omit to check all configured sites.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'wp_browse_tools',
|
|
26
|
+
description: 'List available WordPress tool categories with tool counts. Shows which categories are currently loaded. Use wp_load_tools to activate categories.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'wp_load_tools',
|
|
34
|
+
description: 'Activate WordPress tool categories to make their tools available. After loading, the tools list is automatically refreshed.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
categories: {
|
|
39
|
+
type: 'array',
|
|
40
|
+
items: { type: 'string' },
|
|
41
|
+
description: 'Category names to activate (e.g. ["fluent-crm", "content", "media"]). Use wp_browse_tools to see available categories.',
|
|
42
|
+
},
|
|
43
|
+
deactivate: {
|
|
44
|
+
type: 'array',
|
|
45
|
+
items: { type: 'string' },
|
|
46
|
+
description: 'Optional: category names to deactivate (unload from the tools list).',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ['categories'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const BRIDGE_TOOL_NAMES = new Set(BRIDGE_TOOLS.map(t => t.name));
|
|
55
|
+
|
|
56
|
+
function isBridgeTool(name) {
|
|
57
|
+
return BRIDGE_TOOL_NAMES.has(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function injectBridgeTools(msg) {
|
|
61
|
+
if (!msg.result || !Array.isArray(msg.result.tools)) return;
|
|
62
|
+
for (const tool of BRIDGE_TOOLS) {
|
|
63
|
+
msg.result.tools.push(tool);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { BRIDGE_TOOLS, isBridgeTool, injectBridgeTools };
|