
Normalizing MCP tool output before passing it to the model — the PostToolUse hook pattern
This page has been translated by machine translation. View original
Introduction
When operating AI agents connected to multiple MCP servers, have you ever encountered situations like these?
- The CRM's MCP server returns timestamps as Unix epoch (
1718409600) - The ticketing system uses ISO 8601 (
2026-06-15T09:00:00Z) - The legacy internal API uses
MM/DD/YYYYformat
The date formats returned from three different tools are inconsistent, causing the agent's date handling to be unreliable. You might think "I'll just add normalization rules to the system prompt," but even after adding instructions, failures still occur in a few percent of cases — that's the problem this article addresses.
This article introduces a PostToolUse hook normalization pattern as a reliable solution to this problem.
Why System Prompt Normalization Is Insufficient
The approach of adding instructions like "convert all dates to ISO 8601 before processing" to the system prompt has fundamental limitations.
| Approach | Success Rate | Issues |
|---|---|---|
| No instructions | ~80% | Model guesses and processes the format |
| Add normalization instructions to prompt | ~92% | LLM processing is probabilistic. Can fail with complex inputs |
| Add few-shot examples | ~95% | Improves but never reaches 100%. Token consumption also increases |
| Normalize with PostToolUse hook | 100% | Deterministic transformation via code. Never fails |
LLMs inherently perform probabilistic processing. Rule-based conversions like "convert Unix epoch to ISO 8601" are not something models excel at. There is no reason to leave a problem that can be reliably solved with code to probabilistic reasoning.
What Is a PostToolUse Hook
A PostToolUse hook is a mechanism that allows you to intervene before the results of an MCP tool execution are passed to the model. The processing flow is as follows:

Since only normalized data is ever passed to the model, there is no need to write conversion rules in the prompt.
Implementation Example: Timestamp Normalization
Below is an implementation example of a PostToolUse hook that unifies three types of date formats into ISO 8601.
from datetime import datetime, timezone
import re
def post_tool_use_hook(tool_name: str, tool_output: dict) -> dict:
"""Normalize timestamps in MCP tool output to ISO 8601"""
return normalize_timestamps(tool_output)
def normalize_timestamp(value: str) -> str:
"""Convert an individual value to ISO 8601"""
# Unix epoch (e.g., "1718409600", "1718409600.123")
if re.fullmatch(r"\d{10}(\.\d+)?", value):
dt = datetime.fromtimestamp(float(value), tz=timezone.utc)
return dt.isoformat()
# MM/DD/YYYY (e.g., "06/15/2026")
try:
dt = datetime.strptime(value, "%m/%d/%Y").replace(tzinfo=timezone.utc)
return dt.isoformat()
except ValueError:
pass
# Pass ISO 8601 through as-is (e.g., "2026-06-15T09:00:00Z")
try:
datetime.fromisoformat(value.replace("Z", "+00:00"))
return value
except ValueError:
pass
# Return unknown formats as-is
return value
def normalize_timestamps(obj):
"""Recursively traverse all fields in tool output and normalize"""
if isinstance(obj, str):
return normalize_timestamp(obj)
if isinstance(obj, dict):
return {k: normalize_timestamps(v) for k, v in obj.items()}
if isinstance(obj, list):
return [normalize_timestamps(item) for item in obj]
return obj
Behavior Example
# Before normalization: raw output from each MCP server
raw_outputs = {
"crm": {"customer": "Tanaka Taro", "last_contact": "1718409600"},
"tickets": {"id": "TK-1234", "created": "2026-06-15T09:00:00Z"},
"legacy": {"order_date": "06/15/2026", "status": "shipped"},
}
# After normalization: data passed to the model
for source, output in raw_outputs.items():
normalized = post_tool_use_hook(source, output)
print(f"{source}: {normalized}")
# crm: {"customer": "Tanaka Taro", "last_contact": "2024-06-15T00:00:00+00:00"}
# tickets: {"id": "TK-1234", "created": "2026-06-15T09:00:00Z"}
# legacy: {"order_date": "2026-06-15T00:00:00+00:00", "status": "shipped"}
The data the model receives is always in a unified format.
Other Cases Where This Pattern Is Effective
Normalization via PostToolUse hooks can be applied beyond just dates.
| Case | Normalization Content |
|---|---|
| Currency format unification | "$1,234.56" / "1234.56 USD" / "¥1234" → unified format |
| Phone number normalization | "090-1234-5678" / "+819012345678" → E.164 format |
| Status value mapping | "active" / "ACTIVE" / "1" / true → unified enum value |
| PII masking | Mask email addresses and phone numbers → avoid passing personal information to the model |
| Response size limiting | Truncate large arrays → reduce token consumption |
Principle: Don't Leave Deterministically Solvable Problems to Probabilistic Reasoning
The core of this article is not timestamp normalization itself, but rather the decision criteria for which processing to delegate to the model and which to solve with code.
Processing that should be solved with code:
- Format conversion (dates, currencies, phone numbers)
- Data validation
- Type conversion (string → number)
- Mapping based on fixed rules
Processing that should be delegated to the model:
- Natural language understanding and generation
- Context-dependent judgment
- Interpretation of ambiguous requirements
- Reasoning that combines multiple tools
Simply being conscious of this boundary will greatly improve the reliability of your agent.
Summary
- Data format inconsistencies from multiple MCP servers should be solved with code, not prompts
- Using a PostToolUse hook allows you to reliably normalize tool output before it reaches the model
- The principle of "don't leave deterministically solvable problems to probabilistic reasoning" can be applied to MCP agent design in general