Ctrl K
ComunidadGuías…

agents/subagentToolGates.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Single source of truth for which MCP tools subagents are forbidden from
 * calling — derived from each tool's `mutates` flag, not a hand-maintained list.
 *
 * Subagents share the orchestrator's in-process git working tree, `toolState`,
 * progress comment, and run-scoped pr/branch context. A subagent that calls
 * `checkout_pr` switches the orchestrator's HEAD; one that calls `push_branch`
 * pushes whatever the orchestrator happens to have committed. The 2026-05-18
 * `zed-industries/cloud` incident hit exactly this: a `reviewfrog` lens
 * dispatched `checkout_pr({2582})` mid-review, the orchestrator's next push
 * clobbered an unrelated engineer's branch. PR #796 added runtime backstops
 * inside `checkout_pr`/`push_branch`; this gate stops the call from ever
 * reaching MCP when it originates from a subagent.
 *
 * The denied set is whatever the run actually registers with `mutates: true`
 * (see `action/mcp/shared.ts`). To deny a new state-changing MCP tool to
 * subagents, mark it `mutates: true` at its definition — nothing to add here.
 * Read-only tools (`get_*`, `list_*`, `git_fetch`, …) and the general-purpose
 * `git`/`shell` execution tools are intentionally unmarked: denying them would
 * make review impossible, and their mutations are gated by command-arg
 * validation plus the reviewer system prompt (`action/agents/reviewer.ts`).
 *
 * The gate is enforced at two pre-tool hooks, each fed this derived list:
 *   - opencode: `tool.execute.before` (action/agents/opencodePlugin.ts)
 *   - claude:   `PreToolUse` settings hook (action/agents/claudePretoolGate.ts)
 * Names are the canonical bare form (the FastMCP tool `name`). Each runtime
 * presents them with a different prefix (`mcp__pullfrog__<name>` for claude,
 * `pullfrog_<name>` for opencode); the hooks strip the prefix before comparing.
 */

import { buildOrchestratorTools, type ToolContext } from "../mcp/server.ts";

/**
 * Canonical bare names of every state-mutating MCP tool registered for this
 * run — the set subagents are denied. Derived from the `mutates` flag so it can
 * never silently drift from the registered tool set. Throws if it derives empty
 * (a sign the flag plumbing broke) rather than starting with the gate disabled.
 */
export function subagentDeniedToolNames(
  ctx: ToolContext,
  outputSchema?: Record<string, unknown>
): string[] {
  const names = buildOrchestratorTools(ctx, outputSchema)
    .filter((toolDef) => toolDef.mutates)
    .map((toolDef) => toolDef.name);
  if (names.length === 0) {
    throw new Error(
      "subagent deny list derived empty — no MCP tool is marked `mutates: true`. " +
        "refusing to start with the subagent gate effectively disabled."
    );
  }
  return names;
}