Skip to lesson

AnthropicClaude Certified Architect – Foundations2026-101.5 Apply Agent SDK hooks for tool call interception and data normalization

Normalize results without breaking tool shapes

Completed by the knowledge check

Learning objective

Normalize heterogeneous tool results in PostToolUse with shape-compatible updatedToolOutput before Claude reads them.

Core idea

A research agent that combines several Model Context Protocol (MCP) tools should not have to reinterpret each provider’s timestamp units and status codes on every turn. Put deterministic conversion in the result path, then give Claude a representation it can compare consistently. In the Claude Agent SDK, PostToolUse can replace a completed tool’s output through updatedToolOutput before that output reaches Claude.

Preserve the tool’s output contract while normalizing the data inside it. A consistent business record and a valid tool-result envelope solve different problems. Flattening every result into an arbitrary string can discard error evidence or produce a replacement the runtime rejects.

This is a read-side transformation. The tool has already executed. For preventing a refund or other side effect, use PreToolUse; see 1.5.1. A normalized result cannot undo the operation that produced it.

How it works

Intercept the completed result

Both Python and TypeScript Agent SDKs support PostToolUse callbacks. Register the callback in hooks under PostToolUse and use matcher to select the relevant tool names. For MCP tools, names follow mcp__<server>__<tool>. The illustrative research tools mcp__archive__lookup and mcp__registry__lookup are application-defined examples. A matcher containing a character other than letters, digits, _, -, |, , or spaces is an unanchored regular expression, so mcp__archive__.* also covers a tool the archive server adds later; an exact name does not.

The input contains tool_name, tool_input, and tool_response. In the TypeScript SDK, PostToolUseHookInput.tool_response is unknown, because its shape depends on the tool. Validate the result against the selected adapter’s contract before extracting a field. Do not assume an object cast proves that a timestamp or status is present.

Return the replacement under hookSpecificOutput, alongside hookEventName: "PostToolUse". Python hook output uses these same camelCase field names: updatedToolOutput is not spelled updated_tool_output. The Python and TypeScript reference pages both define it.

Preserve the envelope and change the payload

The replacement must fit the output shape of the tool you intercepted. Built-in tools use structured output objects. For example, the documented Bash result includes stdout, stderr, interrupted, and isImage. Replacing that object with a plain string does not satisfy its output schema.

For built-in tools, a replacement that fails schema validation is ignored and the original output is used. MCP output is passed through without that schema validation. That difference makes application validation more important for MCP, not less: the runtime accepting a value does not establish that the value preserves your tool contract.

For an application-owned MCP tool whose documented contract uses text content to carry a JSON record, normalize the JSON within that text content and preserve the surrounding result shape. Keep error markers and other meaningful fields. If the tool exposes the record in multiple representations, update them consistently rather than leaving contradictory copies.

The return envelope is the same regardless of the adapter’s domain fields. This illustrative callback fragment assumes normalizedResult has already been validated against the intercepted tool’s output contract:

return {
  hookSpecificOutput: {
    hookEventName: "PostToolUse",
    updatedToolOutput: normalizedResult,
  },
};

This fragment shows the SDK interface, not a complete normalization implementation. normalizedResult is the application’s replacement value; it is not an SDK-provided variable or a universally valid MCP envelope.

Define conversion by source contract

Suppose the archive tool emits a Unix timestamp in seconds and a provider-specific numeric status, while the registry tool emits an ISO 8601 timestamp with an offset and a different status vocabulary. The application chooses a canonical payload with observed_at, status, and source. Those are illustrative business fields, not Agent SDK fields.

A source-specific adapter converts the archive’s seconds to an instant and serializes it in the chosen UTC format. Another adapter parses the registry’s offset-bearing timestamp and serializes the same instant format. Keep the source identifier so Claude can attribute the evidence after both records have the same visible shape.

Do not infer seconds versus milliseconds from a convenient guess. Require the tool’s contract to state the unit. Likewise, a timestamp without an offset cannot establish a unique instant unless the source contract supplies a timezone. Mark that record as unusable for time comparison instead of silently inventing one.

Map numeric statuses using the provider’s documented meaning. A code that means “record found” for one source might represent an unrelated state elsewhere. In this scenario, the adapters map each known source value to the application’s vocabulary, such as available or unavailable. Preserve an unknown value as unknown; do not convert every unfamiliar code into success or absence.

These conversion rules are application choices. Anthropic documents where the replacement is delivered, not the semantics of your providers’ dates or codes. Define and test those semantics from the tool contracts you own before relying on the normalized representation for a decision.

