Skip to content

feat(agents): select an agent's own tools at deploy, and claude-code's - #437

Merged
earakely-scale merged 7 commits into
mainfrom
edgararakelyan/fd-4351-m26-claude-code-limited-to-the-computer-envs-mcp-tools
Nov 2, 2026
Merged

earakely-scale merged 7 commits into
mainfrom
edgararakelyan/fd-4351-m26-claude-code-limited-to-the-computer-envs-mcp-tools

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What

A task can now choose which of an agent's own tools it may use, at deploy:

{"id": "agent", "type": "deploy_agent", "env_ids": ["desktop"], "builtin_tools": {"disable": "*"}}

{"disable": "*"} turns off every built-in tool, {"disable": ["WebFetch"]} just those, and {"only": ["Read"]} keeps just those. The bound envs' MCP tools, and agentenv's own when a prompt asks for them, are not the agent's own tools and are never affected. The bench needs {"disable": "*"} for its GUI-only rule.

  • The selection is agent configuration, so it travels on agent-config/v1:
    • The protocol's new BuiltinTools types the field over a Literal of the agent's tools, so the card's agent-config schema names them.
  • deploy_agent takes builtin_tools and checks its shape when the step is built.
    • Before any request to the agent, it refuses the step if the agent's agent-config has no builtin_tools, or if the selection names a tool the card's schema doesn't (for example "'Glob' is not one of [...]").
    • It sends the selection with the name and role, and records it on the agent's card in the run.
    • prompt_agent sends it again with every turn's config, so an agent whose container restarted since its deploy doesn't get its own tools back. Chaos testing caught this: after a restart between two turns, the second turn offered all the CLI's tools.
  • claude-code names all 27 of the pinned CLI's tools.
    • The names were recorded from the CLI itself. Which ones it offers by default depends on the model (the fake model gets no Task tools), and the three MCP resource tools come only with MCP servers. The model sees the subagent tool as Agent, which --tools also accepts.
    • "*" and only become a --tools list (--tools "" for none). A disable list becomes --disallowedTools, so the other tools keep the CLI's defaults rather than all being turned on.
    • Any selection adds --strict-mcp-config, so MCP servers from the agent's own settings files go too.
    • The image installs the newest released protocol, which has no BuiltinTools yet, so claude-code carries a copy of the type (ClaudeCodeTools in claude_bridge.py) until its protocol floor can be raised. A test holds the copy to the protocol type's schema.
    • TOOLS_CLI_VERSION records the CLI the tool names came from, and a test fails when the Dockerfile's CLAUDE_CODE_VERSION moves without it.
  • Codex, Gemini and the other agents don't take the field yet, so a selection for them is refused.
  • The A2A agent validator checks an agent honours it (verify_a2a_builtin_tools). Otherwise an agent could advertise builtin_tools without enforcing it and still pass validation.
    • It deploys a second agent with {"disable": "*"} and no envs, so any tool call it makes is one of its own. Asked to run a shell command, it must make none, or, if it reports no count, reply that it has no tool.
    • An unknown tool must be refused, and agent-config must read the selection back.
    • The validation agent itself is never limited, since the other checks need its tools. An agent without the field is recorded as not supporting it.

Tests

  • Protocol: BuiltinTools on an agent's card: its names in the schema, exactly one of disable and only, and an unknown name answered with a 400.
  • claude-code:
    • the CLI flags for no selection, "*", an only list, an empty only list and a disable list;
    • a tool the CLI doesn't have is refused, and the card lists builtin_tools;
    • the copied type's schema equals the protocol's;
    • the tool names' CLI version equals the Dockerfile's.
  • deploy_agent:
    • the selection is sent once, with the identity;
    • an agent without the field, or a tool name its schema doesn't list, is refused before any request;
    • the selection's shape is checked when the step is built.
  • prompt_agent: every turn sends the selection deploy_agent recorded; an agent deployed without one is sent none.
  • Validator:
    • an agent without the field is recorded as unsupported, with nothing deployed;
    • an agent that honours it passes;
    • a limited turn with tool calls fails;
    • an agent reporting no count must answer NO-TOOLS.
  • Integration (run_bundle_claude_code_test.py): the shipped claude-code against the fake Anthropic endpoint. Each case first runs with no selection to record the CLI's defaults in that environment.
    • The CLI never offers a tool its card doesn't name.
    • With "*", it offers only the echo env's MCP tool, and the call to it still works.
    • With {"disable": ["WebFetch"]}, it offers the defaults minus WebFetch.
    • With {"only": ["Read", "TaskList"]}, it offers just those two, including a tool that's off by default.

