CLAUDE LABJP
MCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attributionMCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attribution
Articles/API & SDK
API & SDK/2026-07-31Advanced

The 400-Character Preview That Was Still Holding On to a Megabyte

Truncating tool output in your own MCP server does not free the original. Node's sliced strings keep the parent alive. Here is the measured 13-character boundary, which flattening tricks actually work, and a heap-snapshot audit script that counts the retained parents for you.

MCP51Node.js4Memory6Performance4Operations16

Premium Article

I wrote a very small MCP server whose only job was to read my own build logs. One tool, one file read, one string returned. To keep the return value manageable I stored only the first 400 characters as a preview and left the full text to be re-read on demand.

As an indie developer running several sites at once, I accumulate little resident processes like this one at a steady drip. Each was supposed to cost me a few dozen megabytes.

A few hours into running it, ps stopped me cold. The resident set of that little process had crossed 200 MB.

The history array was supposed to hold nothing but 400-character previews. Hundreds of them still add up to tens of kilobytes. The memory did not come down.

I spent a long time looking in the wrong places: unconsumed streams, Buffer handles I had forgotten to release, event listeners piling up on a long-lived emitter. None of it.

The culprit was the single line of slice I had written to do the truncating.

A 400-character preview that will not let go of its parent

Start with the smallest reproduction I could build. Create a 4 MB string twenty times, keep only the first 200 characters of each.

// exp1.mjs — node --expose-gc exp1.mjs
function mb(n) { return (n / 1048576).toFixed(1); }
 
const KEEP = 200;
const N = 20;
const SIZE = 4 * 1024 * 1024;
const kept = [];
 
global.gc();
const base = process.memoryUsage().heapUsed;
 
for (let i = 0; i < N; i++) {
  const full = 'x'.repeat(SIZE) + '#' + i;  // stand-in for a big tool result
  kept.push(full.slice(0, KEEP));            // "keep only 200 characters"
}
 
global.gc();
const after = process.memoryUsage().heapUsed;
 
console.log('kept =', kept.length,
            '/ previewLen =', kept[0].length,
            '/ heapDelta =', mb(after - base), 'MB');

The explicit global.gc() calls are there so the result does not depend on when a collection happens to fire. They require the --expose-gc flag.

Every number below comes from Node.js v22.22.3 (V8 12.4.254.21) on Linux x86_64, 4 vCPU, 3.9 GB of RAM, inside a sandbox.

kept = 20 / previewLen = 200 / heapDelta = 80.0 MB

Twenty strings of 200 characters. Four thousand characters in total. Eighty megabytes unreclaimed — exactly 4 MB times 20.

All twenty parents are still alive.

The boundary sits at exactly 13 characters

The obvious follow-up question is how short a preview has to be before it stops pinning the parent. Same script, varying only KEEP, this time with 2 MB parents and ten iterations.

Preview length (chars)UnreclaimedReading
12.00 MBparent freed
52.00 MBparent freed
112.00 MBparent freed
122.00 MBstill safe
1320.00 MBparent pinned
1420.00 MBparent pinned
20020.00 MBparent pinned

Between 12 and 13, the number jumps by an order of magnitude.

The residual 2.00 MB is the last full still being reachable when the measurement runs. It stayed at 2.00 MB whether I looped once or forty times, so I treated it as a constant floor. Above the threshold, the growth is strictly linear.

CallsUnreclaimed (200-char preview, 4 MB parent)
14.00 MB
28.00 MB
520.00 MB
1040.00 MB
2080.00 MB
40160.00 MB

A perfectly straight line. The longer the process runs, the worse it gets.

That 13 lines up with the minimum length at which V8 switches to representing a substring as a reference to its parent plus an offset and a length — a sliced string. Anything shorter is simply copied on the spot, which is why the parent goes away.

Note how that inverts the usual intuition: shorter is safer, but only below a threshold too small to carry any useful information. Trimming your previews to 12 characters technically fixes the leak and destroys the feature.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
The measured 13-character threshold where a preview starts pinning the entire parent string alive (4 MB becomes 80 MB over 20 calls)
A side-by-side table of 5 flattening techniques that work and 7 that silently do not, including template literals, String(), and repeat(1)
A 200-call tool wrapper benchmark: heapUsed 203.5 MB to 4.6 MB, RSS 245.0 MB to 43.6 MB, for a total cost of 16 milliseconds
A 50-line audit script that walks a heap snapshot, sums the retained parents, and exits 1 when they blow a byte budget
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-07-24
Swapping Agent Config Per Session From One Shared Definition
A design pattern for running one base Managed Agent and overriding its model, prompt, tools, MCP servers, and skills per session with agent_with_overrides — with a validated factory and the operational traps I hit along the way.
Claude Code2026-07-16
Your Overnight Session Wakes Up at 3GB — Four Places Memory Piles Up, and How to Tell Them Apart
The Claude Code process I left running overnight had grown to 3.4GB of resident memory by morning. Here are the four accumulation sources closed in 2.1.209, how to separate what's left in your own setup by sampling RSS slope, and a watchdog pattern that folds a session before it hurts.
API & SDK2026-08-19
The listen Stream Kept Reopening Against My Serverless MCP Server
No errors anywhere, yet connections to the MCP server kept piling up overnight. Here is what happens when a fixed platform timeout meets a resubscribe loop, reproduced in a few dozen lines.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →