Skip to content

Tool runtime and security architecture

This page is the architecture analysis for the tools/integrations/security module. It complements the implementation pages by focusing on module boundary, capability injection seams, and the trust pipeline rather than re-listing every tool name or permission string.

Scope: from a model-visible tool schema to either an executed action or a structured denial. Implementation specifics live in Tool runtime, events, and integration flows, Built-in tools and permissions, MCP, plugins, and hooks, and Settings, policy, and integrations.

Module purpose

This module owns the action side of the agent loop. It decides what capabilities exist, what becomes model-visible, who is allowed to invoke them, and how those decisions are propagated to SDK and remote hosts.

It deliberately combines three concerns that cannot be separated in practice:

  1. Tool catalog (built-in, MCP, plugin, external, skill, task tools).
  2. Trust pipeline (visibility filter → permission rules → hooks → tool-specific guards).
  3. Integration surface (MCP, plugins, IDE/Chrome/file, hooks, SDK, Remote Control).

Architecture thesis

The capability plane is built on a capability registry plus a single execution boundary:

  • Different sources (built-ins, MCP, plugins, skills, tasks, external definitions) all contribute through the same registry shape.
  • Every tool call passes through ToolExecutionBoundary, a mediated execution function that combines schema validation, hooks, permission decisions, host control requests, and tool-specific guards before invoking the underlying tool body.

This design makes adding a capability source cheap and adding a security control safe.

Source anchors

Semantic aliasSourceApproximate locationString or symbolArchitectural meaning
BuiltInToolNameConstantcli.jsline ~446, byte 0x309242var Rq="Bash"Built-in tool name constant; representative of the catalog shape.
CapabilityConstantGroupcli.jsline ~1091, byte 0x4804f6TaskCreate, TaskGet, TaskList, TaskUpdate, Skill, TodoWriteCapability constants grouped with skill/task tools.
ToolExecutionBoundarycli.jsline ~4202, byte 0x8a8140function U85Single tool-execution boundary; schema validate → permission decision → execute.
ToolUseRejectedTelemetrycli.jsline ~4202, byte 0x8a9617tengu_tool_use_can_use_tool_rejectedDenial telemetry inside the execution boundary.
ToolUseAllowedTelemetrycli.jsline ~4202, byte 0x8a9a89tengu_tool_use_can_use_tool_allowedAllow telemetry inside the same boundary.
PermissionDeniedRetryFeedbackcli.jsline ~4202, byte 0x8a9a2dThe PermissionDenied hook indicated you may retry this tool call.Denial feedback the model can act on.
PreToolUseAuthorizationHookcli.jsline ~3553, byte 0x876552hookPermissionResult, PreToolUsePreToolUse participates in authorization, not just notification.
CanUseToolBridgecli.jsline ~9564, byte 0xcc4986createCanUseToolHost/SDK/Remote Control bridge wrapping the same permission resolver.
PermissionDeniedFramecli.jsline ~9564, byte 0xcc4a2apermission_deniedSystem frame for deny-shortcut decisions sent to SDK hosts.
CanUseToolControlRequestcli.jsline ~9564, byte 0xcc4dabsendControlRequest({subtype:"can_use_tool"...})Ask path surfaces as a host control request.
McpRuntimeCoordinatorcli.jsline ~19294, byte 0xd917c8function fH9(H)MCP runtime coordinator; capability source for tools/resources/prompts.
McpCommandRegistrarcli.jsline ~9173, byte 0xbf3c40function rR4(H)MCP command tree; user-facing config surface for the same source.
PluginCommandRegistrarcli.jsline ~9297, byte 0xbfca96function fC4(H)Plugin command tree; injects agents/skills/hooks/MCP/output styles.
HookEventTaxonomycli.jsline ~185, byte 0x10b6b4Hook arrays (PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch, PermissionRequest, PermissionDenied, …)Hook event taxonomy used by both authorization and lifecycle.
SkillShellPolicySwitchcli.jsline ~185, byte 0x11a8f2disableSkillShellExecutionManaged-policy switch for skills/custom slash commands.
RemoteControlPolicySwitchcli.jsline ~185, byte 0x11a7e1disableRemoteControlManaged-policy switch for Remote Control entry.
PermissionPromptToolFlagcli.jsline ~19356, byte 0xdb0bc6--permission-prompt-toolPermission prompting delegated to a schema-bearing MCP tool.
ReadBeforeWriteGuardcli.jsline ~3226, byte 0x821507File has not been read yet. Read it first before writing to it.Tool-specific guard inside Edit/Write/NotebookEdit.
McpTimeoutGuardscli.jsline ~4980, byte 0x937240MCP_TIMEOUT, MCP_CONNECT_TIMEOUT_MSCapability-source timeouts; protect the execution boundary from slow servers.

