Skip to content

feat: optional Bearer token auth, screenshot download, and SSE keepalive for CLI/TUI clients#55

Open
ebrainte wants to merge 3 commits into
cloudflare:mainfrom
ebrainte:feat/bearer-auth-screenshot-download
Open

feat: optional Bearer token auth, screenshot download, and SSE keepalive for CLI/TUI clients#55
ebrainte wants to merge 3 commits into
cloudflare:mainfrom
ebrainte:feat/bearer-auth-screenshot-download

Conversation

@ebrainte
Copy link
Copy Markdown

@ebrainte ebrainte commented Mar 31, 2026

Summary

Adds three features to the Cloudflare example worker that improve security, usability, and connection reliability for non-browser MCP clients (CLI tools, terminal UIs like OpenCode, etc.).

Depends on: #54 (Workers runtime compatibility fixes). This PR includes those commits as its base.

Feature 1: Optional Bearer Token Authentication

Protects all routes with Bearer token auth when the MCP_AUTH_TOKEN secret is configured.

wrangler secret put MCP_AUTH_TOKEN
  • Uses crypto.subtle.timingSafeEqual for constant-time comparison
  • Returns 401 with WWW-Authenticate: Bearer header on failure
  • Backward compatible: If MCP_AUTH_TOKEN is not set, all routes remain publicly accessible

Feature 2: Screenshot Download via SSE Stream Interception

Problem: CLI and TUI MCP clients (terminal-based tools) cannot display inline base64 images returned by browser_take_screenshot. The image data is returned as { type: 'image', data: '<base64>', mimeType: 'image/...' } in the JSON-RPC response, but terminal clients have no way to render it.

Solution: A ReadableStream wraps the SSE response stream and:

  1. Detects JSON-RPC responses containing type: 'image' content
  2. Extracts the base64 data into an in-memory store (5-minute TTL)
  3. Replaces the image content with a text message containing a download URL
  4. Exposes /download/<hash> for one-time authenticated file download
# AI agent takes a screenshot via MCP tools
# Instead of receiving opaque base64 data, the response contains:
[Screenshot captured - download: https://your-worker.workers.dev/download/abc123...]

# Download the image:
curl -H "Authorization: Bearer $TOKEN" \
  https://your-worker.workers.dev/download/abc123... -o screenshot.jpg

The download endpoint:

  • Requires the same Bearer token (if auth is enabled)
  • Serves the file once then deletes it (one-time download)
  • Auto-detects JPEG vs PNG from the original MIME type
  • Entries expire after 5 minutes if not downloaded

Feature 3: SSE Keepalive

Problem: The legacy SSE transport (/sse) creates a long-lived HTTP connection. Cloudflare's proxy layer drops idle connections after ~60-90 seconds. When the MCP client sends a tool call after idle time, the POST succeeds (202 Accepted) but the SSE read stream is dead, so the response never arrives — resulting in a timeout.

Solution: Injects SSE comment frames (: keepalive\n\n) every 15 seconds into the SSE stream. SSE comments are ignored by all compliant clients per the spec.

  • Uses setInterval for the keepalive timer
  • Uses ctx.waitUntil(done) to keep the Worker execution context alive for the SSE session lifetime
  • Timer is cleaned up when the stream closes or the client disconnects
  • Only wraps the GET /sse stream (not POST /sse/message)

Changes

File Change
cloudflare/example/src/index.ts Auth middleware, SSE interceptor with keepalive, /download/<hash> endpoint
cloudflare/example/worker-configuration.d.ts Add optional MCP_AUTH_TOKEN to Env type

Testing

Deployed and tested on two Cloudflare accounts. Verified:

  • Auth works: no token → 401, wrong token → 401, correct token → 200
  • SSE stream interception correctly extracts images and injects download URLs
  • /download/<hash> serves valid JPEG/PNG files (verified dimensions/format)
  • One-time download: second request to same hash returns 404
  • Expiry: entries older than 5 minutes are cleaned up
  • Keepalive: SSE stream survives 90+ seconds of idle time without dropping
  • Tool calls succeed after extended idle periods (verified with browser_navigate + browser_snapshot)

…oyment

- Bump compatibility_date to 2025-09-23 for fs.mkdir support in Workers
- Pin @cloudflare/playwright to 1.1.2 with patch replacing fs.mkdtemp
  (not implemented in Workers nodejs_compat) with fs.mkdir + random suffix
- Upgrade agents to ^0.4.0 to fix MCP Server 'Already connected to a
  transport' error on Durable Object hibernation wake-up (CVE-2026-25536)
- Fix vite.config.ts fs alias: use node:fs instead of @cloudflare/playwright/fs
@ebrainte ebrainte force-pushed the feat/bearer-auth-screenshot-download branch from 3effeeb to de1d929 Compare March 31, 2026 16:38
zod-to-json-schema 3.25.x produces empty schemas (strips type, properties,
etc.), causing MCP clients to reject all tool definitions with schema
validation errors. Pin to tilde range ~3.24.6 so only patch updates within
3.24.x are allowed.
- Optional Bearer token authentication via MCP_AUTH_TOKEN secret
- SSE stream interception: extracts base64 image data from
  browser_take_screenshot responses, stores in memory with 5-min TTL,
  replaces with /download/<hash> URL for local retrieval
- /download/<hash> endpoint: auth-protected, one-time use, returns
  the original image data
- SSE keepalive: injects SSE comment (: keepalive) every 15s to prevent
  Cloudflare proxy from dropping idle connections (~60-90s timeout).
  Uses ctx.waitUntil() to keep Worker execution context alive.
@ebrainte ebrainte force-pushed the feat/bearer-auth-screenshot-download branch from de1d929 to df722c9 Compare March 31, 2026 21:05
@ebrainte ebrainte changed the title feat: optional Bearer token auth + screenshot download for CLI/TUI clients feat: optional Bearer token auth, screenshot download, and SSE keepalive for CLI/TUI clients Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants