Skip to main content
ToolPermissionContext is a lightweight, immutable filter that determines whether a given tool name should be blocked before execution. It supports exact-name matching and prefix-based matching, both case-insensitive.
from src.permissions import ToolPermissionContext

ToolPermissionContext

A frozen dataclass. Construct instances via the from_iterables factory rather than directly.

Fields

deny_names
frozenset[str]
Exact tool names to block. All values are stored lower-cased at construction time.
deny_prefixes
tuple[str, ...]
Prefixes to block. Any tool whose lower-cased name starts with one of these prefixes is blocked. All values are stored lower-cased at construction time.

from_iterables

@classmethod
def from_iterables(
    cls,
    deny_names: list[str] | None = None,
    deny_prefixes: list[str] | None = None,
) -> ToolPermissionContext
Factory method. Normalises all inputs to lower-case and returns a frozen ToolPermissionContext.
deny_names
string[]
Exact tool names to block. Pass None or omit to allow all names.
deny_prefixes
string[]
Name prefixes to block. Pass None or omit to disable prefix filtering.
from src.permissions import ToolPermissionContext

ctx = ToolPermissionContext.from_iterables(
    deny_names=["BashTool"],
    deny_prefixes=["mcp_"],
)

blocks

def blocks(self, tool_name: str) -> bool
Returns True if tool_name should be blocked, False otherwise. A tool is blocked when either of the following is true:
  • Its lower-cased name is an exact member of deny_names.
  • Its lower-cased name starts with any value in deny_prefixes.
tool_name
string
required
The tool name to test. Comparison is always case-insensitive.
ctx = ToolPermissionContext.from_iterables(
    deny_names=["BashTool"],
    deny_prefixes=["mcp_"],
)

ctx.blocks("BashTool")        # True  — exact match
ctx.blocks("bashtool")        # True  — case-insensitive exact match
ctx.blocks("mcp_filesystem")  # True  — prefix match
ctx.blocks("FileReadTool")    # False — no match
ctx.blocks("MCP_something")   # True  — prefix match is case-insensitive

PermissionDenial

Recorded when a tool is blocked. Used in TurnResult.permission_denials and passed as denied_tools to submit_message.
from src.models import PermissionDenial

Fields

tool_name
string
required
The name of the tool that was blocked.
reason
string
required
Human-readable explanation for why the tool was blocked.
denial = PermissionDenial(
    tool_name="BashTool",
    reason="destructive shell execution remains gated in the Python port",
)

Integration with the tool surface

ToolPermissionContext integrates with get_tools from src.tools to pre-filter the tool list before routing:
from src.permissions import ToolPermissionContext
from src.tools import get_tools

ctx = ToolPermissionContext.from_iterables(
    deny_names=["BashTool", "ComputerTool"],
    deny_prefixes=["mcp_"],
)

# Only tools not blocked by ctx are returned
allowed_tools = get_tools(permission_context=ctx)
When used with QueryEnginePort.submit_message, pass blocked tools as PermissionDenial instances so the engine records the denial in the turn result:
from src.models import PermissionDenial
from src.query_engine import QueryEnginePort
from src.permissions import ToolPermissionContext

ctx = ToolPermissionContext.from_iterables(deny_names=["BashTool"])
candidate_tools = ["BashTool", "FileReadTool"]

denied = tuple(
    PermissionDenial(tool_name=t, reason="blocked by permission context")
    for t in candidate_tools
    if ctx.blocks(t)
)
allowed = tuple(t for t in candidate_tools if not ctx.blocks(t))

engine = QueryEnginePort.from_workspace()
result = engine.submit_message(
    "run the test suite",
    matched_tools=allowed,
    denied_tools=denied,
)

print(result.permission_denials)
# (PermissionDenial(tool_name='BashTool', reason='blocked by permission context'),)
PortRuntime.bootstrap_session automatically creates PermissionDenial entries for any matched tool whose name contains "bash". For custom gating logic, bypass bootstrap_session and call QueryEnginePort.submit_message directly with your own denied_tools.

Build docs developers (and LLMs) love