Studio Notebook

Claude Code Atlas

Tool Core Contract And Data Models

Learn the shared contract in `Tool.ts`, the runtime context every tool receives, and the defaults that `buildTool()` fills in.

Why this matters

Before you study the registry chapter or the permission chapter, you need one simple mental model: every concrete tool in Claude Code plugs into the same contract. Tool.ts is the file that defines that contract.

Big picture first

Tool.ts does four jobs that later chapters build on:

  1. it defines the session-wide permission shape with ToolPermissionContext
  2. it defines the per-call runtime backpack with ToolUseContext
  3. it defines the shared Tool interface and the Tools collection type
  4. it exports buildTool() so tool authors can start from safe defaults

The companion file types/permissions.ts holds the permission decision types that Tool.ts imports to avoid circular dependencies. That split matters later when you trace permission flow, but the first-pass lesson is simpler: tools, contexts, and permission outcomes are designed to fit together.

Start with the two recurring context objects

ToolPermissionContext

The session-level policy object shared by every tool call in the current session.

export type ToolPermissionContext = DeepImmutable<{
  mode: PermissionMode
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
  alwaysAllowRules: ToolPermissionRulesBySource
  alwaysDenyRules: ToolPermissionRulesBySource
  alwaysAskRules: ToolPermissionRulesBySource
  isBypassPermissionsModeAvailable: boolean
  isAutoModeAvailable?: boolean
  strippedDangerousRules?: ToolPermissionRulesBySource
  shouldAvoidPermissionPrompts?: boolean
  awaitAutomatedChecksBeforeDialog?: boolean
  prePlanMode?: PermissionMode
}>
export const getEmptyToolPermissionContext: () => ToolPermissionContext =
  () => ({
    mode: 'default',
    additionalWorkingDirectories: new Map(),
    alwaysAllowRules: {},
    alwaysDenyRules: {},
    alwaysAskRules: {},
    isBypassPermissionsModeAvailable: false,
  })

This is not one tool call. It is the policy environment around all tool calls. If a session changes mode or adds allow rules, this object changes and the same tool may behave differently.

ToolUseContext

The runtime bundle that one concrete tool invocation receives.

export type ToolUseContext = {
  options: {
    commands: Command[]
    debug: boolean
    mainLoopModel: string
    tools: Tools
    verbose: boolean
    thinkingConfig: ThinkingConfig
    mcpClients: MCPServerConnection[]
    mcpResources: Record<string, ServerResource[]>
    isNonInteractiveSession: boolean
    agentDefinitions: AgentDefinitionsResult
    maxBudgetUsd?: number
    customSystemPrompt?: string
    appendSystemPrompt?: string
    querySource?: QuerySource
    refreshTools?: () => Tools
  }
  abortController: AbortController
  readFileState: FileStateCache
  getAppState(): AppState
  setAppState(f: (prev: AppState) => AppState): void
}

The exported type keeps going after messages: Message[]. The opening slice above shows the front of the object. Later in the same type, Tool.ts continues with fields for transcript state and guardrails:

  messages: Message[]
  fileReadingLimits?: {
    maxTokens?: number
    maxSizeBytes?: number
  }
  globLimits?: {
    maxResults?: number
  }
  toolDecisions?: Map<
    string,
    {
      source: string
      decision: 'accept' | 'reject'
      timestamp: number
    }
  >

Think of ToolUseContext as the backpack that travels with one call. It carries the tool list, the current app state, the abort signal, and the current conversation messages into that call. The full exported type keeps adding more session hooks, progress callbacks, and execution limits after these slices.

The contract every tool satisfies

The full Tool interface is large because it covers execution, permissions, and UI rendering. These two source excerpts show the parts to learn first:

export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  call(
    args: z.infer<Input>,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
    onProgress?: ToolCallProgress<P>,
  ): Promise<ToolResult<Output>>
  description(
    input: z.infer<Input>,
    options: {
      isNonInteractiveSession: boolean
      toolPermissionContext: ToolPermissionContext
      tools: Tools
    },
  ): Promise<string>
  readonly inputSchema: Input
  readonly inputJSONSchema?: ToolInputJSONSchema
  outputSchema?: z.ZodType<unknown>
  inputsEquivalent?(a: z.infer<Input>, b: z.infer<Input>): boolean
  isConcurrencySafe(input: z.infer<Input>): boolean
  isEnabled(): boolean
  isReadOnly(input: z.infer<Input>): boolean
  isDestructive?(input: z.infer<Input>): boolean
  checkPermissions(
    input: z.infer<Input>,
    context: ToolUseContext,
  ): Promise<PermissionResult>

  prompt(options: {
    getToolPermissionContext: () => Promise<ToolPermissionContext>
    tools: Tools
    agents: AgentDefinition[]
    allowedAgentTypes?: string[]
  }): Promise<string>
  userFacingName(input: Partial<z.infer<Input>> | undefined): string
  readonly name: string
  maxResultSizeChars: number
  readonly strict?: boolean

Tool.ts also defines:

export type Tools = readonly Tool[]

That one-line alias matters because it makes “the tool pool for this session” feel like a first-class concept instead of an unnamed array.

Field Meaning First Pass
name Stable identifier used when the model asks to call a tool. Very important
inputSchema The Zod schema that defines the JSON input the model is allowed to send. Very important
call The function that does the actual work after validation and permission checks. Very important
checkPermissions Tool-specific permission logic layered on top of the shared permission system. Very important
prompt The prompt fragment that tells the model how to use the tool correctly. Helpful after the basics
maxResultSizeChars The cutoff that decides when a large tool result should be persisted to disk instead of sent in full. Helpful after the basics

Why buildTool() exists

Concrete tool files usually do not want to re-implement the same default behavior over and over. Tool.ts handles that with ToolDef, TOOL_DEFAULTS, and buildTool():

export type ToolDef<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = Omit<Tool<Input, Output, P>, DefaultableToolKeys> &
  Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>
const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?: unknown) => false,
  isReadOnly: (_input?: unknown) => false,
  isDestructive: (_input?: unknown) => false,
  checkPermissions: (
    input: { [key: string]: unknown },
    _ctx?: ToolUseContext,
  ): Promise<PermissionResult> =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
  toAutoClassifierInput: (_input?: unknown) => '',
  userFacingName: (_input?: unknown) => '',
}
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}

This is the key idea: tool authors write the interesting parts, while buildTool() fills in the boring defaults. That keeps the contract consistent across dozens of tool files.

Takeaways

  • Tool.ts is the shared vocabulary file for the entire tools subsystem.
  • ToolPermissionContext is session policy, while ToolUseContext is the runtime backpack for one call.
  • buildTool() keeps tool implementations smaller by filling in default methods consistently.