Slack MCP Agent — システムアーキテクチャ
v1.3 / 2026-04-20
MVP のシステム構成を定義する。
先行プロジェクト caster-division-ai の思想(Custom Tool パターン、pause/resume、監査ログ)を継承しつつ、
本プロジェクト固有の要件(SQLite / シングルプロセス / 4 月 MVP)に最適化する。
1. 設計原則
| # |
原則 |
説明 |
| A-1 |
シングルプロセスで十分 |
MVP はローカル実行 × 1 人運用。Backend・MCP Server・Web を個別プロセスで起動し、make dev で一括管理 |
| A-2 |
Custom Tool ファースト |
セキュリティが重要な HR データ操作は Custom Tool で Backend 内完結。MCP Server は Managed Agent 経由のツール提供に使用 |
| A-3 |
状態は SQLite に集約 |
Backend と Web が同一 DB ファイルを参照。Backend が書き込み、Web は読み取り主体 |
| A-4 |
Slack が主 UI、Web が副 UI |
ユーザー体験は Slack で完結。Web は管理・監視・設定用 |
| A-5 |
マルチテナント対応を構造で準備 |
全テーブルに tenant_id。MVP はシングルテナントだがスキーマレベルで将来に備える |
| A-6 |
caster-division-ai の継承 |
Custom Tool パターン、承認フロー、監査ログ、DashShell レイアウトを踏襲 |
2. システム構成図
flowchart TD
subgraph user ["User / PM"]
Slack["Slack\n(Channel / Agent Container)"]
WebUI["Web 管理画面"]
end
subgraph backend ["Backend (Python)"]
Bolt["Slack Bolt App\n(Socket Mode)\n- app_mention\n- assistant_*\n- actions"]
Pipeline["Pipeline\nL1: Msg → Task\nL2: Task → Prompt\nL3: Prompt → Steps\nL4: Steps → Exec"]
CTE["Custom Tool Executor\n(freee API 直接)"]
Bolt --> Pipeline --> CTE
end
subgraph web ["Web (Next.js)"]
SC["Server Components → SQLite 読取\nServer Actions → SQLite 書込\n(settings, audit)"]
end
subgraph anthropic ["Anthropic Cloud"]
MAS["Managed Agent Session\n- L4 実行ランタイム\n- MCP Tool 呼出\n- tool_confirmation"]
MCP["Combined MCP Server\n(scripts/combined_mcp_server.py, port 8001)\nhr-freee 系モック + Google Sheets + Slack DM"]
MAS --> MCP
end
FreeeAPI["freee API\n(OAuth 2.0)"]
SQLite["SQLite DB\nagent.db"]
Slack -->|"Socket Mode"| Bolt
WebUI -->|"HTTP"| SC
CTE --> FreeeAPI
SC --> SQLite
backend --> SQLite
MCP -->|"MCP (streamable-http)"| FreeeAPI
2.1 プロセス構成
MVP は開発容易性を優先し、MCP Server を 1 プロセスに統合(scripts/combined_mcp_server.py)して運用する。mcp-servers/hr-freee/ と mcp-servers/common/ のパッケージは単体起動・テスト用に残っているが、make dev では使用しない。
| プロセス |
言語 |
ポート |
起動コマンド |
役割 |
| Backend |
Python |
8000 (設定用、Socket Mode のため外部公開不要) |
python -m src.app (in backend/) |
Slack Bot + パイプライン + Custom Tool Executor |
| Combined MCP Server |
Python |
8001 |
python scripts/combined_mcp_server.py |
HR モック + Google Sheets + Slack DM を単一プロセスで提供 |
| Web |
Node.js |
3100 |
pnpm dev (in web/) |
管理画面 (Next.js) |
| ngrok |
- |
- |
ngrok http --domain=$NGROK_DOMAIN 8001 |
MCP Server を Anthropic Cloud に公開 |
補助(必要時のみ個別起動):
| プロセス |
ポート |
用途 |
mcp-servers/hr-freee/server.py |
MCP_HR_FREEE_PORT (既定 8001) |
hr-freee 単体のユニットテスト・動作確認 |
mcp-servers/common/server.py |
MCP_COMMON_PORT (既定 8002) |
common 単体のユニットテスト・動作確認 |
2.2 通信経路
| From |
To |
プロトコル |
説明 |
| Slack → Backend |
WebSocket (Socket Mode) |
Slack Events / Actions をリアルタイム受信 |
|
| Backend → Slack |
HTTPS |
chat.postMessage, chat.update, views.open |
|
| Backend → Anthropic (L1-L3) |
HTTPS |
messages.create (Anthropic SDK) |
|
| Backend → Anthropic (L4) |
HTTPS |
beta.sessions.create/retrieve/events |
|
| Anthropic → MCP Server |
HTTP (streamable-http) |
Managed Agent がツール実行 (ngrok 経由、combined MCP の単一ポート) |
|
| Backend → freee API |
HTTPS |
Custom Tool Executor 内で直接呼出 |
|
| Backend → SQLite |
ファイル I/O |
aiosqlite (WAL モード) |
|
| Web → SQLite |
ファイル I/O |
Server Components から直接読取 |
|
3. レイヤー構成
3.1 概要
flowchart TD
subgraph presentation ["Presentation Layer"]
P1["Slack (Block Kit cards / Agent Container)"]
P2["Web (Next.js Server Components)"]
end
subgraph application ["Application Layer"]
A1["Slack Event/Action Handlers"]
A2["Pipeline Orchestrator"]
A3["Web Server Actions"]
end
subgraph domain ["Domain Layer"]
D1["L1-L4 Pipeline (LLM 呼出 + 状態遷移)"]
D2["Custom Tool Executor (freee API)"]
D3["Approval Gate (承認/却下/再生成)"]
end
subgraph infrastructure ["Infrastructure Layer"]
I1["SQLite (aiosqlite / WAL)"]
I2["Anthropic SDK (Messages + Managed Agents beta)"]
I3["Slack SDK (slack-bolt)"]
I4["freee API Client (OAuth 2.0)"]
I5["MCP Servers (FastMCP / streamable-http)"]
end
presentation --> application --> domain --> infrastructure
3.2 各レイヤーの責務
| レイヤー |
責務 |
依存方向 |
| Presentation |
Slack Block Kit JSON 構築、Web ページレンダリング |
→ Application |
| Application |
ユーザーイベントの受信、パイプライン起動、状態更新の調整 |
→ Domain |
| Domain |
ビジネスロジック(L1-L4、承認フロー、ツール実行) |
→ Infrastructure |
| Infrastructure |
外部サービス接続、永続化、SDK ラッパー |
(最下層) |
4. 4 層パイプライン詳細
4.1 全体フロー
flowchart TD
UserMsg["User Message"]
subgraph L1 ["L1: Message → Task"]
L1P["Claude messages.create (1-shot)\n→ Task JSON (title, desc, priority)\n→ DB: tasks INSERT\n→ Slack: Task Card 投稿"]
end
subgraph L2 ["L2: Task → Prompt"]
L2P["Claude messages.create (1-shot)\n→ 実行方針テキスト\n→ DB: prompts INSERT\n→ Slack: Prompt Card (承認待ち)"]
end
Gate1{"人間が承認 or 却下"}
L2Reject["却下 → 理由入力\n→ L2 再実行 (新バージョン)"]
subgraph L3 ["L3: Prompt → Process"]
L3P["Claude messages.create (1-shot)\n→ steps JSON (ツール呼出計画)\n→ DB: processes INSERT\n→ Slack: Process Card (承認待ち)"]
end
Gate2{"人間が承認 or 却下"}
L3Reject["却下 → 理由入力\n→ L3 再実行 (新バージョン)"]
subgraph L4 ["L4: Process → Execution"]
L4P["Managed Agent Session\n→ Custom Tool (freee) / MCP (common)\n→ DB: executions INSERT/UPDATE\n→ Slack: Execution Card (進捗更新)\n→ 完了/失敗/中止"]
end
UserMsg --> L1
L1 --> L2
L2 --> Gate1
Gate1 -->|"却下"| L2Reject --> L2
Gate1 -->|"承認"| L3
L3 --> Gate2
Gate2 -->|"却下"| L3Reject --> L3
Gate2 -->|"承認"| L4
4.2 L1-L3: Messages API 呼出
| 層 |
System Prompt |
入力 |
出力 |
max_tokens |
| L1 |
prompts/l1_system.md |
<user_input> ラップされたユーザーメッセージ |
Task JSON (title, description, priority, taskType) |
2048 |
| L2 |
prompts/l2_system.md |
Task JSON + ツールカタログ |
実行方針テキスト (自然言語) |
8192 |
| L3 |
prompts/l3_system.md |
承認済み Prompt + ツールスキーマ (JSON) |
Process steps JSON (steps[]) |
8192 |
共通パターン:
- 全て anthropic.AsyncAnthropic.messages.create (1-shot)
- モデル: claude-sonnet-4-6 (環境変数 CLAUDE_MODEL で上書き可)
- System Prompt はファイルから読み込み
4.3 L4: Managed Agent Session
Anthropic の Managed Agents API (beta) を使用。Agent のランタイムは Anthropic Cloud で実行され、ツール呼出は MCP Server 経由。
sequenceDiagram
participant B as Backend
participant A as Anthropic Cloud
participant M as MCP Server
B->>A: sessions.create(agent_id, env_id)
B->>A: events.send(user.message, steps)
A->>M: MCP tool call
M-->>A: tool result
B->>A: sessions.retrieve (polling)
A-->>B: status: processing / idle
Note over B,A: tool_confirmation が来た場合
B->>A: events.send(allow_once)
B->>A: sessions.events.list (完了確認)
Note over B: summary 抽出
| パラメータ |
値 |
説明 |
| ポーリング間隔 |
3 秒 |
POLL_INTERVAL_SECONDS |
| 最大ポーリング回数 |
120 回 |
MAX_POLL_ATTEMPTS (= 360 秒タイムアウト) |
| tool_confirmation |
allow_once で自動承認 |
MVP では全ツール自動承認 |
freee API を扱う HR 3 ツール (list_employees, list_attendance, calculate_overtime) は Custom Tool Executor で Backend 内実行する。L4 ポーリングループが custom_tool_use イベントを検知し、Backend 内の Router → freee Adapter → freee API の順に処理する。
flowchart TD
MAS["Managed Agent Session"] -->|"custom_tool_use event"| Backend["Backend (polling で検知)"]
Backend --> CTE["Custom Tool Executor"]
CTE --> T1["list_employees\n→ freee GET /api/1/employees"]
CTE --> T2["list_attendance\n→ freee GET /api/1/employees/{id}/work_records"]
CTE --> T3["calculate_overtime\n→ list_attendance 結果を集計 (Backend 内計算)"]
T1 --> Result["events.send(tool_result)"]
T2 --> Result
T3 --> Result
Result -->|"Managed Agent Session 続行"| MAS
セキュリティ上の利点:
- OAuth トークンが Anthropic Cloud に渡らない
- ツール実行ログが Backend 内で完結
- Policy Engine (Post-MVP) で制御可能
5. 承認フローアーキテクチャ
5.1 承認ゲート
stateDiagram-v2
[*] --> 生成中
生成中 --> 承認待ち
承認待ち --> 承認済み : 承認
承認待ち --> 却下 : 却下
却下 --> 生成中 : 理由入力 → 再生成
承認済み --> [*]
Prompt と Process の 2 箇所に同一パターンの承認ゲートを配置。
5.2 却下 → 再生成
- ユーザーが [却下] をクリック
views.open で理由入力モーダルを表示
- 理由を DB に保存 (
rejection_reason, rejected_by)
- 前バージョンの内容 + 却下理由を含めて L2/L3 を再実行
- 新バージョン (version + 1) を DB に INSERT
- Slack カードを新バージョンの承認待ちに更新
5.3 実行中止
- 実行中の Execution カードに [中止] ボタンを表示
- クリック → Managed Agent Session を停止 (
sessions.cancel)
- Execution ステータスを
cancelled に更新
cancelled_by, cancelled_at を記録
- Task ステータスを
cancelled に更新
5.4 再実行
- 失敗した Execution カードに [再実行] ボタンを表示
- クリック → 同じ Process steps で新しい L4 Session を開始
- 新しい Execution レコードを INSERT
- Task ステータスを
running に戻す
6. 認証アーキテクチャ
6.1 Slack 認証
| 項目 |
方式 |
| アプリ配信 |
Socket Mode (WebSocket) |
| トークン |
SLACK_BOT_TOKEN (xoxb-), SLACK_APP_TOKEN (xapp-) |
| 署名検証 |
SLACK_SIGNING_SECRET (Bolt が自動検証) |
| ユーザー識別 |
Slack user ID (event.user / body.user.id) |
6.2 freee OAuth 2.0
sequenceDiagram
participant W as Web 管理画面
participant B as Backend/API
participant F as freee
W->>B: [接続] クリック
B->>F: authorize URL 生成
B-->>W: リダイレクト
W->>F: freee で認可
F-->>B: callback + auth code
B->>F: token exchange
F-->>B: access + refresh token
Note over B: DB 保存 (暗号化)
B-->>W: 接続完了リダイレクト
| 項目 |
方式 |
| トークン保存 |
integrations テーブル (credentials JSON, 暗号化) |
| リフレッシュ |
401 応答時に自動リフレッシュ → リトライ |
| スコープ |
employees:read, work_records:read |
| トークン管理 |
Backend プロセス内のみ。Anthropic Cloud には渡さない |
6.3 Web 管理画面認証
| フェーズ |
方式 |
詳細 |
| MVP |
HTTP Basic 認証 |
Next.js Middleware で全ルートに適用。ADMIN_USER / ADMIN_PASSWORD 環境変数 |
| Post-MVP |
Firebase Auth + Google SSO |
@cast-er.com ドメイン制限。Caster 既存と同構成 |
6.4 Anthropic API
| 項目 |
方式 |
| 認証 |
API Key (ANTHROPIC_API_KEY) |
| SDK |
anthropic.AsyncAnthropic (Backend 内) |
| Managed Agent |
agent_id + environment_id で Session を作成 |
7. 永続化アーキテクチャ
7.1 SQLite 設計方針
| 項目 |
方式 |
理由 |
| WAL モード |
有効 |
読み書き並行性。Web の読取と Backend の書込が同時可能 |
| ULID |
全テーブルの PK |
時系列ソート可能な分散 ID |
| JSON カラム |
steps, results, config, credentials, metadata, details |
柔軟なスキーマ拡張 |
| FK 制約 |
有効 (PRAGMA foreign_keys = ON) |
データ整合性 |
| マイグレーション |
バージョン管理 (SCHEMA_VERSION + _MIGRATIONS dict) |
インクリメンタル DDL |
7.2 テーブル一覧
| テーブル |
主要カラム |
用途 |
tenants |
id, slug, name, agent_id, vault_id |
テナント管理 |
tasks |
id, tenant_id, title, status, channel, thread_ts |
タスク (L1 出力) |
prompts |
id, task_id, version, content, status, rejection_reason |
実行方針 (L2 出力) |
processes |
id, task_id, version, steps (JSON), status, rejection_reason |
ステップ計画 (L3 出力) |
executions |
id, task_id, process_id, session_id, status, results (JSON) |
実行記録 (L4) |
slack_messages |
id, task_id, card_type, channel, message_ts |
Slack カード ↔ DB マッピング |
tool_permissions |
id, tenant_id, tool_name, permission, constraints (JSON) |
ツール権限 (Post-MVP) |
audit_logs |
id, tenant_id, timestamp, actor_type, action, details (JSON) |
監査ログ |
settings |
id, tenant_id, config (JSON) |
テナント設定 |
integrations |
id, tenant_id, provider, status, credentials (JSON) |
外部連携 |
7.3 Backend ↔ Web の DB 共有
flowchart TD
BackendPy["Backend (Python, aiosqlite)\nINSERT/UPDATE\n(tasks, prompts, processes,\nexecutions, audit_logs)"]
WebNode["Web (Node.js, better-sqlite3)\nSELECT (全テーブル)\nUPDATE (settings)\nINSERT (audit_logs)"]
DB["agent.db (WAL)"]
BackendPy --> DB
WebNode --> DB
- Backend: 主要な書き込み担当 (パイプライン進行)
- Web: 読み取り主体 + 設定変更・監査ログ書込
- WAL モードにより同時アクセスが安全
8. MCP Server アーキテクチャ
8.1 MVP 構成
MCP Server は Managed Agent がツールを呼び出す際のインターフェース。Backend が直接呼び出すのではなく、Anthropic Cloud 経由で使用される。MVP では HR 系モックと出力系ツールを 1 プロセス(combined) に統合して運用する。
| サーバー |
ポート |
ツール |
備考 |
Combined MCP Server (scripts/combined_mcp_server.py) |
8001 |
list_employees, list_attendance, calculate_overtime(モック), write_rows_to_google_sheet, send_slack_dm |
make dev で起動する本線。Google Sheets / Slack DM は GOOGLE_SERVICE_ACCOUNT_JSON / SLACK_BOT_TOKEN 設定時のみ実 API。HR 系は常にモックデータを返す(本番実行は Custom Tool Executor で行う) |
mcp-servers/hr-freee/ |
MCP_HR_FREEE_PORT |
HR モックツール |
単体テスト・動作確認用。make dev では未使用 |
mcp-servers/common/ |
MCP_COMMON_PORT |
write_rows_to_google_sheet, send_slack_dm |
単体テスト・動作確認用。make dev では未使用 |
8.2 ツール実行パスの使い分け (MVP)
MVP では freee HR ツールを Custom Tool Executor、出力系ツールを MCP Server で実行する。
| ツール |
実行パス |
理由 |
list_employees |
Custom Tool Executor (Backend 内) |
freee OAuth トークン保護・個人情報 |
list_attendance |
Custom Tool Executor (Backend 内) |
freee OAuth トークン保護・個人情報 |
calculate_overtime |
Custom Tool Executor (Backend 内) |
freee OAuth トークン保護・個人情報 |
write_rows_to_google_sheet |
MCP Server common (Agent 経由) |
書込系・低リスク |
send_slack_dm |
MCP Server common (Agent 経由) |
書込系・低リスク |
| 判断基準 |
Custom Tool Executor |
MCP Server |
| 認証情報の扱い |
Backend プロセス内で保持。Anthropic Cloud に渡さない |
MCP Server プロセス内で保持 |
| 監査 |
Backend ログ + audit_logs テーブル |
Agent イベントログ |
| 適用範囲 |
freee API (HR データ) |
Google Sheets, Slack DM |
8.3 ネットワーク要件
- MCP Server は localhost で起動
- Anthropic Cloud からのアクセスには ngrok トンネルが必要
setup-managed-agent.py で Agent 定義に MCP Server URL を登録
9. 非同期処理アーキテクチャ
9.1 イベント駆動フロー
Slack Bolt は同期ハンドラ内で ack() を呼び、重い処理は asyncio.new_event_loop() で非同期実行。
def handle_approve_prompt(ack, body, client):
ack() # 3 秒以内に応答
async def async_handler():
# L3 実行 (10 秒程度)
# DB 更新
# Slack カード更新
loop = asyncio.new_event_loop()
loop.run_until_complete(async_handler())
9.2 L4 ポーリング
L4 は Managed Agent Session のステータスを 3 秒間隔でポーリング。
flowchart TD
Start["Backend"] --> Create["sessions.create()"]
Create --> Send["events.send(user.message)"]
Send --> Loop{"loop\n(max 120 回 = 360 秒)"}
Loop --> Retrieve["sessions.retrieve() → status"]
Retrieve --> CheckStatus{"status?"}
CheckStatus -->|"processing"| EventsList["sessions.events.list()"]
EventsList --> ToolConf{"tool_confirmation?"}
ToolConf -->|"Yes"| AllowOnce["events.send(allow_once)"] --> Loop
ToolConf -->|"No"| CustomTool{"custom_tool_use?"}
CustomTool -->|"Yes"| RunTool["handle_custom_tool()\n→ events.send(tool_result)"] --> Loop
CustomTool -->|"No"| Loop
CheckStatus -->|"idle"| Summary["break → summary 抽出"]
CheckStatus -->|"error"| ErrorHandle["break → エラー処理"]
注: on_progress コールバックは run_l4_session() のシグネチャに存在するが、MVP 実装ではポーリングループから呼び出されていない。Slack の Execution カードは L4 開始時と完了・失敗・中止の遷移タイミングでのみ更新される。ツール実行ごとのリアルタイム進捗更新は Post-MVP(docs/post-mvp-roadmap.md 参照)。
9.3 中止処理
- ユーザーが [中止] → Backend が
sessions.cancel() を呼出
- 次回ポーリングで
cancelled ステータスを検知
- ポーリングループ終了 → DB + Slack 更新
10. エラー境界
| 境界 |
エラー種別 |
対処 |
| Slack → Backend |
Socket Mode 切断 |
自動再接続 (Bolt 標準) |
| Backend → Anthropic (L1-L3) |
API エラー / タイムアウト |
リトライ (最大 3 回) → 失敗カード表示 |
| Backend → Anthropic (L4) |
Session エラー |
Execution ステータス failed → [再実行] ボタン |
| Backend → freee API |
401 |
トークンリフレッシュ → リトライ |
| Backend → freee API |
429 |
Retry-After に従いリトライ |
| Backend → freee API |
5xx |
最大 3 回 (exponential backoff) |
| Backend → SQLite |
ロックエラー |
WAL + BUSY_TIMEOUT で軽減 |
| Web → SQLite |
接続エラー |
エラーページ表示 |
| MCP Server |
ツール実行エラー |
エラーテキストを Agent に返却 → Agent が判断 |
11. セキュリティ境界
flowchart TD
subgraph trust1 ["信頼境界 1: Backend プロセス"]
FreeeToken["freee OAuth Token\n(暗号化保存)"]
SlackToken["Slack Bot Token\nAnthropic API Key\n(環境変数)"]
Note1["認証情報は Backend 外に出ない\nLLM API に送信されるのは業務データのみ"]
end
subgraph trust2 ["信頼境界 2: Anthropic Cloud"]
ManagedAgent["Managed Agent Session"]
MCPExec["MCP Server 経由のツール実行"]
DataPolicy["データ保持なしポリシー (API)"]
end
subgraph trust3 ["信頼境界 3: MCP Server プロセス"]
Localhost["localhost 起動、ngrok 経由でのみ外部公開"]
ToolExec["ツール実行は MCP Server プロセス内で完結"]
end
trust1 --> trust2
trust2 --> trust3
LLM 入力のセキュリティ
- ユーザー入力は
<user_input> タグで隔離 (Prompt Injection 軽減)
- HR 個人データ (氏名、勤怠) の LLM API 送信は許容
- マイナンバー等の重要個人情報は Post-MVP で対策
12. Post-MVP 拡張ポイント
| 領域 |
MVP |
Post-MVP |
| テナント |
シングル (tenant_id はスキーマに存在) |
Multi-tenant Router + Tenant Config |
| 認証 |
Basic 認証 |
Firebase Auth + Google SSO |
| DB |
SQLite |
PostgreSQL (or Firestore) |
| ツール |
Custom Tool + MCP (3+2 ツール) |
Shared Schema Layer + ベンダーアダプタ |
| デプロイ |
Railway 単一コンテナ (nginx + Backend + MCP + Web) |
Cloud Run / ECS (サービス分割) |
| 監視 |
ログ出力のみ |
Datadog / Cloud Logging |
| Skills |
L4 システムプロンプト固定 |
Managed Agents Skills (動的ローディング) |
| Policy Engine |
全ツール自動承認 |
tool_permissions テーブルで制御 |
| Vault |
環境変数 + DB 暗号化 |
Managed Agents Vault (テナント別) |
変更履歴
| 日付 |
バージョン |
変更内容 |
| 2026-04-18 |
v1.0 |
初版作成 |
| 2026-04-19 |
v1.1 |
freee ツール実行パスを Custom Tool Executor に一本化。MCP Server hr-freee を開発テスト用と明記 (issue #11) |
| 2026-04-20 |
v1.2 |
デプロイを「ローカル」から「Railway 単一コンテナ」に更新 (issue #31) |
| 2026-04-20 |
v1.3 |
実装実態に同期: MCP を combined 構成に修正、Web ポートを 3100 に訂正、L2/L3 max_tokens を 8192 に訂正、on_progress が未配線である旨を明記、Custom Tool Executor のポーリング分岐を追加 |