How to Implement Character Limits and Truncation in MCP Servers: A Production-Ready Pattern

Set a module-level CHARACTER_LIMIT constant (e.g., 25,000 characters), build the full response payload, check its serialized string length, and if the limit is exceeded, slice the data array to roughly half its size while injecting truncated flags and truncation_message metadata before re-serializing.

Large language model (LLM) tool calls can generate massive payloads that overwhelm client applications or exceed transport layer constraints. Learning how to implement character limits and truncation in your MCP (Model Context Protocol) servers ensures reliable responses without breaking downstream consumers. The awesome-codex-skills repository from ComposioHQ provides a battle-tested pattern for safely capping response size while preserving JSON schema integrity.

The Core Pattern for Safe Response Truncation

The reference implementations in mcp-builder/reference/python_mcp_server.md and mcp-builder/reference/node_mcp_server.md follow a five-step defensive strategy. This approach guarantees that the response envelope remains valid JSON even when the underlying data is reduced.

1. Define a Module-Level CHARACTER_LIMIT Constant

Declare a constant at module scope that every tool in your server can import. According to the source code in python_mcp_server.md (lines 9–14) and node_mcp_server.md (lines 77–82), the recommended default is 25,000 characters.


# mcp-builder/reference/python_mcp_server.md

CHARACTER_LIMIT = 25000  # maximum characters in a tool response
// mcp-builder/reference/node_mcp_server.md
export const CHARACTER_LIMIT = 25000; // maximum characters in a tool response

Centralizing this value ensures that a single configuration change updates every tool’s behavior consistently.

2. Build the Complete Response Before Validation

Each tool constructs its full JSON (or markdown) payload in a local response object before any size checking occurs. This happens at lines 15–18 in the Python reference and lines 83–86 in the Node reference.

response = {
    "total": data["total"],
    "count": len(data["items"]),
    "offset": params.offset,
    "items": data["items"],
    "has_more": data["total"] > params.offset + len(data["items"]),
}
result = json.dumps(response, indent=2)

Generating the complete payload first guarantees that no stray fields are accidentally dropped during the truncation phase.

3. Check String Length Against the Limit

Convert the response to a string and evaluate its length. In python_mcp_server.md (lines 18–20) and node_mcp_server.md (lines 86–88), the guard clause is straightforward:

if len(result) > CHARACTER_LIMIT:
    # Truncation logic executes here

This check must happen after serialization because JSON formatting (indentation, escaping) affects the final character count.

4. Truncate Data Arrays, Not the Envelope

When the limit is breached, mutate the items array (or equivalent data field) rather than the wrapper object. The reference implementations slice the array to roughly half its current size, set a truncated boolean to True, and append a human-readable truncation_message. This logic appears at lines 21–27 in the Python file and lines 88–94 in the Node file.

truncated_items = data["items"][: max(1, len(data["items"]) // 2)]
response["items"] = truncated_items
response["truncated"] = True
response["truncation_message"] = (
    f"Response truncated from {len(data['items'])} "
    f"to {len(truncated_items)} items. "
    "Use `offset` or tighter filters to retrieve more."
)

Preserving the response schema (total, count, offset, etc.) allows downstream code to deserialize the payload reliably and detect the truncation state.

5. Re-serialize and Return

After mutating the data array and metadata fields, convert the response back to a string and return it. This final step is shown at line 28 in python_mcp_server.md and line 95 in node_mcp_server.md.

result = json.dumps(response, indent=2)
return result

The resulting string is guaranteed to be under CHARACTER_LIMIT (assuming your data items are not individually massive) and contains all necessary signals for the client to request additional pages.

Complete Implementation Examples

Python (FastMCP) Implementation

The following excerpt from mcp-builder/reference/python_mcp_server.md demonstrates a search tool with full truncation protection:

CHARACTER_LIMIT = 25000  # maximum characters in a tool response

async def search_tool(params: SearchInput) -> str:
    # Build the full response first

    response = {
        "total": data["total"],
        "count": len(data["items"]),
        "offset": params.offset,
        "items": data["items"],
        "has_more": data["total"] > params.offset + len(data["items"]),
    }
    result = json.dumps(response, indent=2)

    # ---- Character‑limit check ----

    if len(result) > CHARACTER_LIMIT:
        # Keep roughly half the items –‑ adjust as needed

        truncated_items = data["items"][: max(1, len(data["items"]) // 2)]
        response["items"] = truncated_items
        response["truncated"] = True
        response["truncation_message"] = (
            f"Response truncated from {len(data['items'])} "
            f"to {len(truncated_items)} items. "
            "Use `offset` or tighter filters to retrieve more."
        )
        result = json.dumps(response, indent=2)

    return result

TypeScript (Node.js) Implementation

The parallel implementation from mcp-builder/reference/node_mcp_server.md follows the identical pattern:

export const CHARACTER_LIMIT = 25000; // maximum characters in a tool response

async function searchTool(params: SearchInput) {
  // Build the full response first
  const response = {
    total: data.total,
    count: data.items.length,
    offset: params.offset,
    items: data.items,
    has_more: data.total > params.offset + data.items.length,
  };
  let result = JSON.stringify(response, null, 2);

  // ---- Character‑limit check ----
  if (result.length > CHARACTER_LIMIT) {
    const truncatedItems = data.items.slice(0, Math.max(1, Math.floor(data.items.length / 2)));
    response.items = truncatedItems;
    response.truncated = true;
    response.truncation_message = `Response truncated from ${data.items.length} to ${truncatedItems.length} items. Use 'offset' or add filters to see more results.`;
    result = JSON.stringify(response, null, 2);
  }

  return result;
}

Document the Constraint in Skill Definitions

Explicitly documenting the limit ensures that reviewers and evaluation harnesses verify its presence. The mcp-builder/SKILL.md file (lines 9–10) lists this requirement as a mandatory feature:

"Implement character limits and truncation strategies (e.g., 25,000 tokens)."

Including this language in your project's SKILL.md or equivalent specification signals that your MCP server follows the standardized pattern for payload safety.

Summary

  • Define CHARACTER_LIMIT (25,000 characters) at module scope in python_mcp_server.md or node_mcp_server.md so all tools share the same threshold.
  • Build the full response first, then measure the serialized string length to avoid accidental field omission.
  • Truncate data arrays, not the envelope, slicing to roughly half the items and setting truncated: true plus a descriptive truncation_message.
  • Re-serialize the mutated object before returning to ensure valid JSON output.
  • Document the behavior in your SKILL.md to maintain compliance with the awesome-codex-skills standard.

Frequently Asked Questions

The awesome-codex-skills repository standardizes on 25,000 characters as defined in both python_mcp_server.md and node_mcp_server.md. This limit balances the need for comprehensive data against typical transport constraints and LLM context window limitations. You can adjust this constant based on your specific infrastructure, but 25,000 provides a safe baseline for most MCP clients.

Should I truncate the JSON string or the data before serializing?

Always truncate the data before serializing. Modifying the string directly risks breaking JSON validity or cutting off mid-unicode-sequence. By slicing the items array (or equivalent data field) and then calling json.dumps or JSON.stringify again, you preserve schema integrity and ensure the truncated metadata fields are properly included in the output.

How do I communicate truncation to the LLM client?

Signal truncation through two dedicated metadata fields: a boolean truncated set to true and a string truncation_message explaining what was removed and how to retrieve the rest (e.g., "Use offset or tighter filters"). This explicit contract allows LLM agents to detect the condition programmatically and request pagination or refined queries.

Where should I define the CHARACTER_LIMIT constant?

Define CHARACTER_LIMIT at module scope near the top of your server file (e.g., lines 9–14 in the Python reference or lines 77–82 in the Node reference). This placement makes the limit visible to all tool functions in the module and allows easy adjustment in a single location rather than hunting through individual tool implementations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →