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

> Implement character limits and truncation in your MCP servers effectively. Learn a production-ready pattern to manage long responses and improve performance.

- Repository: [Composio/awesome-codex-skills](https://github.com/composiohq/awesome-codex-skills)
- Tags: how-to-guide
- Published: 2026-04-26

---

**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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/python_mcp_server.md) and [`mcp-builder/reference/node_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/python_mcp_server.md) (lines 9–14) and [`node_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/node_mcp_server.md) (lines 77–82), the recommended default is **25,000 characters**.

```python

# mcp-builder/reference/python_mcp_server.md

CHARACTER_LIMIT = 25000  # maximum characters in a tool response

```

```typescript
// 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.

```python
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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/python_mcp_server.md) (lines 18–20) and [`node_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/node_mcp_server.md) (lines 86–88), the guard clause is straightforward:

```python
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.

```python
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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/python_mcp_server.md) and line 95 in [`node_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/node_mcp_server.md).

```python
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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/python_mcp_server.md) demonstrates a search tool with full truncation protection:

```python
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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/node_mcp_server.md) follows the identical pattern:

```typescript
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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/python_mcp_server.md) or [`node_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/SKILL.md) to maintain compliance with the awesome-codex-skills standard.

## Frequently Asked Questions

### What is the recommended character limit for MCP server responses?

The **awesome-codex-skills** repository standardizes on **25,000 characters** as defined in both [`python_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/python_mcp_server.md) and [`node_mcp_server.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/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.