Internal decomposition

flowchart TD
Builtins[Built-in tools] --> Registry[Capability registry]
McpTools[MCP servers via McpRuntimeCoordinator] --> Registry
Plugins[Plugins via PluginCommandRegistrar] --> Registry
Skills[Skills] --> Registry
Tasks[Task / subagent tools] --> Registry
External[External/SDK tool defs] --> Registry
Registry --> Filter[Visibility filter: --tools / --allowedTools / --disallowedTools / settings]
Filter --> Visible[Model-visible tool schema]
Visible --> ToolUse[Tool-use delta from model]
ToolUse --> Boundary[Tool execution boundary]
Boundary --> Schema[Schema validation]
Schema --> PreHook[PreToolUse hook]
PreHook --> Decision[Permission decision]
Decision -->|allow| Guards[Tool-specific guards]
Decision -->|deny| Denial[permission_denied frame and PermissionDenied hook]
Decision -->|ask| Host[createCanUseTool -> can_use_tool control request]
Host --> Decision
Guards --> Execute[Tool body]
Execute --> PostHook[PostToolUse / PostToolUseFailure / PostToolBatch]
Denial --> ModelFeedback[denial message to model and optional retry hint]
PostHook --> SessionEvents[session events / telemetry]
Sub-componentResponsibility
Capability registryNormalizes built-in, MCP, plugin, skill, task, and external tool definitions to a common shape.
Visibility filterApplies --tools, --allowedTools, --disallowedTools, settings, and managed policy to decide what the model sees.
Permission resolverCombines allow/deny rules, permission mode, hook output, host responses, and helper-tool prompts into one decision.
ToolExecutionBoundaryThe single place tool calls cross from “model-asked” to “actually-run.”
Hook dispatcherRuns PreToolUse/PostToolUse/PermissionDenied and related events at well-defined points.
McpRuntimeCoordinatorConnects always-load, regular, and claude.ai connector groups; bridges elicitation completion.
PluginCommandRegistrarLoads plugin-provided agents, skills, hooks, MCP servers, output styles, and slash commands.
Integration adaptersIDE auto-connect, Chrome, file-resource startup, status line, helper scripts.

Public interface

Inputs

SurfaceEffect
--tools, --allowedTools/--allowed-tools, --disallowedTools/--disallowed-tools, --permission-mode, --permission-prompt-tool, --dangerously-skip-permissionsShape visibility and approval behavior.
--mcp-config, --strict-mcp-config, claude mcp ...Configure MCP servers and connector behavior.
--plugin-dir, --plugin-url, claude plugin ...Load session-only or marketplace plugins.
--ide, --chrome, --fileActivate IDE/Chrome/file integration sources.
.claude/settings.json, settings.local.json, managed settingsPersistent allow/deny, hook config, plugin trust, policy switches.
Environment: MCP_TIMEOUT, MCP_CONNECT_TIMEOUT_MS, MCP_CONNECTION_NONBLOCKINGCapability-source timeouts and connection mode.

Outputs

OutputConsumer
Tool result (success or error)Model loop.
permission_denied frameSDK hosts and Remote Control bridge.
can_use_tool control request and permission_responseInteractive host or SDK approver.
Hook calls (PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch, PermissionRequest, PermissionDenied)Hook scripts/commands, telemetry.
MCP elicitation completion framesHeadlessFrameMultiplexer.
code_edit_tool.decision and tengu_tool_use_* telemetryTelemetry sinks.

Internal collaborators

CollaboratorContract
Context/model loopEmbeds tool metadata into the model-visible request; sends tool-use deltas to the boundary.
Sessions moduleRecords tool starts/results/errors as session events; resume restores tool/permission state.
Runtime lifecycleProvides settings, managed policy, and CLI permission flags before the loop starts.
Remote/bridge moduleUses createCanUseTool to route ask/deny decisions to remote approvers and hosts.
Hooks subsystemReceives lifecycle events; can mutate input, authorization, and additional context.
Telemetry/opsRecords denial/allow telemetry, MCP auth errors, code-edit decisions, and timeouts.

