Skip to main content

ToolPipe

Pass MCP results by reference, not through the LLM.

ToolPipe is an MCP data-plane proxy that keeps large tool outputs out of LLM context by storing them as references and piping the underlying data directly between tools.

Without ToolPipe:  Tool A → 18 MB → LLM → Tool B
With ToolPipe:     Tool A → ToolPipe storage ├→ LLM: res_xxx └→ Tool B: actual data

The LLM stays the reasoning plane (it sees a small res_* reference plus a preview). ToolPipe is the data plane (it holds the bytes and feeds them to the next tool).

How it works

  1. ToolPipe exposes downstream MCP servers' tools through one server, namespaced (producer_generate_customers, analytics_calculate_statistics).
  2. When a downstream tool returns more than inline_max_bytes (default 32 KiB), ToolPipe stores the payload locally (SQLite metadata + content-hashed blobs) and returns a compact reference envelope instead.
  3. The agent then uses control tools to inspect, read, search, or select from the stored result — or pipes data straight into another tool without the payload ever re-entering context.
100,000 customer objects (~5.1 MiB)
        │ producer_generate_customers
        ▼
ToolPipe res_A (542 B: ref + preview)
        │ mapping customers[].revenue
        ▼ analytics_calculate_statistics
{count: 100000, min: 0.0, max: 4999.0, mean: 2499.5} (60 B)

Measured on the bundled demo: 5,355,595 B → 542 B (~9881x smaller) for the producer step; the final stats response is 60 B.

Installation

Requires Python 3.11+ and uv.

uv sync
uv run toolpipe --help

Configuration

Zero-config first: just add ToolPipe to your agent — no toolpipe.toml needed.

toolpipe serve

On startup ToolPipe auto-discovers the MCP servers you already configured in Claude Code (.mcp.json), Cursor (.cursor/mcp.json), Codex (.codex/config.toml), OpenCode (opencode.json), and Zed (.zed/settings.json), workspace files first. Discovered servers spawn nothing until you approve them: the first time the agent routes a call through one, you are asked — once, for this session, for this workspace, or always — and your choice is saved. Static TOML and explicit imports work as before:

toolpipe serve --config toolpipe.toml
toolpipe serve --import-mcp-json path/to/mcp.json
toolpipe serve --import-codex-toml ~/.codex/config.toml
toolpipe serve --no-discover --no-dynamic-servers

See examples/toolpipe.toml (working demo config). Format:

[toolpipe]
name = "ToolPipe"

[results]
storage_dir = ".toolpipe"
inline_max_bytes = 32768   # below this: pass through unchanged
preview_max_bytes = 4096   # preview budget inside reference envelopes
read_max_bytes = 16384     # max bytes per toolpipe_read_result call
max_result_bytes = 104857600
max_store_bytes = 1073741824
ttl_seconds = 3600

[servers.producer]
command = "uv"
args = ["run", "python", "examples/demo/producer_mcp.py"]

[servers.analytics]
command = "uv"
args = ["run", "python", "examples/demo/analytics_mcp.py"]

[servers.remote]
url = "http://localhost:9001/mcp"

Secrets use ${VAR} substitution and fail fast when unset:

[servers.github]
command = "npx"
args = ["-y", "@example/github-mcp"]

[servers.github.env]
GITHUB_TOKEN = "${GITHUB_TOKEN}"

MCP client config (generic Claude/Cursor/Codex style)

Plug-and-play entry — no config file:

{
  "mcpServers": {
    "toolpipe": {
      "command": "toolpipe",
      "args": ["serve"]
    }
  }
}

Pinned-config entry:

{
  "mcpServers": {
    "toolpipe": {
      "command": "toolpipe",
      "args": ["serve", "--config", "/path/to/toolpipe.toml"]
    }
  }
}
Scope Meaning Stored in
once This one call only Memory
session This ToolPipe process Memory
workspace This project, across restarts <storage_dir>/servers.json
always Everywhere, across restarts ~/.toolpipe/servers.json

