Skip to content
Conduit

mcp server

mcp-loom-mcp

Portable MCP context server for local and cloud LLM workflows: SQLite + FTS5, 17 tools. Early (v0.1.x), not production-ready.

MlkBlk-IT/mcp-loom-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "mlkblk-it-mcp-loom-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-loom-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Nov 8, 2026

⚠️ Note: The rest of this repository (code, comments, scripts, docs, commits, issues) is written in French. Only this README is in English.

version

M8ven Score M8ven Score PyPI npm GitHub stars README views

Loom — mcp-loom-mcp

At a glance
Status⚠️ under repair — not production-ready. The notice below states exactly what is and is not demonstrated.
Versionv0.1.3 — published as mcp-loom-mcp on PyPI, npm and Glama
MCP tools17
Tests110 passed / 0 failed — measured 2026-11-02
StackFastMCP · SQLite + FTS5 (BM25) · 100 % CPU · 0 GPU · 0 paid API
Works todaycontext moved out of the prompt, lexical search, session isolation, sandboxed tool execution, encrypted vector index
Not demonstratedtoken savings — the old 94–99 % figures measured the anchor size alone and are invalid; the large-context cloud benchmark is still pending

⚠️ WORK IN PROGRESS — NOT READY FOR PRODUCTION (updated 2026-11-02)

Loom is under repair and being re-measured. Do not use in production.

  • No token savings have been demonstrated to date. The old figures (saved_pct 94–99%, bench-report.json, bench_results.json, REPORT_FINAL.md) measured the anchor size alone: they are invalid.
  • Current real measurement (LM Studio, small local models, small documents): Loom costs more tokens than the baseline (fixed anchor overhead + tool schemas). The large-context test with a cloud model has not been done yet.
  • Decision criterion frozen before the bench: same success rate with ≥ 35% fewer total tokens (in+out), retrieval and tool tax included. Otherwise: pivot or stop.
  • Code state (measured 2026-11-02, see CHANGELOG.md): 110 passed / 0 failed across the full test suite. The 19 failures of 2026-10-25 were caused by fixes (P9) that had been left in a stash and never re-applied after a remote merge; they are restored.
  • Latest updates (2026-11-02): P9 fixes restored (anchor invariant quota, search rerank + topup, port kind, encoding hints, schemas without anyOf); ctx_get now isolates by session (session_id, cross-session only via ctx_transfer); vector index rebuild reads the encrypted DB and persists after save; Windows sandbox fail-closed with Job Object limits; safer _path_allowed; dashboard now shows a net, de-duplicated saving estimate. Token savings are still not demonstrated (cloud bench pending).
  • The server exposes 17 MCP tools — this section used to list 13. The tool table below is now the measured list.

"Weave the context without cutting the thread." A portable MCP server that keeps the LLM context small by moving it out of the prompt — never by summarizing it.


Dashboard

Dashboard in --demo mode (python dash/server.py --demo, port set by LOOM_DASH_PORT, default 8775). The figures on screen (for example "85% saved") are synthetic demo data, not measurements — see the work-in-progress notice above.

Installation

Published: the repository, PyPI distribution, npm package and Glama listing are public. all carry the same name - mcp-loom-mcp - at version v0.1.3. The names mcp-loom and loom-mcp belong to other projects on those registries — on PyPI a server for Loom's internal GraphQL API, on npm an unrelated DuckDB proxy — and a registry never releases a name. That is why the repository was renamed from Mcp-Loom (the old URL redirects automatically).

# PyPI
uv tool install mcp-loom-mcp          # or: pip install mcp-loom-mcp

# npm
npx -y mcp-loom-mcp
RegistryPackagePage
PyPImcp-loom-mcppypi.org/project/mcp-loom-mcp
npmmcp-loom-mcpnpmjs.com/package/mcp-loom-mcp

The package installs the loom-mcp console script (the server MCP clients spawn) and the loom admin CLI. Point your MCP client at loom-mcp:

{
  "mcpServers": {
    "loom": {
      "command": "loom-mcp"
    }
  }
}

From source

pip install "mcp-loom-mcp @ git+https://github.com/MlkBlk-IT/mcp-loom-mcp.git"

From source, with the dev extras

