mappy_mcp_audit_logs(MCP 監査ログ)
| 項目 | 内容 |
|---|---|
| ステータス | 🟡 設計中 |
| 関連案件 | #13 Mappy MCP サーバー新設 |
| 親ドキュメント | MCP サーバー 全体像 |
| 関連設計 | 認証・テナンシ / 書込安全装置 |
ER 図
概要
MCP サーバー経由で発生したすべての操作を記録する監査ログテーブル。読取・書込・拒否・dry_run・エラー・レート制限・OAuth イベントを漏れなく記録し、不正利用検知・障害解析・コンプライアンス対応の基礎データとする。
設計方針
- 全件記録: tool 呼び出しは成功/失敗を問わず全件
- 代理操作の追跡:
user_id(実行者)とtarget_user_id(対象)を分離 - 検索性: client_id / user_id / tool_name / created_at に複合インデックス
- 保持期間: 標準 1 年(書込系は 3 年、要件確認次第)
- PII 配慮: トークン・パスワードは記録しない、コメント本文等は別ストアへ
- 書込頻度: 高頻度想定のため非同期書込(既存 Jobs キューを利用可)
テーブル定義
mappy_mcp_audit_logs
| No | カラム名(論理) | カラム名(物理) | データ型 | NULL | キー | 説明 |
|---|---|---|---|---|---|---|
| 1 | ID | id | bigint unsigned | NO | PK | 自動採番 |
| 2 | リクエスト ID | request_id | varchar(64) | NO | UK | ULID 等の一意 ID。レスポンスにも含めユーザーが照合可能 |
| 3 | イベント種別 | event_type | varchar(64) | NO | IDX | tool_call / oauth_authorize / oauth_token / oauth_revoke / oauth_register / rate_limit / dry_run / confirm_token_issued / confirm_token_consumed / forbidden / error |
| 4 | クライアント ID | client_id | varchar(64) | YES | IDX, FK | mappy_mcp_oauth_clients.client_id |
| 5 | 認証ユーザー ID | user_id | bigint unsigned | YES | IDX, FK | mappy_users.id(または admin の場合 NULL) |
| 6 | 認証ユーザー種別 | user_kind | varchar(16) | YES | - | mappy / admin |
| 7 | Admin ID | admin_id | bigint unsigned | YES | FK | admins.id(user_kind=admin の場合) |
| 8 | 対象ユーザー ID | target_user_id | bigint unsigned | YES | IDX, FK | mappy_users.id(代理操作時、通常は user_id と同じ) |
| 9 | ツール名 | tool_name | varchar(128) | YES | IDX | location.list 等。OAuth イベントは NULL |
| 10 | 要求スコープ | scope_required | varchar(255) | YES | - | tool 実行に必要だった scope(カンマ区切り) |
| 11 | 与えられたスコープ | scope_granted | text | YES | - | アクセストークンに付与された scope(カンマ区切り) |
| 12 | 入力ハッシュ | input_hash | char(64) | YES | - | SHA-256(入力 JSON 正規化済み) |
| 13 | 入力サマリー | input_summary | json | YES | - | 主要パラメータのみ抜粋(PII / 大容量は除外) |
| 14 | ステータス | status | varchar(32) | NO | IDX | success / failed / forbidden / rate_limited / invalid / queued / partial |
| 15 | エラーコード | error_code | varchar(64) | YES | - | FORBIDDEN_LOCATION 等 |
| 16 | エラーメッセージ | error_message | text | YES | - | 詳細メッセージ |
| 17 | レスポンス件数 | result_count | int | YES | - | リスト系 tool の返却件数 |
| 18 | バッチ ID | batch_id | varchar(64) | YES | IDX, FK | 非同期処理の場合 mappy_mcp_batches.batch_id |
| 19 | 冪等性キー | idempotency_key | varchar(64) | YES | IDX | クライアント提供 |
| 20 | 確認トークン | confirm_token | varchar(64) | YES | - | 一括反映時に発行・消費されたトークン |
| 21 | dry_run | is_dry_run | tinyint(1) | NO | - | 0: 本実行 / 1: dry_run |
| 22 | 経過時間 (ms) | elapsed_ms | int unsigned | YES | - | tool 実行ミリ秒 |
| 23 | リクエスト IP | request_ip | varchar(45) | YES | - | IPv4/IPv6 |
| 24 | User-Agent | user_agent | varchar(255) | YES | - | クライアントの UA |
| 25 | セッション ID | mcp_session_id | varchar(64) | YES | IDX | Streamable HTTP セッション |
| 26 | 作成日時 | created_at | timestamp(3) | NO | IDX | ミリ秒精度(同一秒の順序判定用) |
インデックス
| 名前 | カラム | 用途 |
|---|---|---|
idx_audit_user_created | (user_id, created_at) | ユーザー別の時系列検索 |
idx_audit_target_created | (target_user_id, created_at) | 代理操作監査 |
idx_audit_client_created | (client_id, created_at) | クライアント別の利用分析 |
idx_audit_tool_created | (tool_name, created_at) | ツール別の利用分析 |
idx_audit_status_created | (status, created_at) | 失敗・拒否の集計 |
idx_audit_batch | (batch_id) | バッチ単位の集約 |
idx_audit_idempotency | (idempotency_key) | 重複検知 |
idx_audit_session | (mcp_session_id, created_at) | セッション単位のトレース |
パーティショニング
- created_at による 月次パーティション(書込負荷分散・古いログのアーカイブ容易化)
- 1 年経過パーティションを別 DB / S3 にアーカイブする運用を想定
mappy_mcp_batches
非同期処理(書込系 tool)の状態管理テーブル。batch.status tool の参照元。
| No | カラム名(論理) | カラム名(物理) | データ型 | NULL | キー | 説明 |
|---|---|---|---|---|---|---|
| 1 | ID | id | bigint unsigned | NO | PK | 自動採番 |
| 2 | バッチ ID | batch_id | varchar(64) | NO | UK | ULID。クライアントに返却 |
| 3 | クライアント ID | client_id | varchar(64) | NO | IDX, FK | 発行元クライアント |
| 4 | ユーザー ID | user_id | bigint unsigned | NO | IDX, FK | 発行ユーザー |
| 5 | 対象ユーザー ID | target_user_id | bigint unsigned | NO | - | 代理対応 |
| 6 | ツール名 | tool_name | varchar(128) | NO | - | post.create 等 |
| 7 | ステータス | status | varchar(32) | NO | IDX | queued / processing / done / partial / failed / cancelled |
| 8 | 入力 JSON | input_json | json | NO | - | 元入力(PII配慮済み) |
| 9 | 対象件数 | target_count | int unsigned | NO | - | location_ids 等の件数 |
| 10 | 成功件数 | success_count | int unsigned | NO | - | デフォルト 0 |
| 11 | 失敗件数 | failure_count | int unsigned | NO | - | デフォルト 0 |
| 12 | 失敗詳細 | failures | json | YES | - | per-location の error_code/message |
| 13 | キューイング日時 | queued_at | timestamp | NO | - | dispatch 時刻 |
| 14 | 開始日時 | started_at | timestamp | YES | - | Worker pull 時刻 |
| 15 | 完了日時 | completed_at | timestamp | YES | - | 最終ステータス確定時刻 |
| 16 | 既存 Job ID | job_id | bigint unsigned | YES | - | jobs.id(Laravel Jobs キュー) |
| 17 | 冪等性キー | idempotency_key | varchar(64) | YES | IDX | クライアント提供 |
| 18 | 進捗 (%) | progress | tinyint unsigned | NO | - | 0〜100 |
| 19 | 作成日時 | created_at | timestamp | NO | - | レコード作成日時 |
| 20 | 更新日時 | updated_at | timestamp | NO | - | 最終更新日時 |
インデックス
| 名前 | カラム |
|---|---|
idx_batches_user_status | (user_id, status) |
idx_batches_client_created | (client_id, created_at) |
idx_batches_idempotency | (user_id, idempotency_key) |
mappy_mcp_rate_limit_logs(任意・Redis 補助)
レート制限ヒットの履歴。短期はカウンタ含め Redis、永続的記録は本テーブル。
| No | カラム | データ型 | 説明 |
|---|---|---|---|
| 1 | id | bigint unsigned PK | 自動採番 |
| 2 | scope | varchar(64) | user_per_min / client_per_hour 等 |
| 3 | client_id | varchar(64) | 該当クライアント |
| 4 | user_id | bigint unsigned | 該当ユーザー |
| 5 | tool_name | varchar(128) | 該当ツール |
| 6 | limit_value | int | 制限値 |
| 7 | observed_count | int | 観測カウント |
| 8 | retry_after_seconds | int | クライアントに返した retry-after |
| 9 | created_at | timestamp | 発生日時 |
Redis vs DB
- カウンタ・直近の状態は Redis(高速、INCR + EXPIRE)
- ヒット履歴の永続化と分析用に DB(非同期書込)
入力サマリーの記録方針
input_summary には PII を含めない方針で要点のみ記録する。
tool ごとのサマリー仕様
| tool | input_summary に含めるもの |
|---|---|
location.list | user_id, group_id, limit, has_filter |
location.get | location_id, include_raw |
review.list | user_id, location_id, since, until, has_filter |
ranking.list | user_id, location_id, since, until, aggregate, keyword (truncated 50 chars) |
insight.summary | user_id, location_id/group_id, since, until, metrics |
post.create | location_ids, target_count, has_event, has_cta, has_media, dry_run |
review.reply | review_id, reply_length |
media.upload | location_ids, target_count, media_count, total_bytes |
report.create | template, period, group_id, location_id, include_ai_advice |
PII 除外ルール
| 項目 | 扱い |
|---|---|
投稿本文 (summary) | 記録しない(先頭 50 文字のみ input_summary に) |
口コミ返信本文 (reply) | 記録しない(文字数のみ) |
メディア本体 (media_urls) | URL のドメインのみ記録 |
| 個人情報を含む可能性のあるキーワード | 記録しない |
| アクセストークン / refresh_token / code | 絶対に記録しない |
Audit ログにトークンを含めない
JWT の jti のみ記録、トークン本体は記録禁止。漏洩リスク対策。
レポート用集計クエリ例
① クライアント別の利用状況(直近 30 日)
sql
SELECT
client_id,
COUNT(*) AS total_calls,
SUM(status = 'success') AS success_calls,
SUM(status = 'forbidden') AS forbidden_calls,
SUM(status = 'rate_limited') AS rate_limited_calls,
AVG(elapsed_ms) AS avg_elapsed_ms
FROM mappy_mcp_audit_logs
WHERE event_type = 'tool_call'
AND created_at >= NOW() - INTERVAL 30 DAY
GROUP BY client_id
ORDER BY total_calls DESC;② ツール別の利用ランキング
sql
SELECT
tool_name,
COUNT(*) AS calls,
COUNT(DISTINCT user_id) AS unique_users,
AVG(elapsed_ms) AS avg_elapsed_ms,
MAX(elapsed_ms) AS max_elapsed_ms
FROM mappy_mcp_audit_logs
WHERE event_type = 'tool_call'
AND created_at >= NOW() - INTERVAL 30 DAY
AND status = 'success'
GROUP BY tool_name
ORDER BY calls DESC;③ 代理操作の検出
sql
SELECT
request_id,
user_kind,
admin_id,
target_user_id,
tool_name,
created_at
FROM mappy_mcp_audit_logs
WHERE user_kind = 'admin'
AND target_user_id IS NOT NULL
AND created_at >= NOW() - INTERVAL 7 DAY
ORDER BY created_at DESC;④ 書込失敗の集計
sql
SELECT
tool_name,
error_code,
COUNT(*) AS occurrences,
MIN(created_at) AS first_seen,
MAX(created_at) AS last_seen
FROM mappy_mcp_audit_logs
WHERE status IN ('failed', 'partial')
AND event_type = 'tool_call'
AND created_at >= NOW() - INTERVAL 7 DAY
GROUP BY tool_name, error_code
ORDER BY occurrences DESC;⑤ レート制限ヒット率
sql
SELECT
client_id,
user_id,
COUNT(*) AS rate_hits
FROM mappy_mcp_audit_logs
WHERE status = 'rate_limited'
AND created_at >= NOW() - INTERVAL 24 HOUR
GROUP BY client_id, user_id
HAVING rate_hits >= 10
ORDER BY rate_hits DESC;アラート対象(CloudWatch Alarms 等)
| 条件 | 通知レベル |
|---|---|
| 5 分間で同一 user_id の forbidden が 10 回以上 | WARNING |
| 5 分間で同一 client_id の rate_limited が 50 回以上 | WARNING |
| 1 時間で admin の代理操作が 100 件以上 | CRITICAL |
| 同一 idempotency_key の衝突が 10 件以上 | WARNING |
| OAuth トークン発行失敗率 > 10% | CRITICAL |
| 平均 elapsed_ms > 3000ms (30 分平均) | WARNING |
データ保持・アーカイブ
| データ種別 | 保持期間 | アーカイブ先 |
|---|---|---|
| audit_logs(読取系) | 1 年 | S3 Glacier |
| audit_logs(書込系) | 3 年 | S3 Glacier |
| batches | 3 ヶ月(done/failed) | 削除 |
| batches(cancelled/partial) | 1 年 | S3 |
| rate_limit_logs | 90 日 | 削除 |
月次パーティションをそのままアーカイブする運用とし、削除は DROP PARTITION で実施。
容量見積もり
想定値
| 項目 | 値 |
|---|---|
| Phase 1 のアクティブクライアント数 | 100 |
| 1 クライアントあたり 1 日のツール呼び出し | 100 |
| 1 レコードのサイズ | 約 1 KB(input_summary 含む) |
→ 1 日 10,000 レコード = 約 10 MB → 1 ヶ月 300,000 レコード = 約 300 MB → 1 年 3,650,000 レコード = 約 3.6 GB
Phase 2/3 で書込系が増えれば倍程度を想定。月次パーティションで運用十分。
DDL(参考)
sql
CREATE TABLE mappy_mcp_audit_logs (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
request_id VARCHAR(64) NOT NULL,
event_type VARCHAR(64) NOT NULL,
client_id VARCHAR(64) DEFAULT NULL,
user_id BIGINT UNSIGNED DEFAULT NULL,
user_kind VARCHAR(16) DEFAULT NULL,
admin_id BIGINT UNSIGNED DEFAULT NULL,
target_user_id BIGINT UNSIGNED DEFAULT NULL,
tool_name VARCHAR(128) DEFAULT NULL,
scope_required VARCHAR(255) DEFAULT NULL,
scope_granted TEXT DEFAULT NULL,
input_hash CHAR(64) DEFAULT NULL,
input_summary JSON DEFAULT NULL,
status VARCHAR(32) NOT NULL,
error_code VARCHAR(64) DEFAULT NULL,
error_message TEXT DEFAULT NULL,
result_count INT DEFAULT NULL,
batch_id VARCHAR(64) DEFAULT NULL,
idempotency_key VARCHAR(64) DEFAULT NULL,
confirm_token VARCHAR(64) DEFAULT NULL,
is_dry_run TINYINT(1) NOT NULL DEFAULT 0,
elapsed_ms INT UNSIGNED DEFAULT NULL,
request_ip VARCHAR(45) DEFAULT NULL,
user_agent VARCHAR(255) DEFAULT NULL,
mcp_session_id VARCHAR(64) DEFAULT NULL,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (id, created_at),
UNIQUE KEY uk_request_id (request_id),
KEY idx_audit_user_created (user_id, created_at),
KEY idx_audit_target_created (target_user_id, created_at),
KEY idx_audit_client_created (client_id, created_at),
KEY idx_audit_tool_created (tool_name, created_at),
KEY idx_audit_status_created (status, created_at),
KEY idx_audit_batch (batch_id),
KEY idx_audit_idempotency (idempotency_key),
KEY idx_audit_session (mcp_session_id, created_at)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COMMENT='MCP サーバー監査ログ'
PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
PARTITION p202606 VALUES LESS THAN (UNIX_TIMESTAMP('2026-07-01')),
PARTITION p202607 VALUES LESS THAN (UNIX_TIMESTAMP('2026-08-01')),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
CREATE TABLE mappy_mcp_batches (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
batch_id VARCHAR(64) NOT NULL,
client_id VARCHAR(64) NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
target_user_id BIGINT UNSIGNED NOT NULL,
tool_name VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'queued',
input_json JSON NOT NULL,
target_count INT UNSIGNED NOT NULL,
success_count INT UNSIGNED NOT NULL DEFAULT 0,
failure_count INT UNSIGNED NOT NULL DEFAULT 0,
failures JSON DEFAULT NULL,
queued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP NULL DEFAULT NULL,
completed_at TIMESTAMP NULL DEFAULT NULL,
job_id BIGINT UNSIGNED DEFAULT NULL,
idempotency_key VARCHAR(64) DEFAULT NULL,
progress TINYINT UNSIGNED NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_batch_id (batch_id),
KEY idx_batches_user_status (user_id, status),
KEY idx_batches_client_created (client_id, created_at),
KEY idx_batches_idempotency (user_id, idempotency_key)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COMMENT='MCP 非同期バッチ管理';未確定事項(設計レビューで決定)
| # | 項目 | 候補 |
|---|---|---|
| 1 | パーティション単位 | 月次 / 週次(書込負荷次第) |
| 2 | 保持期間 | 標準 1 年案 / 規制要件次第で延長 |
| 3 | アーカイブ先 | S3 Glacier / Athena 検索可能化 |
| 4 | input_summary の最大サイズ | 1 KB / 4 KB |
| 5 | リアルタイム集計の有無 | Redash 等のダッシュボード化要否 |
| 6 | PII フィルタリングの責務 | MCP Server / Laravel API |
関連ドキュメント
- MCP サーバー 全体像
- 認証・テナンシ — 認可成功・失敗の記録項目
- 書込安全装置 — batches テーブルとの連携
- DB 設計(OAuth クライアント) — client_id 参照元