From dda043b0ec9d8d9e5da7fe51fc48791086417a9b Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Sat, 4 Jul 2026 19:19:00 +0900 Subject: ドキュメント更新 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 205 ++++++++++++++++++++------------ docs/mcp.md | 390 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 519 insertions(+), 76 deletions(-) create mode 100644 docs/mcp.md diff --git a/README.md b/README.md index bf935e0..e86c8f9 100644 --- a/README.md +++ b/README.md @@ -1,128 +1,181 @@ -# tiny-convmem-mcp +# Tiny ConvMem MCP -LLMの会話メッセージをローカルの追記ログへ保存する、Windows向けC17製MCP -サーバです。書込みと削除はディスクへの同期完了後に成功を返し、プロセス再起動時は -ログまたはsnapshotから状態を復旧します。 +Tiny ConvMem MCPは、LLMとの会話をローカル環境へ保存し、あとから検索・参照できる +MCPサーバーです。 -## 必要環境 +会話メッセージをsession単位で蓄積し、本文やタグから検索したり、関連する前後の会話を +取り出したりできます。データは指定したdirectory内に保存され、外部databaseやnetwork +serviceを必要としません。 + +現行versionはWindows x64をサポートしています。将来的には、Linux対応も想定しています。 + +## 主な特徴 + +- **ローカル完結:** 会話データを指定したdirectoryへ保存 +- **永続的な記憶:** MCP hostやPCを再起動しても保存内容を復元 +- **文脈を含む検索:** 本文、summary、tagを検索し、前後のメッセージやrelationも取得 +- **構造化metadata:** 記憶の種類、tier、importance、tag、summary、relationを保持 +- **安全な更新:** revisionを使った競合検出付きのメッセージ更新 +- **耐障害性:** append-only log、CRC検証、snapshot fallback、書込み完了前のdisk flush +- **バックアップ:** JSONL形式のtransactional importとatomic export +- **軽量な実装:** C17で実装されたstdio MCPサーバー + +## 動作環境 - Windows x64 -- Visual Studio Community 2026(Desktop development with C++) +- Visual Studio Community 2026 + - `Desktop development with C++` workload - CMake 4.3以降 - Git -- vcpkg(PATHから実行可能であること) +- vcpkg(`PATH`から実行可能であること) + +## ビルド -## ビルドとテスト +repositoryをcloneし、submoduleを取得してからbootstrap scriptを実行します。 ```powershell git submodule update --init --recursive -.\scripts\bootstrap.ps1 -Configuration Debug +.\scripts\bootstrap.ps1 -Configuration Release ``` -手動実行する場合は次のとおりです。 +生成される実行ファイル: + +```text +build\windows-vs2026\src\Release\tiny-convmem-mcp.exe +``` + +手動でbuildする場合: ```powershell $config = vcpkg z-print-config | ConvertFrom-Json $env:VCPKG_ROOT = $config.'vcpkg-root' + cmake --preset windows-vs2026 -cmake --build --preset windows-vs2026-debug -ctest --preset windows-vs2026-debug +cmake --build --preset windows-vs2026-release +ctest --preset windows-vs2026-release ``` -AddressSanitizer構成は独立した`build/windows-vs2026-asan`を使用します。 +## MCPクライアントへの登録 -```powershell -cmake --preset windows-vs2026-asan -cmake --build --preset windows-vs2026-asan -ctest --preset windows-vs2026-asan -``` +MCPクライアントのstdio server設定へ、実行ファイルと保存先を登録します。設定形式は +クライアントによって異なりますが、一般的なJSONの例は次のとおりです。 -単体テストにはUTF-8/UTF-16、unaligned binary codec、allocation失敗、short I/Oと -操作別I/O失敗が含まれます。自前関数とテストの対応は -[function coverage map](tests/function-coverage.md)を参照してください。 +```json +{ + "mcpServers": { + "tiny-convmem": { + "command": "C:\\path\\to\\tiny-convmem-mcp.exe", + "args": ["--data-dir", "C:\\path\\to\\conversation-memory"] + } + } +} +``` -## 起動 +`--data-dir`は必須です。相対pathも利用できますが、MCP hostの起動directoryに左右されない +絶対pathを推奨します。同じdata directoryを複数processから同時に開くことはできません。 -保存先は必ず明示します。相対パスも使用できます。同じdata directoryを使用する -プロセスの二重起動は拒否されます。 +手動で起動する場合: ```powershell -.\build\windows-vs2026\src\Debug\tiny-convmem-mcp.exe --data-dir .\data +.\tiny-convmem-mcp.exe --data-dir .\conversation-memory ``` -stdoutはMCP JSON-RPC専用、stderrは診断専用です。 +標準入力と標準出力はMCP JSON-RPC専用です。診断messageは標準error出力へ送られます。 ## MCP tools -- `write_message`: 再起動後も利用する会話メッセージを同期永続化 -- `read_messages`: 履歴、session一覧、前後・relation文脈、versioned JSONL exportをviewで取得 -- `search_messages`: content・summary・tagsを検索し、filterとscore内訳を返す -- `update_message`: IDと作成情報を保ったまま本文またはmetadataを差分更新 -- `delete_message`: メッセージをread/searchから即時除外(物理除去はrebuild後) -- `rebuild_index`: 保守時にStoreをcompactし、snapshotとメモリindexを再構築 -- `get_system_health`: 永続化・復旧問題の診断に必要なStore状態と件数を取得 +| Tool | 用途 | +|---|---| +| `write_message` | session、role、contentと任意metadataを新しいメッセージとして保存します。 | +| `read_messages` | session履歴、session一覧、対象メッセージ前後の文脈を取得します。 | +| `search_messages` | content、summary、tagを検索し、score内訳や任意の前後文脈を返します。 | +| `update_message` | IDや作成日時を維持したまま、本文またはmetadataを更新します。 | +| `delete_message` | メッセージをread/search結果から除外します。物理除去はrebuild後です。 | +| `rebuild_index` | logをcompactし、snapshotとmemory indexを再構築します。 | +| `get_system_health` | 件数、file size、recovery状態、I/O、推定memory使用量を返します。 | + +主なmetadata: -ツール結果はMCP TextContent内のJSON文字列です。入力schema、サイズ制限、エラーコード -および物理フォーマットは[設計書](docs/Design.md)を参照してください。 -既知の設計上限と将来対応事項は[設計書](docs/Design.md#12-将来対応事項)にまとめています。 +- `tags`: 検索や分類に使うtag +- `memory_tier`: `short` / `mid` / `long` +- `memory_kind`: `conversation` / `episode` / `semantic` / `procedure` / `preference` / `summary` +- `importance`: 0~100 +- `summary`: 要約文 +- `relations`, `source_message_ids`: メッセージ間の関係 -## 永続化と復旧 +toolの入力parameter、response形式、filter、viewの詳細は +[MCP Tool Reference](docs/mcp.md)を参照してください。実際の入力schemaはMCPの`tools/list`からも +取得できます。 -`active.log` はversion付きlittle-endianレコード、payload CRC32、単調sequenceを持ちます。 -v2はv1 PUT/DELを読取り可能で、UPDATEは変更fieldだけを追記します。 -PUT/DELはsys層のdurability flushが成功するまで`OK`を返しません。末尾で中断したレコード -だけは正常境界まで自動切詰めし、途中のCRC不一致はデータを保存したまま起動を拒否します。 +## バックアップと復元 -`rebuild_index` は新しいgeneration markerを持つログを作成後、同じgenerationとログoffsetを -記録した `snapshot.bin` を置換します。起動時に両者が一致するとsnapshotをロードし、後続 -レコードだけを再生します。不一致または不正なsnapshotは変更せず、ログ全体を再生します。 +serverを停止した状態でJSONLへexportできます。 -削除は呼出時には論理削除です。元本文が `active.log` から除去されるのはcompaction成功時で、 -SSD、バックアップ等からの安全消去は保証しません。`memory_tier` は初期版では属性であり、 -自動要約・昇格・破棄を行いません。 +```powershell +.\tiny-convmem-mcp.exe --data-dir .\conversation-memory ` + --export .\backup.jsonl +``` -## セキュリティとバックアップ +特定sessionだけをexportする場合: -data directory、snapshot、active log、JSONL exportはすべて平文であり、接続済みのstdio -hostは全tool権限を持ちます。CRC32は偶発的な破損検出用で、悪意ある改変の認証では -ありません。取得内容はprompt injectionを含み得るため、host側で命令として信頼しないでください。 +```powershell +.\tiny-convmem-mcp.exe --data-dir .\conversation-memory ` + --export .\session-backup.jsonl --session my-session +``` -data directoryとexport先は専用Windowsアカウントだけが読めるACLにします。 +backupをimportする場合: ```powershell -icacls .\data /inheritance:r /grant:r "$env:USERNAME:(OI)(CI)F" +.\tiny-convmem-mcp.exe --data-dir .\restored-memory ` + --import .\backup.jsonl --policy overwrite ``` -exportは元データと同じ機密区分で保管してください。暗号化が必要ならBitLocker等の上位暗号化 -filesystemを使用します。削除やcompactionはfilesystem、SSD、既存backupからのsecure eraseを -保証しません。 +`--policy reject`を指定すると、同じmessage IDの重複をrejectできます。importは全recordを +検証してstaging logへ書き、完了後にだけ保存先を置換します。 -## 容量とメモリ予算 +## データとセキュリティ -importは1行`1 MiB + 4096 bytes`、100,000 record、decode後合計4 GiBが上限です。 -2-pass importは本文全件を保持せず、compact ID indexと最大1行だけをメモリに保持します。 -全件検証とstaging logのflushが成功した場合だけ`active.log`を置換します。 -`get_system_health.memory_estimate`はstore entry、本文、metadata、search、history、relation indexの -確保量概算を返します。allocator overhead等は含まないため、実working setには余裕を持たせて -ください。現行v1はlive corpusをメモリに保持し、retentionやdisk-backed bodyは実装しません。 +`active.log`、`snapshot.bin`、JSONL backupは平文です。会話に機密情報が含まれる場合は、 +data directoryとbackupへ適切なWindows ACLを設定し、必要に応じてBitLockerなどの暗号化された +filesystemを使用してください。 -## mcpcの管理 +```powershell +icacls .\conversation-memory /inheritance:r /grant:r "$env:USERNAME:(OI)(CI)F" +``` + +注意事項: -mcpc submoduleはコミット `4025fddfedcfa7573803a47236b147733fcb3c07` に固定しています。 -固定版tool APIではoptional/object/array schemaとcallback contextを表現できないため、 -プロジェクト側MCP互換境界がmcpc同梱のmjson parserを利用してraw argumentsとschemaを扱います。 -submoduleのソースは変更しません。詳細は[src/mcp/README.md](src/mcp/README.md)を参照してください。 +- CRC32は偶発的な破損を検出するもので、悪意ある改変を防ぐ認証機能ではありません。 +- 接続したMCP hostは保存、読取り、更新、削除、maintenanceを実行できます。 +- 削除後も、compact前のlogや既存backup、filesystem履歴にデータが残る場合があります。 +- 保存したcontentやmetadataを命令として信頼するかどうかはMCP host側の責任です。 -## コーディングスタイル +## 現在の制約 -プロジェクト所有のCソースとヘッダーは、リポジトリ直下の -[`.clang-format`](.clang-format)に従います。制御文には常に波括弧を付け、1行には -1つのstatementだけを記述します。`third_party/` は整形対象外です。 +- 現行versionはWindows x64のみを正式サポートしています。Linux対応は将来予定です。 +- 単一process、単一writerを対象としています。 +- live corpusと検索indexはmemory上に保持されるため、必要memoryは保存量に比例します。 +- 自動保存判断、自動要約、retention、tierの自動移動、利用頻度による強化・減衰は行いません。 +- import上限は100,000 record、1行あたり約1 MiB、decode後合計4 GiBです。 -Visual Studio同梱のclang-formatを使う場合は、PowerShellで次のように整形できます。 +詳しい設計、物理format、回復動作、将来対応事項は[Design.md](docs/Design.md)にあります。 + +## 開発者向けテスト ```powershell -$formatter = 'C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\Llvm\x64\bin\clang-format.exe' -$files = rg --files src tests -g '*.c' -g '*.h' -& $formatter -i --style=file $files +cmake --build --preset windows-vs2026-debug +ctest --preset windows-vs2026-debug + +cmake --preset windows-vs2026-asan +cmake --build --preset windows-vs2026-asan +ctest --preset windows-vs2026-asan ``` + +関数とtestの対応は[function coverage map](tests/function-coverage.md)を参照してください。 + +## ライセンス + +このprojectは[MIT License](LICENSE)で提供されます。 + +third-party componentにはそれぞれのライセンスが適用されます。詳細は`third_party/`配下の +licenseおよびupstream情報を参照してください。 diff --git a/docs/mcp.md b/docs/mcp.md new file mode 100644 index 0000000..639946f --- /dev/null +++ b/docs/mcp.md @@ -0,0 +1,390 @@ +# MCP Tool Reference + +Tiny ConvMem MCPが公開する7つのtoolの仕様をまとめる。実際のJSON SchemaはMCPの +`tools/list`からも取得できる。 + +## 共通仕様 + +### Tool result + +toolのapplication responseは、MCP `TextContent.text`に格納された**JSON文字列**である。 +呼出側はTextContentを取得した後、その`text`をJSONとしてparseする。 + +成功例: + +```json +{"status":"OK"} +``` + +application errorではMCP tool resultの`isError`がtrueになり、`text`は次の形式になる。 + +```json +{ + "status": "ERROR", + "error": { + "code": "VALIDATION_ERROR", + "message": "invalid arguments" + } +} +``` + +主なerror code: + +| Code | 意味 | +|---|---| +| `VALIDATION_ERROR` | parameter、JSON文字列、値域が不正 | +| `NOT_FOUND` | 対象messageまたはcontextが存在しない | +| `CONFLICT` | `expected_revision`が現在のrevisionと一致しない | +| `BUSY` | resourceが使用中 | +| `IO_ERROR` | logの書込み、flush、置換などに失敗 | +| `CORRUPT_STORE` | 永続化dataの整合性error | +| `INTERNAL_ERROR` | allocationや内部状態のerror | + +### Message object + +各toolが返すmessageは次の形式である。 + +```json +{ + "message_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "session_id": "conversation-1", + "timestamp": 1720843200000, + "role": "user", + "content": "Remember this", + "revision": 1, + "updated_at": 1720843200000, + "metadata": { + "tool_call_id": "optional", + "tags": ["project", "decision"], + "memory_tier": "short", + "memory_kind": "conversation", + "importance": 50, + "summary": "optional summary", + "relations": [ + {"type": "derived_from", "message_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW"} + ], + "source_message_ids": ["01ARZ3NDEKTSV4RRFFQ69G5FAW"] + } +} +``` + +`tool_call_id`と`summary`は未設定時に省略される。その他のmetadata fieldは空配列やdefault値を +含めて出力される。 + +### Metadata JSON + +`write_message.metadata_json`、`update_message.metadata_json`はJSON objectを表す文字列である。 +MCP arguments内では次のように渡す。 + +```json +{ + "metadata_json": "{\"tags\":[\"project\"],\"memory_kind\":\"semantic\",\"importance\":80}" +} +``` + +| Field | 型・制限 | Default | +|---|---|---| +| `tool_call_id` | string、最大256 UTF-8 bytes | 未設定 | +| `tags` | string配列、最大32件、各128 bytes、空文字不可 | `[]` | +| `memory_tier` | `short` / `mid` / `long` / `discard` | `short` | +| `memory_kind` | `conversation` / `episode` / `semantic` / `procedure` / `preference` / `summary` | `conversation` | +| `importance` | integer、0~100 | `50` | +| `summary` | string、最大4096 bytes。空文字は未設定扱い | 未設定 | +| `relations` | 最大32件の`{"type", "message_id"}` | `[]` | +| `source_message_ids` | 26文字ULIDの配列、最大64件 | `[]` | + +relation typeは`depends_on`、`derived_from`、`supersedes`、`related_to`のいずれかである。 + +## `write_message` + +新しいmessageを生成し、diskへflushしてから成功を返す。 + +### Input + +| Parameter | 必須 | 型 | 説明 | +|---|---:|---|---| +| `session_id` | Yes | string | 1~256 UTF-8 bytes | +| `role` | Yes | string | `user` / `assistant` / `system` | +| `content` | Yes | string | 1 byte~1 MiBのUTF-8 text | +| `metadata_json` | No | string | Metadata JSON。defaultは`{}` | + +### Output + +```json +{"status":"OK","message":{...Message object...}} +``` + +`message_id`、`timestamp`、`updated_at`はserverが生成し、初期`revision`は1となる。 + +## `update_message` + +messageのidentity、session、role、作成時刻を維持し、contentまたはmetadataを更新する。 + +### Input + +| Parameter | 必須 | 型 | 説明 | +|---|---:|---|---| +| `message_id` | Yes | string | 対象の26文字ULID | +| `content` | No | string | 新しいcontent。最大1 MiB | +| `metadata_json` | No | string | 更新するmetadata fieldを含むJSON文字列 | +| `expected_revision` | No | integer | 1以上。現在値と違う場合は`CONFLICT` | + +`content`と`metadata_json`の少なくとも一方が必要である。`metadata_json`に含まれないfieldは +維持される。同じ値への更新はno-opとなり、`changed`はfalse、revisionとlogは変更されない。 + +### Output + +```json +{"status":"OK","changed":true,"message":{...Message object...}} +``` + +## `read_messages` + +`view`によりhistory、context、sessionsの3種類を返す。`view`省略時は`history`。 + +### 共通Input + +| Parameter | 必須 | 型 | 説明 | +|---|---:|---|---| +| `view` | No | string | `history` / `context` / `sessions` | +| `limit` | No | integer | 1~1000、default 100 | +| `cursor` | No | string | 前回responseの`nextCursor`。内容はopaque | + +responseは約8 MiBのbudget内で打ち切られる場合がある。 + +### `history` view + +session内のlive messageを作成順に返す。 + +追加parameter: + +| Parameter | 必須 | 型 | 説明 | +|---|---:|---|---| +| `session_id` | Yes | string | 対象session、最大256 bytes | + +```json +{ + "history": [{...Message object...}], + "nextCursor": "optional opaque cursor" +} +``` + +続きを読む場合は同じ`session_id`と`nextCursor`を渡す。 + +### `context` view + +指定message、同一sessionの前後message、relation graphを返す。 + +追加parameter: + +| Parameter | 必須 | 型 | Default | 説明 | +|---|---:|---|---:|---| +| `message_id` | Yes | string | — | 対象の26文字ULID | +| `before` | No | integer | 1 | 同一sessionから前に返す件数、0~10 | +| `after` | No | integer | 1 | 同一sessionから後に返す件数、0~10 | +| `relation_depth` | No | integer | 1 | relation traversal depth、0~2 | +| `relation_types_json` | No | string | 全type | relation type配列を表すJSON文字列 | + +`relation_types_json`の例: + +```json +{"relation_types_json":"[\"derived_from\",\"supersedes\"]"} +``` + +```json +{ + "message": {...Message object...}, + "chronological_context": { + "before": [{...Message object...}], + "after": [{...Message object...}] + }, + "relations": [ + { + "type": "derived_from", + "direction": "outgoing", + "depth": 1, + "message": {...Message object...} + } + ], + "session": {"session_id":"conversation-1","message_count":12}, + "truncated": false +} +``` + +削除済みrelation先では`message`の代わりに`message_id`と`"status":"deleted"`が返る。 +最大50 message相当またはresponse budget到達時は`truncated`がtrueになる。 + +### `sessions` view + +session一覧を時刻とIDの安定順序でpage取得する。`session_id`は使用しない。 + +```json +{ + "sessions": [ + { + "session_id": "conversation-1", + "message_count": 12, + "kind_counts": { + "conversation": 8, + "episode": 1, + "semantic": 2, + "procedure": 0, + "preference": 0, + "summary": 1 + }, + "representative_summary": "latest summary or content", + "started_at": 1720843200000, + "last_message_at": 1720846800000 + } + ], + "nextCursor": "optional opaque cursor" +} +``` + +## `search_messages` + +content、summary、tagsを検索し、scoreの高い順に返す。同scoreではimportance等がtie-breakに +使われる。search自体はmessageやaccess履歴を書き換えない。 + +### Input + +| Parameter | 必須 | 型 | Default | 説明 | +|---|---:|---|---:|---| +| `query` | Yes | string | — | 1~4096 UTF-8 bytes | +| `session_id` | No | string | 全session | 指定sessionだけを検索 | +| `filters_json` | No | string | `{}` | filter objectを表すJSON文字列 | +| `min_importance` | No | integer | 0 | 0~100 | +| `context_size` | No | integer | 0 | 各結果の前後message数、0~3 | +| `limit` | No | integer | 20 | 1~100 | + +`filters_json`の形式: + +```json +{ + "kinds": ["semantic", "preference"], + "tiers": ["mid", "long"], + "tags": ["project", "confirmed"] +} +``` + +argumentsとして渡す場合はescapeした文字列にする。 + +```json +{ + "query": "database decision", + "filters_json": "{\"kinds\":[\"semantic\"],\"tags\":[\"project\"]}", + "min_importance": 60, + "context_size": 1, + "limit": 10 +} +``` + +`kinds`と`tiers`は各配列内でOR、category間はANDとなる。`tags`は指定した全tagを持つmessage +だけが一致する。 + +### Output + +```json +{ + "results": [ + { + ...Message object fields..., + "score": 1.0, + "score_breakdown": {"content":1.0,"summary":0.0,"tags":0.0}, + "matched_fields": ["content"], + "relation_count": 0, + "context_preview": { + "before": [{...Message object...}], + "after": [{...Message object...}] + } + } + ] +} +``` + +`context_preview`は`context_size > 0`の場合だけ出力される。response budgetへ到達すると、 +`limit`未満でも後続結果が省略される。 + +## `delete_message` + +messageを論理削除し、直ちにread/search対象から除外する。 + +### Input + +| Parameter | 必須 | 型 | 説明 | +|---|---:|---|---| +| `message_id` | Yes | string | canonical 26文字ULID | +| `session_id` | No | string | 一致する場合だけ削除するsession guard | + +### Output + +削除した場合: + +```json +{"status":"OK"} +``` + +対象なし、削除済み、またはsession guard不一致の場合: + +```json +{"status":"NOT_FOUND"} +``` + +`NOT_FOUND`はapplication errorではない。本文がlogから物理的に除去されるのは +`rebuild_index`成功後である。 + +## `rebuild_index` + +parameterなし。live messageだけでlogをcompactし、snapshotとmemory indexを再構築する。 +同期処理のため、完了まで他requestを処理しない。 + +```json +{ + "status": "OK", + "messages_indexed": 120, + "records_removed": 35, + "bytes_before": 1048576, + "bytes_after": 524288 +} +``` + +## `get_system_health` + +parameterなし。永続化状態、logical I/O counter、memory estimateを返す。 + +```json +{ + "status": "OK", + "format_version": 2, + "log_size": 524288, + "snapshot_size": 262144, + "active_messages": 120, + "deleted_messages": 3, + "last_sequence": 150, + "update_records": 12, + "dead_bytes": 4096, + "memory_estimate": { + "total_bytes": 2097152, + "store_entries_bytes": 32768, + "body_bytes": 524288, + "metadata_bytes": 65536, + "search_index_bytes": 1048576, + "history_index_bytes": 131072, + "relation_index_bytes": 65536 + }, + "io": { + "write_bytes": 1048576, + "write_calls": 80, + "flush_calls": 20, + "truncate_calls": 0, + "replace_calls": 1 + }, + "recovered_tail": false, + "snapshot_valid": true, + "last_error": "" +} +``` + +`status`は`last_error`が空なら`OK`、診断messageが残っていれば`DEGRADED`となる。 +`memory_estimate`はprojectが管理する主要allocationの概算で、runtimeやallocator overheadは +含まない。 -- cgit v1.2.3