Skip to content

Configurable ability namespaces, isError on failed tool calls, misconfiguration warnings#40

Merged
dennisdornon merged 12 commits into
mainfrom
remove-hardcoded-namespace
Jul 10, 2026
Merged

Configurable ability namespaces, isError on failed tool calls, misconfiguration warnings#40
dennisdornon merged 12 commits into
mainfrom
remove-hardcoded-namespace

Conversation

@dennisdornon

@dennisdornon dennisdornon commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

The mainwp ability namespace was hardcoded into the filter, the tool name conversion, and the reverse lookup, so abilities registered by third-party extensions under their own namespaces were invisible to the server. While smoke-testing the fix for that, two behavioral issues and a docs drift surfaced, and everything lands here together since it was validated as one build.

Configurable namespaces. config.abilityNamespaces (env var MAINWP_ABILITY_NAMESPACES, settings.json field abilityNamespaces with schema support) defaults to ["mainwp"]. The first entry is the primary namespace and keeps unprefixed tool names; abilities from other configured namespaces are exposed as {namespace}__{tool} so MCP tool names stay collision-free. Reverse lookup goes through an index built during fetchAbilities that throws on tool-name collisions rather than silently shadowing one ability under another's name. The ability and category caches carry a namespace signature so a config change invalidates them instead of serving stale, wrongly-filtered data. Documented in the README and docs/configuration.md, including guidance to keep mainwp in the list because the built-in mainwp://site/{id} resource and site ID prompt completions call mainwp/get-site-v1 and mainwp/list-sites-v1 directly.

isError on failed tool calls. Failed tool calls came back as ordinary MCP results: the error JSON sat in content[0].text, the isError flag was missing, and any client or agent framework that branches on isError treated the failure as a success. The cause was that executeTool caught every error and serialized it into plain content, so the isError: true branch in the CallTool handler never fired. executeTool now returns the full CallToolResult shape ({ content, isError? }) and the handler passes it through. Every failed path sets isError: true: unknown tool, input validation failure, ability execution failure, cancellation, safe-mode blocks, and confirmation rejections (PREVIEW_REQUIRED, PREVIEW_EXPIRED, INVALID_PARAMETER, CONFLICTING_PARAMETERS). CONFIRMATION_REQUIRED previews and idempotent NO_CHANGE responses remain ordinary results because those calls did what was asked. The JSON error bodies are byte-for-byte unchanged, so existing consumers keep parsing them. One judgment call worth a reviewer's eye: SAFE_MODE_BLOCKED also sets isError: true, since a refused call reads as a failed call under the MCP spec.

Misconfiguration warnings. A server started with MAINWP_ABILITY_NAMESPACES="acme" boots clean, advertises zero tools, and errors on the built-in resources, with nothing in the logs explaining why. Startup now warns when mainwp is missing from abilityNamespaces, and after the ability fetch a second warning fires when the namespace filter leaves zero abilities, naming the configured namespaces and the pre-filter count. An empty upstream logs its own message (Dashboard returned no abilities) so a dead API is distinguishable from a filter mismatch.

Docs tool counts. The README said both "Over 60 tools" and "64 tools", docs/configuration.md said 64, and a live server currently exposes 62. The count moves with the dashboard version, so both files now say "around 60 tools" with a note that the exact count varies by Dashboard version.

Testing. Unit and integration tests cover the namespace round-trips, every isError path and its absence on success, previews, and NO_CHANGE, and both warnings via the mockLogger pattern. Verified live over stdio against a real dashboard:

Call Result
tools/list 62 tools
count_sites_v1 (success) no isError
unknown tool isError: true, code -32002
get_site_v1 with site_id: -1 isError: true, code -32602
get_site_v1 for nonexistent site 999999 isError: true, upstream mainwp_site_not_found
user_confirmed without preview isError: true, PREVIEW_REQUIRED
delete_site_v1 under MAINWP_SAFE_MODE=true isError: true, SAFE_MODE_BLOCKED
boot with MAINWP_ABILITY_NAMESPACES=acme 0 tools, both warnings on stderr (fetchedCount: 64)
default boot 62 tools, no namespace warnings

To test the full build locally: check out this branch, npm ci && npm run build, and point your MCP client at node /path/to/mainwp-mcp/dist/index.js, or ask for the npm pack tarball.

