the context loom for mcp

Big tool results shouldn’t live in the context window.

mcp‑loom is a stdio proxy for the Model Context Protocol. It catches oversized tabular results before they reach your model, files them in an embedded DuckDB, and hands back a compact envelope — schema, a sample, join hints — plus SQL to query the data across every connected server.

npx mcp-loom/MIT/Node ≥ 20/embedded DuckDB

the cost

One list call can cost you the whole session.

A tool that returns four thousand rows returns them once — as JSON your model skims, half‑remembers, and pays for on every turn after. The answer you wanted is in there. So is the bloat that crowds out everything else.

how it works

Four moves, one thread.

step1

Namespace

Loom is itself a standard MCP server. It re‑exposes every downstream tool as <server>_<tool>. Your client points at one server; Loom fans out to all of them, and forwards small results untouched.

step2

Intercept

When a result crosses the token threshold and looks tabular, Loom ingests it into an embedded DuckDB instead of forwarding the payload. It reads the protocol’s structured channel when present, and falls back to parsing JSON — or even formatted text — under a strict never‑lie bar.

step3

Envelope

The model gets back a loom_dataset_ref: the schema with per‑column stats, a five‑row sample, join hints to other cached datasets, provenance, and an example query. The rows themselves stay in the store.

step4

Query

loom_query runs read‑only SQL over any cached dataset — filter, aggregate, and join across data from different servers. Large query results are cached the same way, so the loom never floods the context it just cleared.

the differentiator

One query, every server.

Each intercepted result becomes a table. Because they all live in the same DuckDB, the model can join them — issues from one server against people from another, orders against inventory, logs against deploys.

The join key isn’t a guess: Loom scores candidate columns by name, type, and actual value overlap, and puts the best matches in the envelope’s join hints. The model reads the hint and writes the join.

two servers, one query — the join key came from the envelope

the prime directive

Never worse than no proxy.

Every failure path — a bad ingest, a profiling error, the engine itself — degrades to passing the original result through, untouched. A half‑built dataset is dropped before it can ever be seen. Interception can only help; it is built so it can never cost you a result.

safe by default

Your data stays where you put it.

Sealed engine. The SQL surface can’t read your filesystem or reach the network. DuckDB starts hardened — external access off, config locked — and a startup self‑test refuses to run if the seal doesn’t hold. The read path is guarded to SELECT only, verified by the engine’s own parser, not a regex.
In memory, then gone. Datasets are session‑scoped and evict under memory pressure — nothing persists across restarts. When you want to keep a result, loom_export writes csv or json to a directory you name, and nowhere else.

configuration

One JSON file, sensible defaults.

Everything is optional but the servers list. The rest tunes when interception fires and how much it’s allowed to hold.

loom.config.json — keys and defaults
KeyDefaultWhat it does
tokenThreshold2000Results estimated above this (chars ÷ 4) get intercepted; smaller ones pass through byte‑identical.
memoryBudgetBytes268435456Soft cache budget (256 MB). Past it, query‑result datasets evict before source ones, oldest first.
duckdbMemoryLimit"512MB"Hard engine backstop. Spills to disk under pressure rather than taking down the process.
exportDir"./exports"The only place loom_export may write. Filenames are basename‑sanitized.
queryTimeoutMs30000Per‑query wall‑clock ceiling; a runaway query is interrupted, not left to hang.
restart5 / 1000msDownstream crash‑restart with exponential backoff, then a clean delist.

get started

Two files and you’re weaving.

Write a loom.config.json listing the servers you want behind Loom. Each entry is a normal MCP stdio server — put per‑server secrets in its own env, never in the shell that launches Loom.

Then point your client — Claude Desktop, Claude Code, Cursor — at Loom instead of at the servers directly. It re‑exposes them all, and the interception is invisible until a result gets big.