mcp-loom について
mcp-loomは、MCPクライアントと複数の下流サーバーの間に入るstdio MCPプロキシです。大きな表形式の結果を組み込みDuckDBに保存し、クエリ可能な簡潔な概要を返します。読み取り専用SQLやサーバー間の結合にも対応します。
主な機能
- 01下流サーバーのツールを集約してプロキシ
- 02大きな表形式の結果をDuckDBに保存
- 03大きな結果を簡潔な概要に置き換え
- 04読み取り専用SQLとサーバー間結合
- 05データセットのスキーマ・統計・出所を確認
- 06CSVまたはJSONでデータセットを出力
活用シーン
- 01大きなツール結果によるコンテキスト消費を削減
- 02複数サーバーからのデータをクエリ
- 03クエリ作成前にデータセットを確認
- 04セッション内のデータセットを出力
MCP 対応
mcp-loomはstdio MCPサーバーとして設定済みの下流サーバーを起動・管理し、ツールを集約して呼び出しを転送します。データセットのクエリ、確認、マテリアライズ、出力を行うツールも提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
███╗ ███╗ ██████╗██████╗ ██╗ ██████╗ ██████╗ ███╗ ███╗
████╗ ████║██╔════╝██╔══██╗ ██║ ██╔═══██╗██╔═══██╗████╗ ████║
██╔████╔██║██║ ██████╔╝█████╗██║ ██║ ██║██║ ██║██╔████╔██║
██║╚██╔╝██║██║ ██╔═══╝ ╚════╝██║ ██║ ██║██║ ██║██║╚██╔╝██║
██║ ╚═╝ ██║╚██████╗██║ ███████╗╚██████╔╝╚██████╔╝██║ ╚═╝ ██║
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
The context loom for MCP.
One proxy in front of N servers. Oversized tool results are woven
into queryable datasets — 48k tokens in, 2k envelope out.
warp: results stream in weft: the loom shuttles across
══════════════════════ ═════════════════════════════
│ │ │ │
│ │ │ │ 48,377 tokens ┄┄┄┄┄┄┄┄┄> ✂ intercept
●━━━━┿━━━━┿━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
│ ●━━━━┿━━━━┿━━━━━━━ ingest → DuckDB ━━━━━━┫
│ │ ●━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
│ │ │ ●━━━━ schema · stats · joins ━━┛
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[ raw floods, dropped ] [ 2,104-token envelope ]
ref · sample · SQL
できること
MCP クライアントがツールに質問すると、1 万行にも及ぶ JSON のダンプが返ってきます。その大半はモデルに必要なかったものですが、今度はコンテキストに保持しなければなりません。Loom はその間に入ります。
結果が小さい場合は、そのまま通します。大きな表形式の結果の場合、Loom は組み込みの DuckDB に取り込み、代わりにコンパクトな エンベロープ をモデルに渡します。エンベロープには、データセット参照、スキーマ、列の統計情報、少数のサンプル行、結合のヒント、出所情報が含まれます。モデルは必要なデータだけを正確にクエリできます。単一のサーバーでは答えられない、異なるサーバーのデータをまたいだ結合も可能です。
効果は次のとおりです。48k トークンのツール結果が、約 2k トークンのエンベロープになります。モデルは生のダンプではなく、回答にコンテキストを使えます。
loom が役立つ場面と、そうでない場面
Loom が真価を発揮するのは、実際の MCP 利用における一般的なケースをカバーする次の 3 つの状況です。
- シェルを持たないクライアント。 Claude Desktop、モバイル、エージェントプラットフォームなど、利用者が最も多いクライアント環境には、代わりに使える CLI がありません。MCP ツールがモデルにとってデータへの唯一の経路なので、構成上、インターセプトが機能します。
- モデルからアクセスできる CLI を持たないサーバー。 内部ツール、SaaS コネクタ、シークレットがサーバーの
envブロック内にしかない認証情報付きデータベースなど、実際のコネクタの大部分が該当します。モデルが迂回するためのgh相当のものはありません。 - 永続的なデータセットに対する監査可能な分析。 Loom は「モデルがダンプをざっと見た」を、「後からも確認できるデータに対して、検証可能なクエリを実行した」に変えます。つまり、出所情報が記録されたデータセットに対する正確な SQL で、後からも再び開いてクエリできます。これはシェルの有無を問わず、あらゆる 場面に当てはまり、「そもそも、なぜ loom を経由するのか」という問いへの率直な答えです。
サーバーをまたいだ結合は、この 3 つすべてで機能します。複数サービスのデータにまたがる質問に答えられる CLI は 1 つもありません。
境界となる点を 2 つ、率直に述べます。
- シェルを使えるエージェントは、直接アクセスできるサービスについて MCP レイヤーを迂回します。 モデルに
bashを使わせ、既知の CLI と環境にある認証情報でアクセスできるサービスを与えると、最も顕著な例である GitHub に対するghでは、loom と MCP の両方を迂回して、より速く、より安価な CLI に直接向かいます。これはエージェントが MCP より CLI を選んでいるのであって、loom の欠陥ではありません。loom は MCP レイヤーの製品であり、ghとしてのghに勝つことはありません。また、これは少数派のケースです。ほとんどのサーバーはモデルからアクセスできる CLI のないものをラップしており、利用者が最も多いクライアント環境にはシェルがありません。 - エンベロープの忠実度には、下流から返される内容による限界があります。 Loom はサーバーから送られたデータを忠実にフラット化しますが、サーバーが送っていないフィールドを追加することはできません。たとえば GitHub MCP サーバーが返す repo オブジェクトは、17 フィールドに絞られており(
stargazersCount、language、forksは含まれません)。loom はその 17 フィールドをすべて忠実に取り込みましたが、それでもスター数のランキングに関する質問には、そのデータだけでは答えられませんでした。この限界は下流側のものであり、loom のものではありません。
仕組み
Loom 自体が stdio MCP サーバーです。Loom は、上流の MCP クライアント 1 つ(Claude Desktop、Claude Code、Cursor など)と、設定した下流の MCP サーバー N 個の間に位置します。
- 設定された各下流サーバーを起動して監視します(ハンドシェイクによるヘルスチェック、バックオフ付きの再起動、再起動予算を使い切ったサーバーのツールの登録解除と
list_changedの送信)。 - すべての下流ツールに
<server>_<tool>という名前空間を付け、集約したツール一覧を上流に再公開し、list_changedによって最新の状態を維持します。 call_toolを適切な下流子プロセスにルーティングします。小さな結果は変更せずに返します。パススルーではバイト単位で同一です。大きな表形式の結果は DuckDB に取り込んでloom_dataset_refエンベロープに置き換えます。取り込みに失敗した場合は、元の結果を変更せずに返します。- キャッシュされたデータセットに対する合成クエリ用のインターフェイスとして、
loom_query(読み取り専用 SQL を制限付きで実行。サーバーをまたぐ結合も可能)、loom_list_datasets、loom_describe(データセットの完全なスキーマ、統計情報、新鮮な結合ヒントを表示)、loom_materialize(派生クエリの結果を新しいデータセットとして固定)、loom_export(封印された csv/json エクスポート)を提供します。取り込みの深さは上流の呼び出しごとに 1 回までです。loom_materializeはこの上限を消費しません。 - シグナルまたは上流の stdin EOF を受け取ると、孤立した子プロセスを残さず、スピル用ディレクトリを削除して、正常にシャットダウンします。
Loom は構造化された結果と整形済みテキストの両方を取り込みます。結果に structuredContent チャネルがある場合はそこから取り込み、それ以外の場合はテキスト内の JSON を解析し、さらにそれも失敗した場合は、厳格な「決して嘘をつかない」基準のもとで整形済みテキストから繰り返し出現するレコードを抽出します(確信が持てないテーブルは決して出力せず、不確実な場合は元の結果を変更せずに返します)。規範仕様の全文については、SPEC.md を参照してください。
使用方法
npx mcp-loom --config loom.config.json
任意の MCP クライアントで、このコマンドを指定してください。--config の代わりに LOOM_CONFIG 環境変数で設定を渡すこともできます。
LOOM_CONFIG=/path/to/loom.config.json npx mcp-loom
(両方が設定されている場合は、--config が優先されます。)最小限の実用的な例については、loom.config.example.json を参照してください。
クライアント別の設定例
Loom は、他の stdio サーバーと同じ方法で MCP クライアントから起動します。クライアントで npx -y mcp-loom --config <absolute path to loom.config.json> を指定してください。
Claude Desktop(claude_desktop_config.json):
{
"mcpServers": {
"loom": {
"command": "npx",
"args": ["-y", "mcp-loom", "--config", "/absolute/path/to/loom.config.json"]
}
}
}
Cursor(.cursor/mcp.json):上記と同じ mcpServers ブロックを使用します。
Claude Code — CLI から追加します。
claude mcp add loom -- npx -y mcp-loom --config /absolute/path/to/loom.config.json
loom.config.json には、絶対パスを使用してください。MCP クライアントは、作業ディレクトリが指定されていない状態でサーバーを起動します。その後、Loom は設定ファイルに記載された下流サーバーを起動し、名前空間を付与します。
設定リファレンス
設定は単一の JSON ファイルで、起動時に zod で検証されます。無効な設定があると、違反しているすべてのフィールドを列挙したわかりやすいエラーを出して、ただちに失敗します。
| フィールド | 型 | デフォルト | 備考 |
|---|---|---|---|
servers | 配列(最小 1 件) | —(必須) | プロキシする下流サーバーです。下記を参照してください。 |
servers[].name | 文字列 | —(必須) | ^[a-z][a-z0-9_]{0,31}$ に一致する必要があり、すべてのサーバー間で一意でなければなりません。loom は予約済み(合成ツール専用)であり、指定すると拒否されます。 |
servers[].command | 文字列 | —(必須) | 起動する実行ファイルです(例:npx)。空であってはなりません。 |
servers[].args | 文字列の配列 | [] | command に渡す引数です。 |
servers[].env | オブジェクト(文字列→文字列) | {} | このサーバーの子プロセスのみに渡す追加の環境変数です。「子プロセスの環境」を参照してください。 |
servers[].envPassthrough | 文字列の配列 | [] | 厳選された安全な基本環境変数に加えて、Loom 自身の環境からこのサーバーの子プロセスに転送する、追加の非シークレット変数名です(デフォルトでは許可しない方式です。起動シェルの環境変数が一括コピーされることはありません)。「子プロセスの環境」を参照してください。 |
servers[].provenanceDenylist | 文字列の配列 | [] | このサーバーのツール名のうち、provenance.args をエンベロープ、loom_describe、loom_list_datasets から完全に省略("[omitted]")する対象です。引数が認証情報のみで構成されるツールに使用します。それ以外のツールでは、キーに基づくヒューリスティックな秘匿化が引き続き適用されます。 |
tokenThreshold | 100 以上の整数 | 2000 | 表形式に変換できるテキスト結果がこの値を超えるとインターセプトが実行されます。大まかな文字数÷4の目安であり、正確ではありません(CJK 文字を過少に見積もります)。 |
memoryBudgetBytes | 1048576 以上の整数 | 268435456(256 MiB) | 追跡対象のデータセットのバイト数に対するソフトな削除上限です。暗黙的なクエリ結果のデータセットを LRU 順に削除し、その後に下流から取り込んだデータを削除します。 |
duckdbMemoryLimit | 文字列、/^\d+(\.\d+)?(KB|MB|GB|TB)$/ | "512MB" | エンジンのハード上限(SET memory_limit)です。OOM を避けるため、算出したスレッド数の上限のもとで処理をディスクにスピルします。 |
exportDir | 文字列 | "./exports" | loom_export が csv および json ファイルを書き込むディレクトリです。 |
queryTimeoutMs | 1 以上の整数 | 30000 | loom_query の実行時間を制限します(interrupt() タイマー)。 |
restart.maxAttempts | 0 以上の整数 | 5 | 下流サーバーごとの連続再起動試行回数の上限です。上限に達すると、そのサーバーは一覧から除外されます(ツールが削除され、list_changed が発行されます)。0 にすると再起動が無効になります。 |
restart.baseDelayMs | 1 以上の整数 | 1000 | 再起動時のバックオフの基本遅延時間です。 |
設定されたサーバーがすべて起動に失敗すると、Loom 自体の起動も失敗します(致命的エラー)。一部のみが失敗した場合、Loom は警告を出して起動し、起動に成功したサーバーのツールを利用できます。
インターセプトとクエリ
大きな表形式のツール結果はインターセプトされ、埋め込み DuckDB ストアにクエリ可能なデータセットとして取り込まれます。tokenThreshold を超え、構造化データを含むツール結果は、プロトコルの structuredContent チャネルがある場合はそこから読み取られ、ない場合はテキスト表示から解析されます。その結果は loom_dataset_ref エンベロープになります。エンベロープには参照 ID、推定されたスキーマ、列の統計情報、サンプル行、結合のヒントが含まれ、トークンコストを低く抑えます。これらのデータセットには、合成ツール loom_query を使ってクエリできます。このツールは DuckDB 方言の読み取り専用 SQL(単一の SELECT または WITH 文)を受け付け、エンジンパーサーによって読み取り専用と判定されます。データセット参照を紛失した場合は、loom_list_datasets で復元するか、現在のセッションで利用可能なすべてのデータセットを一覧表示できます。
データセット参照はセッション単位です。会話中のみ存在し、メモリが逼迫すると自動的に削除されます(暗黙的なクエリ結果が先に削除され、明示的に取り込んだデータはより長く保持されます)。エンベロープのサンプル行は任意です。DuckDB はデフォルトで挿入順を保持しないため、行の順序が重要な場合は必ず明示的な ORDER BY 句を使用してください。トークン推定は大まかな目安であり、CJK 文字を含むテキストを過少に見積もります。そのため、tokenThreshold を頻繁に超える場合は、実際のトークン使用量を監視してください。
loom_describe でデータセットを調査する
データセットの参照に対して loom_describe を呼び出すと、構造と内容を詳しく調査できます。列ごとの統計情報を含む完全なスキーマ、10 行のサンプル、他のデータセットへの最新の結合ヒント(値の重複スコアによって、偶然の名前の一致より実際の結合候補を上位に表示)、来歴、作成日時を確認できます。クエリを実行する前にデータセットの形状を把握したい場合や、中間操作を何度も行った後にデータセットのスキーマを再確認したい場合に使用してください。
loom_materialize でクエリ結果をマテリアライズする
クエリ結果は暗黙的にキャッシュされますが、セッションをまたいで派生結果を保持したい場合や、結果が確実に破棄されないようにしたい場合は、loom_materialize を使用してください。読み取り専用の SELECT 文を渡すと、新しいピン留めされた loom_dataset_ref エンベロープが返されます。マテリアライズされたデータセットは、メモリ不足時にも破棄されません。重要: マテリアライズされたデータセットは JSON テキスト表現で保存されるため、日付列と小数列はテキスト文字列になります。マテリアライズした結果に対して日付演算や数値演算を行う必要がある場合は、SELECT 文に CAST を含めてください。たとえば、クエリ可能な日付列をマテリアライズするには、SELECT CAST(created_at AS DATE) FROM ... のようにします。
loom_export でデータセットをエクスポートする(封印された境界)
キャッシュされたデータセットをファイルにエクスポートするには、loom_export を使用します。設定された exportDir の下にデータセットを csv または json 形式で書き込み(対応形式はこれらのみ)、書き込まれたファイルの絶対パスを返します。セッション外に結果を持ち出す際に使用します。
封印されたエクスポート境界: Loom エンジンはファイルシステムに一切触れません。エクスポートでは、読み取りレーンの SELECT 文を Node プロセスでテキストにシリアライズし、DuckDB ではなく MCP サーバーが書き込みます。これは意図的なセキュリティ上の選択です。エンジンパーサーは文の種類(読み取り専用か書き込みか)を分類し、エンジン自体はファイルシステム操作から保護されています(COPY TO は無効化されており、今後も無効のままです)。セキュリティ境界となるのは、実行時のフィルタリングではなく、エンジンの不変の文分類です。これにより、実行時にクエリが誤分類されたり、ファイルシステムへのアクセス権限が昇格されたりすることはありません。
名前空間
下流の各ツールは、上流では <server>_<tool> として再公開されます。ここで <server> は、そのサーバーの設定名です。ルーティングには内部の双方向マップ(公開名 ⇄ サーバー名+元のツール名)を使用し、公開名を _ で分割することはありません。そのため、アンダースコアを含むサーバー名も正しく処理されます。一般的なクライアントの制限(64 文字)を超える名前は、ハッシュサフィックスを付けて決定的に短縮されます。サニタイズ後に名前が衝突した場合は、数字のサフィックスが付加されます。loom は予約済みのサーバー名であり、loom_ を接頭辞とする合成ツールが実際の下流ツールと衝突することはありません。
子プロセスの環境(デフォルト拒否 — シークレットを設定する前にお読みください)
各下流の子プロセスには、厳選された非シークレットの基本環境(PATH、HOME、TMPDIR、ロケール、および Windows の同等の環境変数)と、そのサーバー自身の設定にある env ブロックのみが渡されます。Loom の起動元となったシェルの環境は引き継がれません。そのため、起動元のシェルでエクスポートされたシークレット(GITHUB_TOKEN、AWS_SECRET_ACCESS_KEY、…)は下流のサーバーに転送されず、侵害された npx -y パッケージが別のサーバー用のシークレットを読み取ることもできません。
サーバーごとのシークレットは、そのサーバー自身の env ブロックに設定してください。
{ "name": "repo", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "ghp_..." } }
あるサーバーでシェルの追加の非シークレット変数が必要な場合は、envPassthrough(デフォルトは [])で明示的に許可してください。
{ "name": "svc", "command": "...", "envPassthrough": ["HTTPS_PROXY", "NODE_EXTRA_CA_CERTS"] }
境界 — 最小権限であり、漏えいゼロではありません。 これは環境変数を介した経路を遮断するものであり、あらゆる経路を遮断するわけではありません。引き継ぎを許可した変数自体にシークレットが含まれる場合があります(HTTPS_PROXY=http://user:pass@host や CA パスなど)。また、悪意のある子プロセスが独自にファイルシステムやネットワークにアクセスする可能性もあります。コンテナや名前空間などの OS レベルでの子プロセスごとの分離が最上位の対策ですが、ゼロ設定の stdio プロキシの対象範囲外です。また、「すべてを引き継ぐ」フラグは意図的に用意していません。
注意点
- 子プロセスの環境変数はデフォルトで引き継がれません。 下流の子プロセスには、厳選された機密情報を含まない基本環境と、サーバーごとの
envのみが渡されます。起動元のシェルのシークレットは転送されません。追加の機密情報を含まない環境変数を渡すには、envPassthroughに指定してください。これは最小権限を実現するための仕組みであり、情報漏えいをゼロにするものではありません(詳しくは上記の子プロセスの環境変数:シークレットを設定する前にお読みくださいをご覧ください)。 - トークンしきい値は大まかな調整値です。 インターセプトは
chars/4による推定値で作動します。これは桁違いの大きさを捉えるための目安であり、正確なトークン数ではありません。また、CJKテキストでは少なめに見積もられます。tokenThresholdは境界値ではなく、規模を調整する値として扱ってください。推定値は結果全体を対象とし、テキストブロックと、存在する場合はシリアライズされたstructuredContentを含みます。 - エクスポートは制限されており、Node で再シリアライズされます。
loom_exportは読み取りレーンのクエリ結果をテキストにシリアライズして csv/json を書き出します。これはNode プロセス内で行われます。エンジンはファイルを書き込みません(COPY TOは無効のままです)。その代わり、エクスポート対象の行は JS 側でシリアライズ処理されます。セッション規模のデータセットなら予算内に十分収まるよう制限されており(100k 行のエクスポートでは約 0.42s を計測)、実際にコストはかかります。エクスポートはコピーであり、コストのかからないハンドルではありません。 - データセット参照はセッション単位で、退避される場合があります。 参照は会話中のみ有効で、メモリが逼迫すると退避されます。暗黙的なクエリ結果が最初に退避され、明示的な取り込み結果は最後に退避されます。マテリアライズされた参照は固定され、退避されません。
ORDER BYを指定しない限り、サンプル行は任意です。 - 下流の
outputSchemaは再公開されません。 Loom は大きな結果をloom_dataset_refエンベロープに置き換える場合があります。そのため、下流ツールが宣言した出力形式を正確に提示できません。スキーマを再公開すると、仕様に厳密に準拠するクライアントが、インターセプトされた結果をすべて拒否してしまうためです。その代わり、Loom 経由でプロキシされるツールの構造化出力は、クライアントによるスキーマ検証の対象になりません。ただし、小さな結果ではstructuredContent自体は変更されずに通過します。
開発
npm ci
npm run build # TypeScript compile (tsc)
stdout はプロトコルです。ログはすべて stderr に出力します。インターセプトのルールと抽出コントラクトを含む完全な仕様は、SPEC.md をご覧ください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。