Summary by CodeRabbit

  • New Features

    • Added configurable ability namespaces via abilityNamespaces setting to control which tools are exposed.
    • Non-primary namespace tools now use {namespace}__toolname naming convention.
  • Bug Fixes

    • Fixed atomic tool index refresh to prevent partial builds on failures.
    • Malformed ability names now dropped with warnings.
    • MCP error responses now properly set isError flag.
  • Documentation

    • Updated configuration and README with namespace and tool naming guidance.

replaces hardcoded mainwp/ filter with an abilityNamespaces allowlist
(default ['mainwp']); non-primary namespaces get a {ns}__ tool prefix.
reverse tool->ability lookup now goes through a cache index.
Build the ability and tool name indices into locals and assign all cache
state together, so a collision throw during a refresh leaves the existing
index intact instead of serving a partially built one. Allow hyphenated
namespaces in ability names so configs like acme-corp round-trip through
execute, while forbidding leading and trailing hyphens in both namespace
regexes and the settings schema. Drop abilities whose names fail the strict
format check at fetch time with a logged warning, so a malformed upstream
name never surfaces as an invalid MCP tool name; the execute-time check
remains as a second layer. Correct the docs on what depends on the mainwp
namespace staying configured: the site resource returns an error payload
and site ID completions come back empty when it is filtered out.

New tests cover collision-safe refresh, hyphenated namespace round-trips,
multi-namespace tool surfacing, malformed-name filtering, tools and
categories cache invalidation on namespace changes, env var dedup and error
attribution, and the prefix-related category listing behavior. Sync
package-lock.json to beta.3.
The changelog covers the configurable namespace work: the new
abilityNamespaces setting, the removed toolNameToAbilityName export, and
the cache and malformed-name hardening. The version stays at beta.3, the
bump the feature commit already made over the published beta.2; the
lockfile is synced to match.
Failed tool calls returned a normal MCP result with the error JSON in
content but no isError flag, so MCP clients and agent frameworks that
branch on isError treated failures as successes.

executeTool now returns the full CallToolResult shape ({ content,
isError? }) instead of bare content. isError: true is set on every
failed-call path: unknown tool, input validation failure, ability
execution failure, cancellation, safe-mode block, and confirmation
rejections (INVALID_PARAMETER, CONFLICTING_PARAMETERS, PREVIEW_REQUIRED,
PREVIEW_EXPIRED). CONFIRMATION_REQUIRED previews and idempotent NO_CHANGE
responses stay non-error since the call did what was asked. The JSON
error body in content is unchanged, so existing consumers still parse it.

Verified live over stdio against the testbed dashboard: unknown tool and
invalid input both return isError: true; successful calls carry no flag.
A server configured with a namespace allowlist that excludes 'mainwp'
(e.g. MAINWP_ABILITY_NAMESPACES="acme") booted clean, advertised zero
tools, and broke the built-in resources with nothing in the logs
explaining why.

Two warnings now cover this:

- Startup warns when 'mainwp' is not in abilityNamespaces, since the
  mainwp://site/{id} resource calls mainwp/get-site-v1 and site ID
  prompt completions call mainwp/list-sites-v1, both of which fail when
  the namespace is filtered out (matches the 'Keep mainwp in the list'
  section in docs/configuration.md).
- fetchAbilities warns when the namespace filter leaves zero abilities,
  naming the configured namespaces and the pre-filter count.

Verified live: booting with MAINWP_ABILITY_NAMESPACES="acme" against
the testbed dashboard logs both warnings on stderr.
CodeRabbit pointed out the zero-abilities warning claimed a namespace
mismatch even when the dashboard itself returned no abilities. An empty
upstream now logs 'Dashboard returned no abilities' instead.
Tool counts had drifted: README said both 'Over 60 tools' and '64
tools', docs/configuration.md said 64, and the live count varies by
dashboard version (the testbed currently exposes 62). Both docs now use
the same 'around 60 tools' phrasing with a note that the exact count
varies by Dashboard version, so the docs stop going stale on every
dashboard release.
The entry covered only the namespace work. The combined PR ships the
isError flag on failed tool calls and the startup warnings in the same
release, so the changelog now mentions both.
CodeRabbit flagged that both namespace integration tests dropped the
execution result. They now assert the parsed body and the absence of
isError. The suggested input schemas were skipped: the server does not
validate arguments against ability schemas at execute time, and
schema-less abilities are a supported case.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2f4d49c4-a31d-4195-a17a-e8cb02cc9342

📥 Commits

Reviewing files that changed from the base of the PR and between db8b81c and 7c8f63c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • .gitignore
  • CHANGELOG.md
  • README.md
  • docs/configuration.md
  • package.json
  • settings.example.json
  • settings.schema.json
  • src/abilities.test.ts
  • src/abilities.ts
  • src/config.test.ts
  • src/config.ts
  • src/confirmation.ts
  • src/help.ts
  • src/index.ts
  • src/naming.test.ts
  • src/naming.ts
  • src/tool-schema.ts
  • src/tools.test.ts
  • src/tools.ts
  • tests/evals/description-quality.test.ts
  • tests/evals/safety-coverage.test.ts
  • tests/evals/schema-quality.test.ts
  • tests/evals/token-budget.test.ts
  • tests/integration/abilities.test.ts
  • tests/integration/tools.test.ts

Walkthrough

This PR implements configurable ability namespace allowlisting for the MCP server. It adds a new abilityNamespaces configuration field to control which WP Ability namespaces are exposed as MCP tools, implements tool-name indexing for reverse resolution, standardizes tool execution results with error flags, and refactors naming/help generation to work with the primary namespace concept.

Changes

Ability namespace allowlisting and tool execution

Layer / File(s) Summary
Configuration schema and loading
src/config.ts, src/config.test.ts, settings.schema.json, settings.example.json
Added required abilityNamespaces non-empty tuple field to Config interface and optional field to SettingsFile; implemented validation regex, defaults, and parsing from MAINWP_ABILITY_NAMESPACES env var with proper precedence and deduplication logic.
Ability discovery and cache consistency with namespace filtering
src/abilities.ts
Implemented namespace signature tracking for cache consistency across config changes; added ability/category filtering by configured allowlist; built toolNameIndex for reverse tool-name lookups; hardened refresh failure handling to discard stale cache when namespace signature mismatches; exported getAbilityByToolName for direct tool-name-based ability resolution.
Ability discovery and caching test suite
src/abilities.test.ts
Added comprehensive tests for namespace filtering defaults, multi-namespace retention, cache invalidation on namespace changes, malformed name skipping, tool-name collision detection, cache discard on signature mismatch, and getAbilityByToolName resolution for primary/non-primary namespaces.
Tool naming conversion and help generation
src/naming.ts, src/naming.test.ts, src/help.ts, src/tool-schema.ts
Refactored abilityNameToToolName to accept primaryNamespace and conditionally strip (for primary namespace) or preserve with __ prefix (for others) with hyphen normalization; removed toolNameToAbilityName; updated generateToolHelp and generateHelpDocument to accept and use primaryNamespace.
Tool execution result shape and confirmation error handling
src/tools.ts, src/confirmation.ts, src/index.ts
Introduced ToolCallResult type with optional isError flag; changed executeTool to return structured result objects; marked confirmation validation failures (missing confirm support, parameter conflicts, invalid tokens, expired previews) with isError: true; updated server integration to use getAbilityByToolName and pass primary namespace to help functions.
Tool execution and confirmation test suite
src/tools.test.ts, tests/integration/tools.test.ts
Updated all assertions to read from result.content[0].text and validate result.isError; added tests for multi-namespace tool-name resolution; covers execution success, validation failures, safe-mode, confirmation flows, cancellation, network errors, and JSON formatting.
Evaluation test configuration updates
tests/evals/description-quality.test.ts, tests/evals/safety-coverage.test.ts, tests/evals/schema-quality.test.ts, tests/evals/token-budget.test.ts
Added abilityNamespaces: ['mainwp'] to baseConfig across all evaluation test suites to narrow ability scope for consistent testing.
Documentation, release notes, and version bump
README.md, docs/configuration.md, CHANGELOG.md, package.json, .gitignore
Added MAINWP_ABILITY_NAMESPACES env var documentation; expanded "Limiting Exposed Tools" section with namespace prefix guidance; introduced "Ability Namespaces" configuration section explaining primary/secondary namespace behavior; updated release notes for beta.3; bumped version; added .context/ to .gitignore.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • mainwp/mainwp-mcp#11: Adds audit logging in executeAbility within src/abilities.ts, which is heavily refactored in this PR for namespace filtering and cache consistency.
  • mainwp/mainwp-mcp#12: Removes namespace configuration and hardcodes ability filtering to mainwp, which is the inverse of this PR's configurable namespace allowlisting feature.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: configurable ability namespaces, isError on failed tool calls, and misconfiguration warnings.
Docstring Coverage ✅ Passed Docstring coverage is 95.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch remove-hardcoded-namespace

Comment @coderabbitai help to get the list of available commands and usage tips.

@dennisdornon
dennisdornon merged commit a6d5efd into main Jul 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant