Skip to content

fix(subagent): pass inherited MCP tools through to workers - #1296

Merged
TheGreatAxios merged 2 commits into
mainfrom
fix/mcp-passthrough-3a1d5f87
Oct 2, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
fix/mcp-passthrough-3a1d5f87

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Resolves CL-9704. Follow-up to CL-9721.

Problem

MCP tools never reached workers. Two layers stripped them, both fail-closed against the built-in catalog:

  • capability-preflight.ts: the static catalog loop returned unknown_tool for any mcp__* requirement.
  • run.ts applyCapabilityFilter: planner/coder envelopes are allowlists of built-ins only (BUILD_TOOLS), so inherited Linear tools were dropped at mount even past preflight.

CL-9704 (Linear MCP discoverable but not callable): the primary session already mounts connected MCP tools into the live registry and promotes them onto the wire on execute — this PR locks that discovery-to-invocation chain with an integration test (tool_search finds mcp__linear__list_teams, promote-on-execute commits its callable schema and dispatches, then mcp__linear__save_issue follows the same path).

Fix (on-demand, requires_tools-gated — not full auto-mount)

Workers do NOT mount every inherited MCP tool. An inherited mcp__* tool mounts only when the dispatch explicitly requests it:

  • capability-preflight.ts: new availableMcpTools input carries the live inherited-MCP set; an mcp__* name present there skips the catalog/allowlist checks. Absent mcp__* names still reject as unknown_tool (never inferred from shape), get no did-you-mean hint, and explicit excludes still withhold live MCP tools as permission_static. Preflight grants exactly the named requirement — a live sibling is never implied.
  • run.ts applyCapabilityFilter: retains an inherited MCP tool only when stamped in requiresTools or named in an allowlist capabilities.tools (intersection with the inherited set). Unrequested inherited MCP tools are dropped in every mode — allow, exclude, and full mount. An explicit exclude still withholds a requested live MCP tool (surfaces as stale_snapshot at the mount echo, normally pre-empted by dispatch preflight).
  • agent-fleet.ts: dispatch threads the live inherited-MCP names (via deps.inheritMcpTools through the worker gate) into preflight, and stamps exactly the requested tools.
  • Primary session: promote-on-execute already declares exactly the called MCP name (search returns cards only and never pre-promotes); new mcp-promote-on-execute.test.ts pins list_teams -> save_issue end to end against a mock Linear server.

Caller note

A worker that wants an MCP tool must name it in requires_tools (validated pre-spawn against the live inherited set). Dispatch with intent=plan/agent=planner rather than general-purpose agents — registry.ts envelopes are by design built-ins-only, and MCP reachability now flows through inheritance + explicit request. On primary, call a searched mcp__* name and promote-on-execute commits its schema.

Verification

  • bun test on capability-preflight.test.ts + agent-fleet-requires-tools.test.ts + run-requires-tools.test.ts: 70 pass, 0 fail
  • bun test on mcp-promote-on-execute.test.ts + tool-search.test.ts + assemble-runtime.test.ts + exa-web-fetch-alias.test.ts: 95 pass, 0 fail
  • bun test src/subagent/: 752 pass, 0 fail
  • bun run typecheck: clean; bun run lint (oxfmt --check + oxlint): clean; bun run check:dead-exports: 0 violations

@TheGreatAxios
TheGreatAxios force-pushed the fix/mcp-passthrough-3a1d5f87 branch from e9e3d30 to 027ea02 Compare October 2, 2026 20:32
requires_tools entries for live inherited-MCP tools (mcp__<server>__<tool>) now validate against the parent's mounted set at dispatch instead of rejecting as unknown_tool, and the worker mount exempts inherited MCP tools from the built-ins-only allowlist strip. Unmounted mcp__ names still reject fail-closed, and explicit excludes still withhold live MCP tools.
@TheGreatAxios
TheGreatAxios force-pushed the fix/mcp-passthrough-3a1d5f87 branch from 027ea02 to 1fe2068 Compare October 2, 2026 20:38
@linear-code

linear-code Bot commented Oct 2, 2026

Copy link
Copy Markdown

CL-9704

On-demand narrowing stripped inherited MCP tools unless stamped in
requiresTools or named in an allowlist, breaking the inherit-by-default
contract: workers inheriting via inheritMcpTools with a parent grant
could no longer execute the inherited tool. Inheritance itself now
counts as the on-demand request - run.ts passes the live inherited MCP
set into applyCapabilityFilter, which retains those names when no
narrower constraint (capabilities/requiresTools) applies. Stamped
dispatches still narrow to the stamped subset, unmounted mcp__ names
still drop fail-closed, and explicit excludes still withhold.

The e2e probes also share worker id "worker" across fresh tmpdirs while
denied-call grant envelopes live in a process-shared store, so one
probe's pending envelope vetoed the next via the retry-from-another-
directory blocker. withWorker now clears the process grant store per
probe using the provided test-only seam.

Also ignore scratch __repro/ dirs (port from main).
@TheGreatAxios
TheGreatAxios merged commit 1ba2599 into main Oct 2, 2026
13 checks passed
@TheGreatAxios
TheGreatAxios deleted the fix/mcp-passthrough-3a1d5f87 branch October 2, 2026 20:50
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