git clone https://github.com/MlkBlk-IT/mcp-loom-mcp.git
cd mcp-loom-mcp
python -m venv .venv
.\.venv\Scripts\Activate.ps1        # Windows
pip install -e ".[dev]"

Admin CLI

The same operations are available on the command line, built on top of the MCP tools:

loom health      # drawer health status
loom stats       # token stats for a session
loom vacuum      # compact the SQLite drawer
loom search      # FTS5 search over stored chunks

Transports

TransportCommandTarget client
stdioloom-mcpClaude Desktop, IDEs, any stdio client

The HTTP transport (streamable-http) and token authentication are not yet tested end to end.

Building and publishing this package: see PUBLISHING.md.


The problem

An LLM agent burns its tokens on bulky context: raw logs, 100 files read, tool outputs. Loom takes that volume out of the prompt and files it in a local drawer. The model keeps a short, dense anchor plus stable IDs. When it needs the detail, it asks Loom back — byte for byte.

MethodLoss
Move (Loom) — "the log is in drawer #7"zero — the content exists, retrievable
Summarize (the others) — "the log says the build failed"possible loss — exact numbers vanish

Loom moves nothing else. That is the whole thesis of the project.


Current state

v0.1.1 — working core, honest test state. 17 MCP tools exposed, a reproducible benchmark on a 21-file corpus, and a test suite that is green: 110 passed / 0 failed (measured 2026-11-02 — CHANGELOG.md). Phase 2 (HTTP transport, rate-limiting and audit wired to the last 5 tools) in progress.


Architecture

LLM context
     │
     │  ctx_store / ctx_store_file / ctx_store_cmd
     ▼
┌──────────────────────────────────────────┐
│  ANCHORING      anchors.py + compress.py │  ← extracted invariants, condensed anchor
│  REDUCTION      profile.py               │  ← strict / balanced / aggressive
│  GUARANTEE      verify.py                │  ← safety net: nothing disappears
│  ENCRYPTION     crypto.py                │  ← Fernet (column) / SQLCipher (DB)
│  REDACTION      redact.py                │  ← secrets masked before storage
│  TRUST          sandbox.py + ratelimit.py│  ← isolated execution + rate limiting
│  TRACEABILITY   audit.py                 │  ← structured JSONL logs
└──────────────────────────────────────────┘
     │
     │  SQLite + FTS5 (store.py)
     ▼
   LOCAL DRAWER  (~/.loom/loom.db)

Modules

ModuleRole
server.pyFastMCP server — 17 tools, thin layer, zero business logic
anchors.pyInvariant extractor (numbers, paths, URLs, versions, hashes, ports, constraints)
compress.pyAnchor + chunk construction, byte-identical guarantee
store.pySQLite drawer + FTS5 index: chunks, anchors, invariants
verify.pyCoverage check — proof that nothing was lost
profile.py3 profiles: strict (zero-loss), balanced, aggressive (opt-in lossy)
tokenizer.pyToken counting (tiktoken cl100k_base, heuristic fallback)
crypto.pyDual-mode encryption: SQLCipher (DB) or Fernet (columns)
redact.pySecret masking (Bearer, sk-, password=, secret=, token=)
sandbox.pyExecution sandbox (Windows Job Object / bwrap-firejail Linux), fail-closed
ratelimit.pyToken bucket per session / IP
audit.pystructlog JSONL audit logs, 30-day rotation
session.pyUUIDv7 sessions, TTL, purge, isolation

Exposed MCP tools (17)

ToolRole
ctx_storeMoves bulky content into the drawer → anchor + IDs + savings report
ctx_store_fileSame, reading server-side (the volume never transits through the prompt)
ctx_store_cmdSame, from a command — opt-in (LOOM_STORE_CMD=1) and sandboxed
ctx_getRetrieves a chunk, byte for byte
ctx_searchFTS5 search over stored chunks → chunk_id, score, snippet
ctx_checkVerifies no critical invariant was lost (the safety net)
ctx_verifyCoverage verification, per session or global
ctx_statsTokens before/after, hit rate, ratio
ctx_profileSwitch profile: strict | balanced | aggressive
ctx_list_sessionsLists sessions (including expired)
ctx_purge_sessionPurges one session
ctx_purge_expiredPurges expired sessions (TTL)
ctx_transferTransfers chunks between two sessions
ctx_vacuumCompacts the SQLite database
ctx_rebuild_indexRebuilds the search index
ctx_vector_statsStatistics of the vector index
ctx_healthDrawer health status

