Skip to content

[Feature] Let ACP clients disable the built-in browser tool (and other tools) per session without editing user config #1890

Description

@addyCooks

Description

An ACP client that provides its own browser has no way to turn off Nanocoder's built-in browser tool for its sessions. The only switch is the top-level disabledTools array in the user's config, which a client should not edit.

I'd like a way for an ACP client to say, when it creates or resumes a session, "don't offer these built-in tools in this session", and for Nanocoder to enforce that and tell the client what it applied.

Tracked from Nano-Collective/nanocoder-desktop#12 (item 4). Related: #1889 (per-session tool registry for client mcpServers) and #1857 (mode changes mid-turn).

Use Case

Nanocoder Desktop has its own in-app browser with its own rules: localhost by default, public sites only after the user opts in, and agent control of public sites only in normal mode. In a desktop session the agent could use Nanocoder's built-in browser tool (added in #1626) instead and skip those rules. The tool can reach public sites (it blocks private and metadata addresses). It asks for approval, except when it is in alwaysAllow, the session is in yolo mode, or the run is headless.

The same applies to any ACP client with its own browser, such as an editor extension that embeds a preview.

Today the only way to turn the tool off is disabledTools in the user's agents.config.json. That is the user's own configuration, and a client rewriting it would leave the tool disabled outside that client.

The desktop needs this before its Milestone 3 (agent control of its browser).

Proposed Solution

Let a client pass a per-session list of disabled tools. My suggestion is _meta on session/new, session/load and session/resume, for example _meta: {"nanocoder/disabledTools": ["browser"]}. ACP reserves _meta for extensions, so no protocol change is needed.

Behavior:

  1. The session's list is merged with the user's global disabledTools. It never replaces it.
  2. The tools are removed from the session's tool list and system prompt, as disabledTools does today.
  3. They are also rejected at execution. If the model calls a disabled tool anyway, the call returns an error and nothing runs.
  4. Subagents started from that session respect the same list.
  5. Other sessions in the same process are unaffected.
  6. The user's config files are never read or modified by this.
  7. Unknown tool names in the list are ignored with a debug log.
  8. The client can verify it. Nanocoder reports the effective disabled list for the session in the _meta of the session/new, session/load and session/resume response. Older versions ignore unknown _meta silently, so without this a client cannot tell whether the request was honored.
  9. The list is not persisted in the session file. The client sends it on every new, load and resume.

Alternatives Considered

  • A process-level switch (a CLI flag such as --disable-tool browser, or an environment variable) for nanocoder --acp. It is simpler, and it would be enough for the desktop, which starts one process per project. But it applies to every session in the process and can't change on session/load or session/resume. If the maintainers prefer it, the same enforcement and reporting should apply.
  • A session/set_config_option entry. More plumbing and not needed for this use case.
  • Route the built-in browser tool to the client's browser. A bigger change, and it ties Nanocoder's tool to a client capability.
  • Accept two browsers and document the difference. This leaves the client's browsing rules bypassable, which is the problem.
  • Edit the user's disabledTools. Ruled out: it conflicts with Nanocoder owning its configuration and leaves the tool disabled outside the client.

Additional Context

  • I have searched existing issues to ensure this is not a duplicate
  • This feature aligns with the project's goals (local-first AI assistance)

Current behavior, checked against main at fc01e23:

  • source/tools/tool-manager.ts:273: getAvailableToolNames(tuneConfig, developmentMode, disabledTools, model) already accepts a disabledTools override, but it replaces the global list: disabledTools ?? getAppConfig().disabledTools.
  • source/acp/acp-agent.ts (~L1058) and source/acp/acp-conversation.ts (~L305) both call it with undefined, so ACP always uses the global list.
  • source/subagents/subagent-executor.ts (~L425) reads only getAppConfig().disabledTools.
  • source/hooks/chat-handler/utils/tool-filters.ts (~L52): partitionToolCalls only checks toolManager.hasTool(name), i.e. that the tool exists in the registry. A tool hidden by disabledTools still runs if the model emits the call.
  • source/tools/browser.tsx (~L86): browser asks for approval unless it is in alwaysAllow or the run is headless. resolveToolApproval (source/tools/approval-policy.ts) also skips approval in yolo mode.
  • browser is already excluded in plan mode.
  • docs/configuration/index.md describes disabledTools as global only.

Implementation Notes (optional)

Sketch: store the session's list on AcpSession when it is registered or resumed. Pass the merged list (global plus session) wherever the ACP code calls getAvailableToolNames, make the execution-time check reject any tool that is not in the session's available list, and pass the same merged list into the subagent executor. Add the effective list to the response _meta.

This overlaps with #1889 (a per-session tool registry). If that lands first, the disabled list can live in the same per-session structure. The two can also be developed independently.

Acceptance criteria:

  • A client can pass disabledTools in _meta on session/new, session/load and session/resume.
  • Those tools are absent from the model's tool list and the system prompt in that session only.
  • A disabled tool the model calls anyway is rejected, not executed.
  • The user's global disabledTools still applies. The lists are merged.
  • Subagents from that session respect the list.
  • Another session in the same process still has the tool.
  • The effective list is reported in the response _meta so a client can verify it.
  • The list is not persisted, and the user's config files are not read or written for this.
  • Docs describe the _meta key.

Test plan:

  • Unit: merging the global and session lists, and unknown names.
  • ACP spec: with browser disabled it is not offered, and a forced browser call returns an error without running.
  • ACP spec: two sessions where only one disables the tool.
  • ACP spec: the response _meta reports the effective list.
  • Subagent spec: a subagent from a disabling session does not get the tool.

Out of scope: routing the built-in browser to a client's browser, changes to the browser tool's own approval or address-blocking rules, and fetch_url and web_search, which also bring web content in and are a separate question.

Activity

  1. added
    architectureNeeds a structural design change
    area:toolsTool implementations and tool-calling
    area:agentAgent loop: context, compaction, subagents, memory, sessions
    on Oct 26, 2026
  2. self-assigned this
    on Oct 26, 2026
  3. added
    needs-triageQueued for automated issue triage
    area:vscodeVS Code extension and host integration
    and removed
    needs-triageQueued for automated issue triage
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

architectureNeeds a structural design changearea:agentAgent loop: context, compaction, subagents, memory, sessionsarea:toolsTool implementations and tool-callingarea:vscodeVS Code extension and host integrationenhancementNew feature or requesthelp wantedExtra attention is needed

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions