Skip to content

feat(permissions): allowed_tools / disallowed_tools schema-level visibility#246

Merged
emal-avala merged 1 commit intomainfrom
feat/config-tool-visibility-filter
Apr 24, 2026
Merged

feat(permissions): allowed_tools / disallowed_tools schema-level visibility#246
emal-avala merged 1 commit intomainfrom
feat/config-tool-visibility-filter

Conversation

@emal-avala
Copy link
Copy Markdown
Member

Summary

Adds permissions.allowed_tools and permissions.disallowed_tools — schema-level tool visibility filtering. Tools on the denylist (or off a non-empty allowlist) are hidden from the model's schema before the LLM sees them, not just gated at call time.

Problem this closes

The existing permissions.rules surface gates authorization at call time — the tool is still in the schema, the model still considers calling it, and a denial fires permission_denied + the model sees an error. Useful, but:

  • The model still spends tokens "considering" the restricted tool on every turn.
  • A 50-tool MCP namespace you want to hide completely still burns ~hundreds of tokens of schema per turn.
  • The model can try creative workarounds because it knows the tool exists.

Schema-level filtering removes the tool entirely.

Config

[permissions]
allowed_tools    = ["FileRead", "Grep", "mcp__*"]
disallowed_tools = ["Bash", "WebFetch"]

Semantics

  • Empty allowlist = all tools pass (backwards-compatible default).
  • Non-empty allowlist = strict — only listed tools are visible.
  • Deny wins over allow — safe to add to the denylist without auditing overlaps.
  • Trailing * wildcard matches prefixes (e.g. mcp__* hides every MCP-namespaced tool). * in other positions is treated literally — matcher is deliberately minimal for readability.
  • Applied in schemas(), core_schemas(), and deferred_names() — so ToolSearch can't resurrect a hidden tool.

Wiring

main.rs installs the filter on the ToolRegistry immediately after default_tools() and before MCP discovery. The registry re-filters on each schemas() call, so there's no ordering dependency with later tool registrations.

Tests (9 new registry + schema updates)

  • Empty filter allows everything
  • Allowlist restricts to listed names
  • Denylist wins over allowlist
  • Trailing-* wildcard matches prefix
  • Denylist wildcard hides whole namespace
  • Non-trailing * is treated literally (explicit restriction test)
  • schemas() / core_schemas() / deferred_names() all honor the filter

Full tools::registry + permissions_integration suites pass. Clippy clean under -D warnings.

Test plan

  • cargo test -p agent-code-lib --lib tools::registry
  • cargo test -p agent-code-lib --test permissions_integration
  • cargo clippy --workspace --tests --no-deps -- -D warnings
  • cargo fmt --all --check

…bility

Adds two new `PermissionsConfig` fields that hide tools from the
model's schema before the LLM ever sees them:

    [permissions]
    allowed_tools    = ["FileRead", "Grep", "mcp__*"]
    disallowed_tools = ["Bash", "WebFetch"]

Distinct from the existing `rules` surface. Rules gate authorization
at call time — the tool is still in the schema, the model still
considers calling it, and a denial fires permission_denied + the
model sees an error. Visibility filtering removes the tool from the
schema entirely, so:

- The model never considers it (can't hallucinate a workflow around
  a tool that isn't there).
- Prompt token budget is reclaimed (for large MCP surfaces this can
  be hundreds of tokens per turn).

Semantics:
- Empty allowlist = all tools pass; non-empty = strict allowlist.
- Denylist always wins over allowlist — operators can safely add
  denylist entries without auditing the allowlist for overlaps.
- Trailing `*` wildcard matches name prefixes (e.g. `mcp__*` hides
  every MCP-namespaced tool). `*` in other positions is treated
  literally — the matcher is deliberately minimal.
- Applied in `schemas()`, `core_schemas()`, and `deferred_names()`,
  so ToolSearch can't resurrect a hidden tool either.

Wired from `config.permissions.allowed_tools` /
`config.permissions.disallowed_tools` into the `ToolRegistry` at
CLI startup, after default tools are registered but before MCP
proxy tools are appended. The registry re-filters on each
schemas() call so there's no ordering dependency with MCP
discovery.

Tests (9 new in registry, plus schema coverage):
- Empty filter allows everything
- Allowlist restricts to listed names
- Denylist wins over allowlist
- Wildcard matches prefix
- Denylist wildcard hides whole namespace
- Non-trailing `*` is treated literally
- schemas / core_schemas / deferred_names all honor the filter

Full test suite passes. Clippy clean.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@emal-avala emal-avala merged commit 32ca58a into main Apr 24, 2026
14 checks passed
@emal-avala emal-avala deleted the feat/config-tool-visibility-filter branch April 24, 2026 06:54
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