Administration operations (purge, vacuum, health, list_sessions) are also available as CLI: python -m loom <cmd>.


Profiles

ProfileBehaviorLoss
strictWidened anchor, all invariants visiblenone — default mode
balancedTightened anchor, priority invariants keptnone on stored content
aggressiveMinimal anchorthe anchor becomes lossy — explicit opt-in, always reported

Metrics — and what they actually mean

This is the most important part of the project: badly named metrics make a benchmark lie. Here is the exact meaning of each measure.

MetricDefinitionWhat it does not say
saved_pctTokens saved in the anchor (what the model sees in context)Not the total stored volume — chunks are on demand
coverageFraction of invariants visible in the anchor without retrieval25% coverage is not a loss: the rest is retrievable
retrievableFraction of content retrievable via chunksAlways 1.0 in the measurements below
losslessNo critical invariant lostVerified by verify.py, not assumed
gain_net(anchor tokens + needed chunk tokens) / original tokensMeasured on 10 planted facts per file
identicalLegacy field, not tracked since bench v2Always false in bench_results.json — do not read proof into it

Proven feasibility — measured benchmark

Corpus: 21 real files (6.87 MB — code review, config, incident, doc, analysis, session, migration) × 3 sizes × 3 profiles = 63 measurements, 1,405,483 tokens processed per profile. Tokenizer tiktoken:cl100k_base. Corpus reproducible via scripts/make_dataset.py.

Averages per profile (21 files)

ProfileTokens savedAnchor coverageCompression latencyRetrievalLosslessCritical invariants lost
strict83.33%67.04%144.6 ms28.7 ms✅0
balanced89.55%53.84%144.9 ms28.6 ms✅0
aggressive94.17%40.77%145.1 ms28.0 ms✅0

retrievable = 1.0 and critical_missing_retrievable = 0 across all 63 measurements.

Breakdown by size

ProfileSmall (13.6 k tok.)Medium (168.3 k tok.)Large (1,223.6 k tok.)
strict56.68% · 99.34% cov. · 4.4 ms94.33% · 76.78% cov. · 52.7 ms98.99% · 24.99% cov. · 376.7 ms
balanced72.51% · 94.56% cov. · 4.4 ms96.54% · 56.29% cov. · 53.1 ms99.60% · 10.68% cov. · 377.4 ms
aggressive84.29% · 80.03% cov. · 4.4 ms98.37% · 38.16% cov. · 52.7 ms99.86% · 4.12% cov. · 378.2 ms

Reading: latency is dominated by invariant extraction (regex), not by anchor size — hence near-identical latencies across profiles on the same file. On a 1.2 M-token file, the strict anchor drops to ~25% visible coverage but 100% of the content stays retrievable: exactly the intended trade-off.

Reproduce

.venv/Scripts/python.exe bench_dataset.py   # → bench_results.json (63 measurements)

Proven artifacts

ArtifactFileProof
MCP serversrc/loom/server.py17 tools, 416 lines
Safety netsrc/loom/verify.pycoverage + invariants not lost
Encryptionsrc/loom/crypto.pyPBKDF2-HMAC-SHA256 600k iterations → Fernet (AES-128-CBC + HMAC)
Sandboxsrc/loom/sandbox.pyWindows Job Object (CPU, RAM, process count) / bwrap Linux
Redactionsrc/loom/redact.py5 pattern families, LOOM_REDACT=1 by default
Corpusdataset/21 files, 6.87 MB, 7 types × 3 sizes
Benchmarkbench_results.json63 measurements, real tokenizer
Teststests/103 test functions in 20 files — 83 pass, 19 fail, 1 collection error

Test suite

The suite is not green. Measured on 2026-10-25 — full breakdown in PROGRESS.md:

