npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mconroy-cf/webmcp-auth-workers

v0.1.0-experimental.0

Published

EXPERIMENTAL — Web Bot Auth verification, per-agent rules, rate limiting, and BLOCK_ALL kill-switch for WebMCP tool surfaces on Cloudflare Workers. Published to gather partner feedback; expect breaking changes; not for production traffic.

Readme

⚠️ EXPERIMENTAL — partner-feedback release.

This package is published to a personal scope (@mconroy-cf/) so external partners can install and try it without waiting on the formal Cloudflare release process. Expect breaking changes; not for production traffic. Send feedback to [email protected] — we'd rather know what's missing than have you wait for a polished v1. Once an official Cloudflare-scoped release ships, this package will deprecate and point at it.

@mconroy-cf/webmcp-auth-workers

Web Bot Auth verification, per-agent rules, rate limiting, content-digest enforcement, KYA hooks, and a BLOCK_ALL kill-switch for WebMCP tool surfaces fronted by Cloudflare Workers.

import {
  Authenticator,
  httpDirectory,
} from "@mconroy-cf/webmcp-auth-workers";

const auth = new Authenticator({
  // Tool-level ACLs are configured below — the Authenticator will
  // automatically force `contentDigest: "required"` on the verifier.
  defaultPolicy: { allowTools: ["search", "get_product"] },
  agents: [
    { agentId: "openai-chatgpt", allowTools: "*" },
    { agentId: "noisy-startup",  denyTools: ["checkout"] },
  ],
  directory: httpDirectory({
    url: "https://allow-list.merchant.example.com/agents.json",
  }),
  rateLimiter: env.MCP_RATE,
  blockAll:    env.BLOCK_AGENTS === "true",
  verify: {
    nonceStore: env.NONCE_STORE,
    // No `jwksUrls` configured → trust the directory each request's
    // Signature-Agent header advertises (in-band discovery).
    signatureAgentDiscovery: "trust",
  },
});

export default {
  async fetch(request: Request, env: Env) {
    const decision = await auth.authorize(request);
    if (!decision.ok) return decision.toResponse();

    return fetch(env.MCP_ORIGIN_URL, {
      method: request.method,
      headers: { ...Object.fromEntries(request.headers), ...Object.fromEntries(decision.forwardHeaders()) },
      body: decision.rawBody || null,
    });
  },
};

What this does

Implements the merchant-side enforcement points for agent traffic on WebMCP tool surfaces:

  • ✅ Verifies the Web Bot Auth HTTP message signature (built on RFC 9421) on every request.
  • ✅ Parses multi-label Signature / Signature-Input Structured Field Dictionaries.
  • ✅ Resolves keyid as the RFC 7638 SHA-256 JWK thumbprint, with a kid-string fallback for legacy directories.
  • ✅ Honours the Signature-Agent header for in-band directory discovery, per draft-meunier-http-message-signatures-directory.
  • ✅ Verifies Ed25519, RSA-PSS-SHA512 (spec default), and RSA-PSS-SHA256 (compat).
  • ✅ Enforces freshness (expires - created ≤ 8 min, created ≤ now ≤ expires).
  • ✅ Rejects replayed nonces via KV-backed cache.
  • ✅ Optional RFC 9530 Content-Digest verification — automatically required when tool-level ACLs are configured, because tool ACLs are unsound without body integrity.
  • ✅ Resolves the verified keyid to a stable agent identity via a pluggable directory (staticDirectory, httpDirectory, or your own).
  • ✅ Per-agent allow/deny tool ACLs.
  • ✅ Per-agent rate limiting via the Workers Rate Limiting API (keyed on the verified identity, not IP).
  • ✅ Optional KYA token verification — bring your own verifier.
  • BLOCK_ALL kill-switch.

Install

npm install @mconroy-cf/webmcp-auth-workers

Three entry points, in increasing order of opinion

1. verifyWebBotAuth(request, options)

Pure signature verifier. Returns { ok: true, rawBody, info } or a failure with toResponse(). Use this if you want to write your own policy layer.

import { verifyWebBotAuth } from "@mconroy-cf/webmcp-auth-workers";

const result = await verifyWebBotAuth(request, {
  nonceStore: env.NONCE_STORE,
  signatureAgentDiscovery: "trust",
});
if (!result.ok) return result.toResponse();
console.log("signed by", result.info.keyid, "label", result.info.label);

2. Authenticator

Full policy gate: signature → directory → ACL → rate limit → KYA → forward. Sound-by-default: with tool ACLs configured the Authenticator upgrades the verifier to contentDigest: "required" and refuses to start if the merchant has explicitly set it to "optional".

3. httpDirectory({ url }) / staticDirectory(map) / composeDirectories(...)

Directory implementations. There is no library-wide default URL — the spec's primary discovery mechanism is the per-request Signature-Agent header, which the verifier honours when signatureAgentDiscovery: "trust" (default for verifyWebBotAuth, inherited by Authenticator).

Options matrix

Authenticator constructor

| Option | Type | Default | Notes | |---|---|---|---| | verify | VerifyWebBotAuthOptions | {} | Forwarded to the verifier. | | directory | AgentDirectory | none | Required when agents is non-empty. Without one, the keyid is surfaced as the agent id. | | blockAll | boolean | false | Kill-switch. When true, every request returns 403 blocked_by_policy. | | defaultPolicy | ToolPolicy | undefined | Applied to any agent without a per-agent override. Without any policy at all, the Authenticator admits any verified agent. | | agents | AgentPolicy[] | [] | Per-agent overrides. Match by agentId (resolved from the directory). | | extractToolName | (rawBody) => string | JSON-RPC tools/call.params.name | Override for non-JSON-RPC surfaces. JSON-RPC batches return undefined. | | rateLimiter | RateLimiter | none | Workers Rate Limiting API binding. Limits keyed on agentId. | | kya | KyaPolicy | { mode: "off" } | Optional KYA token verification. |

verifyWebBotAuth options

| Option | Type | Default | Notes | |---|---|---|---| | jwksUrls | string[] | [] | Statically-trusted directory endpoints (highest trust). | | signatureAgentDiscovery | "trust" \| "ignore" | "trust" | Honour the Signature-Agent header for in-band directory discovery. | | selfJwks | JwkSet | undefined | Inline keys for same-origin lookups (Workers can't fetch their own origin). | | nonceStore | NonceStore (KV-shaped) | warns if omitted | Replay protection. Required in production. | | maxWindowSeconds | number | 480 (8 min) | Freshness cap. | | contentDigest | "optional" \| "required" | "optional" | When "required", request MUST carry content-digest AND it MUST be in covered fields. | | now | () => number | Date.now()/1000 | Override only in tests. | | testPublicKey | { keyid, base64 } | undefined | Dev-only Ed25519 fallback. Remove from production. |

Failure reasons

Stable, machine-readable reason codes returned on result.reason. Safe to branch on.

| Reason | Status | Layer | |---|---|---| | missing_signature_headers | 401 | signature | | signature_input_malformed | 400 | signature | | missing_required_param | 400 | signature | | wrong_tag | 401 | signature | | unsupported_alg | 400 | signature | | timestamp_not_integer | 400 | signature | | window_too_large | 401 | signature | | created_in_future | 401 | signature | | signature_expired | 401 | signature | | nonce_replay | 401 | signature | | unknown_keyid | 401 | signature | | unsupported_covered_field | 400 | signature | | missing_required_covered_field | 400 | signature | | signature_malformed | 400 | signature | | signature_invalid | 401 | signature | | content_digest_required | 400/401 | signature | | content_digest_invalid | 401 | signature | | content_digest_mismatch | 401 | signature | | blocked_by_policy | 403 | policy (BLOCK_ALL) | | agent_not_in_directory | 403 | policy | | agent_denied | 403 | policy | | tool_denied | 403 | policy (allow/deny ACL) | | rate_limited | 429 | policy | | kya_required | 401 | KYA | | kya_invalid | 401 | KYA (or 500 if mis-configured) |

Threat model

The library answers four threats explicit in the partner conversations that motivated this work:

  1. "A real customer using Gemini's side panel" vs. "an automated agent calling our tools remotely from a script." Web Bot Auth + directory resolution means only requests carrying a signature minted by an agent operator a directory we trust knows about will pass. Unsigned requests fail with missing_signature_headers.
  2. Replay across hosts or paths. @authority (or @target-uri / @path) is required under signature. A captured signature can't be replayed against a different merchant or a privileged path.
  3. Replay against the same host. Each accepted nonce is recorded in KV with an 8-minute TTL. Subsequent requests with the same nonce inside that window fail with nonce_replay.
  4. Body tampering on tool calls. Web Bot Auth doesn't sign the body by default. The Authenticator forces contentDigest: "required" whenever a tool-level ACL is in play, so an attacker can't swap the JSON-RPC body to call a different tool than the one the agent signed.
  5. "We changed our mind, we want to turn this off right now." The blockAll flag is a single boolean a merchant can flip via env var to return 403 to every signed agent caller without redeploying any tool logic.

Out of scope (for this version)

  • Concurrent-millisecond replays against a single nonce — KV is not transactional. Substitute a Durable Object NonceStore for that guarantee. The verifier is unchanged.
  • Cross-isolate cache coherence for the JWKS / directory caches. Both are 5-minute per-isolate; a rotated key takes that long to propagate everywhere.
  • Directory authority signatures (spec §5.2) — directory responses themselves can be signed. Today we trust the transport (HTTPS) for the directory fetch.
  • data: URI Signature-Agent values (inline directory bytes). Loading JWK material straight out of the request deserves its own audit; deferring to a follow-up.
  • Body integrity without content-digest. If the agent operator doesn't include content-digest in the covered fields, the body isn't bound to the signature. Verifier surfaces this as info.contentDigestVerified: false. Decide whether to admit such requests by setting contentDigest: "required" on the verifier (or let the Authenticator do it for you when ACLs are configured).

License

MIT.