Part of FD-4351 (M2.6). Design doc: CUA Env: claude-code limited to MCP tools (M2.6).

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no blocking findings remain.

Summary

Adds builtin_tools selection at deploy and keeps it in each turn’s config. Claude Code turns the selection into CLI flags, while the validator checks that agents honor it.

  • Tasks can choose which of an agent’s own tools stay available.
  • Claude Code applies tool selections to its CLI without removing task MCP tools.
  • Agent checks now test whether a runtime honors tool selections.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["deploy_agent: builtin_tools"] --> B["Check agent card schema"]
  B --> C["Send config and record selection"]
  C --> D["prompt_agent resends selection each turn"]
  D --> E["Claude Code applies CLI tool flags"]
Loading

Reviews (6) · Last reviewed commit: "test: drop an unused import from the bui..." · Reviewed by Greptile

earakely-scale and others added 2 commits November 1, 2026 17:51
A task can now say which of an agent's own tools it may use: deploy_agent takes builtin_tools, {"disable": "*"},
{"disable": [names]} or {"only": [names]}. It travels on agent-config/v1, as configuration does. The protocol's
BuiltinTools types the field over a Literal of the agent's tools, so the card's schema names them, and its enabled()
says what a selection leaves on. deploy_agent refuses, before any request, an agent whose agent-config has no
builtin_tools or a selection naming a tool its schema doesn't.

claude-code names the pinned CLI's 24 tools and turns a selection into --tools with the tools left on, plus
--strict-mcp-config, so its model is offered only those and the task env's MCP servers. The bench needs
{"disable": "*"} for its GUI-only rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ploy_agent's check needs no A2AAgent

The claude-code image installs the newest released agentenv-framework-protocol, which has no BuiltinTools yet, so
the agent declares the same fields and checks itself until its floor can be raised. The builtin_tools check takes
the agent-config extension from its caller, since deploy_agent imports A2AAgent only inside execute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 2, 2026 02:41
earakely-scale and others added 3 commits November 1, 2026 18:47
…to turn off keeps the rest at their defaults

In the agent's container the CLI's default tools depend on the model (the fake one gets no Task tools) and gain the
three MCP resource tools when MCP servers are attached, so the card names every tool the CLI has, and the test
checks the CLI offers nothing it doesn't name. A disable list now goes to --disallowedTools, so the rest keep the
CLI's defaults instead of every named tool being turned on; "*" and only stay a --tools list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…every turn

Chaos testing restarted the claude-code container between two turns: the second turn offered all the CLI's own
tools again, since the selection lived only in the agent's memory. prompt_agent now sends the selection deploy_agent
recorded on the agent's card with each turn's config, so a restarted agent has it before the turn runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent whose agent-config takes builtin_tools could advertise it and never enforce it, and validation would pass.
The new verify_a2a_builtin_tools step deploys a second agent with {"disable": "*"} and no envs, so any tool call it
makes is one of its own: asked to run a shell command it must make none (or, reporting no count, say it has no tool),
an unknown tool must be refused, and agent-config must read the selection back. The validation agent itself is never
limited, since the other checks need its tools. An agent without the field is recorded as not supporting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale and others added 2 commits November 1, 2026 21:00
…eld to the protocol and the pinned CLI

- ClaudeCodeTool and ClaudeCodeTools move into claude_bridge, so the CLI command takes the typed selection instead of
  a dict a2a_server dumped and the bridge read back by key.
- A test holds ClaudeCodeTools to the protocol's BuiltinTools[ClaudeCodeTool] schema, so the copy can't drift.
- TOOLS_CLI_VERSION records the CLI the tool names were taken from; a test fails when the Dockerfile's
  CLAUDE_CODE_VERSION moves without it.
- BuiltinTools loses enabled(), which nothing called.

Also from the validator e2e on a VM: the builtin_tools check now reads a selection back without the fields an agent
leaves unset (the protocol's readback returns them as null), and the episode rules count the new step among the
agent validator's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit ba199e3 into main Nov 2, 2026
25 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-4351-m26-claude-code-limited-to-the-computer-envs-mcp-tools branch November 2, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant