Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 7 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@ GitHub webhook receiver and local MCP extension for GitHub notification workflow
`github-webhook-mcp` receives GitHub webhook events, persists them to a local `events.json`, and exposes them to AI agents through MCP tools.
It is designed for notification-style workflows where an AI can poll lightweight summaries, inspect a single event in detail, and mark handled events as processed.

The standard mode is standalone: while the local MCP server is running, it can also host a local webhook listener in the same process.

Detailed behavior, event metadata, trigger semantics, and file responsibilities live in [docs/0-requirements.md](docs/0-requirements.md).

## Features

- Receives GitHub webhook events over HTTPS and persists them locally.
- Exposes pending events to MCP clients through lightweight polling tools.
- Can run as a standalone local MCP + webhook listener without a separate Windows service.
- Supports real-time `claude/channel` notifications in Claude Code.
- Supports direct trigger mode for immediate Codex reactions per event.
- Ships as a Node-based `.mcpb` desktop extension and as an `npx` MCP server.
Expand All @@ -29,7 +26,6 @@ Download `mcp-server.mcpb` from [Releases](https://github.com/Liplus-Project/git
1. Open Claude Desktop → **Settings** → **Extensions** → **Advanced settings** → **Install Extension...**
2. Select the `.mcpb` file
3. Enter the path to your `events.json` when prompted
4. If you want the extension itself to receive GitHub webhooks, also fill in the local webhook port and webhook secret prompts

### Claude Desktop / Claude Code — npx

Expand All @@ -42,10 +38,7 @@ Add to your Claude MCP config (`claude_desktop_config.json` or project settings)
"command": "npx",
"args": ["github-webhook-mcp"],
"env": {
"EVENTS_JSON_PATH": "/path/to/events.json",
"WEBHOOK_PORT": "8080",
"WEBHOOK_SECRET": "your_secret",
"WEBHOOK_EVENT_PROFILE": "notifications"
"EVENTS_JSON_PATH": "/path/to/events.json"
}
}
}
Expand All @@ -61,9 +54,6 @@ args = ["github-webhook-mcp"]

[mcp.github-webhook-mcp.env]
EVENTS_JSON_PATH = "/path/to/events.json"
WEBHOOK_PORT = "8080"
WEBHOOK_SECRET = "your_secret"
WEBHOOK_EVENT_PROFILE = "notifications"
```

### Python (legacy)
Expand All @@ -81,68 +71,19 @@ WEBHOOK_EVENT_PROFILE = "notifications"

## Configuration

### Standard standalone mode

When `WEBHOOK_PORT` is set, the Node.js MCP server starts a local webhook listener in the same process.
This is the recommended mode for lightweight local use and plugin-style distribution.

### 1. Configure the local MCP server

Set at least:

- `EVENTS_JSON_PATH`
- `WEBHOOK_PORT`
- `WEBHOOK_SECRET`

Optionally set:

- `WEBHOOK_EVENT_PROFILE=notifications`

### 2. Set up Cloudflare Tunnel

```bash
cloudflared tunnel login
cloudflared tunnel create github-webhook-mcp
cp cloudflared/config.yml.example ~/.cloudflared/config.yml
# Edit config.yml with your tunnel ID and domain
cloudflared tunnel run
```

### 3. Configure the GitHub webhook

- Payload URL: `https://webhook.yourdomain.com/webhook`
- Content type: `application/json`
- Secret: same value as `WEBHOOK_SECRET`
- Recommended event profile:
- Issues
- Issue comments
- Pull requests
- Pull request reviews
- Pull request review comments
- Check runs
- Workflow runs
- Discussions
- Discussion comments

If your webhook is temporarily set to `Send me everything`, set `WEBHOOK_EVENT_PROFILE=notifications` and the embedded receiver will ignore noisy events such as `workflow_job` or `check_suite`.

### Legacy Python receiver mode

Use this mode only if you want a separate long-running receiver process or direct trigger queue behavior.

#### 1. Install receiver dependencies
### 1. Install receiver dependencies

```bash
pip install -r requirements.txt
```

#### 2. Start the webhook receiver
### 2. Start the webhook receiver

```bash
WEBHOOK_SECRET=your_secret python main.py webhook --port 8080 --event-profile notifications
```

#### 3. Set up Cloudflare Tunnel
### 3. Set up Cloudflare Tunnel

```bash
cloudflared tunnel login
Expand All @@ -152,7 +93,7 @@ cp cloudflared/config.yml.example ~/.cloudflared/config.yml
cloudflared tunnel run
```

#### 4. Configure the GitHub webhook
### 4. Configure the GitHub webhook

- Payload URL: `https://webhook.yourdomain.com/webhook`
- Content type: `application/json`
Expand All @@ -170,7 +111,7 @@ cloudflared tunnel run

If your webhook is temporarily set to `Send me everything`, start the receiver with `--event-profile notifications` and it will ignore noisy events such as `workflow_job` or `check_suite`.

#### 5. Optional direct trigger mode
### 5. Optional direct trigger mode

Use the bundled Codex wrapper if you want the webhook to launch `codex exec` immediately.

Expand All @@ -190,7 +131,7 @@ python codex_reaction.py --workspace /path/to/workspace --resume-session <thread
If you want webhook delivery to stay notification-only for a workspace, create a `.codex-webhook-notify-only`
file in that workspace. The bundled wrapper will skip direct Codex execution and leave the event pending.

### Optional channel push notifications
### 6. Optional channel push notifications

The Node.js MCP server supports Claude Code's `claude/channel` capability (research preview, v2.1.80+). When enabled, new webhook events are pushed into your session automatically.

Expand Down
52 changes: 12 additions & 40 deletions docs/0-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,9 @@ GitHub Webhook イベントを受信・永続化し、MCP プロトコル経由
- AI エージェントは MCP stdio トランスポートで接続する
- 単一マシン上でローカル動作する(分散構成は対象外)
- イベント永続化は JSON ファイルベース(DB 不要)
- 標準モードは、MCP サーバー起動中のみ受信するスタンドアロン構成とする

## Architecture

### Standard standalone mode

```
GitHub ──POST──> Cloudflare Tunnel ──> local webhook event server (:8080, embedded)
|
v
events.json
^
|
Node.js MCP server (stdio, same process)
^
|
AI Agent (Codex / Claude)
```

標準モードでは、Node.js MCP サーバーが起動中のみローカル webhook event server を同居起動する。
Webhook 受信と MCP 提供は同一実体で兼任してよく、イベントストアは既存の `events.json` をそのまま使う。

### Legacy service-style mode

```
GitHub ──POST──> Cloudflare Tunnel ──> FastAPI :8080 ──persist──> events.json
^
Expand All @@ -51,21 +30,19 @@ GitHub ──POST──> Cloudflare Tunnel ──> FastAPI :8080 ──persist
AI Agent (Codex / Claude) optional trigger command
```

常駐サービスや direct trigger queue が必要な場合は、従来どおり Python webhook receiver を別プロセスで起動できる。

### Components
システムは二つのコンポーネントで構成される:

1. **Node.js standalone MCP server(標準)** — `npx github-webhook-mcp` または MCPB 経由
- MCP stdio サーバーとして起動する
- `WEBHOOK_PORT` が設定されている場合、同一プロセスでローカル webhook event server を起動する
- `events.json` の読み書きを担当する

2. **Legacy webhook receiver(Python)** — `python main.py webhook`
1. **Webhook 受信サーバー(Python)** — `python main.py webhook`
- HTTP 受信、イベント永続化、optional な direct trigger queue を担当
- trigger command が設定されている場合、保存済みイベントごとに直列実行される

3. **Legacy MCP server(Python 互換)** — `python main.py mcp`
- 従来互換用の stdio MCP 実装
2. **MCP ツールサーバー** — 二つの実装が存在する:
- **Python 実装:** `python main.py mcp`
- **Node.js 実装:** `mcp-server/` ディレクトリ(`npx github-webhook-mcp` または MCPB 経由)

両 MCP 実装は同一の events.json を読み書きし、同一の5ツールを提供する。
Python 実装は webhook 受信サーバーと同一エントリポイントで起動する。
Node.js 実装は独立パッケージとして配布される(MCPB: Claude Desktop 向け、npx: Codex 向け)。

## Functional Requirements

Expand All @@ -78,7 +55,6 @@ GitHub ──POST──> Cloudflare Tunnel ──> FastAPI :8080 ──persist
| F1.3 | 署名不一致時は HTTP 401 を返す |
| F1.4 | シークレット未設定時は署名検証をスキップする |
| F1.5 | `GET /health` でヘルスチェックを提供する(`{"status": "ok"}`) |
| F1.6 | Node.js standalone MCP server は、`WEBHOOK_PORT` 設定時に同一プロセスで `POST /webhook` と `GET /health` を提供できる |

### F2. イベントフィルタリング

Expand Down Expand Up @@ -162,7 +138,7 @@ Python 実装と Node.js 実装の両方が、以下の同一ツールセット
}
```

### F5. Direct Trigger Execution(legacy Python receiver)
### F5. Direct Trigger Execution

| ID | 要件 |
|----|------|
Expand Down Expand Up @@ -247,12 +223,8 @@ Python 実装と Node.js 実装の両方が、以下の同一ツールセット
| N2.5 | trigger working directory | `--trigger-cwd` / `WEBHOOK_TRIGGER_CWD` | なし |
| N2.6 | success 時に pending を維持するか | `--keep-pending-on-trigger-success` | false |
| N2.7 | events.json パス(Node.js MCP) | `EVENTS_JSON_PATH` | `mcp-server/../events.json` |
| N2.8 | 埋め込み webhook listener の bind host(Node.js standalone) | `WEBHOOK_HOST` | `127.0.0.1` |
| N2.9 | 埋め込み webhook listener のポート(Node.js standalone) | `WEBHOOK_PORT` | 未設定(無効) |
| N2.10 | 埋め込み webhook listener のシークレット(Node.js standalone) | `WEBHOOK_SECRET` | なし(検証スキップ) |
| N2.11 | 埋め込み webhook listener のイベントプロファイル(Node.js standalone) | `WEBHOOK_EVENT_PROFILE` | `all` |
| N2.12 | 処理済みイベント保持日数 | `PURGE_AFTER_DAYS` | 1 |
| N2.13 | チャンネル通知の有効/無効 | `WEBHOOK_CHANNEL` | 有効(`0` で無効) |
| N2.8 | 処理済みイベント保持日数 | `PURGE_AFTER_DAYS` | 1 |
| N2.9 | チャンネル通知の有効/無効 | `WEBHOOK_CHANNEL` | 有効(`0` で無効) |

優先順位: CLI 引数 > 環境変数 > デフォルト
Node.js MCP サーバーは環境変数のみで構成する(CLI 引数なし)。
Expand Down
24 changes: 4 additions & 20 deletions mcp-server/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"manifest_version": "0.3",
"name": "github-webhook-mcp",
"display_name": "GitHub Webhook MCP",
"version": "0.4.0",
"description": "Browse pending GitHub webhook events and optionally receive them locally in standalone mode.",
"long_description": "GitHub Webhook MCP helps Claude review and react to GitHub notifications from a local event store. In standalone mode it can also host a local webhook listener in the same process, so the extension can receive and persist GitHub events while Claude is running.",
"version": "0.4.1",
"description": "Browse pending GitHub webhook events. Pairs with a webhook receiver that writes events.json.",
"long_description": "GitHub Webhook MCP helps Claude review and react to GitHub notifications from a local event store. It surfaces lightweight pending-event summaries, exposes full webhook payloads on demand, and lets users mark handled events as processed without exposing the event file directly.",
"author": {
"name": "Liplus Project",
"url": "https://github.com/Liplus-Project"
Expand All @@ -26,11 +26,7 @@
"${__dirname}/server/index.js"
],
"env": {
"EVENTS_JSON_PATH": "${user_config.events_json_path}",
"WEBHOOK_PORT": "${user_config.webhook_port}",
"WEBHOOK_SECRET": "${user_config.webhook_secret}",
"WEBHOOK_EVENT_PROFILE": "notifications",
"WEBHOOK_HOST": "127.0.0.1"
"EVENTS_JSON_PATH": "${user_config.events_json_path}"
}
}
},
Expand All @@ -40,18 +36,6 @@
"type": "string",
"required": true,
"title": "Events JSON Path"
},
"webhook_port": {
"description": "Optional local port for the embedded webhook listener. Leave empty to disable receiving.",
"type": "string",
"required": false,
"title": "Webhook Port"
},
"webhook_secret": {
"description": "Optional GitHub webhook secret used to verify incoming requests.",
"type": "string",
"required": false,
"title": "Webhook Secret"
}
},
"tools": [
Expand Down
4 changes: 2 additions & 2 deletions mcp-server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "github-webhook-mcp",
"version": "0.4.0",
"version": "0.4.1",
"description": "MCP server for browsing GitHub webhook events",
"type": "module",
"bin": {
Expand All @@ -13,7 +13,7 @@
],
"scripts": {
"start": "node server/index.js",
"test": "node --test test/*.test.js",
"test": "node --check server/index.js && node --check server/event-store.js",
"pack:mcpb": "mcpb pack"
},
"dependencies": {
Expand Down
18 changes: 1 addition & 17 deletions mcp-server/server/event-store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import iconv from "iconv-lite";
Expand Down Expand Up @@ -59,7 +58,6 @@ export function load() {

export function save(events) {
const filePath = dataFilePath();
mkdirSync(dirname(filePath), { recursive: true });
writeFileSync(filePath, JSON.stringify(events, null, 2), PRIMARY_ENCODING);
}

Expand All @@ -84,20 +82,6 @@ export function getPending() {
return load().filter((e) => !e.processed);
}

export function addEvent(eventType, payload) {
const events = load();
const event = {
id: randomUUID(),
type: eventType,
payload,
received_at: new Date().toISOString(),
processed: false,
};
events.push(event);
save(events);
return event;
}

export function getEvent(eventId) {
for (const event of load()) {
if (event.id === eventId) return event;
Expand Down
Loading