Skip to main content
Glama
hkrlogic

Webhook2MCP

by hkrlogic

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.

Related MCP server: mcp-sqlite

Requirements

  • Python 3.12+

  • uv (recommended)

Setup

cp .env.example .env
# set a strong HOOK_SECRET in .env

uv sync

Run

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)

# 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

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:

{
  "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.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Local MCP event log for multi-agent workspaces, enabling structured event publishing, cursor-based polling, and status checks via SQLite-backed sidecar.
    10
    355 PyPI
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP tool server providing SQLite database access for AI agents.
    MIT