Releases: basgr/cf-webmcp
v0.4.1 - Duplicate WebMCP tool-name renderer crash
Registering the same WebMCP tool name twice on a page kills the Chrome renderer
(bad_message 345, RFHI_WEBMCP_REGISTER_DUPLICATE_TOOL_NAME) - a Mojo IPC
validation kill that try/catch cannot trap. cf-webmcp emits two registration
surfaces per page (the bootstrap's registerTool calls and any <form toolname>
it stamps), so a name shared across [[tools]] and [[forms]] would crash.
- Build guard: the build now fails on a duplicate name within
[[tools]],
within[[forms]], or shared across the two. - Runtime de-dupe: the injected bootstrap skips registering any tool whose
name is already declared on the page as a<form toolname>(covers names
hand-stamped in origin HTML, which the build cannot see). - Host detection: probes
document.modelContext(current, Chrome 150+)
before the deprecatednavigator.modelContext(146-149).
v0.4.0 - extensionless manifest + runtime-compat bootstrap
Highlights
Extensionless manifest path is the default
The canonical WebMCP manifest now lives at /.well-known/webmcp (extensionless), matching the convention of IANA-registered well-known suffixes (api-catalog, openid-configuration). The legacy /.well-known/webmcp.json is kept as a 301 redirect alias so existing links and cached rel="webmcp" references keep working.
- No action required: rebuilding moves the canonical manifest to
/.well-known/webmcpand advertises it in theLinkheader,<link rel="webmcp">,llms.txt, andagents.md. - To keep
.jsoncanonical instead, set[manifest].path = "/.well-known/webmcp.json"and[manifest].aliases = ["/.well-known/webmcp"](or[]to drop the redirect). - The 301 emits
X-Robots-Tag: noindexandX-Content-Type-Options: nosniff(CI-enforced for every/.well-known/*route).
Bootstrap runtime compatibility
The injected in-page bootstrap was corrected against the current WebMCP runtime:
- Host detection registers tools on whichever object exposes
registerTool:navigator.modelContext(current Chrome Canary) ordocument.modelContext(Apr 2026 WebMCP draft). It no-ops when neither is present. - MCP tool-result shape: each tool's
executenow returns{ content: [{ type: "text", text }], isError }instead of the raw cf-webmcp{ ok, data }envelope. The full executor envelope is carried as thetextpayload (the agent keeps structured success/error), andisErroris set unless the envelope is an explicitok: true. ThePOST /_webmcp/exec/<tool>endpoint is unchanged and still returns the envelope; only the in-page tool adapts it.
Other
- Acknowledgements: added Chudi Nnorukam's freeCodeCamp "A Developer's Guide to WebMCP".
- Docs (
docs/upgrade.md) document the migration and the 301 alias.
Quality gate
- Security review: no critical/high findings.
- 258 tests pass (233 worker + 25 node); typecheck clean.
Full changelog: v0.3.9...v0.4.0
v0.3.9 - llms.txt token-budget hints + rel="alternate" markdown link
Two enrichments of existing discovery surfaces, both gated on the
llms_txt feature, prompted by a cross-review against Addy Osmani's
agentic-seo auditor.
llms.txt token-budget hints
The synthesized /llms.txt WebMCP block now annotates its pairing-page and
tool-catalogue links with (~N tokens) context-budget hints, e.g.:
- Pairing page: https://example.com/mcp (~2100 tokens)
- Tool catalogue: https://example.com/.well-known/webmcp.json (~580 tokens)
Counts are computed at build time over the exact bodies the Worker
serves (manifest JSON, landing HTML) with a dependency-free ~4-chars/token
heuristic, emitted as a generated constant. agents.md and api-catalog
links stay unannotated (synthesized at request time, thin pointer docs).
This clears the one point agentic-seo's llms.txt check docked the demo
(9/10 → 10/10).
rel="alternate" markdown link
HTML pages now carry <link rel="alternate" type="text/markdown" href=".../llms.txt"> alongside the existing rel="describedby" tag,
matching the convention agentic-seo and specification.website use to
advertise a markdown representation. Reuses the same attribute escaping;
HTML-only (not added to the Link header).
Notes
- A cross-review of agentic-seo found it is primarily a local-repo CI
auditor: in URL-only mode only 3 of its 10 checks run, so its headline
score is not a meaningful external grade. Lighthouse 13.3.0's
agentic-browsing category remains the recommended external validator. - Security review: clean. All new values are build-time config-derived
integers or an already-hardened link-tag clone; no new untrusted-input path.
Tests
- 232 worker + 25 node = 257 green. Typecheck clean.
v0.3.8 - Lighthouse validation, toolchain refresh, specification.website cross-review
Validation
- README now documents that cf-webmcp's discovery surfaces pass Google's
Lighthouse 13.3.0 agentic-browsing audit category end-to-end on
Chrome Canary 150.x. An independent third-party auditor confirms both
injected and hand-stamped tools without TOML/source access.
Security
- Bumped the dev test/build toolchain to clear all 11 Dependabot advisories
(4 high, 7 moderate). Affected only the toolchain - the deployed Worker
bundle was never vulnerable.vitest2 -> 4.1.7@cloudflare/vitest-pool-workers0.5 -> 0.16.10 (migrated
vitest.config.tsto the newcloudflareTest()plugin pattern per
Cloudflare's official v3->v4 codemod)wrangler4.90 -> 4.95 (clears thewrangler pages deployOS-command-
injection advisory; cf-webmcp useswrangler deploy, not the affected
path)
npm audit: 0 vulnerabilities.
Discovery polish
- HTTP
Linkheader entries now carry RFC 8288title="..."human-readable
metadata on every advertised rel (webmcp,api-catalog,agent-skills,
describedby). Thedescribedbytitle matches the wording used by
specification.websitefor the same
target, for free interop with peer publishers. No protocol impact.
Scope clarification
docs/scope.mdadds A2A agent cards as an explicit out-of-scope entry,
plus a new "Adjacent agent-readiness surfaces" section pointing at
specification.website(Joost de Valk)
as the worked-example reference for the surfaces cf-webmcp deliberately
does not ship (markdown source endpoints, robots.txtContent-Signal,
web-bot-auth, schemamap, DNS-AID, etc.).- README acknowledges
specification.websiteas an independent reference
converging on the same Agent Skills Discovery, RFC 9727 api-catalog,
rel="describedby"llms.txt, andrel="agent-skills"conventions.
v0.3.7 - rel="describedby" pointing at /llms.txt (RFC 8288)
Small discoverability patch. cf-webmcp's existing /llms.txt is now advertised via the IANA-registered rel="describedby" relation (RFC 8288), in addition to the existing webmcp / api-catalog / agent-skills rels.
Why
The previous three rels we emit on every response are:
rel="webmcp"— private, not IANA-registeredrel="api-catalog"— IANA-registered via RFC 9727rel="agent-skills"— private (Cloudflare RFC v0.2.0 does not register a rel)
Generic agent-aware scanners that anchor only on registered rel-types therefore find one entry. Adding rel="describedby" pointing at our existing /llms.txt gives those scanners a fourth, RFC-registered way to discover a description of the site — without adding any new well-known endpoints.
Surfaced by Suganthan Mohanadasan's May 2026 post which highlights describedby as the RFC-registered relation for site descriptions.
What's emitted
HTTP Link header (every response):
Link: <https://example.com/.well-known/webmcp.json>; rel="webmcp",
<https://example.com/.well-known/api-catalog>; rel="api-catalog",
<https://example.com/.well-known/agent-skills/site/SKILL.md>; rel="agent-skills",
<https://example.com/llms.txt>; rel="describedby"; type="text/markdown"
HTML <head> (injected on every HTML response):
<link rel="describedby" type="text/markdown" href="https://example.com/llms.txt">The type="text/markdown" hint is per RFC 8288 so scanners can pre-decide whether to fetch.
Gate
Both surfaces emit only when:
[features].llms_txt = true, AND[llms_txt].mode != "passthrough"
Same pattern as our other discovery surfaces: don't advertise a route we don't serve.
Tests
- 3 new tests in
src/link-header.test.ts(gate logic) - 2 new tests in
src/injection/html-rewriter.test.ts(link-tag injection) - 226 worker tests + 24 build-config tests = 250 total, all green
- typecheck clean
Live verification
$ curl -sI https://webmcp.basgr.com/ | grep -oE 'rel="[^"]+"' | sort -u
rel="agent-skills"
rel="api-catalog"
rel="describedby"
rel="webmcp"Compatibility
- No config changes required. No
schema_versionbump. - Existing publishers upgrading get the new entry automatically on next deploy.
- Disable via
[features].llms_txt = falseor[llms_txt].mode = "passthrough"(already-supported toggles).
Upgrade
cd cf-webmcp
git fetch
git checkout v0.3.7
npm install
npm run build
wrangler deployv0.3.6 - SRI on injected bootstrap; docs/security.md
Two related changes: Subresource Integrity (SRI) on the injected bootstrap <script>, and a new docs/security.md documenting what cf-webmcp does and does not defend against.
SRI on the bootstrap <script>
The HTMLRewriter injection that fires on every HTML response now emits:
<script src="https://example.com/_webmcp/bootstrap.<hash>.js" defer
integrity="sha384-<base64>" crossorigin="anonymous"></script>The sha384 digest is computed at build time over the exact UTF-8 bytes of the bootstrap source and embedded as BOOTSTRAP_SRI in the generated config module. Browsers refuse to execute the bootstrap if its body has been substituted between server and client.
Covers:
- CDN cache poisoning serving a modified bootstrap at our URL
- MITM on non-HTTPS legs (HTTPS already handles most cases; SRI is belt-and-suspenders)
- Internal bug where the worker accidentally serves a stale or wrong bootstrap
Does not cover:
- Compromised publisher who controls the TOML and re-deploys with a malicious bootstrap (they control the integrity hash too)
- Cross-origin prompt-injection via tool descriptions — see
docs/security.md - Service worker hijacks that strip the integrity attribute before the browser sees it
docs/security.md
New page making explicit what cf-webmcp does and does not enforce, anchored on the load-bearing claim:
Tool descriptions are not a security boundary.
Earlence Fernandes (UCSD) published a finding in April 2026 showing how a poisoned WebMCP tool description on attacker site A can prompt-inject an agent into invoking tools on victim site B via the agent's cross-tab model context. Browser SOP is doing its job; the agent runtime carries context across origins outside the browser.
cf-webmcp serves tool descriptions verbatim per the publisher's TOML and structurally cannot sanitise them — descriptions ARE the content agents consume. The doc spells out:
- The attack class and why it falls outside cf-webmcp's layer
- Publisher operational implications (never paste UGC into descriptions; treat hint/description bodies as model-visible text; if your TOML is CMS-generated, gate fields behind editorial review)
- Agent-runtime trust model (treat tool endpoints as public read-only HTTP)
- The new SRI behaviour as a separate, network-layer defence
- Vulnerability reporting (email vs public issue)
Cross-links from README.md, docs/scope.md, docs/form-injection.md, docs/agent-skills.md so publishers see the call-out at the point they're authoring the risky content. docs/deployment.md gets a new "Subresource Integrity (SRI) on the injected bootstrap" section with CSP interaction notes (script-src 'self' works; 'strict-dynamic' needs a nonce or hash-pin; explicit sha384-X hash-source allowlists work alongside SRI).
Config
[features]
subresource_integrity = true # defaultTurn off only if a downstream tooling layer cannot accept the integrity attribute. Flipping it off is a deliberate security regression, not a casual cleanup.
Tests
- 4 new HTMLRewriter cases (attribute presence/absence, escape safety against malformed integrity values, attribute order on the tag)
- 3 new build-config cases (sha384-base64 shape, byte-exact match against sha384(bootstrap.js), null when feature off)
- All existing fixtures extended with
features.subresource_integrity - 245 tests green (221 worker + 24 build-config)
Live verification
$ curl -s https://webmcp.basgr.com/ | grep -oE '<script[^>]*bootstrap[^>]*>'
<script src="https://webmcp.basgr.com/_webmcp/bootstrap.b44e5237.js" defer
integrity="sha384-vLLOpLJLQjO6YdeIDZB70nFto3GYFSr0aqLdEeDUDNVkCinMQDVew8TQEuTMAPxY"
crossorigin="anonymous">
$ curl -s https://webmcp.basgr.com/_webmcp/bootstrap.b44e5237.js | \
openssl dgst -sha384 -binary | base64
vLLOpLJLQjO6YdeIDZB70nFto3GYFSr0aqLdEeDUDNVkCinMQDVew8TQEuTMAPxY
# MATCHCompatibility
- No
schema_versionbump. New field is additive optional. - Existing publishers upgrading get SRI automatically on next deploy.
- The injected script tag is byte-different (it now includes
integrityandcrossorigin). If you depend on a specific HTML diff or CSP hash for the injected HTML, you may need to refresh that. - For publishers with strict CSP
script-srcsource lists: SRI integrates cleanly withsha384-Xhash-source entries (the same string works for both CSP and SRI). Seedocs/deployment.md.
Upgrade
cd cf-webmcp
git fetch
git checkout v0.3.6
npm install
npm run build
wrangler deployv0.3.5 - /.well-known/agent-skills/index.json (Cloudflare RFC v0.2.0)
cf-webmcp now publishes the Cloudflare Agent Skills Discovery RFC v0.2.0 index file at /.well-known/agent-skills/index.json. Agent runtimes that scan well-known paths for a skill index now find this site's SKILL.md (already published since v0.3.0) listed with a SHA-256 digest for integrity verification.
What's emitted
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"name": "<slugified-site-name>",
"type": "skill-md",
"description": "<site description>",
"url": "/.well-known/agent-skills/site/SKILL.md",
"digest": "sha256:<64hex>"
}
]
}The digest is computed at build time over the synthesised SKILL.md body (frontmatter + body) and embedded as a constant in the generated config module. No runtime hashing, no cache invalidation surprises.
Config
[features]
agent_skills_index = true # default
[agent_skills_index]
path = "/.well-known/agent-skills/index.json" # default per RFC
mode = "synthesize" # synthesize | passthroughmerge and replace are intentionally not supported:
mergewould require fetching origin on every request to compute a stable digestreplaceis functionally identical tosynthesizefor our one-skill case
Behaviour when SKILL.md is unstable
If [agent_skills].mode = "merge", the SKILL.md content mixes in origin content and the build-time digest cannot represent the served body. The index route returns 404 with an explanatory body rather than serve a stale digest. Switch [agent_skills].mode to synthesize or replace to enable the index, or set [features].agent_skills_index = false to silence it.
$schema version-lock
cf-webmcp emits the $schema URI for v0.2.0 of the RFC. If the RFC bumps incompatibly (as v0.1.0 → v0.2.0 already did), we revise in a future release. Per the spec, agents MUST check $schema and skip unknown versions cleanly.
Discovery surfaces
Three pointers to the same skill now coexist:
- The SKILL.md itself at
/.well-known/agent-skills/site/SKILL.md - The manifest's
links.agent_skills_indexfield - The new
/.well-known/agent-skills/index.jsonindex document
No HTTP Link rel is defined by the RFC; discovery of the index is well-known-path only.
Build-time hardening
- Path-collision check. The build refuses configs where two Worker-owned surfaces (manifest, landing, llms.txt, robots.txt, agents.md + aliases, api-catalog, agent-skills + aliases, agent-skills-index) claim the same path. Router uses first-match semantics, so a collision would silently break the second-listed surface.
- End-to-end digest equivalence test. Locks the contract that the build-time digest matches
sha256(agentSkillsResponse body)byte-for-byte. Defends against future refactors of either producer drifting.
Tests
- 9 new tests in
src/routes/agent-skills-index.test.tscovering modes, 404 fallback, digest semantics,$schemacorrectness, cache headers, byte-stable output, slug-name derivation, end-to-end digest match - 2 new tests in
src/router.test.ts(routes when on, falls through when off / passthrough) - 7 new tests in
scripts/build-config.test.ts(manifest links presence/absence,AGENT_SKILLS_DIGESTemission per mode, path-collision rejection) src/x-robots.test.tsextends exhaustiveRecord<RouteMatch["kind"], ...>coverage to the new kind- 239 total tests, all green (218 worker + 21 build-config)
Live verification
$ curl -s https://webmcp.basgr.com/.well-known/agent-skills/index.json | jq
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"name": "cf-webmcp",
"type": "skill-md",
"description": "WebMCP at the edge...",
"url": "/.well-known/agent-skills/site/SKILL.md",
"digest": "sha256:318719d76991ecc284f3d4ad15479e3ef655da90c482d31581239c0eaccfb6de"
}
]
}
$ curl -s https://webmcp.basgr.com/.well-known/agent-skills/site/SKILL.md | sha256sum
318719d76991ecc284f3d4ad15479e3ef655da90c482d31581239c0eaccfb6de -
# MATCHCompatibility
[features].agent_skills_indexdefaults totrue. Existing publishers upgrading get the index automatically on next deploy.schema_versionstays at1; no breaking schema changes.- Publishers using
[agent_skills].mode = "merge"will see the index route return 404 with an explanatory body. Either flip the underlying mode tosynthesize/replaceor disable the feature. - Path-collision check is new: a publisher whose TOML accidentally claims the same path for two surfaces will see the build fail fast with a clear error.
Upgrade
cd cf-webmcp
git fetch
git checkout v0.3.5
npm install
npm run build
wrangler deployTemplates updated with the new block. If you carry a custom TOML, copy [agent_skills_index] from templates/default.toml or rely on the Zod defaults.
v0.3.4 - WebMCP ToolAnnotations
A spec-completeness pass. The bootstrap script now emits the two ModelContextTool members cf-webmcp was previously not setting: annotations and title. Brings the registerTool() call into shape with the current W3C draft at webmachinelearning/webmcp.
What the spec says
dictionary ModelContextTool {
required DOMString name;
USVString title; // <-- optional, was missing
required DOMString description;
object inputSchema;
required ToolExecuteCallback execute;
ToolAnnotations annotations; // <-- optional, was missing
};
dictionary ToolAnnotations {
boolean readOnlyHint = false;
boolean untrustedContentHint = false;
};cf-webmcp's bootstrap now emits both. WebMCP-aware browsers (Chrome 148+ with the flag) read the annotations during registration; runtimes that ignore them are unaffected.
Annotations are derived from the executor type
[[tools]].executor.type already tells cf-webmcp what each tool does. The bootstrap sets sensible defaults:
| Executor type | readOnlyHint |
untrustedContentHint |
|---|---|---|
sitemap_filter |
true |
false |
rss_feed |
true |
true |
dom_extract |
true |
true |
http_json |
true |
true |
http_get |
true |
true |
All five built-in executors are read-only by construction (none mutate origin state), hence readOnlyHint: true across the board. Four of five surface origin-fetched content the agent should treat as untrusted; sitemap_filter returns structurally-constrained URL + lastmod strings only, so its untrustedContentHint defaults to false.
Per-tool overrides
When the executor-type defaults are wrong for your case, set them explicitly in TOML:
[[tools]]
name = "site_status"
description = "Current health of this site."
[tools.executor]
type = "http_get"
url_template = "https://example.com/_status.json"
[tools.annotations]
read_only_hint = true
untrusted_content_hint = false # we author this content ourselvesSnake-case in TOML (read_only_hint, untrusted_content_hint) maps to the spec's camelCase (readOnlyHint, untrustedContentHint) in the generated JS. Publishers write the natural TOML form; the bootstrap matches the WebIDL.
Optional title
A human-friendly tool label distinct from the machine-friendly name. Surfaces in tool pickers and similar UI. No default - emitted only when set.
[[tools]]
name = "search_pages"
title = "Page Search"
description = "Search the cf-webmcp site by keyword."docs/scope.md update
The "MCP server cards" out-of-scope bullet now spells out the exact well-known path third-party write-ups reference (/.well-known/mcp/server-card.json), notes cf-webmcp does not run a streamable HTTP MCP endpoint, and documents the GET /mcp landing vs POST /mcp JSON-RPC path conflict for publishers running both side by side. Includes the concrete workaround: set [webmcp_landing].path = "/pair" to free /mcp for the MCP JSON-RPC endpoint.
Tests
- 4 new build-config tests: default annotations per executor type, override behaviour, title surface
- 206 worker tests + 15 build-config tests = 221 total, all green
- CI green at 8f46fcb
Compatibility
- No
schema_versionbump. New fields are additive optionals; existing TOML configs compile unchanged. - The generated bootstrap is byte-different from v0.3.3 because every tool now carries an
annotationsobject. Theconfig_hashis derived from the TOML, not the generated JS, so the bootstrap URL does not change for publishers whose TOML is unchanged. Browsers with the v0.3.3 bootstrap cached at the same URL will serve stale until cache expires; a no-op TOML edit (e.g. a comment change) rotates the hash and forces a fresh fetch. - No new dependencies, no breaking API changes, no schema changes that require publisher action.
Live verification
$ curl -s https://webmcp.basgr.com/_webmcp/bootstrap.<hash>.js \
| grep -o 'annotations":{[^}]*}'
annotations":{"readOnlyHint":true,"untrustedContentHint":false}
annotations":{"readOnlyHint":true,"untrustedContentHint":true}
annotations":{"readOnlyHint":true,"untrustedContentHint":true}Upgrade
cd cf-webmcp
git fetch
git checkout v0.3.4
npm install
npm run build
wrangler deployTemplates are unchanged. If you want to add title or override annotations per tool, edit your webmcp.toml.
v0.3.3 - X-Robots-Tag noindex enforced
Policy
Every URL cf-webmcp serves under /_webmcp/* or /.well-known/* now emits X-Robots-Tag: noindex. The two explicit exemptions:
/llms.txtat apex — some publishers want it surfaced in AI-search indexes/robots.txtat apex — special-cased by every major crawler regardless
/mcp landing is at apex; not in scope of the rule but already had noindex set by design (it's a per-visitor pairing surface).
What was missing
The audit found nine response paths under the protected prefixes that emitted no x-robots-tag:
| Where | Path |
|---|---|
jsonResponse default headers |
All /_webmcp/exec/* error envelopes + success + cache-hit relay |
exec.ts 405 / preflight 204 |
/_webmcp/exec/* method-not-allowed / CORS preflight |
widget.ts 405 / 503 |
/_webmcp/widget.*.js method-not-allowed / asset-missing |
health.ts 404 / 401 |
/_webmcp/health disabled / unauthorized |
worker.ts SSRF rejection 502 (off-host redirect) |
reachable from any merge-mode route, including those under /.well-known/* |
worker.ts SSRF rejection 502 (malformed final URL) |
same |
agents-md.ts passthrough relay |
/.well-known/agents.md when origin returns unexpected content-type |
api-catalog.ts passthrough relay |
/.well-known/api-catalog same |
agent-skills.ts passthrough relay + 301 redirect |
/.well-known/agent-skills/<slug>/SKILL.md and case-variant aliases |
All nine now stamp X-Robots-Tag: noindex.
How regressions are caught
src/x-robots.test.ts is the exhaustive coverage test. It uses Record<RouteMatch["kind"], "noindex_required" | "exempt"> so the file fails to compile if a new route kind is added to router.ts without classification. For every "noindex_required" kind, it builds the canonical response via the relevant handler and asserts the header is present. A cross-check verifies that no "exempt" kind's canonical path is actually under a protected prefix (catches misclassification).
This is the contract going forward: adding a new route handler under /_webmcp/* or /.well-known/* requires updating the classification map and emitting the header.
Live verification
$ curl -sI https://webmcp.basgr.com/.well-known/webmcp.json | grep -i x-robots
x-robots-tag: noindex
$ curl -sI https://webmcp.basgr.com/.well-known/api-catalog | grep -i x-robots
x-robots-tag: noindex
$ curl -sI https://webmcp.basgr.com/.well-known/agents.md | grep -i x-robots
x-robots-tag: noindex
$ curl -sI https://webmcp.basgr.com/.well-known/agent-skills/site/SKILL.md | grep -i x-robots
x-robots-tag: noindex
$ curl -sI https://webmcp.basgr.com/.well-known/agent-skills/site/SKILLS.md | grep -i x-robots
x-robots-tag: noindex
$ curl -sI https://webmcp.basgr.com/_webmcp/health | grep -i x-robots
x-robots-tag: noindex
# Exemptions correctly do NOT carry the header:
$ curl -sI https://webmcp.basgr.com/llms.txt | grep -i x-robots
$ curl -sI https://webmcp.basgr.com/robots.txt | grep -i x-robotsTests
- 11 new coverage tests in
src/x-robots.test.ts - 206 worker tests + 11 build-config tests, all green
- typecheck clean
- CI green at 163b144
Compatibility
- No breaking changes.
schema_versionstays at1. - Existing publishers upgrading get the headers automatically on next deploy. If you depended on a cf-webmcp response under
/_webmcp/*or/.well-known/*being indexable (you shouldn't have), this release stops it.
Upgrade
cd cf-webmcp
git fetch
git checkout v0.3.3
npm install
npm run build
wrangler deployv0.3.2 - Patch: woocommerce template fix, wrangler 4, extra path-char hardening
Fixes
templates/woocommerce.toml inheritance now builds clean
The shipped woocommerce template inherits from wordpress.toml (which uses example.com placeholders), but the woocommerce template itself was using shop.example.com. The build-time SSRF allow-list check correctly flagged this: inherited wordpress tool URLs (pointing at example.com) didn't match the child's allowed_origins (shop.example.com).
Fixed by aligning the woocommerce template to the same example.com placeholder convention. Publishers still copy the template and replace example.com with their actual domain - no change to deploy workflow. The template now compiles cleanly out of the box (10 tools total: 6 inherited from wordpress, 4 woocommerce-specific).
PathString now also blocks U+2028 / U+2029
JS LINE SEPARATOR and PARAGRAPH SEPARATOR are legal in modern URL parsers but pre-ES2019 inline <script> contexts treat them as line terminators. cf-webmcp doesn't currently inline paths into JS, but blocking is free defense-in-depth and keeps the path char-set future-compatible.
Two new test cases added to the existing rejection sweep in scripts/build-config.test.ts.
wrangler bumped 3.x → 4.x
CI was warning about the out-of-date wrangler. wrangler@4.90.1 is a clean drop-in:
- Worker bundle build, worker deploy, pages deploy, r2 object put - all verified compatible
vitest-pool-workerskeeps its own pinned wrangler 3.x internally (intentional, no behavior split at deploy time)- 195 worker tests + 11 build-config tests green under wrangler 4
- Compatibility dates pinned at
2024-09-23across all wrangler tomls, so no runtime semantics drift - New defaults (
upload_source_maps,observability) remain opt-in; cf-webmcp doesn't set them
Cosmetic
wrangler.example.tomlnow setscompatibility_flags = ["nodejs_compat"]to match the dev/test wrangler configs.package.jsonversion bumped0.1.0→0.3.2to match the release tag.
Compatibility
- No breaking changes.
schema_versionstays at1. - The PathString tightening rejects characters no legitimate path uses.
- The wrangler bump preserves all CLI args we depend on. If your own deploy uses commands or flags we don't (e.g. some legacy
wrangler kv:keysyntax), check the wrangler 4 migration notes.
Upgrade
cd cf-webmcp
git fetch
git checkout v0.3.2
npm install # picks up wrangler 4
npm run build
wrangler deploy