Design decisions

  1. Single boundary, multiple sources. Every tool call goes through ToolExecutionBoundary regardless of source. This keeps telemetry, hooks, and permission policy consistent for built-in, MCP, plugin, skill, and task tools.
  2. PreToolUse participates in authorization. Hooks can allow, ask, deny, defer, supply updatedInput, or add additionalContext. Treating hooks as authorization (not just notification) lets policy logic live outside the bundle.
  3. Permission decisions are tri-modal. Allow/deny shortcut directly through telemetry; ask is surfaced as a structured can_use_tool control request. SDK hosts only see ask flows; deny is observable but never blocks on a host round-trip.
  4. Deny is opinionated, not silent. A PermissionDenied hook can return retry information, which is converted into a model-visible meta message: “The PermissionDenied hook indicated you may retry this tool call.” This keeps the model loop self-correcting.
  5. MCP and plugins are first-class capability sources. They go through the same registry and visibility filter as built-ins; they cannot bypass the boundary.
  6. Required vs optional MCP. McpRuntimeCoordinator splits configs into alwaysLoad and normal groups so essential capabilities are present before the model runs, while optional servers can defer or fail without blocking startup.
  7. Helper tools can prompt for permission. --permission-prompt-tool lets an MCP tool with a JSON schema be the approval UI; this keeps the runtime’s approval channel pluggable.
  8. Managed policy can disable extension points. disableSkillShellExecution, disableRemoteControl, disableAgentView, and similar switches are intentionally part of the trust pipeline; they are not separate code paths.
  9. Tool-specific guards complement permissions. Edit/Write/NotebookEdit require a prior Read; WebFetch enforces domain: syntax; WebSearch rejects wildcards. These are local invariants, not permission rules.

Trust pipeline summary

flowchart TD
Source[capability source] --> Visible{visible?}
Visible -->|no| Hidden[not in model schema]
Visible -->|yes| Call[tool call]
Call --> Schema[schema validation]
Schema --> PreHook[PreToolUse hook]
PreHook -->|allow / updatedInput| Decision[permission resolver]
PreHook -->|deny| Block[deny path]
PreHook -->|ask| Host[can_use_tool control_request]
PreHook -->|defer| Decision
Host --> Decision
Decision -->|allow| Guards[read-before-write / web syntax / mcp timeout]
Decision -->|deny| Block
Guards --> Run[tool body]
Run --> Events[PostToolUse / telemetry / session events]
Block --> Frame[permission_denied frame + PermissionDenied hook]
Frame --> ModelMeta[model meta message + optional retry hint]

The pipeline is intentionally one-way except for the ask loop. Hooks can shape the decision, but they cannot bypass the registry or the boundary.

Failure modes

FailureBehavior
Tool input fails schema validationToolExecutionBoundary returns a structured error before any hook or permission decision; no execution.
MCP tool returns 401 / token expiredtengu_mcp_tool_call_auth_error is emitted and a user-facing reauth error is raised.
MCP server slow or unreachableMCP_TIMEOUT/MCP_CONNECT_TIMEOUT_MS enforce limits; coordinator can retry transient remote failures.
Hook script crashes or hangsThe boundary handles the missing/invalid result; lifecycle continues with a denial or a deferred decision.
--permission-prompt-tool references a missing or non-MCP toolRuntime writes an error and exits; this is enforced before the loop starts.
File edit without prior readRead-before-write guard rejects with a precise model-facing message asking for a refresh Read.
Managed policy disables a capability mid-sessionVisibility filter recomputes; in-flight calls complete but new calls become invisible.

Extension points

ExtensionHow it plugs in
New built-in toolRegister a constant and definition into the capability registry; rely on the existing visibility filter and boundary.
New MCP serverAdd via --mcp-config, settings, or plugin; runtime coordinator handles connection, deduplication, and elicitation.
New plugin capabilityUse plugin schema (agents, skills, hooks, mcpServers, outputStyles, lspServers); do not register tools directly.
New hook eventExtend the hook event array and ensure the relevant runtime point emits it.
Custom approval UIImplement an MCP tool with a JSON schema and pass it through --permission-prompt-tool.
Org policyUse managed settings switches (disableSkillShellExecution, disableRemoteControl, …) rather than patching the boundary.

Caveats

  • The capability registry’s exact internal shape is bundled; this page documents what the registry must produce, not the precise object layout.
  • Hook dispatch order and concurrency rules are implementation-defined for related events (PostToolBatch vs many PostToolUse); rely on the implementation pages for current details.
  • --dangerously-skip-permissions is intentionally a sharp tool; documents should not describe it as a normal operating mode.

Created and maintained by Yingting Huang.