Approvals persist connect info with ${VAR} references unresolved (resolved secrets are never written to disk). Removing a server revokes it everywhere but keeps stored results. If your client cannot render approval prompts, the agent asks in chat and calls toolpipe_approve_server instead.

Control tools

Tool Purpose
toolpipe_inspect_result Metadata + bounded preview for a res_* ref
toolpipe_read_result Bounded byte-slice reads (diagnostic; prefer select for JSON)
toolpipe_select_result JMESPath selection, e.g. customers[].revenue (small→inline, large→new ref)
toolpipe_search_result Substring/regex search; line matches for text, leaf paths for JSON
toolpipe_release_result Drop a stored result immediately
toolpipe_pipe_result Pipe mapped data directly into a downstream tool
toolpipe_pending_servers Discovered servers waiting for approval
toolpipe_approve_server Approve a server (once/session/workspace/always)
toolpipe_add_server Manually register a server discovery cannot see
toolpipe_remove_server Revoke a server (stored results are kept)
toolpipe_list_servers Servers with scope, status, tool counts (env redacted)
toolpipe_set_policy auto/always/never per server or server:tool
toolpipe_get_policy Configured policies (default is auto)

Selectors are JMESPath only — no Python/Jinja/shell evaluation, ever.

CLI

toolpipe serve [--config toolpipe.toml] [--log-level INFO]
toolpipe serve --import-mcp-json mcp.json --import-codex-toml config.toml
toolpipe serve --no-discover --no-dynamic-servers
toolpipe inspect res_abc123 --config toolpipe.toml
toolpipe results --config toolpipe.toml
toolpipe stats --config toolpipe.toml     # includes "estimated bytes avoided"
toolpipe clean --config toolpipe.toml     # expired results only

Logs go to stderr (stdout is the MCP protocol channel). Payloads and secrets are never logged.

Demo

uv run toolpipe serve --config examples/toolpipe.toml

Then, from any MCP client: producer_generate_customers(count=100000) returns a res_* reference; toolpipe_pipe_result with mapping {"values": "customers[].revenue"} into analytics_calculate_statistics returns the stats without the array crossing context.

Limitations

  • STDIO upstream only; one process = one result scope (no multi-user mode).
  • JSON and text virtualization only — images/audio/binary pass through.
  • No CSV/row operations, no streaming, no cross-hose shared refs.
  • Storage pressure fails loudly instead of evicting (StorageLimitError); no LRU.
  • No save_result(path=...) by design (avoids path-traversal policy).

Security

  • JMESPath only; eval/exec/shell/Jinja/JS are not supported anywhere.
  • Refs are opaque random tokens, never filesystem paths or hashes.
  • Pipe targets must be approved downstream tools; toolpipe_* control tools are rejected.
  • First use asks for approval showing the exact command; deny persists nothing.
  • Scope files keep ${VAR} references unresolved; listings redact env values.
  • never policy disables virtualization only — a context-bloat footgun, not a data leak.
  • All size/TTL limits enforced; stdout carries protocol output only.

Development

uv sync
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv build

See CHANGELOG.md for release history.

Release files for toolpipe 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for toolpipe 0.1.0
File Size Uploaded
toolpipe-0.1.0.tar.gz 156.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for toolpipe 0.1.0
File Interpreter ABI Platform
toolpipe-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 207.1 kB

Release files / toolpipe-0.1.0.tar.gz

Download URL toolpipe-0.1.0.tar.gz
Size 156.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fda02fa3ee993494138a695c069c32714b1573dbbcdd66bd4eef31841eb7172a
BLAKE2b-256 checksum
How to use checksums
dfaa43aa440eac18efe2cf1340df4a856d5e463a9f0c06a5815a79abdc269f2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / toolpipe-0.1.0-py3-none-any.whl

Download URL toolpipe-0.1.0-py3-none-any.whl
Size 50.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bc8de5e2f7292ccbc3422c5e4e3de20c95b72483d7d8492f5fa3ca85bc963220
BLAKE2b-256 checksum
How to use checksums
34dddc7b28401baef8a89b5d5617a6181803c9c0cb4e4caa4236b7efb908ffaf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page