ctx-zip
Keep your agent context small and cheap by zipping large tool results out of the conversation and into storage. ctx-zip automatically persists bulky tool outputs (JSON/text) to a storage backend and replaces them in the message list with short, human- and model-friendly references. You control when and how compaction happens, including a simple "last-N messages" strategy for long-running loops.
Works primarily with the AI SDK for agents and loop control. See: AI SDK – Loop Control: Context Management.
What problem it solves
- Context bloat: Tool calls often return large payloads (logs, search results, files). Keeping these in the message history quickly exhausts the model context window and raises costs.
- Slower iterations: Bigger histories mean slower prompts and higher latency.
How it solves it
- Persist large tool outputs to a storage backend (local filesystem or Vercel Blob) and replace them with concise references (e.g.,
Written to storage: blob://prefix/abc.txt).
- Out-of-the-box reader tools let the model follow references and read/search on demand (e.g.,
readFile, grepAndSearchFile).
- Configurable boundaries let you decide what to compact (entire history, since the last assistant/user text, or preserve the first N messages such as system/instructions).
- Works with AI SDK agent loops via
prepareStep so you can also layer a simple, robust "last-N" message-retention strategy.
Installation
npm i ctx-zip
pnpm add ctx-zip

Quickstart: generateText with prepareStep (last-N + compaction)
The example below shows how to keep only the last N messages while also compacting tool results to storage on each step. It follows the AI SDK prepareStep pattern for loop control.
Out-of-the-box tools for reading storage references
After compaction, the model will see short references like Written to storage: blob://prefix/<key>. The agent can then retrieve or search that content using the built-in tools below. Add them to your tools map so the model can call them when it needs to re-open persisted outputs.
- readFile: Reads a full file by
key from a storage URI (file://... or blob:).
- grepAndSearchFile: Runs a regex search over a file in storage.
Usage:
import { generateText, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import {
compactMessages,
createReadFileTool,
createGrepAndSearchFileTool,
} from "ctx-zip";
const storageUri = `file://${process.cwd()}`;
const result = await generateText({
model: "openai/gpt-4.1-mini",
tools: {
readFile: createReadFileTool(),
grepAndSearchFile: createGrepAndSearchFileTool(),
},
stopWhen: stepCountIs(6),
prompt: "Use tools to research, summarize, and cite sources.",
prepareStep: async ({ messages }) => {
const compacted = await compactMessages(messages, {
storage: storageUri,
boundary: { type: "first-n-messages", count: 20 },
});
return { messages: compacted };
},
});
console.log(result.text);
Notes:
- The compactor recognizes reader/search tools like
readFile and grepAndSearchFile so their outputs aren’t re-written; a friendly "Read from storage" reference is shown instead.
- You can pass your own
storageReaderToolNames to extend this behavior for custom reader tools. If you provide additional reader tools, include them in the tools map and add their names to storageReaderToolNames so compaction treats their outputs as references rather than rewriting to storage.
Tool inputs (model-provided):
- readFile:
{ key: string; storage: string }
- grepAndSearchFile:
{ key: string; storage: string; pattern: string; flags?: string }
By default, compactMessages treats readFile and grepAndSearchFile as reader tools and will not re-write their results back to storage; instead it replaces them with a short reference to the source so the context stays lean.
Configuration Options
compactMessages(messages, options) accepts:
interface CompactOptions {
strategy?: "write-tool-results-to-storage" | string;
storage?: string | StorageAdapter | undefined;
boundary?:
| "since-last-assistant-or-user-text"
| "entire-conversation"
| { type: "first-n-messages"; count: number };
serializeResult?: (value: unknown) => string;
storageReaderToolNames?: string[];
}
- strategy: Currently only
write-tool-results-to-storage is supported.
- storage: Destination for persisted tool outputs. Provide a URI (
file://..., blob:) or an adapter.
- boundary:
since-last-assistant-or-user-text (default): Compact only the latest turn.
entire-conversation: Re-compact the full history.
{ type: "first-n-messages", count: N }: Preserve the first N messages (useful for system instructions) and compact the rest.
- serializeResult: Customize how non-string tool outputs are converted to text before writing.
- storageReaderToolNames: Tool names whose outputs will be replaced with a reference back to the source instead of being re-written.
Storage Backends
ctx-zip supports local filesystem and Vercel Blob out of the box. Choose one via a URI in CompactOptions.storage or by passing a constructed adapter.
Local filesystem (default)
- URI form:
file:///absolute/output/dir
- If omitted, ctx-zip writes under
process.cwd().
Examples:
await compactMessages(messages, { storage: "file:///var/tmp/ctx-zip" });
import { FileStorageAdapter } from "ctx-zip";
await compactMessages(messages, {
storage: new FileStorageAdapter({ baseDir: "/var/tmp/ctx-zip" }),
});
Vercel Blob
- URI form:
blob: (optionally blob://prefix)
- Env: set
BLOB_READ_WRITE_TOKEN (this single token is sufficient)
Examples:
await compactMessages(messages, { storage: "blob:" });
import { VercelBlobStorageAdapter } from "ctx-zip";
await compactMessages(messages, {
storage: new VercelBlobStorageAdapter({ prefix: "my-agent" }),
});
.env example:
BLOB_READ_WRITE_TOKEN=vcblt_rw_...
Implement a custom storage adapter (S3, Supabase, etc.)
Adapters implement a minimal interface so you can persist anywhere (S3, Supabase, GCS, Azure Blob, databases, …):
export interface StorageAdapter {
write(params: { key: string; body: string | Uint8Array; contentType?: string }): Promise<{ key: string; url?: string }>;
readText?(params: { key: string }): Promise<string>;
openReadStream?(params: { key: string }): Promise<NodeJS.ReadableStream>;
resolveKey(name: string): string;
toString(): string;
}
Example: S3 (sketch):
import { S3Client, PutObjectCommand, HeadObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import type { StorageAdapter } from "ctx-zip";
class S3StorageAdapter implements StorageAdapter {
constructor(private bucket: string, private prefix = "") {}
resolveKey(name: string) {
const safe = name.replace(/\\/g, "/").replace(/\.+\//g, "");
return this.prefix ? `${this.prefix.replace(/\/$/, "")}/${safe}` : safe;
}
async write({ key, body, contentType }: { key: string; body: string | Uint8Array; contentType?: string }) {
const s3 = new S3Client({});
const Body = typeof body === "string" ? new TextEncoder().encode(body) : body;
await s3.send(new PutObjectCommand({ Bucket: this.bucket, Key: key, Body, ContentType: contentType }));
return { key, url: `s3://${this.bucket}/${key}` };
}
toString() {
return `s3://${this.bucket}${this.prefix ? "/" + this.prefix : ""}`;
}
}
You can apply the same pattern to Supabase Storage, GCS, Azure Blob, or any other service.
Tips
- Pair compaction with AI SDK loop control to dynamically trim history and adjust models/tools per step. See: AI SDK – Loop Control: Context Management.
- When preserving long-lived system instructions, consider
boundary: { type: "first-n-messages", count: N }.
- For debugging, use the file backend first (
file://...) to inspect outputs locally, then switch to blob: for production.
API Surface
From ctx-zip:
- Compaction:
compactMessages(messages, options) and CompactOptions
- Strategies:
detectWindowStart, messageHasTextContent (advanced)
- Storage Adapters:
FileStorageAdapter, VercelBlobStorageAdapter, createStorageAdapter(uriOrAdapter)
- Utilities:
resolveFileUriFromBaseDir, grepObject (advanced)
- Tools:
createReadFileTool, createGrepAndSearchFileTool (recognized as reader tools by default)
MCP Sandbox Explorer
ctx-zip now includes MCPSandboxExplorer - a tool that enables efficient code execution with MCP (Model Context Protocol) servers, reducing context bloat and improving agent performance.
The Problem: Context Bloat from MCP Tools
Traditional MCP integration loads all tool definitions upfront into the model's context window. With hundreds or thousands of tools across multiple MCP servers, this approach:
- Overloads context: Tool definitions can consume hundreds of thousands of tokens before the model even sees your prompt
- Lowers Accuracy: Running and loading tool results in a loop can quickly bloat the context which can ultimately lead to lower accuracy
- Increases costs: Every tool definition and intermediate result passes through the model
- Slows responses: Large context windows increase latency
The Solution: Progressive Discovery + Code Execution
Instead of loading all tools upfront, MCPSandboxExplorer:
- Transforms MCP tools into TypeScript modules with JSDoc documentation
- Loads them into a sandbox file system (Vercel, E2B, or Local)
- Gives agents filesystem exploration tools (ls, cat, grep, find) to progressively discover only what they need
- Lets agents write and execute code that calls MCP tools, processing data in the sandbox before returning results
Result: >80%+ reduction in token usage for complex multi-tool workflows. Intermediate results stay in the sandbox, only final outputs flow through the model's context.

Benefits
- 📉 Massive Token Savings: Load only the tools you need, not all thousands upfront
- ⚡ Faster Responses: Smaller context windows = lower latency
- 💰 Lower Costs: Process data in sandbox, not through expensive LLM calls
- 🔒 Better Privacy: Sensitive data stays in sandbox, never touches model context
- 🎯 Progressive Discovery: Agents explore and load tools on-demand
- 🧩 Tool Composition: Chain multiple MCP calls in a single code execution
- 📊 Data Processing: Filter, transform, aggregate data before returning to model
Read more about this technique: Code execution with MCP: Building more efficient agents
Installation
The MCP Sandbox Explorer and all sandbox providers are included with ctx-zip:
npm install ctx-zip
All sandbox providers (@modelcontextprotocol/sdk, @e2b/code-interpreter, @vercel/sandbox) are installed automatically as optional dependencies. No additional installation needed!
Sandbox Providers
MCPSandboxExplorer uses a pluggable sandbox provider architecture, allowing you to choose the execution environment that best fits your needs:
Built-in Providers
| Vercel | Vercel apps, quick prototyping | API key | Paid tier available |
| E2B | Production, TypeScript execution, custom packages | API key | Paid tier available |
| Local | Debugging, development, learning | None | Free |
Vercel Sandbox (Default):
import { MCPSandboxExplorer, VercelSandboxProvider } from "ctx-zip";
const provider = await VercelSandboxProvider.create({
timeout: 1800000,
runtime: "node22",
});
const explorer = await MCPSandboxExplorer.create({
servers: [
{ name: "grep-app", url: "https://mcp.grep.app" },
{ name: "linear", url: "https://mcp.linear.app/mcp" }
],
sandboxProvider: provider,
});
E2B Sandbox:
import { MCPSandboxExplorer, E2BSandboxProvider } from "ctx-zip";
const provider = await E2BSandboxProvider.create({
apiKey: process.env.E2B_API_KEY,
});
const explorer = await MCPSandboxExplorer.create({
servers: [
{ name: "grep-app", url: "https://mcp.grep.app" },
{ name: "linear", url: "https://mcp.linear.app/mcp" }
],
sandboxProvider: provider,
});
Local Sandbox (For Development):
import { MCPSandboxExplorer, LocalSandboxProvider } from "ctx-zip";
const provider = await LocalSandboxProvider.create({
sandboxDir: "./.sandbox",
});
const explorer = await MCPSandboxExplorer.create({
servers: [
{ name: "grep-app", url: "https://mcp.grep.app" },
{ name: "linear", url: "https://mcp.linear.app/mcp" }
],
sandboxProvider: provider,
});
Custom Sandbox Providers
You can implement your own sandbox provider (Docker, AWS Lambda, etc.) by implementing the SandboxProvider interface:
interface SandboxProvider {
writeFiles(files: SandboxFile[]): Promise<void>;
runCommand(command: SandboxCommand): Promise<CommandResult>;
stop(): Promise<void>;
getId(): string;
getWorkspacePath(): string;
}
See SANDBOX_PROVIDERS.md for detailed documentation on:
- Implementing custom providers
- Provider comparison and features
- Advanced provider usage examples
Quick Start
import { generateText, stepCountIs } from "ai";
import { MCPSandboxExplorer, VercelSandboxProvider } from "ctx-zip";
const provider = await VercelSandboxProvider.create();
const explorer = await MCPSandboxExplorer.create({
servers: [
{ name: "grep-app", url: "https://mcp.grep.app" },
{
name: "linear",
url: "https://mcp.linear.app/mcp",
headers: {
Authorization: `Bearer ${process.env.LINEAR_OAUTH_TOKEN}`,
},
},
],
sandboxProvider: provider,
});
await explorer.generateFileSystem();
const tools = explorer.getAllTools();
const result = await generateText({
model: "openai/gpt-4.1-mini",
tools,
stopWhen: stepCountIs(15),
prompt: "Search the codebase using grep-app and create a Linear issue",
});
console.log(result.text);
await explorer.cleanup();
Authentication
MCP servers often require authentication via headers (API keys, Bearer tokens, etc.). Store tokens in environment variables and pass them via the headers property:
LINEAR_OAUTH_TOKEN=lin_api_your_token_here
import dotenv from "dotenv";
dotenv.config();
const explorer = await MCPSandboxExplorer.create({
servers: [
{
name: "grep-app",
url: "https://mcp.grep.app",
},
{
name: "linear",
url: "https://mcp.linear.app/mcp",
headers: {
Authorization: `Bearer ${process.env.LINEAR_OAUTH_TOKEN}`,
},
},
],
sandboxProvider: provider,
});
Note: For OAuth-based MCP servers, obtain tokens through the provider's OAuth flow (outside the sandbox) beforehand. Interactive OAuth flows are not supported inside sandbox environments.
Generated File Structure
The explorer generates a file tree in the sandbox workspace. The base path depends on your provider:
- Vercel:
/vercel/sandbox/servers/
- E2B:
/home/user/servers/
- Local:
./.sandbox/servers/
servers/
├── grep-app/
│ ├── search.ts # Type definitions + function
│ ├── getFileContent.ts
│ └── index.ts # Re-exports all tools
├── linear/
│ ├── createIssue.ts
│ ├── listIssues.ts
│ └── index.ts
└── _client.ts # MCP routing client
Each tool file contains:
- TypeScript interfaces for input/output
- JSDoc documentation
- Executable function that calls the MCP tool
Example generated file:
import { callMCPTool } from '../_client.js';
export interface CreateIssueInput {
title: string;
description?: string;
teamId: string;
}
export interface CreateIssueOutput {
id: string;
title: string;
url: string;
}
export async function createIssue(
input: CreateIssueInput
): Promise<CreateIssueOutput> {
return callMCPTool<CreateIssueOutput>('linear', 'createIssue', input);
}
Available AI SDK Tools
The explorer provides these tools for agents:
- sandbox_ls: List directory contents (files and folders)
- sandbox_cat: Read file contents (view tool definitions)
- sandbox_grep: Search for patterns in files
- sandbox_find: Find files by name pattern
- sandbox_exec: Execute TypeScript code in the sandbox
API Reference
MCPSandboxExplorer.create(config)
Initialize a new sandbox explorer.
interface SandboxExplorerConfig {
servers: MCPServerConfig[];
sandboxProvider?: SandboxProvider;
sandboxOptions?: {
timeout?: number;
runtime?: 'node22' | 'python3.13';
vcpus?: number;
};
outputDir?: string;
}
interface MCPServerConfig {
name: string;
url: string;
headers?: Record<string, string>;
useSSE?: boolean;
}
Methods
generateFileSystem(): Fetch tools and generate file system
getExplorationTools(): Get ls, cat, grep, find tools
getExecutionTool(): Get code execution tool
getAllTools(): Get all tools (exploration + execution)
getToolsSummary(): Get info about discovered tools
getSandboxProvider(): Get the sandbox provider instance
cleanup(): Stop the sandbox and clean up
Use Cases
- Progressive API Discovery: Let agents explore available APIs before writing code
- Context Efficiency: 98%+ reduction in token usage by processing data in sandbox
- Self-Documenting Tools: Generated files include full type definitions and JSDoc
- Safe Code Execution: Code runs in isolated sandbox environments
- Multi-MCP Integration: Connect to multiple MCP servers simultaneously
- Agent Workflows: Agents can read documentation, write code, and execute it
Examples
The library includes several examples demonstrating different use cases:
npm run example:mcp-vercel
npm run example:mcp-e2b
npm run example:mcp-local
See the examples/ directory for complete working examples with detailed comments.
Built with ❤️ by the team behind Langtrace and Zest.