line-approval-mcp
by fumy98
README.md
# line-approval-bridge
AI エージェント(Claude Code の定期タスク、シェルスクリプトなど)から **あなたの LINE に通知し、返信・承認を待って、その答えで処理を分岐する** ための小さなブリッジです。
```
Claude Code / スクリプト ──(MCP / CLI)──▶ Cloudflare Worker ──push──▶ あなたの LINE
▲ │ ▲ │
└──────── 結果をポーリング ───────────┘ └──── Webhook(返信)────────┘
```
- `notify(text, send_at?)` … 一方向の通知。`send_at` を付けると **サーバー側で予約**し、Mac がスリープしていてもその時刻に届く
- `ask_approval(question, options, timeout)` … 「承認 / 却下」などのボタン付きで質問し、**回答が来るまで待つ**
- `get_reply(id)` … 回答の取得(待ちの再開)
## 公式 LINE Bot MCP Server との違い
[公式 LINE Bot MCP Server](https://github.com/line/line-bot-mcp-server) は **送信専用** です。メッセージを送ることはできますが、ユーザーの返信を受け取ってエージェントに返す仕組みはありません。
line-approval-bridge は **人間の判断を待つ(human-in-the-loop)** ことに特化しています。
| | 公式 LINE Bot MCP Server | line-approval-bridge |
|---|---|---|
| メッセージ送信 | できる | できる |
| 返信の受信(Webhook) | なし | あり(署名検証つき) |
| 承認ボタンを出して待つ | なし | `ask_approval` |
| 返信内容で処理を分岐 | なし | `answer.value` で分岐 |
| 自由入力の返信 | なし | あり(`#ID` 指定で過去の依頼にも回答可) |
| 受信者の制限 | — | 許可した userId のみ |
## 特徴
- **無料で動く**: Cloudflare Workers 無料プラン + Workers KV、LINE コミュニケーションプラン(無料・月 200 通)
- **安全**: `X-Line-Signature`(HMAC-SHA256)を検証。許可リストにない userId は無視。API は Bearer トークン必須
- **返信は通数を消費しない**: 「受け付けました」の返事は reply API(無料)で返す
- **MCP サーバー + CLI**: Claude Code からはツールとして、シェルからは終了コードで分岐
- **ローカル開発モード**: LINE に実際に送らず、署名付き Webhook をシミュレートして全フローを試せる
## なぜ Cloudflare Workers か(Vercel ではなく)
- KV ストアが Workers に標準で付いてくる(Vercel は外部の Redis 連携が必要で、設定項目が増える)
- 無料枠で十分(1 日 10 万リクエスト、KV 読み取り 10 万回・書き込み 1,000 回)
- Web Crypto がそのまま使えるので、署名検証が依存ライブラリなしで書ける
- スリープ(コールドスタート)が実質なく、LINE の Webhook にすぐ応答できる
---
## セットアップ
所要時間は 30 分ほどです。**トークンやパスワードはすべてご自身で発行・入力してください。** このリポジトリやチャットに貼る必要はありません。
### 必要なもの
- LINE アカウント(スマホの LINE アプリ)
- Cloudflare アカウント(無料)
- Node.js 20 以上、git
### 1. LINE 公式アカウントを作り、Messaging API を有効にする
1. [LINE Official Account Manager](https://manager.line.biz/) を開き、LINE アカウントでログイン
2. 「アカウントを作成」から LINE 公式アカウントを作る(名前は何でもよい。例: `My Agent`)
3. 作ったアカウントを開き、右上の **設定** → 左メニュー **Messaging API** → **「Messaging API を利用する」**
4. プロバイダーを選ぶ(なければ新規作成。名前は何でもよい)→ OK
5. 同じ設定画面の左メニュー **応答設定** で次のようにする
- **応答メッセージ: オフ**(自動返信がブリッジの返事と混ざるのを防ぐ)
- **Webhook: オン**
- あいさつメッセージ: お好みで
### 2. トークンと自分の userId を確認する
[LINE Developers コンソール](https://developers.line.biz/console/) を開き、手順 1 のプロバイダー → チャネル(公式アカウント名)を選びます。
| 確認する値 | 場所 | このあと入れる先 |
|---|---|---|
| チャネルシークレット | 「チャネル基本設定」タブ → チャネルシークレット | Worker の secret `LINE_CHANNEL_SECRET` |
| あなたの userId(`U` で始まる 33 文字) | 「チャネル基本設定」タブ → 一番下の「あなたのユーザーID」 | Worker の secret `ALLOWED_USER_IDS` |
| チャネルアクセストークン(長期) | 「Messaging API設定」タブ → 一番下「チャネルアクセストークン(長期)」の **発行** | Worker の secret `LINE_CHANNEL_ACCESS_TOKEN` |
続けて「Messaging API設定」タブにある **QR コードを LINE アプリで読み取り、公式アカウントを友だち追加** してください(友だちでないと通知が届きません)。
### 3. Worker をデプロイする
```bash
git clone https://github.com/fumy98/line-approval-bridge.git
cd line-approval-bridge
npm install
cd api
npx wrangler login # ブラウザが開くので Cloudflare にログインして許可
npx wrangler kv namespace create KV
```
最後のコマンドが表示する `id = "..."` を、`api/wrangler.toml` の `REPLACE_WITH_YOUR_KV_NAMESPACE_ID` と置き換えます。
次に secret を 4 つ登録します。コマンドごとに入力を求められるので、値を貼り付けて Enter(画面には表示されません)。
```bash
npx wrangler secret put LINE_CHANNEL_SECRET # 手順 2 のチャネルシークレット
npx wrangler secret put LINE_CHANNEL_ACCESS_TOKEN # 手順 2 のチャネルアクセストークン(長期)
npx wrangler secret put ALLOWED_USER_IDS # 手順 2 のあなたの userId(複数ならカンマ区切り)
openssl rand -hex 32 # API_TOKEN 用のランダム文字列を作る(控えておく)
npx wrangler secret put API_TOKEN # ↑で作った文字列
npx wrangler deploy
```
`deploy` の最後に `https://line-approval-bridge.<あなたのサブドメイン>.workers.dev` が表示されます。これが **Worker の URL** です。
### 4. LINE に Webhook URL を登録する
LINE Developers コンソール →「Messaging API設定」タブ → **Webhook URL** の「編集」に
```
https://line-approval-bridge.<あなたのサブドメイン>.workers.dev/webhook
```
を入れて「更新」→「検証」で **成功** と出れば OK。その下の **Webhook の利用** をオンにします。
(コンソールを開かなくても、手順 5 のあとで `node client/dist/cli.js setup-webhook` を実行すれば、LINE の API 経由で URL の登録と疎通テストまで行えます。)
### 5. クライアント(CLI / MCP)を準備する
```bash
cd .. # リポジトリ直下に戻る
npm run build
```
環境変数を 2 つ使います(`.env.example` 参照)。シェルで使う場合は `~/.zshrc` などに書きます。
```bash
export LINE_APPROVAL_URL="https://line-approval-bridge.<あなたのサブドメイン>.workers.dev"
export LINE_APPROVAL_TOKEN="<手順 3 で作った API_TOKEN>"
```
動作確認:
```bash
node client/dist/cli.js notify "テスト通知です"
node client/dist/cli.js ask "承認テストです。どちらかを押してください" --wait 300 --print value
```
LINE に届いたメッセージの「承認」ボタンを押すと、ターミナルに `承認` と表示されます。
### 6. Claude Code に MCP サーバーとして登録する
```bash
claude mcp add line-approval --scope user \
-e LINE_APPROVAL_URL="https://line-approval-bridge.<あなたのサブドメイン>.workers.dev" \
-e LINE_APPROVAL_TOKEN="<API_TOKEN>" \
-- node "$(pwd)/client/dist/mcp.js"
```
`--scope user` にすると、すべてのプロジェクトと定期タスクから使えます。値は手元の `~/.claude.json` にだけ保存されます。`claude mcp list` で `line-approval` が `Connected` になっていれば完了です。
### つまずきやすい点(実際に詰まったところ)
| 症状 | 原因と対処 |
|---|---|
| LINE Developers コンソールで Messaging API のチャネルが作れない | 先に [LINE Official Account Manager](https://manager.line.biz/) で公式アカウントを作り、そこで「Messaging API を利用する」を押します(手順 1)。Developers コンソール側から始めると迷子になりがちです |
| 自分の userId がどこにあるか分からない | 「チャネル基本設定」タブの**一番下**の「あなたのユーザーID」(`U` で始まる 33 文字)。LINE アプリのプロフィールにある ID(@〜 や任意の ID)とは別物です |
| 通知が届かない(エラーも出ない) | 公式アカウントを**友だち追加していない**ことが多いです。「Messaging API設定」タブの QR コードを LINE アプリで読み取ります |
| ボタンを押す前に「メッセージありがとうございます」等の自動返信が来る | 応答設定の **応答メッセージをオフ** にします(手順 1-5)。オンのままだとブリッジの返事と混ざります |
| PC / Mac 版の LINE でボタンが出ない | 古い版はクイックリプライ(スマホのみ表示)だけでした。今の版はメッセージ内にボタンを出すので、`npx wrangler deploy` し直せば PC 版でも押せます |
| 返信しても `pending` のまま | Webhook URL の登録漏れか「Webhook の利用」がオフです。`node client/dist/cli.js setup-webhook` を実行すると、登録と疎通テストをまとめて行い、オフなら警告を出します |
| `wrangler secret put` で貼り付けても何も表示されない | 仕様です(入力は画面に出ません)。貼り付けて Enter を押せば登録されます。登録済みの名前は `npx wrangler secret list` で確認できます(値は出ません) |
| `wrangler.toml` の KV id を書き換え忘れた | `deploy` が KV のエラーで止まります。`npx wrangler kv namespace list` で id を確認して書き換えます |
| トークンを `~/.zshrc` に直接書きたくない | 別ファイル(例 `~/.config/line-approval-bridge/env`、権限 600)に `export` 2 行を書き、`~/.zshrc` から `source` すると、設定ファイルを人に見せるときに安全です |
| Claude Code で `line-approval` のツールが見えない・古いまま | `claude mcp add` のあとや `npm run build` し直したあとは、Claude Code(デスクトップアプリ)を再起動します。定期タスクから確認なしで使うには、許可に `mcp__line-approval__notify` などを追加します |
| 時刻を指定した通知がスリープ中に送られない | Mac 側で `sleep` や定期タスクで待つと、スリープ中は動きません。`--at` / `send_at` の予約送信を使うと Worker 側で送るので届きます(「予約送信」の節) |
### トークンの置き場所まとめ
| 値 | 置き場所 | git に入るか |
|---|---|---|
| `LINE_CHANNEL_SECRET` / `LINE_CHANNEL_ACCESS_TOKEN` / `ALLOWED_USER_IDS` / `API_TOKEN` | Cloudflare の Worker secret(`wrangler secret put`) | 入らない |
| `LINE_APPROVAL_URL` / `LINE_APPROVAL_TOKEN` | シェルの環境変数、または `claude mcp add -e` | 入らない |
| ローカル開発用の値 | `api/.dev.vars`(`.gitignore` 済み) | 入らない |
| KV namespace id | `api/wrangler.toml` | 入る(秘密情報ではない) |
---
## 使い方
### MCP ツール(Claude Code から)
| ツール | 引数 | 戻り値 |
|---|---|---|
| `notify` | `text`, `send_at?`(ISO 8601・タイムゾーン付き。例 `2026-10-18T07:00+09:00`) | `sent`、予約時は `scheduled for <時刻> [id=XXXXXX]` |
| `ask_approval` | `question`, `options?`(既定 `["承認","却下"]`、最大 13 個・各 20 文字), `allow_free_text?`(既定 true), `timeout?`(期限・秒、既定 86400), `wait_seconds?`(この呼び出しで待つ秒数、既定 600) | `status`(`answered` / `expired` / `pending`), `answer.value`, `answer.kind`(`option` / `text`), `id` |
| `get_reply` | `id`, `wait_seconds?` | 同上 |
`wait_seconds` を過ぎても回答がなければ `pending` と `id` を返します。エージェントは `get_reply(id)` を呼べば待ちを再開できます(依頼そのものは `timeout` まで有効)。
### 例: 週の動画トピックを LINE で承認する
Claude Code の定期タスクのプロンプトに、次のように書きます。
```text
1. 来週の動画トピックを 7 本考えて topics.md に書く。
2. line-approval の ask_approval を呼ぶ。
question: 「来週のトピック案です」+ 7 本のタイトルを番号付きで
options: ["承認", "却下"]
timeout: 43200(12 時間)
3. 返ってきた answer で分岐する。
- 「承認」: topics.md の状態を approved にしてコミットする。
- 「却下」: 何もせず notify で「今週は見送りました」と送る。
- 自由入力(例「3 番を別の話題に」): 指示どおり直して、もう一度 ask_approval する(最大 3 回)。
- expired / pending: notify で「未承認のため保留しました」と送って終了する。
```
### CLI(シェルスクリプトから)
```bash
line-approval notify "<text>" [--at 2026-10-18T07:00+09:00] # --at でサーバー側に予約
line-approval scheduled # 予約中の通知を一覧
line-approval cancel <id> # 予約を取り消し
line-approval ask "<question>" [--options 承認,却下] [--no-free-text] [--timeout 秒] [--wait 秒 | --no-wait] [--print value|json]
line-approval get <id> [--wait 秒] [--print value|json]
line-approval quota # 今月の送信数
```
(`npm link -w client` で `line-approval` コマンドとして使えます。そうしない場合は `node client/dist/cli.js`。)
### 予約送信(時刻指定の通知)
Mac 上のタイマー(`sleep` や cron)で時刻を待つと、スリープ中は送れません。`--at` / `send_at` を使うと予約が Worker の KV に保存され、Cloudflare の Cron Trigger(毎分)が時刻になったら送るので、手元の端末の状態に関係なく届きます。
```bash
line-approval notify "7時です。ゴミ出しの日" --at "2026-10-18T07:00+09:00"
# => 予約しました: #K7Q2MX 2026-10-17T22:00:00.000Z
line-approval scheduled
line-approval cancel K7Q2MX
```
- 時刻は **タイムゾーン付きの ISO 8601** で指定します(`+09:00` か `Z`)。`2026-10-18T07:00` のようにタイムゾーンがないものは `400` になります
- 過去・現在の時刻を指定した場合は、その場ですぐ送ります
- 送信は分単位です。指定した分の Cron 実行で送られるので、**最大 1〜2 分ほど遅れる**ことがあります
終了コード: `0` 回答あり / `2` 期限切れ / `3` まだ回答待ち(`--wait` 経過) / `1` エラー
```bash
answer=$(line-approval ask "本番にデプロイしますか?" --timeout 3600 --print value)
case "$?:$answer" in
0:承認) ./deploy.sh ;;
0:*) echo "却下またはコメント: $answer" ;;
*) echo "回答なし" ;;
esac
```
### LINE 側での答え方
- **ボタンを押す**(いちばん確実)
- **ボタンと同じ文字を送る**(例: `承認`)→ 最新の依頼への回答
- **自由に書いて送る**(例: `3 番だけ差し替えて`)→ 最新の依頼への自由入力回答(`allow_free_text` が true の場合)
- **`#ID 内容` と送る**(例: `#K7Q2MX 却下`)→ 古い依頼にも回答できる。ID はメッセージ内の `#K7Q2MX`
- 回答は最初の 1 回だけ有効。期限切れ・回答済みの依頼にはそう返事します
## HTTP API
すべて `Authorization: Bearer <API_TOKEN>` が必要です(`/health` と `/webhook` を除く)。
| メソッド | パス | 本文 / 結果 |
|---|---|---|
| `POST` | `/api/notify` | `{ "text": "...", "to"?: "U...", "sendAt"?: "2026-10-18T07:00+09:00" }` → `200 { "ok": true }`。`sendAt` が未来なら `202 { "ok": true, "scheduled": { id, text, sendAt, createdAt } }` |
| `GET` | `/api/scheduled` | `{ "scheduled": [{ id, text, sendAt, createdAt }, ...] }`(送信予定順) |
| `DELETE` | `/api/scheduled/:id` | 予約の取り消し → `{ "ok": true }`(ないときは `404`) |
| `POST` | `/api/requests` | `{ "question": "...", "options"?: [...], "allowFreeText"?: true, "timeoutSeconds"?: 86400, "to"?: "U..." }` → `201` 依頼オブジェクト |
| `GET` | `/api/requests/:id` | 依頼オブジェクト `{ id, status, question, options, answer: { kind, value, answeredAt } \| null, ... }` |
| `GET` | `/api/quota` | `{ "limit": 200, "used": 12 }` |
| `POST` | `/webhook` | LINE からの Webhook(署名検証) |
| `GET` | `/health` | `{ "ok": true }` |
`to` を省略すると `DEFAULT_TO`(なければ `ALLOWED_USER_IDS` の先頭)に送ります。`to` に許可リスト外の userId を指定すると `400` になります。
## 制限と注意
- **月 200 通**: LINE の無料プランでは push(`notify` と `ask_approval`)が 1 回 1 通として数えられます。「受け付けました」の返事は reply なので数えられません。`line-approval quota` で確認できます
- **反映に最大 1 分ほどかかることがあります**: Workers KV は結果整合のため、ボタンを押してから `answered` が読めるまで遅れる場合があります。人の承認待ちという用途では問題にならない想定です
- **ボタンはメッセージ内(Flex Message)に表示**されるので、スマホでも PC 版 LINE でも押せます。スマホでは入力欄の上にもクイックリプライのボタンが出ます(こちらは最新のメッセージのみ)
- **予約送信の上限**: 予約は同時に 100 件まで(超えると `409`)、365 日先まで。予約はすべて KV の 1 キー(`sched:index`)にまとめて保存し、毎分の Cron は `get` 1 回だけ読みます(KV の `list` は無料枠で 1 日 1,000 回までのため使いません)。読み取りは 1 日 1,440 回、書き込みは予約・取り消し・送信のときだけです
- **予約送信の注意**: KV は結果整合のため、予約直後や送信直後に一覧へ反映されるまで遅れることがあり、まれに送信が 1 分ほど遅れたり、同じ予約が 2 回届いたりする可能性があります。LINE への送信が失敗した場合は次の分に再試行します(最大 5 回)。予約した通知も月 200 通に数えられます
- **1 対 1 のトークのみ**: グループ・複数人での承認は対象外です
## ローカル開発
LINE に実際には送らず(`LINE_DRY_RUN=true`)、ログに出すだけのモードです。
```bash
cp api/.dev.vars.example api/.dev.vars
npm run dev # http://localhost:8787
# 別のターミナルで
export LINE_APPROVAL_URL=http://localhost:8787 LINE_APPROVAL_TOKEN=local-dev-token
node client/dist/cli.js ask "テスト" --wait 120 --print value
# さらに別のターミナルで、LINE で返信したことにする(ID は dev サーバーのログの #XXXXXX)
npm run simulate -- tap XXXXXX 0 # ボタン 0 番を押す
npm run simulate -- text "承認" # 文字で返信
npm run simulate -- text "hi" --user Uother # 許可リスト外のユーザー(無視される)
```
テスト:
```bash
npm test # 署名検証・許可リスト・依頼のライフサイクル・CLI・MCP
npm run typecheck
```
## ディレクトリ構成
```
api/ Cloudflare Worker(Webhook 受信・HTTP API・KV)
client/ クライアントライブラリ、CLI(line-approval)、MCP サーバー(line-approval-mcp)
scripts/ ローカル開発用の Webhook シミュレータ
```
API(`api/`)とクライアント(`client/`)は分離しており、クライアントは HTTP 経由でのみ API を呼びます。
---
## English summary
**line-approval-bridge** lets an AI agent (Claude Code scheduled tasks, scripts) send you a LINE message and **wait for your reply or approval**, then branch on it. The official LINE Bot MCP Server can only send; this project adds the receiving side: a Cloudflare Worker that verifies LINE webhook signatures, accepts replies only from allowlisted userIds, stores them in Workers KV, and exposes a bearer-token HTTP API. Scheduled notifications (`sendAt` / `--at`) are stored in a single KV key and sent by a per-minute Cron Trigger, so they arrive even while your machine sleeps. A stdio MCP server (`notify`, `ask_approval`, `get_reply`) and a CLI (exit codes for branching) wrap the API. Runs on free tiers (Cloudflare Workers + LINE's 200 messages/month plan; acknowledgements use the free reply API). Includes a local dry-run mode with a signed-webhook simulator and tests for signature verification, the allowlist and the request lifecycle.
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues