Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
34f8fa6
Harden validation, error classification, and ability caching
dennisdornon Jul 10, 2026
aa167fd
Consolidate test fixtures and strengthen assertions
dennisdornon Jul 10, 2026
7f29e68
Add typecheck gate and lint scripts/
dennisdornon Jul 10, 2026
2da7eca
Harden confirmation gating, permission checks, and transport timeouts
dennisdornon Jul 12, 2026
306841b
Close dry_run bypass, fix unknown-tool error code, and fold duplicate…
dennisdornon Jul 13, 2026
6a7c8cc
Apply CodeRabbit loop fixes: namespace-aware guards, safer manual tests
dennisdornon Jul 14, 2026
727c2d6
Sanitize hostile ability input schemas before tool conversion
dennisdornon Jul 16, 2026
0cffd49
Add packed-package MCP acceptance harness with live, fixture, and age…
dennisdornon Jul 16, 2026
37c9e20
Classify structured not-found errors correctly and cover the confirma…
dennisdornon Jul 16, 2026
57035cb
Add agent confirmation, completions, and transport-limit acceptance c…
dennisdornon Jul 16, 2026
174e4aa
Add check-site and ability-read acceptance scenarios
dennisdornon Jul 16, 2026
09d2aa9
Require the confirmation token for destructive execution
dennisdornon Jul 16, 2026
962b103
Expand the agent acceptance layer from 4 to 9 questions
dennisdornon Jul 17, 2026
562fca5
Widen the nonexistent-site answer matcher beyond gap-bounded phrasings
dennisdornon Jul 17, 2026
663e2dc
Drop the bare mcp bin alias and scrub release cosmetics
dennisdornon Jul 17, 2026
ca67ce2
Flatten the SSL warning and drop prompt-array banner comments
dennisdornon Jul 17, 2026
f81c3ee
Allow conditional remedies in the safe-mode refusal matcher
dennisdornon Jul 17, 2026
92cd5a4
Pack dependency tarballs from script-stripped staged copies
dennisdornon Jul 17, 2026
d286943
Canonicalize nested arguments in the confirmation preview key
dennisdornon Jul 17, 2026
a69a2a9
Add the layered how-it-works framing to the README
dennisdornon Jul 17, 2026
13663b4
Give canonicalized objects a null prototype and fix README heading le…
dennisdornon Jul 17, 2026
7fa7a5e
Fix the six production findings from the CodeRabbit PR review
dennisdornon Jul 17, 2026
746df8f
Harden the acceptance harness per the CodeRabbit review batch
dennisdornon Jul 17, 2026
8c27198
Scope confirmation state to the config identity and publish review de…
dennisdornon Jul 17, 2026
b35a097
Address the fourth CodeRabbit round: schema maps, matcher, process trees
dennisdornon Jul 17, 2026
0b70eea
Arm the deadline fallback from the timeout callback via a shared helper
dennisdornon Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# CodeRabbit configuration.
# The path_instructions below record deliberate design decisions so reviews
# do not re-flag them. Each carries its rationale inline.

reviews:
path_instructions:
- path: 'src/index.ts'
instructions: >-
Do not suggest filtering the static mainwp://abilities and
mainwp://help resources through allowedTools/blockedTools. This is a
documented policy decision (docs/security.md): the tool policy
governs listing and execution, while static resources describe the
full ability catalog so operators can see what a broader policy
would expose. Anything that resolves or executes an ability
(tool calls, completions, mainwp://site/{id}) is policy-gated.

- path: 'docs/troubleshooting.md'
instructions: >-
Do not suggest moving used/mismatched confirmation_token cases out of
the PREVIEW_REQUIRED section. The code (src/confirmation.ts) returns
PREVIEW_REQUIRED for unknown, already-used, cross-tool, and
argument-mismatched tokens; PREVIEW_EXPIRED only fires when the
pending preview still exists but is older than 5 minutes. The doc
matches the code.

- path: 'src/config.ts'
instructions: >-
Do not suggest restricting skipSslVerify to local-looking dashboard
hosts. Hostname-based classification is unreliable (internal DNS
names, LAN IPs, and public IPs with self-signed certificates are all
legitimate). The flag is opt-in, off by default, scoped to a
per-request undici dispatcher, and announced with a startup MITM
warning.

- path: 'src/session.ts'
instructions: >-
Do not suggest resetting the module-global sessionDataBytes counter
inside createServer(). Production runs one createServer per process
(stdio transport), so the counter starts at zero; resetting there
would zero the cap out from under a concurrently running instance
sharing the module. Tests reset explicitly via resetSessionData().

- path: 'src/http-client.ts'
instructions: >-
Do not suggest requiring error.name === 'AbortError' before
converting errors to ETIMEDOUT in readLimitedBody's catch. Undici
surfaces mid-stream aborts as varying error shapes (often TypeError
"terminated", not AbortError), so the deadline.timedOut flag is the
reliable signal there; the strict check is only safe in createFetch
where fetch() itself rejects.
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
- name: Lint
run: npm run lint

- name: Typecheck (src + tests + scripts)
run: npm run typecheck

- name: Check version consistency
run: npm run check-version

Expand All @@ -51,3 +54,27 @@ jobs:
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

acceptance-fixture:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Run packed fixture acceptance tests
run: npm run test:acceptance:fixture
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,12 @@ test-results/
# Local agent/review artifacts (generated)
.context/

# Local agent instructions (contain machine-specific paths)
CLAUDE.md
AGENTS.md

# Override global gitignore for CI/CD
!.github/

# Local log output (never tracked; may contain request data)
logs/
50 changes: 49 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
# Changelog

All notable changes to mainwp-mcp are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/).
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

