memorybank について
memorybankは、`memory_bank`ナレッジベース向けのMCPサーバーです。ベースが定めるルールに沿って文書構造を初期化し、読み書きを管理します。状態はMarkdownファイルのみで保持し、書き込みには明示的な呼び出しが必要です。
主な機能
- 01ナレッジベース構造の初期化
- 02ベースのルールに基づく書き込み検証
- 03文書メタデータによる読み取り先の案内
- 04全文検索と依存グラフの探索
- 05文書とコードのドリフト報告
活用シーン
- 01プロジェクトの構造化された記憶文書を作成
- 02管理された文書を安全に作成・更新
- 03関連文書の検索と依存関係の追跡
- 04コードと乖離した古い文書の特定
MCP 対応
`memory_bank`ナレッジベース向けのMCPサーバーで、初期化、読み取り、検索、検証、ルーティング、グラフ探索、書き込みのツールをMCPクライアントに公開します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mcp-memorybank
memory_bank ナレッジベース向けの MCP サーバーです。何もない状態からナレッジベースを作成し、ナレッジベース自身が定めたルールに基づいてすべての書き込みを検証し、ナレッジベースを読み込まずに適切なドキュメントへ案内し、ドキュメントの内容よりコードが先に進んでいる箇所を報告します。
独自の判断で書き込みを行うことはありません。すべてのドキュメントは、目的、上流の依存関係、インデックスエントリーを伴う明示的な呼び出しによって登録されます。セッション終了時のフックが収集する内容は隔離領域に入り、人間だけがそこから取り出せます。
仕様 — specification.md、計画 — implementation-plan.md、シード済みのナレッジベースの構成と、その限界 — architecture.md。
計画に記載された作業はすべて完了しています:E0 インデックス、E1 読み取り、E2 検証、E3 グラフ、E4 検索と差分、E5 書き込み。
定量的に得意なことと、そうでないこと
実際に使用された6つのナレッジベースについて、印象ではなくセッションを解析して集計しました:
writing into the bank, through the server 985 calls
reading the bank, through the server 187 calls
reading the bank, around the server 1755 calls (cat, grep, Read)
bank_route specifically 33 calls
書き込み機能はうまく機能しています。 bank_create、bank_edit、bank_update_section は、使うよう指示されているかどうかにかかわらず、すべてのナレッジベースで使われています。ルールに従ったドキュメントを手作業で書くより、ツールを呼び出すほうが簡単だからです。手作業では、フロントマターを再現し、derived_from を解決し、セクションインデックスに登録する必要があります。また、どこかを間違えれば検証に失敗します。サーバー呼び出しの90%は書き込みです。
読み取り機能はほとんど迂回されています。 10回に9回、エージェントはどのドキュメントが必要かを bank_route に尋ねる代わりに、cat と grep でナレッジベースを開きます。これは、ルーティングの回答が悪いという主張ではありません。その点を測定した人はいません。そもそも尋ねられていないという主張です。あらゆるリポジトリでモデルが使うのはシェルであり、その習慣を覆すものがここにはないからです。
ここから導かれることは2つあり、どちらも好ましいものではありません。このREADMEで最初に紹介している機能であり、以下のトークン計算の前提にもなっているルーティングが、最も使われていません。既存ドキュメントの編集は、サーバーが行わない機能として仕様に記載されていました(§8 を参照)。しかし、プロジェクトを支えているのは、結果としてこの機能でした。
根拠のある唯一の改善策は、エージェントに使わせる方法 の段落です。この説明を含む4つのナレッジベースでは、含まない2つと比べてルーティングの頻度が8倍高く、読み取りの32%に対して4%です。これは6つのプロジェクト間の相関であり、適切な検証を進めています。詳しくは backlog.md の A-06 を参照してください。
仕組み
データベースも、デーモンも、設定もありません。Markdown ファイルのディレクトリが状態のすべてで、サーバーはその内容を読み取って MCP を介して応答します。サーバーの知識はすべてファイルから再構築されるため、プロセスを終了しても何も失われません。そもそも、ほかの場所には何も保持されていないからです。
インデックスと、ファイルウォッチャーが不要な理由
起動時にサーバーはナレッジベース全体を1回走査し、呼び出しごとに再び走査します。実行するのは readdir、stat と、mtime が変化したファイルのみの解析です。368件のドキュメントがあるナレッジベースを初回構築するには約85 ms、変更のない呼び出しの前に行う走査には11~13 msかかります。
これがファイルウォッチャーを使わない理由です。ウォッチャーを使えば数ミリ秒を節約できますが、その代わりに、インデックスが気づかないうちにディスクと食い違うという種類のエラーが発生する可能性があります。これは、ディスクの内容を正しく伝えることを唯一の役割とするツールでは避けるべきです。低コストの確認方法なら、その両方を満たせます。
ドキュメントがどう変換されるか
各ファイルは一度だけ解析され、レコードになります。frontmatter のフィールド、第 2 レベルのセクション見出しとその行範囲、バイトサイズ、mtime に加え、ファイルに含まれていない情報である レイヤー が記録されます。レイヤーはパス(dna、knowledge、decision、delivery、flow、inbox、other)から算出されます。
frontmatter は、gray-matter を使って一か所でのみ処理されます。YAML が無効な場合(purpose 内の引用符で囲まれていないコロンがよくある例です)も、ドキュメントがインデックスから漏れることはありません。メタデータは行ごとに復元され、解析エラーは bank_validate が報告できるよう保持されます。タイプミスでルーティングからドキュメントが消えることは、順位が低くなることよりも問題です。
契約はバンクから取得する
doc_kind、doc_function、status、derived_from が必須かどうか、どのドキュメントが宣言上のルートか――これらはすべて、更新時にバンク自身の dna/frontmatter.md と dna/governance.md から読み取られます。何もハードコードされておらず、値の集合は固定されていません。
これは単なる配慮ではありません。バンクによって自らの語彙は異なり、固定の列挙型では、doc_kind だけでも実際のドキュメントのかなりの割合が拒否されます。dna/ がないバンクでも動作します。サーバーは縮退モード(読み取り、ルーティング、検索、構造ルール)で動作し、誰も宣言していない契約を強制するのではなく、そのことを伝えます。
ルーティング:本文ではなくヘッダーを順位付けする
bank_route は「これについて何を読めばよいか」に答えます。各ドキュメントについて、人が手作業で書いた情報だけを読み取り、本文は読みません。対象は固定の重みを持つ 4 つのフィールドです。
| フィールド | 重み | 評価方法 |
|---|---|---|
canonical_for | 5 | 質問がどれだけ事実キーをカバーしているか。単語が 1 つでも一致したかどうかではありません |
purpose | 3 | 単語の重なり |
title | 2 | 単語の重なり |
| セクション見出し | 1 | 最も一致度の高い見出し。回答をセクション単位で読めるよう、この見出しも返されます |
その後、素点に倍率が適用され、ランキングの判断を実際に担うのがこの倍率です。
- レイヤー。
knowledge×1.5、decision×1.2、dna/flow/other×1.0、delivery×0.6、inbox×0.2。しばらく運用されているバンクでは、ドキュメントの大半が delivery に属するため、delivery の重みは下げられています。これをしないと、ルールについての質問に、そのルールに触れている完了済みの機能が返されてしまいます。 - ステータス。
active×1、draft×0.7、archived×0.2。 - 完了済みの作業。
delivery_status: doneまたはcancelledの場合、スコアはさらに半分になります。完了した機能は履歴であり、回答ではありません。 - 意図。 質問に why、rationale、instead of、почему、вместо が含まれる場合、一連の結果全体を decision 寄りに重み付けし直します。
decision×1.6、knowledge×1.2 となります。「X はなぜ」と「X とは何か」は別の質問であり、最初に同じドキュメントが返されるべきではありません。
テンプレートは結果に表示されません。実際のドキュメントと構造上同じであり、どのリストもテンプレートだらけになってしまうためです。
検索:異なる質問に対する別のインデックス
bank_search はルーティングの代替ではなく、もう一方の問いに答えます。ルーティングが手書きのヘッダーを順位付けする(「何について書かれたドキュメントか」)のに対し、検索は本文を読みます(「この文字列が実際にどこに出現するか」)。識別子、エラーメッセージ、リテラルは本文にしかありません。
同じ mtime チェックを使い、ドキュメント本文の転置インデックスを増分構築します。クエリは確度に応じた 3 段階で一致します。入力どおりの単語は重み 10、もう一方の言語での同等語は 6、複合トークンの断片は 1 です。また、すべての概念に一致するドキュメントは、一部の概念に一致するドキュメントより上位になります。断片の段階があることで、FT-042 を検索したとき、FT-* の行が 70 個ある features/README.md の一覧が、該当機能そのものより上位に来るのを防げます。
書き込み:1回の操作で完了するか、何も書き込まないか
bank_create はドキュメントを書き込み、規約で求められるフロントマターを記入し、同じ呼び出しの中でセクションインデックスに登録します。これにより、登録を忘れることはありません。登録忘れは、バンクが劣化する最も一般的な原因です。登録時には、そのインデックスの最後のエントリ(テーブルの行、箇条書き、番号付き項目)の形式を引き継ぐため、手書きのファイルが再フォーマットされることはありません。
これらの処理を行う前に、理由を明示して処理を拒否します。パスがすでに使用されている、derived_from の参照先が解決できない、canonical_for がすでに別のドキュメントに割り当てられている、パスがバンクのルート外に出ている、または .md で終わっていない場合です。拒否された場合は一切書き込みません。不完全なファイルも、孤立したインデックス行も残りません。
これらのチェックから、2つのルールが導かれます。そして、それこそが書き込み機能に価値がある理由です。SSoT の競合と壊れたエッジが、このサーバーを通じてバンクに入ることはありません。どちらも、ファイルをサーバーの知らないところで編集した場合にのみ発生します。
グラフ
bank_graph は derived_from を幅優先でたどり、ノード数に上限を設けます。そのため、ハブとなるドキュメントを起点にバンク全体が読み込まれたり、循環によって処理が止まらなくなったりすることはありません。各エッジは、内部(ノード)、external(バンクのルート外。モノレポではバンクが入れ子になっています)、または broken のいずれかに分類されます。up はドキュメントの基盤となるもの、down はそのドキュメントを変更した場合の影響範囲です。
外部エッジをたどるのは1ホップだけです。対象のフロントマターを読み取り、neighbours に返します。それ以外の情報は返しません。インデックスへの登録、検証、検索は行わず、対象自身のエッジもたどりません。境界はそのまま保たれます。ただし、モノレポではこうしたエッジに実際の判断が含まれているため、影響範囲がリポジトリの境界で黙って途切れるのは、不完全なのではなく誤りです。読み取りを省略するには、neighbours: false を渡してください。
ドリフト
bank_drift は、検証では答えられない問いに答えます。「何かが不足しているか」ではなく、「書いた内容が古くなっていないか」です。コードについて説明するドキュメントには anchors: にそのコードを列挙し、サーバーはドキュメントに最後に変更を加えたコミットと、コードに最後に変更を加えたコミットを比較します。コードがしきい値を超えて先行している場合はその旨を示します。また、アンカーが存在しなくなったパスを指している場合は、常にその旨を示します。これは、ドキュメントの記述対象となるコードが変わったことを意味するためです。
どのドキュメントがどのファイルを所有しているかを推測することはありません。対応関係は手動で注釈されているか、存在しないかのどちらかです。そのため、注釈されていないバンクには、理由を示す注記とともに、正直に空の結果が返されます。また、結果を報告するだけで編集は行いません。6か月の隔たりが重要かどうかは、タイムスタンプだけで判断できることではありません。
シード済みバンクの内容
bank_init は24個のファイルを書き込みます。dna/ のガバナンス一式、ルートインデックス、8つのセクションレジストリ、4つのフロードキュメント、テンプレートの場所を示すポインター、そして内容を埋めるための2つの下書きです。さらに3つのセクション(epics、prd、prompts)は、最初から空の状態で用意されるのではなく、ドキュメントで必要になった時点でインデックスとルートインデックスのエントリを含めて作成されます。
ドキュメントテンプレートはコピーされません。テンプレートはサーバーに同梱され、bank_create はそこから読み取るため、バンク内にテンプレートがある場合とまったく同じように動作します。テンプレートを変更するプロジェクトでは --materialize-flows を使ってコピーでき、その後はバンク内のコピーが優先されます。説明用のフローはコピーされます。人が読み、ルーティングの回答に使うためです。
dna/ もコピーされますが、これだけは必須です。これはバンクを評価する際の基準であり、コーパスとともに移動することを前提に設計されています。最上位ディレクトリごとのレイヤーとランキングの重みも、すべてそこに宣言されています。セクション名を変更するバンクは、そのテーブルに変更を記載して重みを維持します。何も宣言しないバンクには、組み込みのマップが適用されます。
できないこと
編集によってドキュメントを作成することはありません。新しいドキュメントはすべて bank_create を通すため、パスのチェック、解決可能な derived_from、インデックスエントリがそろわない限り、バンクに何も追加されません。_inbox/ 内のものを独自に昇格させることもありません。スキーマを勝手に作ったり、バンクが宣言していないルールを適用したりすることもありません。_inbox/ の外にあるドキュメントを削除することもありません。また、バンクのルート外にあるもののインデックス作成、検証、検索も行いません。bank_graph が隣接するバンクへ行うのは、ヘッダーを読み込んでそこで止まる、1 ホップだけの処理です。
トークンのコスト
サーバーを接続するのは無料ではなく、コストは 2 種類の形で発生します。以下の数値は、出荷されたビルドで、実際の JSON-RPC ペイロードの文字数を数え、1 トークンあたり 3.5 文字として換算したものです。密度の高い JSON は、文章に適した 4 文字よりもこの値に近くなります。
固定費はリクエストごとに発生します。ツールが呼び出されるかどうかにかかわらず、定義がコンテキストに含まれるためです。
| 文字数 | 約トークン数 | |
|---|---|---|
| サーバーの指示 | 343 | 98 |
| 16 個のツール定義 | 21,790 | 6,226 |
| 5 個のプロンプト | 1,346 | 385 |
| 4 個のリソース | 908 | 259 |
| リクエストあたりの合計 | 24,387 | 約 7,000 |
最も定義のコストが高いのは bank_create で、約 710 トークンです。7 つのパラメーターに加え、ゲートの説明が必要だからです。最も安い bank_changed は約 183 トークンです。
この表は、バンクがあるプロジェクトで発生するコストです。バンクがないプロジェクトには bank_init だけが提供され、コストは約 450 トークンです。--off または .memorybank-off ファイルで無効化したプロジェクトでは、空のサーフェスに約 25 トークンかかります。一度インストールすれば、すべてのプロジェクトで使えます を参照してください。サーバーがこの区別を設けているのは、そうしなければグローバルインストールによって、マシン上のすべてのプロジェクトで全額がかかり、バンクを持つ予定のないプロジェクトにも課金されるためです。
変動費によって、固定費を取り戻せます。 27 ドキュメントのバンクの場合:
| 文字数 | 約トークン数 | |
|---|---|---|
| バンク全体を読む | 91,319 | 26,091 |
bank_route の回答、結果 3 件 | 839 | 240 |
| 回答で示された 3 件のドキュメント | 8,386 | 2,396 |
ルーティングの回答は、コーパス全体を読むのにかかる 26,091 トークン分の情報を 240 トークンで示します。適切な 3 件のドキュメントを読む場合と比べても 10 分の 1 です。文章は返さず、パス、タイトル、purpose、推論の一文だけを返すためです。その後、何を読むかは根拠に基づいて判断できます。
bank_read にも個別の上限があります。1 バッチあたり 40,000 バイト、およそ 11,000 トークンです。20 件のドキュメントを要求すると、予算内に収まる分と、予算に達しなかった項目のリストがセクションごとに返されるため、1 回の呼び出しでコンテキストがあふれることはありません。
損益分岐点について。 約 7,000 トークンのコストは、サーバーが不要な読み込みを 1 回防げば回収できます。十分な規模のバンクであれば、それはすぐに起こります。同じ系譜に属する 4 つのバンクで測定した結果:
27 documents ~26k tokens to read whole
78 documents ~87k
96 documents ~158k
112 documents ~305k
この範囲の上限では、コーパスはコンテキストウィンドウ 2 つ分に達し、どんな価格であっても読むのは現実的ではありません。一方、ルーティングされた回答は数百トークンに収まります。
正直な結論としては、小さなバンクではサーバーのほうが割高です。コーパス全体がまだコンテキストウィンドウに収まるなら、cat のほうが 7,000 トークンの常時コストより安く、フィールドテストでもまさにそれが測定されました。全体を読めるほど小さなバンクでは、ルーティングは「すでにコンテキストにある」状態と競合し、負けます。サーバーがコストに見合うのは、読むには大きくなりすぎたコーパスです。
そして、購入しているものの中でトークンは重要な要素ではありません。ツールの半分はコストを節約せず、bank_create、bank_validate、bank_drift はコストを増やすだけです。これらはバンクの構造を保つために存在します。コンテキストの予算をどれだけ増やしても、それだけで構造を保つことはできません。ルーティングによる節約は副次的な効果であり、主な目的ではありません。
実行方法
プロジェクトへの組み込み — <project>/.mcp.json:
{
"mcpServers": {
"memorybank": {
"command": "npx",
"args": ["-y", "@maxweb4u/mcp-memorybank", "--root", "./memory_bank"]
}
}
}
依存するようになったら、バージョンを固定してください — @maxweb4u/[email protected]。そうすれば、確認していないプロジェクトでサーバーの仕様が予期せず変わることはありません。
npm の類似性チェックでは、スコープなしの mcp-memorybank は受け入れられません。句読点が正規化され、すでにレジストリにある無関係な mcp-memory-bank と区別できなくなるため、パッケージにはスコープが付いています。パッケージがインストールするコマンドは、引き続き mcp-memorybank です。
プロジェクトにまだバンクがない場合は、まず初期化してください。このコマンドはスケルトン、ガバナンス一式、各セクションに登録されたインデックスを1つずつ作成します。この方法で作成されたバンクは、構造上、検証に必ず合格します:
npx @maxweb4u/mcp-memorybank --root ./memory_bank --init "Project Name"
Node 22 が必要です。hooks/README.md で説明しているフックには、jq と git も必要です。
すべてのプロジェクトに一度だけインストールする
プロジェクトごとの .mcp.json は明示的で、リポジトリと一緒に共有できるため、上記の形式になっています。別の方法として、このマシン上のすべてのプロジェクトに対して1つのエントリを設定できます:
claude mcp add --scope user memorybank -- npx -y @maxweb4u/mcp-memorybank --root ./memory_bank
--root ./memory_bank はプロジェクトディレクトリを基準に解決されるため、バンクがあるすべてのプロジェクトで1つのエントリを使えます。
注意点として、ユーザー単位のサーバーには、クライアント側でプロジェクトごとに無効化する方法がありません。disabledMcpjsonServers が制御するのは .mcp.json のエントリだけで、それ以外は対象外です。そのため、プロジェクトにどの範囲を提供するかは、サーバー自身が判断します:
| プロジェクトの状態 | 表示される内容 | リクエストあたりのコスト |
|---|---|---|
| バンクがある | すべて | 約7,000トークン |
| バンクがない | bank_init のみ | 約450トークン |
--off で指定されている、または隣接する .memorybank-off ファイルがある | 何も表示されない | 約25トークン |
グローバルインストールが現実的なのは、中央の行があるためです。これまで一度もバンクを持ったことのないプロジェクトのコストは、完全な機能の約7%です。また、提供される bank_init は、そのプロジェクトで意味のある唯一の呼び出しです。特別な設定は不要です。ルートにドキュメントがない場合、サーバーはこのように動作します。
「ここでは使わない」と伝える方法は2つあり、判断を誰が管理するかが異なります。
--off <path> はマシンに属します。 繰り返し指定でき、1つのパスでその配下すべてを対象にできます:
claude mcp add --scope user memorybank -- npx -y @maxweb4u/mcp-memorybank \
--root ./memory_bank --off /path/to/a/repo/you/do/not/govern
指定したプロジェクトには何も書き込まれません。パスはクライアント自身の設定に保存されるため、そのリポジトリの git status にファイルが現れることも、同僚に見つかるものもありません。そのため、他のチームが作業する商用リポジトリや、異なる階層に複数のバンクがあるプロジェクトには、この方法が適しています。1つのパスですべてを対象にできます。
.memorybank-off はプロジェクトに属します。 プロジェクトディレクトリ内の空ファイルで、バンクの中ではなく隣に置きます。サーバーを使わないという判断は、バンクではなくプロジェクトが下します:
touch .memorybank-off
プロジェクトそのものが拒否を示す必要がある場合、このファイルはリポジトリと一緒に共有できます。そのため、誰にも知られたくない場合にはこの方法を使わないでください。
利用可能な範囲は、セッション開始時に一度だけ決まります。例外は bank_init です。バンクがなかったプロジェクトでバンクを初期化すると、セッションを再起動せずに残りのツールがすぐに使えるようになります。
コマンドをインストールし、プロジェクトごとに接続する
3つ目の方法です。複数の人やマシンでリポジトリを共有する場合は、この方法をおすすめします。 一度インストールします。
npm i -g @maxweb4u/mcp-memorybank
次に、サーバーを使う各プロジェクトに、パスではなくコマンドを指定する6行の .mcp.json を用意します。
{
"mcpServers": {
"memorybank": {
"command": "mcp-memorybank",
"args": ["--root", "./memory_bank"]
}
}
}
これは、このセクションの冒頭にあるファイルと同じですが、npx -y @maxweb4u/mcp-memorybank をインストールしたコマンドに置き換えています。これによって得られるものは次のとおりです。
プロジェクトごと、npx 経由 | ユーザー単位、どこでも | インストールしたコマンド、プロジェクトごと | |
|---|---|---|---|
| バンクを使う新しいプロジェクト | ファイル1つ | そのまま使える | ファイル1つ |
| バンクを使わないプロジェクト | 約450トークン | 約450トークン | 何もなし |
| 対象外にするリポジトリ | .memorybank-off | --off <path> | ファイルを追加しない |
| 起動コスト | npx で約0.6秒 | npx で約0.6秒 | 約0.1秒 |
| Git に絶対パスを含める必要 | なし | なし | なし |
| 同僚のマシンで動作するか | はい | いいえ | はい、npm i -g の後 |
| バージョン | ファイルに固定 | ファイルに固定 | インストールされているバージョン |
3つ目の列だけが、バンクを使わないプロジェクトに一切コストがかからず、リポジトリを対象外にするための設定もどこにも必要ありません。ファイルを追加しないだけです。
その代わり、プロジェクトごとに手作業が必要で、バージョンは記録された番号ではなく、最後に npm i -g でインストールされたものになります。
フックにも同じインストールが必要ですが、理由は異なります。hooks/README.md を参照してください。
クローンから
サーバー自体を開発する場合や、複数のプロジェクトから1つのビルドを参照する場合は、次のようにします。
npm install && npm run build
node dist/cli.js --root /path/to/project/memory_bank
この方法で書いた .mcp.json には dist/cli.js への絶対パスが必要なため、プロジェクトが1台のマシン上にある1つのチェックアウトに依存します。開発中なら問題ありませんが、そのまま使い続ける用途には適しません。
このリポジトリ内では npx は動作しません。 サーバー自身のチェックアウトから npx @maxweb4u/mcp-memorybank を実行すると、sh: mcp-memorybank: command not found で失敗します。npx は指定された名前がローカルの package.json と一致することを検出し、パッケージはすでに存在すると判断します。そして、ローカルの node_modules/.bin でバイナリを探しますが、そこには何もリンクされていません。公開済みパッケージに問題があるわけではありません。同じコマンドをほかのディレクトリから実行すれば動作します。そのため、ユーザーが実際に入手するものを確認したい場合は、このリポジトリ内ではなく、一時ディレクトリで実行してください。
新しいバージョンを公開する際は、順序が重要です。作業ツリーではなく tarball 上で何を検証するか、公開前にどのピンを更新し、公開後にどのピンを更新するかが決まっています。詳細は releasing.md に記載されています。
エージェントに使い方を伝える
サーバーを接続するとツールを利用できるようになりますが、エージェントがそれを使うとは限りません。何も指示しなければ、モデルは多くの場合 README.md を開いてセクションの索引をたどります。ほかの場所でいつもそうしているからです。そして、このサーバーがなくそうとしているのは、まさにその読み方です。
プロジェクトの CLAUDE.md に段落を1つ追加すれば解決します。
# Memory bank
This project is served by the `memorybank` MCP server. **Its tools all begin with `bank_`; load them
at the start of a session and look at what is there** — the set grows, and anything not named here
still exists. Working the bank through them instead of through `sed`, `cat` and `grep` is the point:
they keep the frontmatter, the section indexes and the ownership rules intact, which a shell cannot.
- **Finding.** `bank_route` answers "which document covers this"; `bank_search` finds a literal
across the bank; `bank_read` takes a path and a section, so a long document costs one section.
Reach for those before grepping `memory_bank/`.
- **Changing.** `bank_edit` replaces one exact fragment — a backlog row, a table cell, a heading.
`bank_update_section` writes a whole named section. `bank_set_status` moves a document between
`draft`, `active` and `archived`, and releases its `canonical_for` when it retires.
- **Creating.** `bank_create` writes the frontmatter the contract asks for and registers the document
in its section index in the same operation. A document written by hand is reachable from no index.
- **Checking.** Run `bank_validate` before committing anything under `memory_bank/`, and say what it
found. Never promote a note out of `_inbox/` on your own — that is a review step for the owner.
この文面は好みの問題ではありません。実測に基づいています。この段落がないと、仕組みはただ置かれたままになります。bank_route が読み込まれていたある作業セッションでは、9時間の間、一度も呼び出されませんでした。以前の段落では5つのツールを明示していたため、使われたツールも5つだけでした。名前を挙げたものだけで、それ以外は使われませんでした。プレフィックスを示して調べるよう伝え、名前を列挙する代わりに作業ごとにまとめたところ、書き直しで新たに言及したツールがすべて使われるようになりました。
baseline five named rewritten
writes into the bank, shell 20 0 0
bank_edit 0 19 14
bank_route / bank_search 0/0 0/0 2/2
shell searches of the bank 18 11 8
bank_validate findings 3→5 6 0
対照実験で、その理由がはっきりします。CLAUDE.md をまったく与えなかったプロジェクトでも、ツール群を自力で見つけました。bank_edit は30回、bank_create は18回使われました。それでも、自身のバンクにシェルで7回書き込みを行い、そのすべてが、すでに使っていたツールとまったく同じ形式でした。バンクを機能させるのはサーバーです。指示だけでは何も検証できません。選択を一貫させるのは段落です。どちらも、もう一方の代わりにはなりません。field-test.md を参照してください。
ターミナルからのデバッグ
CLI は、エージェントを介さずにバンクを参照するための、読み取り中心のサブセットであり、ツールの機能をそのまま反映するものではありません。bank_drift、bank_edit、bank_update_section、bank_set_status、
bank_set_field、bank_discard は MCP 専用です。npm からインストールした場合のコマンドは mcp-memorybank、クローンから実行する場合は
node dist/cli.js です。
mcp-memorybank --root <bank> --stats
mcp-memorybank --root <bank> --route "filter thresholds" --limit 5
mcp-memorybank --root <bank> --read domain/rules.md --section Thresholds
mcp-memorybank --root <bank> --validate --summary
mcp-memorybank --root <bank> --validate --rule broken-derived-from
mcp-memorybank --root <bank> --graph domain/rules.md --direction down --depth 1
mcp-memorybank --root <bank> --search "FT-042" --limit 5
mcp-memorybank --root <bank> --changed HEAD~5
mcp-memorybank --root <bank> --create adr/ADR-...-name.md --kind adr --title "..." --purpose "..." --derived ../engineering/architecture.md --dry-run
mcp-memorybank --root <new-path> --init "Project Name" --dry-run
mcp-memorybank --root <bank> --materialize-flows
mcp-memorybank --root <bank> --list-inbox
mcp-memorybank --root <bank> --promote _inbox/note.md --to engineering/thing.md --derived ../dna/principles.md --dry-run
利用できる機能
| ツール | 内容 |
|---|---|
bank_route | 「これについて何を読めばよいか」を示します。canonical_for、purpose、title、セクション見出しを基に順位付けします |
bank_read | バイト数の上限内で、ドキュメント全体、1 つの第 2 レベルのセクション、または 1 回の呼び出しで複数のドキュメントを読み込みます |
bank_validate | dna/ でバンク自体が宣言しているルールに照らして、バンクをチェックします |
bank_graph | derived_from をたどります。down はこれに依存するもの(影響範囲)、up はこれが基づいているものです |
bank_drift | 各ドキュメントを anchors: で宣言されたコードと照合し、コードのほうが先行している箇所を報告します |
bank_search | ドキュメント本文を全文検索します。識別子、名前、リテラルが対象です |
bank_changed | git の参照または ISO 日付以降の変更を示します。リポジトリより古い参照を指定すると最初のコミットが対象になり、リポジトリのないバンクではすべてが返されます |
bank_init | 何もない状態からバンクを作成します。スケルトン、dna/、フロー、セクションインデックスが作成されます |
bank_materialize_flows | 同梱のドキュメントテンプレートをバンクにコピーし、以後はバンクがそれらを所有します |
bank_create | バンク独自のテンプレートからドキュメントを作成し、ゲートと _inbox を使ってインデックスに登録します |
bank_edit | 本文中の完全一致する箇所を 1 つ置き換えます。表の行、手順、見出しなどが対象です。一致箇所が曖昧な場合は拒否します |
bank_update_section | 既存ドキュメントの指定したセクションの本文を書き換え、それ以外はそのままにします |
bank_set_status | ドキュメントを別のライフサイクル状態に移し、有効化を保護するゲートを実行します |
bank_set_field | purpose、delivery_status、title のいずれかのフロントマター値を、元の引用形式を保って変更し、purpose と title をインデックスエントリに引き継ぎます |
bank_promote | メモを _inbox/ から正規のレイヤーに移し、登録したうえで元のファイルを削除します |
bank_discard | 隔離されたメモを記録に残して破棄します。対象は _inbox/ のみで、理由が必須です |
| リソース | 内容 |
|---|---|
memorybank://index | テンプレートを除く全ドキュメントの注釈付きインデックス |
memorybank://schema/frontmatter | バンク独自の dna/frontmatter.md をそのまま表示します |
memorybank://health | 最新の bank_validate 実行結果をルール別にまとめたもの |
memorybank://inbox | 隔離中の項目を古い順に表示します |
プロンプト:session-start(プロジェクトの概要、進行中の作業、未解決事項を、すべてを読むのではなくインデックスと差分から確認)、route-then-read(最初にルートを特定し、次に読み込み)、check-before-commit(検証を実行し、すべての指摘を説明)、record-adr(意思決定を ADR にまとめる)、review-inbox(隔離中の項目を確認)。
このうち 2 つは、設計上の想定ではなく、実際の作業セッションで起きたことを受けて追加されました。ある午後、エージェントは bank_read を一度も呼び出さず、シェルループで cat を 85 回実行しました。10 個のドキュメントを読むには、片方の方法ではツール呼び出しが 10 回、もう片方では 1 回で済むため、大きなドキュメントがコンテキストウィンドウに入らないようにする機能であるセクション単位の読み込みは、検討される前に計算上の不利で敗れました。そこで、リストを受け取れる bank_read が追加されました。また、毎回のセッションで最初に聞かれるのは「これは何か、どこで中断したか、未解決なのは何か」でしたが、ルーティングでは答えられませんでした。その質問を受け取る先がなかったため、エージェントはバンク全体を読み込んでいました。そこで session-start が追加されました。
バンクの作成
mcp-memorybank --root <new-path> --init "Project Name"
24 個のファイルを書き込みます。dna/(レイヤーテーブルを含む 8 つのガバナンス文書)、4 つのフロー文書、シード済みの 8 セクションそれぞれのインデックスとルートインデックス、テンプレートの配置先を示すポインター、そして 2 つのドラフト、product/context.md と engineering/testing-policy.md です。これらのドラフトは、テンプレートと機能の完了ゲートの両方から参照されます。
さらに 3 つのセクション、epics/、prd/、prompts/ は事前に作成されません。文書で必要になった最初の時点で、bank_create がそのセクションとインデックス、ルートインデックスのエントリーを作成し、その旨を伝えます。
スターターセットは starter/ にあり、コピーされた時点でバンクに属します。自由に書き換えてください。サーバーが内容を押し付け続けることはありません。コピーされないのは flows/templates/ です。シード済みバンクの内容を参照してください。
新規作成したバンクは、指摘事項ゼロで検証に合格します。 これこそ、他者のバンクをコピーするのではなく bank_init を使う理由です。コピーすると、そのバンクの欠陥まで引き継いでしまいます。
テンプレートと隔離領域
書き込みの仕組みについては仕組みで説明しています。以下では、コンテンツの取得元と、未レビューのメモの行き先について説明します。
bank_create は、渡された本文を書き込みます。本文が渡されなかった場合は、対応するテンプレートの本文を書き込みます。文書を作成してから、あとで本文を書き込むのではなく、body を渡してください。見出しだけで作成された文書は、サーバーのすべてのゲートを迂回して、シェル経由で内容が追加されがちです。
既存の文書にアクセスするツールは 3 つあります。それぞれの使い分けは、整然とした分類というより、実測に基づいています。
bank_update_section は、名前を指定した 1 つのレベル 2 セクションの本文を書き換え、フロントマター、タイトル、その他すべてのセクションには手を加えません。このツールがあるのは、bank_init が product/context.md と engineering/testing-policy.md を、内容を埋めることを前提としたドラフトとして用意する一方、bank_create は既存のパスを正しく拒否するためです。その結果、サーバーが作成を求める 2 つの文書を、サーバー自身が書き込めない状態になっていました。存在しないセクションを指定すると、追加されるのではなく、実在するセクションの一覧とともに拒否されます。そのため、見出しを誤入力しても、気付かないうちにセクションが追加されることはありません。
bank_update_section と bank_create は、呼び出しからではなくファイルからもコンテンツを取得できます。指定するのは contentFile と bodyFile です。これは、実測に基づく理由があって追加されました。あるセッションで、スクリプトを使って他の 19 文書から 610 行のテーブルを生成し、すべてのゲートを迂回してバンクに直接書き込みました。ツールに書き込む能力がなかったからではなく、テーブル全体を文字列引数として渡す必要があったためです。そのテーブルは、エージェントのコンテキストに一度も入っていませんでした。計算によって生成されたコンテンツは、管理対象にするために読み上げ直す必要がないはずです。また、JSON 文字列として渡されない本文なら、文字列としてのシリアライズに失敗することもありません。
bank_edit は、完全に一致する断片を 1 つ置き換えます。セクション単位で書き換える方式は、多くの編集には粒度が合わないことが分かりました。作業セッション中にバンクへシェル経由で行われた 11 回の書き込みのうち 5 回は、数十行に及ぶセクション内の数行だけを変更していました。テーブルの 1 行、計画の 2 ステップ、議論の 1 段落などです。さらに 1 回は、見出しの名前を変更していました。セクション全体を置き換えるには、編集箇所以外の変更しない内容もすべて再送する必要があるため、エージェントは毎回、代わりに文字列置換を使うようになります。契約は既知のものと同じです。完全一致する文字列が 1 回だけ現れる場合に限り置換し、それ以外は拒否して、最初の出現箇所を推測するのではなく一致数を報告します。同じ語句が 2 回現れる場合は、section で検索範囲を絞り込めます。検索は本文を対象とするため、フロントマターには構造上手が届きません。見出し名は変更できますが、canonical_for をひそかに書き換えることはできません。
bank_set_statusはドキュメントのライフサイクルステータスを別のステータスに変更するもので、ほかの2つではフロントマターにアクセスできないため用意されています。bank_initが作成し、bank_update_sectionが内容を記入したドラフトは、いずれかの時点でアクティブにする必要があります。これを行うツールがなければ、エージェントはフロントマターをストリームエディターで編集することになります。何もない状態からバンクを構築したセッションで、そのような編集が実際に行われていました。シェルに任せる編集としては、これが最悪です。status: activeはderived_fromを必須にする条件ですが、ガバナンスが守るためにある唯一のステータス遷移が、その条件を回避できてしまっていました。ここでは遷移時にチェックが実行され、_inbox/内のノートは明確に拒否されます。隔離状態から出すには、ノートの配置とステータスの設定を一度に行うbank_promoteを使うためです。
アーカイブは、何かを手放す唯一の遷移です。ownerByKeyはステータスを参照しないため、canonical_forを宣言したままドキュメントをアーカイブすると、キーを引き継ぐべき後継ドキュメントが引き続きブロックされます。bank_createは後継ドキュメントの作成を拒否し、「すでに所有されています」と、もう誰も読まないドキュメントの名前を示します。そのため、releaseCanonicalで許可されない限り、所有者のアーカイブは拒否され、同じ書き込み処理の一部としてキーも削除されます。こちらも実測では、ドキュメントをアーカイブしたセッションでpythonの正規表現を使ってブロックが削除されていました。それが唯一の方法だったためです。
bank_set_fieldはそれ以外のフロントマターを扱うもので、同じ理由から用意されています。4つのバンクで通常の作業を5日間行ったところ、サーバーを迂回した11回の書き込みのうち8回はフロントマターの編集でした。purposeが6回、delivery_statusが2回です。bank_editではブロックにアクセスできず、bank_set_statusが扱うキーは1つだけだからです。bank_routeが順位付けに使うのはpurposeなので、ルーティングが依存するフィールドを修正できるのはシェルだけでした。このツールはスカラー値を1つだけその場で設定し、ほかのバイトはすべてそのままにします。行で使われていた引用符の形式を維持し、YAMLで読み戻したときに値が変わらないよう、必要に応じて引用符を追加します。また、delivery_statusとdecision_statusをdna/と照合し、変更されたpurposeまたはtitleを、それを重複して記載するインデックスエントリーにも反映します。ガバナンスの対象となるフィールドは拒否されます。statusには独自の制約があり、doc_kindとdoc_function、およびすべてのリストフィールドも対象です。
bank_createは、バンクにflows/templates/があればそこからテンプレートを取得し、なければサーバーに同梱されたテンプレート群から取得します。テンプレートはラッパーです。インスタンス化するドキュメントは、## Instantiated Frontmatterと## Instantiated Bodyの下にある2つのブロックに記述されています。サーバーはその2つのブロックだけを展開し、タイトルを置換し、日付のプレースホルダーを埋め、テンプレートにガバナンスとして含まれるmust_not_defineを引き継ぎます。不明なdoc_kindは拒否ではなく警告になります。dryRunを使うと、フロントマター、選択されたテンプレート、インデックス行を含む結果全体が表示され、何も書き込まれません。
inbox: trueを指定すると、テンプレートも登録も行わず、ドキュメントをstatus: draftの状態で_inbox/に配置します。隔離されたドキュメントはunregistered-docルールの対象外であり、bank_routeとbank_searchからは一切返されません。アクセスできるのはbank_read、memorybank://inboxリソース、bank_promote経由のみです。レイヤーの重みでスコアを下げる方法も最初に試しましたが、同じではありませんでした。バンクを作ったばかりの頃、ある質問への回答となるノートが検索結果の上位3件に入ったのです。隔離によって防ぐべきなのは、まさにこの事態です。
bank_promoteは隔離状態を解除します。ノートの本文は記述されたまま保持され、フロントマターは契約に照らして再構築されます。移動先のテンプレートから引き継ぐのはガバナンスフィールドのみで、ドキュメントはインデックスに登録され、元のファイルは削除されます。適用される制約はbank_createと同じですが、独自の制約が2つ追加されます。_inbox/からのみプロモートでき、プロモート先は外部に限られます。プロモート後のステータスはactiveなので、derived_fromに関するガバナンス要件はここでは緩和されません。
bank_discard は3つ目の結果です。実際の隔離データを2回目にレビューしたとき、サーバーの外に出て処理する必要があったために存在します。メモは昇格するか、bank_update_section でその事実をすでに管理している文書に統合するか、破棄できます。しかし、最初の2つだけが実装されていたため、エージェントはメモを統合した後、シェルで rm を実行し、消費したばかりのメモを消そうとしました。3つの結果を示しながら2つしかサポートしないツールは、あらゆるゲートを通り抜けて3つ目に到達させてしまいます。対象は _inbox/ のみで、理由の指定も必須です。メモは記録に残るか、残りません。
隔離データは Stop フックによって追加されます。詳しくは hooks/README.md を参照してください。
2つの言語でのクエリ
バンクは英語で記述します。これはガバナンス上のルールであり、ここでそれを緩和することはありません。質問は別の話です。手入力する質問は英語以外であることが多く、英語のフィールドに対する語彙ランキングではまったく答えられません。「где пороги фильтрации」は、purpose にも canonical_for にも出現しない3つの単語に分割されるため、結果は単に誤っているのではなく、空になります。
そのため、bank_route と bank_search は、クエリの単語を文字列ではなく概念として扱います。各単語には、実際のバンク群の title / purpose / canonical_for における最頻出語から構築したドメイン辞書に基づき、もう一方の言語での対応語が付与されます。これにより、次の3つのことが実現します。
- 1つの概念は1回だけスコアに加算されます。 1つの
purposeに英語の対応語が3つ一致しても、ヒットは1回であり、クエリを拡張しただけで一致語の幅によって直接入力したクエリを上回ることはありません。 - 同点の場合、実際に入力された単語が優先されます。 翻訳語のスコアは低くなります。これは、言語が混在するバンクでのみ影響し、その場合は入力された語が最上位にランクされます。
- ロシア語は語形変化するため、辞書の項目は単語ではなく語幹の接頭辞です。
порогは пороги / порогов / порогам をカバーします。動詞の接頭辞を削除するのは、残りの部分が既知の語幹に一致する場合だけです。そのため、задеплоитьからdeploymentにたどり着けます。
実際のバンクで測定したところ、ロシア語の確認用質問では、英語の同等の質問と同じ最初の文書が返されます。検索は双方向です。インデックスではキリル文字も単語の文字として扱われるため、ロシア語の出典を引用した文書も検索でき、ロシア語のクエリからも同じ辞書を通じて英語の文章にたどり着けます。
未知の単語は、入力されたとおりにそのまま扱います。FT-042 や filter_thresholds のような識別子が辞書にかけられることはありません。
バンクには独自の単語を追加できます。 組み込み辞書には、その元となった分野、つまりエージェント、デプロイ、フロントエンド、バックエンドに関する語彙が含まれています。書籍の解析を扱うプロジェクトをサーバーに設定すると、ロシア語側では корпус、книга、прогон が機能しなくなります。回答の質が低いのではなく、結果が空になります。同じ質問を英語ですれば、3つのうち3つがヒットします。英語側ではバンクから語彙を読み取るため、この問題は起きません。一方、バンクはガバナンス上のルールで英語になっているため、ロシア語側の語彙をバンクから読み取ることはできません。そこで、必要に応じて dna/vocabulary.md に記述します。
| Russian stem | English |
|---|---|
| корпус | corpus, collection |
| книг | book, books, ebook |
| сегмент | segmentation, segmenter, sentence |
左の列は語幹、つまりすべての語形に共通する最短の接頭辞です。左側がキリル文字で、2列ではない行は無視されるため、表の前後に文章や見出しを含められます。更新のたびに再読み込みされるので、編集内容は再起動なしで反映されます。このファイルは bank_init では作成されません。必要のないバンクに空のファイルを置くべきではないためです。
検証ルール
| ルール | 深刻度 | 検出内容 |
|---|---|---|
broken-derived-from | error | エッジの参照先が、それを宣言したドキュメントから解決できない |
invalid-frontmatter | error | パーサーが拒否する YAML。通常は purpose に引用符で囲まれていないコロンがある |
cycle-in-derived-from | error | A が B から派生し、B が A から派生している |
ssot-conflict | error | 同じ canonical_for キーを主張するドキュメントが 2 つある |
missing-derived-from | error | 上流が指定されていない active の非ルートドキュメント |
must-not-define-violated | error | 定義しないと宣言したキーをドキュメントが定義している |
dangling-index-entry | error | 存在しないファイルへのリンクがインデックスにある |
unknown-enum-value | warning | dna/ で宣言されている値以外の doc_kind / doc_function / status |
unregistered-doc | warning | どのインデックスからもリンクされていないドキュメント。ナビゲーションから到達できない |
unresolved-rule-reference | warning | 解決できないパスでルールを参照している文章 |
unknown-layer-value | warning | dna/ のレイヤーテーブルに存在しないレイヤーが記載されている。その行は無視される |
no-contract | warning | バンクに dna/ がないため、契約ルールを実行できない |
順序はアルファベット順ではありません。バリデーターの構築中に各ルールが実行された回数の多い順に並べているため、効果の高いルールが先に表示されます。
構造ルールはどのバンクでも動作します。契約ルール(missing-derived-from、サイクル、unknown-enum-value)は、バンクが dna/governance.md で自ら宣言している場合にのみ適用されます。サーバーが適用するのはバンクのルールであり、サーバー自身のルールではありません。
実装が仕様と異なる点に関する判断
- スキーマはバンクから読み取ります。
doc_kind/doc_function/statusは固定の選択肢ではなく、dna/frontmatter.mdとdna/governance.mdから読み取るオープンセットです。固定の列挙型では、実際のバンクにあるドキュメントのかなりの割合が拒否されます。 - ドキュメントにはレイヤーがあります(
dna/knowledge/decision/delivery/flow)。レイヤーはパスから導出され、ランキングで重み付けされます。これがないと、ドキュメントの 80% がデリバリーの作業記録であるバンクでルーティングに失敗します。 derived_fromはドキュメントを基準に相対解決され、文字列と{ path, fit }の両方の形式に対応します。- ルートの外に出る参照は、壊れた参照として数えるのではなく、
externalとしてマークされます。モノレポではバンクが入れ子になるためです。 - YAML が無効なドキュメントもインデックスから除外されません。 実際のバンクにある 22 個のドキュメントでは、
purposeに引用符で囲まれていないコロンがあります。メタデータは行ごとに復元され、エラーはbank_validateが報告するために保持されます。 - テンプレートは
bank_routeの結果とmemorybank://indexから除外されます。 bank_routeとbank_searchは異なる問いに答えます。 前者は手書きのヘッダーをもとにランキングします(「これはどのドキュメントに書かれているか」)。後者は文章を検索します(「この文字列はどこにあるか」)。検索では、完全一致するトークンの重みを、その断片の 10 倍にしています。そうしないと、FT-042というクエリで、70 行のFT-*があるfeatures/README.mdのレジストリが、その機能自体より上位に表示されてしまいます。- クエリはバイリンガルですが、バンクはそうではありません。 ランキングでは、クエリの単語をもう一方の言語の同等語に展開し、概念を 1 回だけスコアリングします。ドキュメントは英語のままで、質問だけは別の言語でも構いません。
- フロントマターの解析箇所は 1 か所だけです。 オプションなしの
gray-matterは入力テキストをキーにメモ化します。また、例外を投げた後、フロントマター全体がcontent内に入ったオブジェクトをキャッシュとして返すことがあります。これは警告なしに起きます。常にオプションを渡し、解析は単一のsplitFrontmatterを通して行います。 - グラフでは 3 種類のエッジ結果を区別します。 内部(ノード)、外部(
external— 入れ子のバンク)、壊れた参照(broken)です。ノード数に上限を設けた幅優先探索を使うため、ハブとなるドキュメントがバンク全体を取り込んだり、サイクルでループしたりすることはありません。
テスト
npm test
3 つの階層に分かれた 195 件のテストがあり、それぞれ異なる目的があります。
生成 — test/acceptance-generated.test.ts は、現在の bank_init でバンクを初期化し、bank_create だけを使って内容を追加したあと、プランの準備状況の判定基準を適用します。対象は、両方の言語でのルーティング、サーバー自身が書き込んだエッジで構成されるグラフ、検索、git の差分、すべての拒否ゲートです。一時ディレクトリと git だけで実行できるため、この階層のテストは常に成功する状態を維持する必要があります。また、この設計で最も強く主張している点も検証します。サーバーだけを使って完全に構築したバンクは、指摘事項ゼロで検証に合格し、書き込みを 3 回拒否したあとも合格します。
フィクスチャ — test/fixture-bank/ は、意図的に違反を含めたバンクです。壊れたエッジ、事実に対する 2 人目の所有者、孤立した項目、壊れた YAML、ルート外の参照が含まれています。bank_create ではこれらを作れないため、あえて手書きの状態にしています。
コーパス — test/acceptance.test.ts は、指定した任意のバンクに対して同じ判定基準を実行します。設計上の判断を最初に測定した方法でもあります。これらのバンクは利用者のものであり、リポジトリ外にあるため、このテストスイートはオプトイン方式です。
MEMORYBANK_TEST_ROOTS=/path/to/projects npm run test:corpus
変数が設定されていない場合はスキップします。設定されているのにバンクが見つからない場合は、スキップせず失敗します。パスの入力ミスがテスト合格として扱われてはなりません。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。