Separate replacement, annotation, and failure handling

additionalContext appends information alongside a result; it does not replace the result. Use it to explain that a source value is ambiguous, but use updatedToolOutput when the desired outcome is the normalized result itself.

A top-level decision: "block" in PostToolUse adds feedback beside the result. It does not hide the original output or reverse the completed call. The updatedMCPToolOutput field is restricted to MCP tools; prefer updatedToolOutput, which covers both MCP and built-in tools.

A failed tool execution has its own event, PostToolUseFailure. Its event-specific output accepts additionalContext to annotate the failure, but has no output-replacement field. Do not route failures through a normalizer that manufactures a successful record. Preserve the evidence needed to distinguish “the source reports no record” from “the lookup failed.”

The hook must return before Claude proceeds if the replacement is required. A background asynchronous hook output cannot modify the operation. If a PostToolUse callback times out, the runtime keeps the tool result and continues; see 1.5.1 for the default timeout and its configuration. Therefore, this hook alone is not a guarantee that unnormalized or sensitive data never reaches Claude. Test the failure path and use a tool implementation that returns the required representation directly when that contract must hold even without a successful hook.

The replacement reaches Claude only when the callback returns in time and, for a built-in tool, passes schema validation.

The replacement reaches Claude only when the callback returns in time and, for a built-in tool, passes schema validationStarts at a completed tool's output, read by the PostToolUse callback. If the callback times out, the original output is kept. Otherwise it returns updatedToolOutput. A built-in tool's replacement that fails schema validation is ignored and the original output is used; one that passes is used, and MCP output is passed through without that validation. Either way, one of the two reaches Claude.returns in timetimes outbuilt-inMCP, passed throughyesno, ignoredCompleted tooloutputPostToolUsecallbackBuilt-in or MCPtool?Passes schemavalidation?updatedToolOutputOriginal outputClaude

Key distinctions and trade-offs

Semantic normalization versus shape compatibility: Converting a date correctly does not make an arbitrary replacement compatible with a built-in tool’s schema. Verify both the value and the envelope.

Replacement versus annotation: updatedToolOutput substitutes the value sent to Claude. additionalContext adds guidance alongside it. Choose the former when heterogeneous records must actually become comparable.

Model-visible output versus telemetry: The rewrite changes what Claude receives. Tool telemetry records the original output before the hook runs. Do not treat this mechanism as redaction of every audit or analytics surface.

Architecture / decision scenario

A multi-agent research coordinator combines archive and registry evidence about an incident. Both lookup tools are matched by a PostToolUse callback. The archive adapter converts documented Unix seconds and maps its known status code. The registry adapter parses its ISO 8601 offset and maps its status vocabulary. Each replacement preserves the corresponding MCP tool’s result shape.

Claude receives comparable timestamps, canonical statuses, and source identifiers. An unknown code remains visibly unknown instead of becoming “available.” A failed registry request stays a failure, so the coordinator seeks additional evidence rather than concluding that no record exists.

The team also tests a wrong-shaped built-in replacement and observes the original output reaching Claude. A deliberately timed-out normalization callback retains its original tool result too. If normalized output is mandatory for downstream consumers, the team moves that requirement into the tool’s return contract instead of assuming every hook attempt succeeds.

Common exam traps

  • Using additionalContext when the requirement is to replace the result.
  • Replacing a structured built-in output with an arbitrary string.
  • Assuming MCP passthrough validates the application’s normalized schema.
  • Guessing Unix timestamp units or provider-specific status meanings.
  • Treating PostToolUse blocking as prevention of a completed side effect.
  • Assuming the rewrite also changes telemetry or survives callback timeout.
  • Explaining every provider’s units and codes in the prompt instead of converting them in code before Claude reads them.

Where the exam guide differs

Note:

Task Statement 1.5 frames PostToolUse normalization as a deterministic guarantee that heterogeneous formats are converted “before the agent processes them.” The product scopes that guarantee. A callback that times out leaves the original result in place; a replacement that fails a built-in tool’s output schema is ignored; MCP output is not schema-checked at all; and telemetry records the original. In an exam item, PostToolUse with updatedToolOutput remains the answer for normalizing results, but a design that must never expose a raw format returns the canonical shape from the tool itself.

What to remember

Read tool_response, normalize through the correct source adapter, and return shape-compatible hookSpecificOutput.updatedToolOutput. Preserve unknowns and failures. The rewrite prepares evidence for Claude after execution; it neither prevents side effects nor guarantees replacement when the hook fails.

Knowledge check

References