$ .venv/Scripts/python.exe -m pytest -q --ignore=tests/test_allowed_dirs.py
103 test functions in 20 files  ->  83 passed, 19 failed
  • 1 collection error — tests/test_allowed_dirs.py imports loom.server._path_allowed, a helper that was replaced by an inline LOOM_ALLOWED_DIRS check. The test is stale, the code is newer.
  • 19 failures — 15 come from a code refactor that was not followed by a test update (renamed APIs, removed kwargs, the tool list growing from 13 to 17), and 4 are real anchor regressions (port kind no longer produced, rare kinds no longer reaching the strict anchor, dates still extracted as numbers).
  • verify, redact, audit and sandbox exist and their tests pass.

The safety-net subset (green)

FileTestsGuarantee
test_safety_net.py16Byte-identical chunks, strict not lossy, edge-case coverage
test_anchor_vectors.py9Invariant extraction on reference vectors
test_compress_anchor.py4Anchor + chunk construction, invariant budget
test_roundtrip.py4Store → retrieve byte-identical on a real SQLite drawer
test_server_integration.py4Full MCP round-trip (store/get/check/profile)
test_stdio_server.py1The server actually responds over STDIO transport

3 real bugs found by the tests (they would never have surfaced otherwise):

  1. FastMCP 4.x no longer accepts readOnlyHint= as a kwarg → worked around with annotations=ToolAnnotations(...).
  2. chunk_id typed int while MCP clients send strings → tolerant str | int signature.
  3. Chunk splitting was losing bytes (strip("\n")) → broke the "byte-identical" promise. Fixed, with safety net + parameterized test cases.

Configuration (environment variables)

VariableDefaultEffect
LOOM_DB~/.loom/loom.dbSQLite drawer path
LOOM_DB_PASSWORD(empty)Enables encryption (Fernet or SQLCipher)
LOOM_REDACT1Secret masking (0 = disabled)
LOOM_STORE_CMD0Enables ctx_store_cmd (sandboxed)
LOOM_TTL_DAYS30Session expiry
LOOM_RATE_LIMIT(on)Token bucket per session / IP
LOOM_RPM / LOOM_BURST—Token-bucket rate and burst
LOOM_LOG_LEVELINFOLog verbosity

Known limitations

  • ctx_store_cmd is not sandboxed on every platform: it refuses to run if no sandbox is available (fail-closed).
  • Redaction breaks byte-identity of masked zones — an accepted trade-off, reported in the report.
  • No full database encryption on Windows: SQLCipher has no prebuilt wheel (C build failure) → fallback to column-level Fernet, with the FTS5 index remaining in clear for search.
  • chmod 600 is ignored on NTFS → protect the drawer directory manually.
  • HTTP transport (streamable-http) and token authentication are not yet tested end to end.
  • Rate-limiting and audit are wired to 8 of the 17 tools; 9 remain to be wired.

Methodology

  • Safety net first: the invariant extractor and verifier were written before the compression logic. If the verifier cannot prove something survived, the tool refuses to compress.
  • Deterministic extraction: regex and heuristics only, never an LLM. A verifier that depends on an LLM cannot prove coverage.
  • Honest measurement: every report gives before/after tokens counted by a real tokenizer, never estimated.
  • Audit before action: reproducible benchmark, versioned corpus, per-session report in docs/.
  • Portable by construction: SQLite + FTS5, no proprietary SDK, CPU-first, compatible with local or cloud model workflows.

What this project demonstrates

  1. Moving ≠ summarizing — lossy compression is a choice, not a technical necessity. Loom proves it with retrievable = 1.0 across 63 measurements.
  2. The token budget is an architecture problem, not a prompt problem — the right answer is an indexed local drawer, not a shorter prompt.
  3. Honest metrics do more work than flattering metrics — naming coverage "visible in the anchor" instead of "covered" prevents a benchmark that lies.
  4. An AI project can be CPU-first and provider-flexible - Python + SQLite, no GPU required, with local or cloud model workflows supported by the surrounding MCP client.

Author

Malik — IT Support student (DEP), self-taught Focus: Sysadmin + Cyber + AI + Cloud

Ownership and license

Project origin and evidence · License


⚡ Built on CPU. Proven on disk. ⚡


Original project and documentation by Malik Belkacemi · MLKBLK-IT · © 2026

↑↓ navigate openesc close