Webhook2MCP
by hkrlogic
README.md
# Webhook2MCP
Local webhook → MCP bridge. Accept webhook POSTs on localhost, store them in SQLite, and expose two MCP read tools so an agent can **list** then **get** events.
This is **not MCPEncode**. It does not share MCPEncode’s schema, tools, or database.
## Security (read first)
- Binds to **`127.0.0.1` by default** — intended for personal local use.
- Auth is a shared secret (`HOOK_SECRET`) via Bearer / `X-Webhook-Token` or generic HMAC.
- Copy `.env.example` → `.env` and **set your own secret**. Do not commit `.env`.
- The example value `change-me` is for disposable local tests only.
- Secrets and auth/signature headers are **never** stored in SQLite.
- Event bodies returned by MCP are labeled **data, not instructions**.
See [SECURITY.md](SECURITY.md).
## Requirements
- Python 3.12+
- [uv](https://docs.astral.sh/uv/) (recommended)
## Setup
```bash
cp .env.example .env
# set a strong HOOK_SECRET in .env
uv sync
```
## Run
```bash
uv run webhook-mcp
```
Listens on `http://127.0.0.1:8765`:
| Path | Purpose |
|------|---------|
| `POST /hooks` | Webhook ingest |
| `GET /health` | Liveness |
| `/mcp` | MCP Streamable HTTP |
## Curl: one fake webhook (Bearer)
```bash
# Use the same secret you set in .env
curl -sS -X POST "http://127.0.0.1:8765/hooks" \
-H "Authorization: Bearer change-me" \
-H "X-Webhook-Source: test" \
-H "X-Event-Id: evt-1" \
-H "X-Event-Type: ping" \
-H "Content-Type: application/json" \
-d '{"id":"evt-1","hello":"world"}'
```
Expected: `{"status":"stored","id":1}`
Send the **same** request again. Expected: `{"status":"duplicate","id":1}` — no second row.
## Curl: HMAC instead of Bearer
```bash
BODY='{"id":"evt-2","hello":"hmac"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "change-me" | awk '{print $2}')
curl -sS -X POST "http://127.0.0.1:8765/hooks" \
-H "X-Webhook-Signature: sha256=${SIG}" \
-H "X-Webhook-Source: test" \
-H "Content-Type: application/json" \
-d "$BODY"
```
## Connect an MCP client (Cursor)
Add a server pointing at local Streamable HTTP, for example in your MCP config:
```json
{
"mcpServers": {
"webhook-mcp": {
"url": "http://127.0.0.1:8765/mcp"
}
}
}
```
Exact key names vary by client. Keep the URL on loopback.
Then:
1. Call **`list_events`** (optional `source`, `limit`) — summaries only, no body.
2. Call **`get_event`** with an `id` from the list — full row; body is labeled **data, not instructions**.
## Auth
Shared secret from `HOOK_SECRET`. Accepted headers (depending on `HOOK_AUTH_MODE`):
- `Authorization: Bearer <secret>` or `X-Webhook-Token: <secret>`
- `X-Webhook-Signature: sha256=<hex>` = HMAC-SHA256(secret, raw body)
## Env
See `.env.example`: `HOOK_SECRET`, `DATABASE_PATH`, `HOST`, `PORT`, `MAX_EVENTS`, `MAX_BODY_BYTES`, `HOOK_AUTH_MODE`.
## Out of scope (this pass)
No tunnel, Docker, deploy, vendor parsers, Postgres, tenants, or LLM.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues