Rename the read-only scan verb to `vigiles audit` — a local, zero-config Lighthouse-style report scoring a harness across four deterministic rings (Truthfulness/Triggering/Structure/Tested) with each finding's fix inline + a shareable HTML report and a versioned AuditReport JSON.
audit is a local report, not a CI step (CI uses `vigiles lint`). The executing checks (live MCP resolution + trigger-rate + the adoption preview) run only behind one interactive consent; there is no execution flag. The safety battery is not an audit ring — it lives in the vigiles/testing API.
Adds the adoption-gateway preview ("what would vigiles catch in your repo?"): the model drafts a spec from the instruction file, the cross-reference engine verifies every drafted ref deterministically.
BREAKING CHANGE: the `scan` verb is renamed to `audit` (no alias); the `--check-hooks`/`--verify-mcp`/`--trigger`/`--fix-plan`/`--explain`/`--deep`/`--measure`/`--fast`/`--no-measure` flags are removed.
3.4 KiB
mcp-tool-resolves
Cross-reference an mcp__server__tool in a subagent's tools: contract against
the plugin's declared MCP servers. If the plugin declares its own servers
(.mcp.json / manifest mcpServers) and a contract names a server that isn't
among them, the tool can't resolve — a dead contract entry.
This is the MCP half of the tool cross-reference:
subagent-tool-contract verifies a subagent's built-in
tools but passes any mcp__* token through unchecked; this rule verifies the MCP
half. Same detector vigiles audit uses (verifyMcpToolServers in
src/core/mcp-tool.ts) — one detector, two callers.
What it flags
Given a plugin that declares github in .mcp.json:
# agents/reviewer.md
tools: Read, mcp__github__search, mcp__linear__create, mcp__ide__getDiagnostics
mcp__github__search— server declared → ✓mcp__ide__getDiagnostics— a harness built-in server (allowlisted) → ✓mcp__linear__create— server not declared → ✗ can't resolve
High-precision calibration
Auditing third-party plugins means flagging only high-confidence dead references. Three guards, each grounded in a real plugin from the mid-2026 sweep:
- Gate on a declared set. Only flags when the plugin ships a
mcpServersdeclaration. A plugin that declares no servers reaches user-global / project-level ones (the normal pattern — ananddtyagi's agents referencemcp__ide__*with no.mcp.json), so flagging there would cry wolf. - Allowlist built-ins. A harness-provided server
(
HarnessDialect.knownMcpServers, e.g. Claude Code'side) is available without a declaration and is never flagged. - Skip the plugin-namespaced form. Claude Code rewrites a plugin's own MCP
tool to
mcp__plugin_<plugin>_<server>__<tool>(observed on han's playwright-mcp). Those segments join ambiguously and the ref is by construction the plugin's own server, so the form is left uninterpreted.
Configuration
{ "rules": { "mcp-tool-resolves": "warn" } }
Severity
| Value | Behavior |
|---|---|
"error" |
vigiles lint exits non-zero (2) on an unresolvable MCP tool |
"warn" (default) |
Prints a warning, exits 0 |
false |
Skip the check |
Scope
Subagent contracts (agents/*.md tools:). Reads declared servers from
.mcp.json and the plugin manifest (.claude-plugin/plugin.json). Codex's TOML
[mcp_servers] is not yet parsed (a documented gap — the JSON Claude Code shape
is the common case).
A skill's allowed-tools is a different namespace and is deliberately not
checked here, mirroring subagent-tool-contract.
Why
An mcp__* tool is how a subagent reaches an MCP server's capability. A contract
naming a server the plugin forgot to declare (or typo'd) is a silent capability
gap — the tool simply isn't there at runtime. Catching it deterministically,
before it ships, completes the cross-referencing across the whole tool
surface.
See also
- subagent-tool-contract — the built-in half of the check.
- mcp-config — a declared MCP server that can't start.