Whisper · Docs
Serverless & edge

Cloudflare Workers

A Worker's fetch() leaves on Cloudflare's anycast IP, not your agent's — so the moment your agent lands on the edge, it loses the identity that made it an agent.

whisper-edge gives it back: verify any Whisper identity with zero setup, and egress through a real routable /128 with one secret — plus a drop-in MCP tool set for the Agents SDK.

The problem: workerd has no raw egress identity

workerd (the Workers runtime) gives you fetch() and, behind the cloudflare:sockets flag, a raw TCP connect() — but no bind(), no source-address control, and no UDP, so WireGuard (Whisper's Tier‑1) is off the table entirely. Worse, cloudflare:sockets' startTls() pins the TLS SNI to the socket you already opened, so you cannot open a TCP connection to a SOCKS5/CONNECT proxy and then negotiate a second, nested TLS session to the real HTTPS origin inside it — the standard way a proxy client reaches an https:// target (RFC 9110 §9.3.6, the CONNECT method) simply doesn't compose on workerd. That rules out Whisper's Tier‑1.5 SOCKS5/CONNECT egress for any https:// target from a Worker.

With stock tools — proving the pain, no Whisper involved:

// A plain Worker, no Whisper. Every outbound request looks like Cloudflare, not your agent.
export default {
  async fetch() {
    const r = await fetch("https://api.ipify.org?format=json");
    return Response.json(await r.json());
    // → {"ip":"104.21.63.10"}  — a Cloudflare anycast address, unrelated to any agent
  },
};

There is no dig, curl, or ip binary inside workerd to fix this from within — the sandbox has no shell and no raw sockets worth using. The fix has to be an HTTPS-native protocol, which is exactly what Whisper's edge SDK and forward gateway are.

Keyless tier: verify + RDAP, one fetch()

Before you route anything through an agent, you can check whether an address or hostname is a real Whisper agent identity — no API key, because this is public data: reverse DNS, a DNSSEC-validated forward-confirm, a DANE TLSA pin, and (per whisper verify --trustless) a transparency-log inclusion check.

Inside the Worker, whisper-edge runs the whole chain — reverse DNS, a DNSSEC-validated forward-confirm, the DANE TLSA pin, and a transparency-log check — in one fetch(), no resolver library and no crypto:

import { verify, verifyDetails, rdap } from "whisper-edge";

export default {
  async fetch(request: Request): Promise<Response> {
    const addr = new URL(request.url).searchParams.get("addr");
    if (!addr) return new Response("usage: ?addr=<agent /128 or FQDN>\n", { status: 400 });
    const ok = await verify(addr);                       // boolean
    return Response.json({ address: addr, is_whisper_agent: ok, details: ok ? await verifyDetails(addr) : null });
  },
};

verifyDetails() returns the individual verdicts (dane_ok, dnssec_ok, ptr_ok, …) so you can log why a check failed instead of a bare false — see sdk-edge for the full type. The raw records behind those verdicts, and how to re-derive them from a terminal, are in Verify an agent.

Egress: the forward gateway, not raw sockets

Since cloudflare:sockets can't nest TLS for a CONNECT tunnel, whisper-edge's Cloudflare-egress path uses transport: "forward": one HTTPS request, to forward.whisper.online/forward, carrying your API key and the target URL inside the (already-terminated) TLS session — no tunneling required. The gateway dials the real origin itself, sourced from your agent's own /128 out of 2a04:2a01::/32 (announced by AS219419) — the same address binding every Whisper egress tier uses — and streams the response back over the same HTTPS connection it received the request on. One hop, standard HTTP semantics (RFC 9110), works identically on every runtime that only has fetch() — which is every edge runtime, not just Workers. Full protocol detail: forward-gateway.

With stock tools — the gateway is just HTTPS, so you can drive it with curl from anywhere to sanity-check a target before wiring it into a Worker:

curl -s -X POST https://forward.whisper.online/forward \
  -H "Authorization: Basic $(printf 'w:whisper_live_your_key' | base64)" \
  -H "X-Whisper-Target: https://api.ipify.org?format=json"
# → {"ip":"2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"}
#   the reply also carries  X-Whisper-Egress-Source: 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4

With Whisper, the same call from inside a Worker:

import { agentEgress } from "whisper-edge";

export default {
  async fetch(request: Request, env: { WHISPER_API_KEY: string }): Promise<Response> {
    const egress = await agentEgress(env.WHISPER_API_KEY, "acef2002a323d40d4", { transport: "forward" });
    try {
      const res = await egress.fetch("https://api.ipify.org?format=json");
      return Response.json(await res.json());
      // → {"ip":"2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"} — the agent's own /128, not Cloudflare's edge
    } finally {
      egress.close();
    }
  },
};

The full two-tier reference Worker (?addr= for keyless verify, ?op= for the control plane) is in whisper-examples/cloudflare/worker.ts:

npm i
wrangler secret put WHISPER_API_KEY   # optional — keyless endpoints work without it
wrangler deploy

Agents SDK: registerWhisperTools()

If your Worker is an agent — a Cloudflare Agents SDK McpAgent running as a Durable Object — whisper-examples/cloudflare/agents-sdk adds Whisper as four MCP tools with one function call, so the LLM driving the agent gets identity + egress as tool calls, never as code it has to write itself:

import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerWhisperTools } from "./whisper-mcp.js";

export class WhisperMcp extends McpAgent {
  server = new McpServer({ name: "whisper", version: "1.0.0" });
  async init() {
    registerWhisperTools(this.server, this.env);   // + your own tools
  }
}
tool tier what it does
whisper_verify keyless is this IPv6 /128 or FQDN a real Whisper agent (PTR + DANE + DNSSEC + transparency)?
whisper_rdap keyless the RDAP registration record for an agent /128
whisper_agents keyed control plane: register / identity / list / policy / logs / connect / revoke
whisper_egress_fetch keyed fetch a URL sourced from the agent's routable /128, via transport:"forward"

The keyed tools return a clear "set WHISPER_API_KEY" message rather than a stack trace when no key is configured — the keyless half is always fully functional so the tool set is useful even before you provision a key. wrangler secret put WHISPER_API_KEY unlocks the other two; the key lives in the Worker's secret store and the model never sees it. Point any MCP client (or another Agent via this.mcp.connect(url)) at https://<your-worker>/mcp (streamable HTTP) or /sse.

Both example Workers — the plain two-tier fetch handler and the Agents-SDK MCP server — live under github.com/whisper-sec/whisper-examples/tree/main/cloudflare, proven end-to-end against a provisioned demo agent.

Next

sdk-edge — the full whisper-edge API (verify, resolve, rdap, control, agentEgress) · forward-gateway — the HTTPS egress protocol behind transport:"forward" · mcp — the standalone Whisper MCP server for non-Workers hosts.