Agent notification infrastructure — an MCP server deployed on Cloudflare Workers that receives messages from agents and forwards them to configured notification channels.
- MCP over Streamable HTTP — compatible with Claude Code, Claude web connector, and any MCP client
- OAuth 2.1 + PKCE — sign-in flow for the Claude.ai web connector, with Dynamic Client Registration (RFC 7591) and Protected Resource Metadata (RFC 9728)
- Multi-instance channels — configure multiple Telegram / Discord instances via
TELEGRAM_INSTANCES/DISCORD_INSTANCESJSON arrays - CSRF protection — HMAC-SHA256 with HttpOnly/Secure cookie
Agent (Claude Code / claude.ai / any MCP client)
│
▼ MCP over Streamable HTTP (OAuth 2.1 + PKCE)
Dovecote (Cloudflare Worker + OAUTH_KV)
│
├──▶ Telegram Bot API
├──▶ Discord Webhook
└──▶ Slack Webhook
| Tool | Description |
|---|---|
send_notification |
Send a message to a specified notification channel |
list_channels |
List all available notification channels |
- Runtime: Cloudflare Workers (TypeScript, Hono)
- Transport: Streamable HTTP (SSE)
- Storage: Cloudflare KV (
OAUTH_KV— OAuth clients/grants/tokens + encrypted channel config) - Auth:
@cloudflare/workers-oauth-provider(OAuth 2.1)
-
Install dependencies:
bun install
-
Create a
.dev.varsfile (see.dev.vars.example):OAUTH_PASSWORD=your-authorize-page-password COOKIE_ENCRYPTION_KEY=$(openssl rand -base64 32) TELEGRAM_INSTANCES=[{"id":"default","botToken":"...","chatId":"..."}] DISCORD_INSTANCES=[{"id":"default","webhookUrl":"..."}]
-
Run locally:
bun run dev
-
Run tests:
# Run all tests bun test # Run E2E tests only (local mode) bun test test/e2e/
- Wrangler CLI installed
- Cloudflare account
-
Login to Cloudflare
wrangler login
-
Set secrets
./scripts/setup-worker-vars.sh # or bun run deploy:secretsThis will prompt you to enter secrets. If
.dev.varsexists, it will use values from there as defaults.Required:
OAUTH_PASSWORD— password shown on the/authorizepage (Claude.ai OAuth flow)COOKIE_ENCRYPTION_KEY— HMAC key for the CSRF cookie (base64, 32 bytes)
Optional (notification channels, JSON arrays):
TELEGRAM_INSTANCES—[{"id":"default","botToken":"...","chatId":"..."}]DISCORD_INSTANCES—[{"id":"default","webhookUrl":"..."}]
For the staging environment:
WRANGLER_ENV=staging ./scripts/setup-worker-vars.sh.Also create a KV namespace in the Cloudflare dashboard and write its id into the
[[kv_namespaces]]block ofwrangler.toml(bindingOAUTH_KV). -
Deploy the worker
./scripts/deploy.sh # or bun run deployThe script will output the worker URL (e.g.,
https://dovecote.your-subdomain.workers.dev) -
Verify deployment
TEST_BASE_URL=https://dovecote.your-subdomain.workers.dev bun run deploy:verify
This runs smoke tests to verify OAuth metadata, closed DCR, and endpoint availability. For detailed deployment procedures including client provisioning, see docs/deploy-runbook.md.
When adding a connector on Claude.ai, fill in the MCP endpoint URL including the /mcp suffix (e.g., https://dovecote.<sub>.workers.dev/mcp). The bare base URL will fail OAuth discovery and surface as "Authorization with the MCP server failed." Claude redirects to /authorize, which asks for OAUTH_PASSWORD; on success it completes the OAuth 2.1 + PKCE flow to obtain an access token, and subsequent MCP calls carry the Bearer token automatically.
-
Run E2E tests against production (optional)
TEST_BASE_URL=https://dovecote.your-subdomain.workers.dev \ bun test:e2e:remote
For testing notification channels on production:
TEST_BASE_URL=https://dovecote.your-subdomain.workers.dev \ TEST_TELEGRAM_INSTANCES='[{"id":"default","botToken":"...","chatId":"..."}]' \ TEST_DISCORD_INSTANCES='[{"id":"default","webhookUrl":"..."}]' \ bun test:e2e:remote
By default, E2E tests run in local mode using app.fetch() (in-process testing):
bun test test/e2e/Requires .dev.vars with valid credentials.
To test against a deployed worker, set environment variables:
TEST_BASE_URL=https://dovecote.your-subdomain.workers.dev \
bun test test/e2e/In remote mode:
- Tests use actual HTTP requests via global
fetch() - Tests that require custom environment configurations are skipped (they only apply to in-process testing)
dovecote implements defense-in-depth security controls:
- OAuth 2.1 + PKCE: Authorization flow requires S256 code challenge (plain challenge rejected)
- Closed Dynamic Client Registration: Public DCR disabled; clients provisioned via operator-only
/admin/bootstrap-clientendpoint - CSRF Protection: HMAC-signed cookie validation on authorization form submission
- Rate Limiting: 5 requests per 60 seconds per IP address on admin endpoints
- Audit Trail: All authorization and privileged operations logged to KV with 90-day TTL
- Anti-Clickjacking Headers:
/authorizeendpoint servesContent-Security-Policy: frame-ancestors 'none'andX-Frame-Options: DENY - Scope-Based Access Control:
dovecote:notify– Send notifications via configured channelsdovecote:env:read– High privilege: Read environment profiles from KV storage. Grant with caution.
Please report security vulnerabilities privately via GitHub Security Advisories. Do not file public issues for security problems.
MIT