Normalizing MCP tool output before passing it to the model — the PostToolUse hook pattern

Normalizing MCP tool output before passing it to the model — the PostToolUse hook pattern

We will introduce a pattern for deterministically resolving data format inconsistencies returned from multiple MCP servers using PostToolUse hooks rather than prompts. Along with an implementation example of timestamp normalization, we will explain the dividing line between processing that should be solved in code and processing that should be left to the model.
2026.07.12

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/YYYY format

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:

mcp-post-tool-use-hook-deterministic-normalization-flow

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

Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

Share this article