The server warns at startup when a bearer token (`MAINWP_TOKEN`) is configured without a complete username and application password pair. The WordPress Abilities API rejects bearer tokens, so a token-only setup fails with 401s at request time; the warning surfaces the problem at startup instead.

### Changed

Destructive tool calls now go through strict confirmation gating. A bare call to a confirm-capable tool, with no preview and no confirmation token, returns a `PREVIEW_REQUIRED` error instead of proceeding with a logged warning. **This is breaking for clients that relied on the old skip**: run the preview step first, then confirm. Abilities that require confirmation but expose no `dry_run` parameter no longer get a fabricated dry-run call; they return a token-issuing `CONFIRMATION_REQUIRED` response with `preview: null`, and execution proceeds once the client confirms with that token.

Nested objects and arrays of objects in the input of GET/DELETE abilities are now rejected with an invalid-params error. They used to be serialized into the query string as `[object Object]`, which the Dashboard silently misread.

Malformed boolean configuration now fails startup instead of logging a warning and falling back to the default. Accepted values are `true/1/yes/on` and `false/0/no/off`; anything else stops the server with an error naming the variable.

### Removed

The package no longer installs a global command named `mcp`. That name is too generic for a public package and collides with other MCP tooling. The command is `mainwp-mcp`, and `npx @mainwp/mcp` keeps working since the package now has a single bin entry.

### Fixed

The installed `mainwp-mcp` command now starts when invoked through npm's bin symlink. The entry-point check compared the module URL against `process.argv[1]` without resolving symlinks, so the CLI exited silently with status 0 when run via `npx` or a `node_modules/.bin` link. The check now resolves the invoked path first.

Confirmation previews and tokens are now scoped to the dashboard and principal that issued them. The preview state is module-level, so with multiple `createServer(config)` instances in one process a token issued against one dashboard could confirm the same tool and arguments against another. Preview keys now carry a config identity hash, matching the isolation the abilities cache already enforces.

Confirmation preview keys now serialize nested arguments faithfully. The previous serialization dropped nested values (including keys named `__proto__` and objects inside arrays), so a confirmation call could swap nested argument values past the token binding. Arguments are canonicalized recursively onto null-prototype objects before keying.

Tool schemas for destructive tools now declare the `confirmation_token` parameter, and the advertised confirmation flow names the token step. Clients that validate arguments against the schema could not send the token the server requires, and the described flow still matched the old tokenless behavior. Confirm-only abilities without `dry_run` no longer promise a preview in their description.

Passing `confirm: true` together with a declared `dry_run: true` no longer forwards `confirm` upstream. The dry-run call now goes out with `confirm` stripped, matching the preview path, so upstream handlers never see the ambiguous combination.

A malformed ability entry in the Dashboard response (a null entry or a non-string name) is now skipped with a warning instead of throwing and failing the whole catalog refresh.

A malformed property value inside an ability's input schema (a string or array where an object belongs) is now coerced to an empty object instead of crashing the whole tools/list response during description backfill. Property maps are also built with null prototypes, so a hostile parameter named `__proto__` survives as a real schema property instead of polluting the map and skewing the confirm/dry_run detection.

The abilities cache signature now includes `skipSslVerify` and `maxResponseSize`, so a strictly configured server instance never reuses data fetched by an instance with TLS verification disabled or a larger response cap.

Confirmed execution of a destructive tool now always requires the `confirmation_token` issued by the preview. The server used to fall back to matching the pending preview by tool name and arguments, so `user_confirmed: true` with the same arguments executed without the token, letting a caller confirm a preview it never read. A tokenless confirmation now returns `PREVIEW_REQUIRED` and the issued token stays valid.

A "site not found" error from a live Dashboard now surfaces with the resource-not-found error code. The Dashboard reports a nonexistent site as HTTP 403 with the `mainwp_site_not_found` error code, and the classifier trusted the status before the structured code, so clients received a permission-denied error and recovered down the wrong path. Structured not-found codes now classify first.

Passing `dry_run: true` to an ability that does not declare a `dry_run` parameter now returns an invalid-parameter error instead of skipping the confirmation flow. The server used to forward the parameter upstream, and a handler that ignores unknown input would have run the destructive operation without confirmation.

Request timeouts and client cancellations are no longer conflated. The request timeout stays armed while the response body is read, and timing out surfaces as a retryable `ETIMEDOUT`; an abort from the caller surfaces as a cancellation rather than a timeout.

Spurious `tool_list_changed` notifications after every cache refresh are gone. Tool schema enrichment was mutating the cached abilities in place, so each refresh compared a different fingerprint and notified clients even when nothing had changed. Enrichment now works on a copy.

Error classification prefers the structured HTTP status from the API response (401, 403, 404, 429, 5xx) over parsing the error message text, so error codes stay correct when upstream wording changes.

## [1.0.0-beta.3] - 2026-06-10

Expand Down
Loading
Loading