Skip to content

[FEATURE]: Granular per-agent tool permissions (allow/deny individual tools) #17607

Closed

Description

@Jehu

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Note: #5529 requests per-agent filesystem boundaries (path-level sandboxing). Our request is complementary but different: we need tool-level granularity — controlling which specific tools (built-in and MCP) each agent can access, not which filesystem paths.

Describe the enhancement you want to request

Problem: OpenCode's per-agent permission config only supports coarse categories (allow, ask, deny for read, edit, bash, mcp). There is no way to allow/deny individual tools per agent.

For example, you cannot express:

  • Agent A may use mcp__muninn__* tools but not edit or bash
  • Agent B may use edit and bash but not mcp__muninn__*
  • Agent C may use mcp__github__* but not mcp__docker__*

Why this matters: In multi-agent orchestration systems, different agents have different roles with different trust levels. A PM agent should access project management tools but never edit code. A dev agent should edit code but not access CRM tools. Currently, MCP tools are all-or-nothing — mcp: "allow" grants access to every registered MCP server.

Current workaround: We use two layers:

  1. session.permission — blocks denied tool categories via permission dialog
  2. body.tools in the API message — pre-filters the tool list before the LLM sees it (using the deprecated but functional tools field)

This works but is fragile:

  • body.tools is deprecated and may be removed
  • Session permissions are too coarse (can't distinguish between MCP servers)
  • The workaround requires maintaining an allowlist externally

Proposed solution: Extend the per-agent permission config to support tool-level granularity:

{
  "agents": {
    "pm-agent": {
      "tools": {
        "allow": ["mcp__muninn__*", "mcp__huginn__*", "read"],
        "deny": ["edit", "bash", "mcp__docker__*"]
      }
    },
    "dev-agent": {
      "tools": {
        "allow": ["edit", "bash", "read", "glob", "grep"],
        "deny": ["mcp__muninn__*", "mcp__crm__*"]
      }
    }
  }
}

Key design points:

  • Glob patterns for MCP tool names (e.g., mcp__server__*)
  • allow acts as whitelist — only listed tools are available
  • deny acts as blacklist — all tools except listed are available
  • If both specified, deny takes precedence
  • Default behavior unchanged (all tools available) for backwards compatibility

Use cases:

  • Multi-agent orchestration with role-based tool access
  • Security isolation — prevent agents from accessing tools outside their domain
  • Compliance — audit which agents can access which external services
  • Reduced token usage — agents with fewer tools need less system prompt space

Related: #5529 (filesystem boundaries — complementary but different scope)

Environment: OpenCode v1.2.25. The user.tools filtering in the API still works as a workaround but the field is marked deprecated.

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 15, 2026
  2. github-actions commented on Mar 15, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  3. Jehu commented on Mar 15, 2026

    @Jehu
    Author

    Thanks for flagging the related issues. I've reviewed both:

    If #7399 gets implemented with the per-agent override pattern shown there, it would solve ~80% of our needs. We'd still want built-in tool filtering per agent, but that could be a follow-up.

    Happy to close this as a duplicate of #7399 if the maintainers prefer — or keep it open as an extension that covers built-in tools as well.

  4. Jehu commented on Mar 15, 2026

    @Jehu
    Author

    Bug Report: body.tools overwrites session permissions

    While working around the missing granular tool permissions, we discovered a concrete bug in the current implementation:

    Location: packages/opencode/src/session/prompt.ts (lines ~170-181)

    const permissions: PermissionNext.Ruleset = []
    for (const [tool, enabled] of Object.entries(input.tools ?? {})) {
      permissions.push({ permission: tool, action: enabled ? "allow" : "deny", pattern: "*" })
    }
    if (permissions.length > 0) {
      session.permission = permissions  // ← OVERWRITES, does not merge
      await Session.setPermission({ sessionID: session.id, permission: permissions })
    }

    Impact: When body.tools is sent on a message, it replaces the session-level permissions set via create_session(permission=...) instead of merging with them. This means:

    1. Any permissions set at session creation are lost on the first message that includes body.tools
    2. Tools not present in the body.tools dict (but denied at session level) revert to the default "ask" behavior — effectively bypassing the deny rules

    Our workaround: We stopped sending body.tools entirely and rely exclusively on create_session(permission=...) for tool access control.

    Suggested fix: Either merge body.tools permissions with existing session permissions, or document that body.tools is a full replacement (and ideally deprecate it in favor of session-level permissions only).

    This is directly relevant to this issue — even if per-agent tool filtering is implemented, the overwrite behavior would break any multi-message session where permissions are set at creation time.

  5. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. stuckinsnow commented on May 14, 2026

    @stuckinsnow

    Another vote for this. I think it should allow custom modes/agents in config that can be activated via session/set_mode, that way you can switch in the same session. But also, adding a prompt in the config would be worthwhile as well. For example...

    {
      "agent": {
        "reviewer": {
          "mode": "primary",
          "prompt": "You are in review mode. You are read-only. Report findings using the annotation tool.",
          "tools": {
            "read": true,
            "grep": true,
            "glob": true,
            "edit": false,
            "write": false,
            "bash": false
          },
          "mcp": {
            // Only these MCP server tools are visible to the model in this mode
            "annotation-server": true,
            "file-editor-server": false
          }
        },
        "scoped-editor": {
          "mode": "primary",
          "prompt": "Use the file_edit MCP tool to make changes. If rejected, move on.",
          "tools": {
            "edit": false,
            "write": false
          },
          "mcp": {
            "file-editor-server": true,
            "annotation-server": false
          }
        }
      }
    }

    Then an ACP client sends session/set_mode with {"modeId": "reviewer"} and the model's visible tools and system prompt switch accordingly, so it's more knowledgeable as to what happened in the switch.

  7. rekram1-node commented on Jun 23, 2026

    @rekram1-node
    Contributor

    the tools thing isnt recommended, u can use permissions to achieve all this, see our docs for example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions