mini-app-mcp について
mini-app-mcpは、構造化された永続ストレージを必要とするエージェント向けのMCPサーバーです。schema.yamlでテーブルを定義し、1つのプロセスで複数のSQLiteテーブルを管理します。CRUDやスキーマ操作はMCP経由に限定されています。
主な機能
- 01schema.yamlによるテーブル定義
- 021プロセスで複数テーブルをマウント
- 03レコードのCRUD操作
- 04スキーマとクエリエイリアスの管理
- 05一括操作とスナップショット
- 06読み取り専用のスキーマ・文書リソース
活用シーン
- 01エージェント用の構造化プロジェクトデータ保存
- 02ユーザー・プロジェクト単位のテーブル管理
- 03データやスキーマ変更の一括適用
- 04スナップショットによるバックアップと復元
MCP 対応
CRUD、スキーマ管理、クエリエイリアス、スナップショットをMCPツールとして提供し、スキーマやエラー、ツール文書の読み取り専用リソースも公開します。stdioとStreamable HTTPに対応しています。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm ghcr.io/ynishi/mini-app-mcp:0.20.0ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/ynishi/mini-app-mcp:0.20.0stdioツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mini-app-mcp
エージェントファーストの CRUD ストア MCP サーバー — schema.yaml 駆動、SQLite バックエンド、単一デーモンで複数テーブルを管理します。
概要
mini-app-mcp は、1 つ以上の SQLite テーブルを単一の実行プロセスで管理する軽量な MCP サーバーです。各テーブルの構造は schema.yaml ファイルのみで定義します。マイグレーションも、REST API も、GUI もありません。CRUD は MCP ツール経由でのみ公開されるため、構造化された永続ストレージを必要とするエージェントのバックエンドとして最適です。
設計原則
- 唯一のスキーマ定義元としての
schema.yaml— フィールド名、型、必須制約は起動時に YAML から読み込まれます。アプリケーションコードにハードコードされたフィールドはありません。 - 単一デーモンで複数テーブルを管理 — 単一のサーバープロセスが、設定されたユーザースコープおよびプロジェクトスコープのディレクトリにあるすべてのテーブルを検出し、マウントします。専用のレガシーモード(
MINI_APP_SCHEMA+MINI_APP_DB)では、従来の単一テーブル動作を維持します。 - MCP 専用のエントリーポイント — HTTP/REST/CLI による CRUD インターフェースはありません。すべての読み書きは MCP ツール経由で行います。
- 構造化された JSON エラー — すべてのエラーレスポンスには機械可読な
codeフィールドが含まれ、エージェントがプログラムからエラーを処理できます。
schema.yaml の形式
table: issues
title: "Issue tracker"
description: "Tracks bugs and feature requests for a project."
fields:
- name: title
type: string
required: true
description: "Short one-line summary of the issue."
- name: state
type: string
required: false
- name: tags
type: array
required: false
テーブルレベルの任意項目である title と description は、テーブルについて人や AI が読み取れるメタデータを提供します。各フィールドのエントリーにも、任意の description 文字列を指定できます。これら 3 つのキーは OpenAPI 3.1/JSON Schema 2020-12 の命名規則に従い、info ツールの出力と schema://json リソースに含まれます。
対応する型:string、number、boolean、array、object。
設定
マルチテーブルモード(推奨)
| 環境変数 | デフォルト | 説明 |
|---|---|---|
MINI_APP_USER_DIR | ~/.mini-app/ | ユーザースコープのテーブルのベースディレクトリです。各サブディレクトリはテーブル名として扱われ、schema.yaml と <table>.db が必要です。 |
MINI_APP_PROJECT_DIR | ./.mini-app/ | プロジェクトスコープの上書きディレクトリです。ここにあるテーブルは、同じ名前のユーザースコープの定義を完全に置き換えます。 |
MINI_APP_BACKUP_RETENTION | 10 | _backup/ 内でテーブルごとに保持するバックアップコピー(YAML と DB スナップショットのペア)の最大数です。この上限を超える古いコピーは、バックアップの書き込みごとに直ちに削除されます。 |
MINI_APP_SNAPSHOT_RETENTION | 10 | _snapshots/ 内でテーブルごとに保持するスナップショット世代の最大数です。MINI_APP_BACKUP_RETENTION とは厳密に分離されており、_snapshots/ のファイルのみを削除し、_backup/ には一切触れません。 |
起動時に両方のディレクトリをスキャンしてテーブルを検出します。同じテーブル名の場合、プロジェクトスコープの定義がユーザースコープの定義より優先されます。
レガシー単一テーブルモード
| 環境変数 | デフォルト | 説明 |
|---|---|---|
MINI_APP_SCHEMA | ./schema.yaml | スキーマ定義ファイルのパス |
MINI_APP_DB | (なし — 設定が必要) | SQLite データベースファイルのパス |
MINI_APP_SCHEMA と MINI_APP_DB を設定すると、サーバーはレガシーモードで起動し、テーブルを 1 つだけマウントします。このモードでは、すべてのツールで table 引数を省略できます。
すべての変数は、作業ディレクトリ内の .mini-app-mcp.env ファイルにも記述できます。
MCP ツール
すべてのツールには、対象テーブルを選択する任意の table 引数があります。マルチテーブルモードではこの引数が必須です。省略するとエラーコード TABLE_REQUIRED が返されます。不明なテーブル名を指定すると、エラーコード TABLE_NOT_FOUND が返されます。レガシー単一テーブルモードでは、この引数を省略できます。
| ツール | 説明 |
|---|---|
info | 解析済みのスキーマ(テーブル名、フィールド定義)を JSON として返します |
create | 新しい行を挿入します。data オブジェクトをスキーマに照らして検証します |
get | id で単一の行を取得します。id が36文字未満の場合は、UUID のプレフィックスとして扱います。一致が0件の場合は NOT_FOUND を返し、一致が2件以上の場合は候補リストとともに AMBIGUOUS_ID を返します。36文字の UUID の場合は、常に完全一致の処理を行います。省略可能な fields セレクターを受け付けます。指定すると、返される data オブジェクトを、スキーマフィールド名で指定されたサブセットに絞り込みます。 |
list | 省略可能な limit/offset ページネーションを指定して行を返します。省略可能な fields セレクターを受け付けます。指定すると、返される data オブジェクトを、スキーマフィールド名で指定されたサブセットに絞り込みます。 |
update | id で既存の行を更新します。id が36文字未満の場合は、UUID のプレフィックスとして扱います(解決ルールについては get を参照)。デフォルトのモードは merge(RFC 7396)です。省略されたフィールドは保存済みの行の値が保持され、null 値は省略可能なフィールドを削除するか、必須フィールドの場合は Validation エラーを発生させます。完全置換(0.9 より前の動作)を行うには、"mode": "replace" を渡します。 |
delete | id で行を削除します。id が36文字未満の場合は、UUID のプレフィックスとして扱います(解決ルールについては get を参照)。 |
reload | MINI_APP_USER_DIR/MINI_APP_PROJECT_DIR を再スキャンし、テーブルレジストリをアトミックに置き換えます。設定されている場合は、レガシーの MINI_APP_SCHEMA と MINI_APP_DB も再適用されます。{ mounted, added, removed } を返します。制限事項:ファイルウォッチャーはなく、明示的な呼び出しが必要です。レジストリ全体を置き換えるため、テーブル単位の部分的な再読み込みはできません。既存の行に対するスキーマ移行は行いません。reload を同時に呼び出した場合は、最後の呼び出しが優先されます。 |
schema_create | 指定した scope(project または user)の下に新しい schema.yaml を作成し、テーブルを即時に登録します。書き込みを行わずにプレビューするには dry_run: true を渡します。テーブルがすでに存在する場合は SCHEMA_EXISTS で失敗します。 |
schema_update | 既存テーブルの schema.yaml を新しい定義で置き換えます(全体を上書き)。書き込み前に、以前の YAML と SQLite スナップショットを _backup/ にバックアップします。ディスクに変更を加えずにフィールドの変更をプレビューするには dry_run: true を渡します。 |
schema_delete | テーブルの schema.yaml を削除し(_backup/ に移動)、ライブレジストリから登録を解除します。SQLite テーブルには変更を加えず、削除もしません — DDL の変更は引き続きオペレーターの責任です。プレビューするには dry_run: true を渡します。 |
schema_batch | 1つの SQLite SAVEPOINT 内で、ops[] の配列をアトミックに実行します。いずれかの操作が失敗すると、それより前のすべての操作がロールバックされ、YAML と DB は変更されません。すべての操作は同じテーブルを対象にする必要があります。操作ごとの結果を返すか、失敗した操作のインデックスを含む BATCH_ABORTED エラーを返します。 |
data_snapshot | SQLite hot backup API を使用して、マウントされている1つまたはすべてのテーブルのポイントインタイム SQLite スナップショットを作成します。スナップショットは <scope_root>/_snapshots/<table>.<unix_secs>.db に書き込まれます。対象を限定するには table および/または scope を渡します。どちらも省略すると、マウントされているすべてのテーブルのスナップショットを作成します。ファイルを作成せずに操作(対象テーブル、行数、削除予定件数)をプレビューするには dry_run: true を渡します。保持数は MINI_APP_SNAPSHOT_RETENTION(デフォルトは10)で制御され、MINI_APP_BACKUP_RETENTION とは独立しています。 |
alias_create | テーブルに名前付きクエリエイリアスを登録します。name と、filter(ListFilter 式)または filter_template(MiniJinja テンプレート文字列。filter とは同時に指定できません)を受け付けます。さらに、省略可能な params_schema(テンプレートエイリアスのパラメーター名を示す文字列の配列)、default_limit、description も指定できます。エイリアス名はテーブルごとに一意です。重複する名前を指定すると ALIAS_ALREADY_EXISTS が返されます。エイリアスはテーブルごとにスコープされ、そのテーブル自身の SQLite データベースに保存されます。 |
alias_list | テーブルに登録されているすべてのエイリアスを、{ name, filter, default_limit, description, params_schema } オブジェクトの JSON 配列として返します。 |
alias_run | 保存済みのエイリアスを名前で実行します。実行時に保存済みの default_limit を上書きする、省略可能な limit と offset を受け付けます。パラメーター化されたエイリアス(filter_template を使って作成したもの)の場合は、キーと値のペアがテンプレートに挿入される params オブジェクトも受け付けます。省略可能な fields セレクターを受け付けます。指定すると、返される data オブジェクトを、スキーマフィールド名で指定されたサブセットに絞り込みます。params_schema が設定されていて params が省略されている場合は、ALIAS_PARAMS_REQUIRED を返します。テンプレートのレンダリングに失敗した場合は ALIAS_TEMPLATE_ERROR を返します。list ツールと同じ形式の結果を返します。名前が不明な場合は ALIAS_NOT_FOUND を返します。 |
alias_delete | テーブルの名前付きエイリアスを削除します。エイリアスが存在しない場合は ALIAS_NOT_FOUND を返します。 |
MCP リソース
上記のツールに加えて、サーバーは URI で指定できる読み取り専用の リソース を 7 つ公開しています。リソースは、変更を伴うツールを呼び出さずにスキーマ定義やリファレンスドキュメントを取得したいエージェント向けです。
| URI | MIME | 内容 |
|---|---|---|
schema://yaml | application/yaml | 生の schema.yaml ファイルの内容(リクエストごとにディスクから読み込みます) |
schema://json | application/json | JSON としての解析済み SchemaConfig(info ツールが返す内容と同じ形式です) |
schema://json-schema | application/schema+json | スキーマのフィールドから導出した JSON Schema(draft-07)です。create / update の呼び出し前に、data 引数の検証に使用します |
docs://quickstart | text/markdown | エージェント向けクイックスタート(モードの判定+最初の呼び出し手順+ほかの docs:// リソースへの案内)。バイナリにコンパイルされています。人間向けのこの README とは異なります。このリソースは MCP セッション内から読み込んでください |
docs://tools | text/markdown | 17 個すべての MCP ツールと入力形式をまとめたチートシート |
docs://errors | text/markdown | サーバーが返すエラーコードのリファレンステーブル |
docs://filters | text/markdown | list および alias_create で使用するフィルターオブジェクト(Eq/In/Like/Or/And)の作成ガイド |
info ツールと schema://json リソースは同等の内容を返しますが、用途は異なります。info は呼び出し可能なツールであり(会話中の単発の情報確認に適しています)、一方、リソースは URI で指定でき、クライアントからサブスクライブしたり、キャッシュしたりできます。
使用方法
--mcp(stdio トランスポート)または --mcp-http(ストリーム可能な HTTP トランスポート)でサーバーを起動します。これらが唯一のエントリーポイントです。REST/CLI CRUD インターフェースはありません。
マルチテーブルモード
各テーブルの schema.yaml と <table>.db を ~/.mini-app/<table>/(ユーザースコープ)または ./.mini-app/<table>/(プロジェクトスコープ)に配置し、追加の環境変数を設定せずに起動します。
mini-app-mcp --mcp
すべてのマウント済みテーブルを提供するには、.mcp.json に一度だけ登録します。
{
"mcpServers": {
"mini-app": {
"command": "mini-app-mcp",
"args": ["--mcp"]
}
}
}
マルチデバイスモード(ストリーム可能な HTTP)
SQLite ファイルを管理する中央デーモンを 1 つ実行し、任意の数のデバイスからリモート MCP クライアントとして接続します。すべてのデバイスが同じ単一プロセスと通信するため、単一ライターのストレージモデルが維持されます。デバイス間の同期や競合処理は存在せず、必要もありません。
# Central host — loopback (same-machine clients, e.g. behind an SSH tunnel)
mini-app-mcp --mcp-http # default bind 127.0.0.1:8484
# Central host — LAN exposure requires a token (generate one once):
# openssl rand -hex 32
MINI_APP_HTTP_TOKEN=<random-string> mini-app-mcp --mcp-http --bind 0.0.0.0:8484
.mcp.json でのクライアント登録(ネットワーク上の任意のデバイス):
{
"mcpServers": {
"mini-app": {
"type": "http",
"url": "http://central-host:8484/mcp",
"headers": { "Authorization": "Bearer <random-string>" }
}
}
}
セキュリティモデル:
- ループバックバインド(デフォルト):
MINI_APP_HTTP_TOKENは任意です。トランスポートに組み込まれたHostヘッダー検証(ループバックのみ)が DNS リバインディングを防ぎます。 - 非ループバックバインド:
MINI_APP_HTTP_TOKENを必ず設定してください。設定されていない場合、起動は拒否されます。その場合、すべてのリクエストにAuthorization: Bearer <token>を含める必要があります。 - トランスポートの暗号化(TLS)や、より高度な認可は意図的に対象外としています。必要に応じて、リバースプロキシで TLS を終端してください。
中央ホスト用のデーモンテンプレートは contrib/ にあります。
- Linux:
contrib/systemd/mini-app-mcp.service(systemd ユーザーユニット。インストール手順はファイルヘッダーを参照) - macOS:
contrib/launchd/io.github.ynishi.mini-app-mcp.plist(launchd ユーザーエージェント。インストール手順はファイルヘッダーを参照)
自分のマシンではなく Fly.io で中央デーモンをホストする場合(永続ボリューム、エッジ TLS、ベアラー認証、毎日のバックアップ用 cron)については、docs/runbooks/fly-io-deploy.md と contrib/fly/fly.toml を参照してください。
従来のシングルテーブルモード
MINI_APP_SCHEMA=./schema.yaml MINI_APP_DB=./issues.db mini-app-mcp --mcp
または、.mini-app-mcp.env で設定します。
MINI_APP_SCHEMA=./schema.yaml
MINI_APP_DB=./issues.db
ダンプ/ファイルへのマテリアライズ
mini-app-mcp は、作成または更新された各行を Markdown ファイルとしてディスクに書き込めます。これは、ファイルからコンテキストを読み取るエージェントや、git でレコードをバージョン管理する場合、または人がすばやく確認する場合に便利です。
仕組み
create または update の呼び出しが成功するたびに、サーバーはファイルを書き込み(または上書き)します。
<dump-dir>/<id>.md
ファイル形式は次のとおりです。
# <title-field value>
<body-field value>
デフォルトでは、delete を実行してもダンプファイルは削除されません(レコードはアーカイブとしてディスクに残ります)。
schema.yaml でのダンプの有効化
schema.yaml に dump: セクションを追加します。
table: issues
fields:
- name: title
type: string
required: true
- name: body
type: string
required: false
dump:
dir: ./issues # optional; default: <cwd>/.mini-app/<table>/
title_field: title # optional; default: title
body_field: body # optional; default: body
sync: write-only # optional; default: write-only
| キー | デフォルト | 説明 |
|---|---|---|
dump.dir | <cwd>/.mini-app/<table>/ | <id>.md ファイルの書き込み先ディレクトリです。相対パスはサーバーの作業ディレクトリを基準に解決されます。 |
dump.title_field | title | Markdown の見出しとして使用する、保存済み JSON 行のフィールド名です。 |
dump.body_field | body | Markdown の本文として使用する、保存済み JSON 行のフィールド名です。 |
dump.sync | write-only | 同期方向です。実装されているのは write-only のみです。bidirectional を設定してもエラーにはなりませんが、警告が記録され、write-only として動作します。 |
git でのダンプファイルの除外
ダンプファイルをバージョン管理の対象にしない場合は、.mini-app/(またはカスタムの dump.dir)を .gitignore に追加します。
.mini-app/
スキーマ管理
mini-app-mcp は、サーバーを再起動せずに実行時にテーブルスキーマを管理するための 4 つのツールを公開します。
テーブルの作成
schema_create(scope="project", table="notes", title="Notes", description="Personal notes.", fields=[...])
このツールは <scope_dir>/notes/schema.yaml に書き込み、新しいテーブルを稼働中のレジストリにただちに登録します。同じテーブル名で再度呼び出すと、SCHEMA_EXISTS が返されます。省略可能な title および description 引数は YAML ファイルに書き込まれ、info または schema://json からただちに確認できます。個々のフィールド項目に description 文字列を含めることもできます。
スキーマの更新
schema_update(scope="project", table="notes", title="Notes", description="Updated notes.", fields=[...])
上書きの前に、サーバーは既存の schema.yaml と、その時点の SQLite スナップショットを <scope_dir>/_backup/notes.<timestamp>.yaml および <scope_dir>/_backup/notes.<timestamp>.db にバックアップします。書き込み後、稼働中のレジストリが更新されます。DDL マイグレーションは適用されず、基盤となるテーブル構造は変更されません。
スキーマの削除
schema_delete(scope="project", table="notes")
schema.yaml は _backup/ に移動され、テーブルは稼働中のレジストリから登録解除されます。SQLite データベースファイルは変更されません。 テーブルの削除や変更は、引き続きオペレーターの責任です。
アトミックな一括操作
schema_batch(ops=[
{ "op": "create", "table": "notes", "scope": "project", "fields": [...] },
{ "op": "data_insert", "table": "notes", "data": { "title": "first note" } }
])
すべての操作は、単一の SQLite SAVEPOINT 内で実行されます。いずれかの操作が失敗すると、一括処理全体がロールバックされ、YAML ファイルは書き込まれず、レジストリも変更されません。1 回の一括処理に含まれるすべての操作は、同じテーブルを対象にする必要があります。
dry_run プレビュー
4 つのスキーマツールはすべて、dry_run: true を受け付けます。ドライランモードでは、ツールは影響件数(rows、fields_added、fields_removed)を計算して返しますが、YAML ファイル、SQLite テーブル、バックアップディレクトリには書き込みません。
バックアップの保持
バックアップファイルは <scope_dir>/_backup/ に蓄積されます。サーバーは保持上限(デフォルトではテーブルごとに 10 組)を超えた最も古いコピーを自動的に削除します。上限は MINI_APP_BACKUP_RETENTION で変更できます。
データスナップショット
data_snapshot は、YAML ファイルに触れたりスキーマを変更したりせずに、マウントされた 1 つ以上のテーブルのスタンドアロン SQLite スナップショットを作成します。
data_snapshot(table="notes", scope="project")
data_snapshot() # snapshot all mounted tables
data_snapshot(dry_run=true) # preview without writing
スナップショットは SQLite のホットバックアップ API(rusqlite::Connection::backup)を使用して <scope_root>/_snapshots/<table>.<unix_secs>.db に書き込まれるため、処理中もソースデータベースは開いたまま書き込み可能です。保持上限(デフォルトは 10)は MINI_APP_SNAPSHOT_RETENTION で個別に管理され、_backup/ には一切影響しません。
スナップショットのアップロード(S3 互換、オプトイン)
s3-upload ビルド機能を有効にすると、data_snapshot は書き込まれた各スナップショットを、エンドポイントだけで選択した任意の S3 プロトコル対応の送信先(AWS S3、Backblaze B2(S3-Compatible API)、Cloudflare R2、MinIO)にプッシュできます。
cargo install mini-app-mcp --features s3-upload
data_snapshot(upload=true) # snapshot all tables, then upload each
data_snapshot(table="notes", upload=true)
設定は環境変数のみで行います(.mini-app-mcp.env を使用)。認証情報がツール呼び出しの引数を介して渡されることはありません。
| 環境変数 | 必須 | 説明 |
|---|---|---|
MINI_APP_S3_ENDPOINT | はい | S3 互換のエンドポイント URL。B2 の場合:https://s3.<region>.backblazeb2.com |
MINI_APP_S3_BUCKET | はい | バケット名 |
MINI_APP_S3_ACCESS_KEY_ID | はい | アクセスキー ID(B2 の場合:Application Key ID) |
MINI_APP_S3_SECRET_ACCESS_KEY | はい | シークレットアクセスキー(B2 の場合:Application Key) |
MINI_APP_S3_PREFIX | いいえ | オブジェクトキーのプレフィックス(デフォルトは mini-app-snapshots/) |
MINI_APP_S3_REGION | いいえ | 署名リージョン。未設定の場合は、s3.<region>.<domain> 形式のエンドポイントから自動的に判定されます(B2/AWS のリージョナルエンドポイント)。そのため、B2 ユーザーは省略できます。リージョンがエンドポイントに含まれないホスト(MinIO、R2)では、ダミー値 us-east-1 にフォールバックします。明示的な値が常に優先されます。B2 はリージョンが一致しないと拒否するため、エンドポイントと一致する場合にのみ設定してください |
MINI_APP_S3_VIRTUAL_HOSTED_STYLE | いいえ | true = 仮想ホスト形式のアドレス指定(bucket.endpoint/key)。デフォルトの false = パス形式(endpoint/bucket/key)。MinIO ではパス形式が必須で、AWS/B2/R2 でも使用できます |
MINI_APP_S3_CHECKSUM | いいえ | sha256 = put 時に x-amz-checksum-sha256 を送信します。デフォルトは none です。一部の S3 互換プロバイダーはチェックサムヘッダーを 400 InvalidArgument: Unsupported header で拒否するためです |
動作:
- スナップショットを書き込む前に設定が検証されます。この機能なしでビルドされたバイナリ、または環境変数の設定が不完全な場合は、ただちに
UPLOAD_NOT_CONFIGUREDを返します。 - 個別のアップロード失敗は致命的ではありません。ローカルのスナップショットとパージ結果はそのまま有効で、失敗はレスポンスフィールド
upload_errors[]にテーブルごとに報告されます。成功したアップロードはuploaded[]に{table, key, bytes}として表示されます。 - リモート側の保持管理は対象外です(既知の制限事項)。サーバーがリモートオブジェクトを削除することはありません。古い世代を期限切れにするには、バケットのライフサイクルルールを設定してください。
- 互換性に関する注意:アップロードは単一の
PutObjectリクエスト(マルチパートなし)で行われます。また、MINI_APP_S3_CHECKSUM=sha256で明示的に有効にしない限り、x-amz-checksum-*ヘッダーは送信されません。これにより、一部の S3 互換プロバイダーで問題を起こす aws-sdk 側の 2 つの落とし穴は、デフォルトでは発生しません。
この機能を使わない方法:サーバーはそのままにして、スナップショットを外部ツールで送信します。たとえば、cron エントリー mini-app snapshot → rclone copy ~/.mini-app/_snapshots b2:my-bucket/mini-app-snapshots や、restic backup ~/.mini-app のジョブを使えます。認証情報を外部ツールに保持したまま、同等の結果を得られます。
定期バックアップ
contrib/backup/mini-app-backup.sh を使うと、アップロードを cron ジョブとして実行できます。稼働中の --mcp-http デーモンに対して data_snapshot(upload=true) を呼び出し(streamable HTTP 経由で initialize → tools/call、依存関係:curl、jq)、呼び出しが失敗した場合、または upload_errors[] が空でない場合はゼロ以外の終了コードを返します。そのため、失敗は cron メール/journal/supercronic のログに表示されます。ローカルのデーモンに対して実行する場合:
# daily 03:00 — MINI_APP_URL / MINI_APP_HTTP_TOKEN via env if non-default
0 3 * * * /path/to/contrib/backup/mini-app-backup.sh
コンテナーイメージには、supercronic とともにスクリプトが mini-app-backup として同梱されています。BACKUP_CRON(例:0 3 * * *)を設定すると、エントリーポイントはサーバーと並行して、スケジュールに従ってスクリプトを実行します(contrib/docker/entrypoint.sh を参照)。
リレーショングラフの使用方法
mini-app-mcp はテーブルに依存しませんが、一般的なパターンとして、型付きエッジをノード間に必要とするエージェント向けに、単一のテーブルをリレーショングラフレイヤーとして使用します(例:ペルソナ AI のメモリ:sister_of、member_of_studio、mother_of)。
参照スキーマは examples/schemas/relations.schema.yaml にあります。
| フィールド | 型 | 必須 | 備考 |
|---|---|---|---|
from | string | はい | ソースノード ID |
to | string | はい | ターゲットノード ID |
type | string | はい | エッジラベル(sister_of、member_of_studio、...) |
ts | number | はい | アサートされた時刻の Unix 秒 |
strength | number | いいえ | [0.0, 1.0] のエッジの重み。デフォルトは 1.0 |
metadata | object | いいえ | 自由形式の属性 |
source_entry_id | string | いいえ | ジャーナル/ログエントリへの外部参照 |
一括挿入(N 個のエッジをアトミックに挿入)
schema_batch と query 操作を使用して、1 つの SAVEPOINT で N 個のエッジを登録します。
{
"ops": [
{
"op": "query",
"sql": "INSERT INTO rows (id, data, created_at, updated_at) VALUES (?1, ?2, ?3, ?4)",
"params": ["<uuid>", "{\"from\":\"persona-x\",\"to\":\"a\",\"type\":\"sister_of\",\"ts\":1779330000,\"strength\":1.0}", "1779330000", "1779330000"]
}
]
}
いずれかの操作が失敗すると、バッチ全体がロールバックされます。
一括置換(エッジセット全体の置換)
専用の replace 操作を使用すると、1 つのアトミックな SAVEPOINT でエッジセットを置き換えられます。サーバーが UUID の生成、タイムスタンプ、JSON のシリアル化を処理するため、呼び出し元が指定するのは match の対象範囲と items リストのみです。
{
"ops": [
{
"op": "replace",
"table": "relations",
"match": {"from": "persona-x", "type": "sister_of"},
"items": [
{"from": "persona-x", "to": "a", "type": "sister_of", "ts": 1779330000, "strength": 1.0},
{"from": "persona-x", "to": "b", "type": "sister_of", "ts": 1779330000, "strength": 1.0},
{"from": "persona-x", "to": "c", "type": "sister_of", "ts": 1779330000, "strength": 0.8}
]
}
]
}
replace 操作は、単一の SAVEPOINT 内で DELETE WHERE(match)と N 個の INSERT を実行します。いずれかの処理が失敗すると、両方のフェーズがロールバックされます。レスポンスには操作ごとの影響件数が含まれます。
{
"committed": true,
"ops_executed": 1,
"affects": {"deleted": 5, "inserted": 3}
}
セマンティクス:
matchの各キーは、AND で結合された個別のjson_extract(data, '$.key') = value条件になります。match の値はスカラー(string/number/bool/null)である必要があります。- 空の
match({})は、テーブル全体を誤って消去するのを防ぐため、VALIDATION_ERROR で拒否されます。 - SQL の実行前に、
itemsの各項目がテーブルのスキーマに照らして検証されます。 replace操作で対応できないケースでは、query操作(一括挿入に示す生 SQL のエスケープハッチ)も引き続き使用できます。
エッジの一覧表示(対象範囲 1000 件未満)
list ツールは、created_at DESC 順で行を返し、limit は最大 1000 に制限されます。オプションの filter 引数を使用すると、スキーマで検証されたフィールドに対してサーバー側で行をフィルタリングできます。
フィルターの種類
| type | 説明 |
|---|---|
eq | 等価条件:{"type": "eq", "field": "...", "value": ...} |
in | 集合への包含条件:{"type": "in", "field": "...", "values": [...]} |
like | 文字列フィールドの部分一致:{"type": "like", "field": "...", "pattern": "..."}。% は任意の部分文字列に一致し、_ は任意の 1 文字に一致します。string 型のフィールドに限定されます。 |
or | OR 条件:{"type": "or", "filters": [...]} |
and | AND 条件:{"type": "and", "filters": [...]} |
or と and は再帰的に Vec<ListFilter> を受け付け、任意の深さで条件をネストできます。
制約:フィールド名はテーブルの schema.yaml に登録されている必要があります(不明なフィールドには VALIDATION_ERROR が返されます)。値は、スキーマで宣言されたフィールドの型に照らして型チェックされます。これは、BatchOp::Replace の match 値に適用される型付きスカラー検証と同じです。filter を省略すると、limit/offset の範囲内にあるすべての行が返されます(完全な後方互換性があります)。
メールボックス形式の OR フィルターの例
1 回の呼び出しで、宛先が「alice」または「broadcast」のメッセージを取得します。
{
"filter": {
"type": "or",
"filters": [
{"type": "eq", "field": "to", "value": "alice"},
{"type": "eq", "field": "to", "value": "broadcast"}
]
}
}
クライアント側のフィルタリングで十分な小規模なグラフの場合:
edges = mini_app.list(table="relations", limit=1000)
sisters_of_x = [
e for e in edges
if e["data"]["from"] == "persona-x" and e["data"]["type"] == "sister_of"
]
更新のセマンティクス:マージと置換
update ツールは、オプションの "mode" フィールドで制御される 2 つのモードをサポートしています。
マージモード(デフォルト)
update(table="notes", id="<uuid>", data={"state": "done"})
// or equivalently:
update(table="notes", id="<uuid>", data={"state": "done"}, mode="merge")
マージモードは、RFC 7396(JSON Merge Patch)の浅いマージのセマンティクスに従います。
- 存在しないフィールドは保持されます —
dataに含まれないキーは、保存されている値を維持します。 - null 以外の値は上書きされます —
dataに含まれる null 以外の値を持つキーは、そのキーの保存値を置き換えます。ネストされたオブジェクトは全体が置き換えられます(ディープマージは行われません)。 nullはオプションフィールドを削除します — キーの値がnullで、スキーマ内のフィールドがrequired: falseの場合、保存された行からそのフィールドが削除されます。- 必須フィールドに対する
nullは検証エラーになります — キーの値がnullで、フィールドがrequired: trueの場合、書き込みが行われる前に呼び出しはVALIDATION_ERRORで失敗します。 - マージ後の結果全体にスキーマ検証が実行されます — マージ後、マージされたオブジェクト全体がスキーマに照らして検証されます。制約違反(必須フィールドの不足、型の不一致)がある場合は検証エラーが返され、行は更新されません。
置換モード
update(table="notes", id="<uuid>", data={...}, mode="replace")
置換モードでは、完全な置き換えが行われます。data がスキーマに照らして検証された後、そのまま書き込まれ、保存された行全体が上書きされます。これは、バージョン 0.9 より前の update と同じ動作です。以前のデフォルトに依存し、部分的な data オブジェクトを送信している呼び出し元は、元の動作に戻すために mode="replace" に切り替えてください。
モードの選択
| モード | 使用する場面 |
|---|---|
merge(デフォルト) | 部分更新 — 変更したいフィールドだけを送信します。省略した既存フィールドは変更されません。 |
replace | 全体の置き換え — 行の完全な状態を指定します。省略したフィールドは削除されます。 |
フィールドの射影
list、get、alias_run はすべて、返される data オブジェクトに含めるスキーマフィールドを制限する任意の fields 引数を受け取ります。セレクターにかかわらず、id、created_at、updated_at は常にレスポンスエンベロープに含まれます。
セレクターの形式
{"mode": "all"} // default: return all schema fields
{"mode": "list", "fields": ["title", "state"]} // return only the named fields
fields を省略しても完全な後方互換性が保たれます。既存の呼び出し元は変更なしで完全な行を受け取ります。
使用例
list(table="issues", fields={"mode": "list", "fields": ["title", "state"]})
get(table="issues", id="<uuid>", fields={"mode": "list", "fields": ["title"]})
alias_run(table="issues", name="recent_open", fields={"mode": "list", "fields": ["title", "state"]})
検証
不明なフィールド名は、クエリが実行される前に VALIDATION_ERROR で拒否されます。フィールド名の検証では、保存された行に実際に存在するキーではなく、schema.yaml の正規のフィールド定義が参照されます。そのため、特定の行にそのフィールドが存在しない場合でも、射影のエラーを確実に検出できます。
クエリエイリアス
クエリエイリアスを使うと、ListFilter 式(または MiniJinja フィルターテンプレート)を短い名前で保存して、毎回フィルター JSON を繰り返さずに、必要に応じて呼び出しごとに limit / offset を上書きして再実行できます。
静的エイリアスの作成
alias_create(
table="notes",
name="recent_open",
filter={"type": "eq", "field": "state", "value": "open"},
default_limit=20,
description="Open notes, newest first"
)
エイリアスは、テーブル固有の SQLite データベース内の _aliases に保存されます。エイリアス名はテーブルごとに一意です。同じ名前で alias_create を再度呼び出すと、ALIAS_ALREADY_EXISTS が返されます。
パラメーター化エイリアス(フィルターテンプレート)の作成
パラメーター化エイリアスでは、固定フィルターの代わりに MiniJinja のテンプレート文字列を使用します。実行時に呼び出し元がパラメーター値を指定すると、実行前にその値がテンプレートにレンダリングされます。
alias_create(
table="issues",
name="by_state",
filter_template='{"type": "eq", "field": "state", "value": "{{ state }}"}',
params_schema=["state"],
default_limit=50,
description="Filter issues by state (parameterized)"
)
filter_template と filter は相互排他的です。必ずどちらか一方だけを指定してください。params_schema はパラメーター名を指定する任意の配列です。指定すると、alias_run が params オブジェクトで想定するキーを文書化できます。
エイリアスの実行
静的エイリアス(パラメーターなし):
alias_run(table="notes", name="recent_open")
# → uses stored default_limit=20
alias_run(table="notes", name="recent_open", limit=5)
# → runtime limit overrides the stored default_limit
alias_run(table="notes", name="recent_open", limit=10, offset=20)
# → pagination with runtime overrides
パラメーター付きエイリアス(params を使用):
alias_run(table="issues", name="by_state", params={"state": "open"})
# → renders template → {"type": "eq", "field": "state", "value": "open"}
# → validates as ListFilter → executes Store::list
alias_run(table="issues", name="by_state", params={"state": "open"}, limit=10)
# → runtime limit override with params injection
alias_run は保存済みのフィルターを解決するか、テンプレートをレンダリングし、結果を Store::list に渡します。呼び出し時に limit が指定されている場合は default_limit を上書きします。省略された場合は、エイリアスの default_limit が使用されます。offset は常に実行時のみの引数です(保存されません)。
パラメーター付きエイリアスのエラーケース:
| 状況 | エラーコード |
|---|---|
params_schema が設定されているが、params が省略されている | ALIAS_PARAMS_REQUIRED |
| MiniJinja のレンダリングに失敗する(構文エラーまたは変数の欠落) | ALIAS_TEMPLATE_ERROR |
レンダリング結果が有効な JSON または有効な ListFilter ではない | VALIDATION_ERROR |
エイリアスの一覧表示と削除
alias_list(table="notes")
# → [{"name": "recent_open", "filter": {...}, "default_limit": 20, "description": "...", "params_schema": null}]
alias_list(table="issues")
# → [{"name": "by_state", "filter": null, "default_limit": 50, "description": "...", "params_schema": ["state"]}]
alias_delete(table="notes", name="recent_open")
エイリアスの分離
各テーブルの _aliases ストレージは物理的に分離されています。notes のエイリアスは notes.db に、issues のエイリアスは issues.db に保存されます。グローバルなエイリアス名前空間はなく、エイリアス操作によって別のテーブルに属するエイリアスを読み書きすることもできません。
集計
query_aggregate は、1 回のツール呼び出しで 1 つまたは複数のテーブルに対して COUNT / SUM / AVG / MIN / MAX / GROUP BY を実行します。複数テーブルのソースは、SQLite の ATTACH DATABASE を介してリテラルな UNION ALL で結合されます。これにより、呼び出し元がテーブルごとに行を取得してクライアント側で集計する場合に発生する N+1 の往復をなくします。このツールは読み取り専用で、冪等です。
形式
{
"sources": { "kind": "single", "value": "<table>" }, // or "multi" + ["t1","t2",...]
"filter": null, // optional, same ListFilter shape as `list`
"aggregator": { "kind": "count" } // or sum / avg / min / max / group_by
}
結果は外部タグ付きのため、呼び出し元は kind に基づいて処理を振り分けられます。
{ "kind": "count", "value": 42 }
{ "kind": "value", "value": 3.14 }
{ "kind": "groups", "value": [ { "key": "a", "count": 12, "value": 7.0 }, ... ] }
単一ソースの集計
// COUNT all rows in `emo`.
{ "name": "query_aggregate", "arguments": {
"sources": { "kind": "single", "value": "emo" },
"aggregator": { "kind": "count" }
}}
// SUM the `amount` field.
{ "name": "query_aggregate", "arguments": {
"sources": { "kind": "single", "value": "ledger" },
"aggregator": { "kind": "sum", "field": "amount" }
}}
複数テーブルの集計
Multi は、SQLite の ATTACH DATABASE を介してすべてのバックエンド .db ファイルをマウントし(上限 10)、外側の集計を適用する前に、テーブルごとのサブクエリ間を UNION ALL で結合します。
// COUNT rows across two same-shape tables.
{ "name": "query_aggregate", "arguments": {
"sources": { "kind": "multi", "value": ["events_a", "events_b"] },
"aggregator": { "kind": "count" }
}}
// GROUP BY tag with HAVING and an inner SUM(amount).
{ "name": "query_aggregate", "arguments": {
"sources": { "kind": "multi", "value": ["events_a", "events_b"] },
"aggregator": {
"kind": "group_by",
"by_field": "tag",
"having": { "type": "eq", "field": "tag", "value": "a" },
"inner": { "kind": "sum", "field": "amount" }
}
}}
すべての Multi ソースが互換性のあるスキーマを共有していることは、呼び出し元の責任です。テーブルごとのフィールド検証は Phase 2 で対応予定です。アグリゲーター固有のエラー(ソースが空、ATTACH の上限超過、ネストされた GroupBy、UTF-8 ではない db パス)は AGGREGATOR_ERROR(data.code)を返します。不明なテーブル名は TABLE_NOT_FOUND を返し、フィールドまたは識別子の拒否は VALIDATION_ERROR を返します。
グローバルエイリアス(Phase 2)
Phase 2 では、複数のテーブルにまたがるエイリアスを単一のグローバルストレージ(<project_dir>/_global.db + <user_dir>/_global.db、検索の優先順位は Project → User)に統合します。alias_create MCP ツールは、新たに任意指定の sources 引数と aggregator 引数を受け付けます。これにより、1 つのエイリアスで複数のテーブルにまたがり、集計ロジックを保存できます。
// Multi-table alias with COUNT aggregator (replays via execute_aggregate).
{ "name": "alias_create", "arguments": {
"name": "combined_events_count",
"sources": { "kind": "multi", "value": ["events_a", "events_b"] },
"aggregator": { "kind": "count" },
"filter": { "type": "like", "field": "tag", "pattern": "%" }
}}
// Pattern source — resolved against the live registry's table list at
// alias_run time. Matches all currently-mounted `events_*` tables.
{ "name": "alias_create", "arguments": {
"name": "all_events_count",
"sources": { "kind": "pattern", "value": "events_*" },
"aggregator": { "kind": "count" },
"filter": { "type": "like", "field": "tag", "pattern": "%" }
}}
// alias_run dispatches to execute_aggregate transparently.
{ "name": "alias_run", "arguments": { "name": "all_events_count" } }
// → { "kind": "count", "value": <sum across all events_* tables> }
従来の table 引数も引き続き受け付けられ、sources = { "kind": "single", "value": "<table>" } に暗黙的に正規化されます。table と sources の両方を指定するとエラーになります。
移行:既存のテーブルごとの _aliases の行は、TableRegistry::mount_from_dirs の呼び出しごとに、プロジェクトスコープの _global_aliases に自動的にコピーされます。この移行はロスレス(従来の 5 つのフィールドをすべて保持し、sources = Single(<table>) を設定)かつ冪等(INSERT OR IGNORE により競合をスキップ)であるため、サーバーの再起動ごとに実行しても安全です。将来のマイナーリリースまではロールバック経路を維持するため、テーブルごとの _aliases テーブルは削除されません。
Phase 2 の制限事項
Multi/Patternソースのエイリアスにはaggregatorが必要です。指定せずに実行すると、構造化エラーが返されます(テーブルごとのlist経路では、テーブルをまたぐ行を処理できません)。- パターングロブでサポートされるのは
*(1 つ以上)のみです。?/[]は今後の改訂用に予約されています。 - パターンマッチの結果、テーブルが 0 件になった場合、
alias_runの実行時にAGGREGATOR_ERRORが返されます(早期に表面化し、ATTACH DATABASEまで処理を遅らせません)。 - フィルター検証では、最初のソーステーブルのスキーマのみを使用します。テーブル間のスキーマの不一致は呼び出し元の責任です(
query_aggregateと同じ制約です)。 - レガシーな単一テーブルモード(
MINI_APP_USER_DIR/MINI_APP_PROJECT_DIRがない場合)では、テーブルごとのStore::alias_*経路にフォールバックします。このモードではPatternソースは拒否されます。
行履歴の自動記録
すべての create、update、delete 呼び出しでは、影響を受けた行の完全な JSON スナップショットが、同じ SQLite データベース内の _row_history テーブルに自動で書き込まれます。スナップショットはデータ変更と同じトランザクション内でアトミックに書き込まれます。つまり、データの DML と履歴レコードの両方がコミットされるか、どちらもコミットされないかのいずれかです。
記録される内容
| イベント | op の値 | data_json |
|---|---|---|
create | "create" | 新しい行の JSON |
update | "update" | マージ後の結果の JSON |
delete | "delete" | 削除された行の JSON |
各履歴レコードには、単調増加する version カウンター(table + row id ごと)と、Unix 秒単位の recorded_at タイムスタンプも記録されます。
操作前の状態は、直前のエントリの data_json から取得できるため、重複して保存されません(以前のバージョンで書き込まれたデータベースでは、従来の prev_data_json カラムを引き続き読み取れますが、新しいエントリでは null のままとなり、履歴の書き込み量を半減できます)。
2 層ストレージ:生データ+圧縮アーカイブ
履歴は破棄されませんが、ディスクを使い尽くすこともありません(かつては、178KB の行を 949 回更新すると、完全なコピーの履歴で 222MB を占めていました)。生データの _row_history テーブルには、行ごとに最新のエントリのみが保持され、それより古いエントリは同じ書き込みトランザクション内で、自動的に zstd 圧縮された JSONL チャンクとして _row_history_archive に格納されます。ほぼ同一の JSON バージョンは約 3 桁高い圧縮率で圧縮されるため、長い履歴も低コストで保持でき、完全な復元も可能です。row_restore とバージョン一覧は、アーカイブを透過的に参照します。
| 環境変数 | デフォルト | 意味 |
|---|---|---|
MINI_APP_HISTORY_KEEP_RECENT | 16 | 行ごとに保持する、生データ(非圧縮)のエントリ数(取り消し用のホットセット) |
MINI_APP_HISTORY_CHUNK_MIN | 48 | 1 つのアーカイブチャンクにまとめるエントリの最小数(KEEP_RECENT + CHUNK_MIN を超えると格納が開始されます) |
テーブルごとに無効化する(history: off)
ハートビートのタイムスタンプやログキャッシュ形式の行など、自動ジョブによって高頻度で書き込まれるテーブルでは、schema.yaml で履歴を完全に無効化できます。
table: tick_stamps
history: off # default: full
fields:
- name: label
type: string
required: true
history: off を設定すると、そのテーブルに対する変更では _row_history エントリが一切書き込まれません(また、row_restore で復元できるものもありません。そのため、書き込みごとの履歴に復旧上の価値がない場合にのみ使用してください)。
row_restore による行の復元
row_restore は、指定された Unix タイムスタンプ以前で最新のスナップショットを取得し、ライブ行として書き戻します。行が削除されていた場合は、同じ id で再挿入されます。行がまだ存在する場合は、スナップショットのデータで置き換えられます。
{
"name": "row_restore",
"arguments": {
"table": "issues",
"id": "550e8400-e29b-41d4-a716-446655440000",
"at_unix_secs": 1750000000
}
}
レスポンスの形式は get と同じで、id、data、created_at、updated_at を含む完全な行オブジェクトです。
パージ SOP
履歴レコードは更新のたびにパージされるわけではありません。パージは、希望する保持ポリシーを指定してアプリケーションコードから purge_old_history を呼び出す、独立した操作です。
- 保持期間:30 日(30 日より古いレコードは削除されます)
- 行ごとの最大バージョン数:100(100 件を超える場合は、各行の最も古いバージョンが削除されます)
履歴の蓄積によって通常の create/update/delete 呼び出しに遅延が加わらないよう、パージは意図的に自動フックへ組み込まれていません。
部分編集ツール
3 つのツールを使うと、行全体を取得または置換せずに、エージェントが個々の文字列フィールドを確認し、対象を絞って編集できます。文字列以外のフィールド(配列、オブジェクト、数値、ブール値)は TYPE_ERROR で拒否されます。
content_view
文字列フィールドの内容を行番号付き(cat -n 形式)で返します。view_range を指定すると、連続した範囲に出力を制限できます(1 始まりで、両端を含みます)。
// View lines 12–30 of a body field
content_view(table="issue", id="26a8a7f9", field="body", view_range=[12, 30])
// → {"content": " 12 first visible line\n 13 ...\n"}
// Omit view_range to get the full field
content_view(table="issue", id="26a8a7f9", field="body")
content_replace
文字列フィールド内にある old_str の一意な出現箇所を new_str に置き換えます。
// Unique replacement — returns the number of substitutions
content_replace(table="issue", id="26a8a7f9", field="body",
old_str="foo", new_str="bar")
// → {"matches": 1}
// Scope the search to a line range before replacing
content_replace(table="issue", id="26a8a7f9", field="body",
old_str="foo", new_str="bar", view_range=[1, 10])
// Replace every occurrence in one call
content_replace(table="issue", id="26a8a7f9", field="body",
old_str="foo", new_str="bar", replace_all=true)
// → {"matches": 4}
old_str が見つからない場合、ツールは STRING_NOT_FOUND を返します。複数箇所に見つかり、replace_all が設定されていない場合、ツールは candidates リストとともに AMBIGUOUS を返します。
content_insert
文字列フィールドの特定の位置に、1 行以上を挿入します。
// Prepend (line=0 inserts before the first line)
content_insert(table="issue", id="26a8a7f9", field="body",
line=0, content="Prepended line")
// → {"line": 0, "inserted_lines": 1}
// Insert after line N (1-indexed)
content_insert(table="issue", id="26a8a7f9", field="body",
line=5, content="Inserted after line 5")
// → {"line": 5, "inserted_lines": 1}
line の値が total_lines + 1 より大きい場合は、OUT_OF_RANGE が返されます。3 つのツールはいずれも、通常の row_history フックを通じて変更を記録するため、すべての編集を完全に監査でき、row_restore を使って復元できます。
ストレージに関する注意事項
SQLite データベースは、reload の実行中に安全に同時アクセスできるよう、WAL ジャーナルモードで開かれます。各 .db ファイルの隣にサイドカーファイル <db>.db-wal と <db>.db-shm が作成されます。これらは SQLite によって管理されるため、手動で削除しないでください。
MCP Registry
mini-app-mcp は MCP Registry に登録されています。OCI イメージは、タグ付きリリースごとに GitHub Container Registry に公開されます。
Docker(Rust ツールチェーン不要)
{
"mcpServers": {
"mini-app": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/path/to/data:/data",
"ghcr.io/ynishi/mini-app-mcp:latest"
]
}
}
}
ライセンス
MIT OR Apache-2.0
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。