Publish an MCP tool as a static, hash-verified file. Run it sandboxed,
on demand, with zero infrastructure — the way static site hosting did
to web servers: don't run Apache, publish HTML.
Running someone else's tool code is a trust decision
MCP clients like Claude or Cursor can call arbitrary third-party
tools. Executing that code directly in your backend means it can
read your secrets, hit your database, phone home, or loop forever.
You either trust the author fully, or you don't run the tool.
Typical MCP server
Someone has to run a process, 24/7
Third-party code shares your runtime
Trust the author, or don't install
mcpwasm
The tool is a file. No process to run.
Code executes isolated in QuickJS-wasm
SHA-256 pinned; a flipped byte excludes it
How it works
Five steps, all sandboxed, no state kept between requests.
A publisher site ships /llms.txt plus per-skill tool.js and SKILL.md.
The gateway downloads llms.txt, fetches each tool.js, and verifies its SHA-256.
Verified tools load into a fresh QuickJS-wasm context, one per skill.
Tool code calls host.fetchOrigin — scoped to the publishing origin only; anything else throws inside the sandbox.
The gateway maps MCP tools/list / tools/call over JSON-RPC 2.0 back to the client.
Four ways to use it today
All four are real, deployed, and tested — not roadmap.
Gateway turnkey MCP server
Point any MCP client at the deployed gateway. It discovers, verifies, and sandboxes a publisher's skills on every request.
Run any origin's skills locally over stdio — including origin memory (verified BM25 search, 0.4.0), each skill's SKILL.md recipe served as an MCP resource (0.5.0), and multi-project origins via scopes: kdd__search_knowledge with per-scope memory (new in 0.6.0). No gateway, no server, on either end.
The whole runtime in a browser tab (new in 0.7.0): discovery, byte-for-byte SHA-256 verification, one QuickJS-wasm sandbox per tool, scopes and per-scope memory. The publisher only needs CORS — GitHub Pages already sends it. No server on their side, no Node on yours.
js
import { connectStaticSkills } from"@rckflr/mcpwasm/web";
const skills = await connectStaticSkills("https://mauricioperera.github.io", opts);
await skills.callTool("search_knowledge", { q: "how do I publish?" });
Where this fits: four ways an agent gets a third-party tool
Same underlying question — "can an agent safely run someone else's tool code?" — four different answers.
Traditional MCP server
Client: install & configure a full MCP client
Isolation: tool code runs with the server's full privileges
Integrity: none — you trust the server's response completely
Discovery: a separate tools/list RPC, before any call
To publish: a live process, running 24/7, somewhere
WebMCP in-page, unsandboxed
Client: none — a browser API or the page's own bridge
Isolation: none — runs in-page with full page privileges
Integrity: none — trust whatever the page currently serves
Discovery: tools register as a side effect of the page loading
To publish: whatever hosts your page — can be fully static
mcpwasm npx, zero config
Client: npx @rckflr/mcpwasm <origin> — one command
Isolation: QuickJS-wasm sandbox, only a scoped host.fetchOrigin out
Integrity: SHA-256 pinned in llms.txt, verified before running
Discovery: running the verified file registers its tools
To publish: 100% static — the client does the work
mcpwasm no client at all
Client: none — connectStaticSkills() runs it in your own process
Isolation: same sandbox, same verification, zero extra process
Integrity: same SHA-256 pin, checked in your own runtime
Discovery: same mechanism — but nothing nudges an agent to look
To publish: static hosting + CORS — GitHub Pages qualifies
Verified live, not just claimed
margin-demo is a
no-client skill (create_document / decode_document) hosted on
plain GitHub Pages — no backend at all. Given only its URL, an unmodified agent
(Codex) treated it as a regular web page and never found the tools on its own. Adding
three explicit discovery layers — a visible "for agents" note, an
agent-setup/prompt.md (the same pattern
Cloudflare uses),
and a one-click "copy prompt for agent" button — closed the gap: the same agent then
found and called the tools on the first try, including a full
read-modify-write cycle (decode_document → edit → create_document)
from a single copied prompt. Discoverability, for any of the four
approaches above, isn't automatic — it has to be engineered in.
tab-agent is a personal AI agent that is nothing but static files + your browser: shared memory (the cq-git commons over the GitHub API), verified tools (this runtime, hash-pinned & sandboxed), and a model you own — the provider credential is never exposed (run it in-tab via WebGPU, your endpoint, or an operator proxy). It answers the personal-agent supply-chain problem by inverting it: tools are verified, not screened.
None of them alone is enough. Together they bound what a published skill can claim — the same four rings the whole ecosystem uses, for any runtime that implements the spec.
01
Integrity
Every tool.js is pinned by SHA-256 in llms.txt. A single flipped byte and the skill is excluded — not degraded, not executed.
02
Authenticity
Signed human review — pre-registered Ed25519 keys or keyless Sigstore identities (the spec's recommended default since v0.4). Honest today: one registered reviewer. The mechanism scales; the reviewer network hasn't yet.
03
Freshness
An attestation isn't a one-time stamp: it carries an expiry window and voids itself the moment the attested file changes — "still true" has a shelf life, not a permanent seal.
04
Stability
A consumer --lock lockfile pins each skill's hash on first use — a later change is rejected loudly, not applied silently, even if it's the publisher's own account that changed it. Local runtime today; gateway and browser don't support it yet.
These four rings describe the published skill itself — verifiable by any runtime that implements the spec, independent of who's running it. mcpwasm adds one more layer when it actually executes a verified skill: each one runs isolated in its own QuickJS-wasm context — no fetch, no process, no disk, only an explicit, origin-scoped host.fetchOrigin out.
What it costs
Real numbers from the deployed gateway, not synthetic estimates.
0ms
sandbox overhead, warm
55ms sandboxed vs 53ms raw ping
0ms
full gateway overhead
96ms via gateway vs 90ms direct API
0ms
p95, 10 concurrent requests
before → after the instance pool + preheat
0ms
cold discovery miss
~210–400ms range, compile + sha256 + fetch
Single-client benchmark from México to the Cloudflare Workers edge — this
is latency of one observer, not a load test. Full matrix,
methodology, and raw data are in
BENCHMARK.md.
The deployed gateway requires a bearer token (not published here). Full
curl walkthrough — including the open demo, bookstore, and docs
publishers — is in the
README.
From a static site to a live MCP server
llms-txt-skills is the format; mcpwasm is a runtime for it. A publisher serves hash-pinned, attested skills once, the standard way — the runtime discovers, verifies, and runs each as an MCP tool. The whole contract between them is one tool_sha256 and its attestation.
A publisher serves an llms.txt whose ## Skills section lists each executable skill with its tool.js and tool_sha256 — mirrored in /.well-known/agent-skills/index.json and signed in attestations.json. Optionally, a hash-pinned BM25 snapshot (one llms-skills memory command over an OKF bundle) adds serverless search over the site's own knowledge. This is exactly what the llms-txt-skills spec defines. Since llms-skills 0.4.0, the bundle can also carry signed freshness: a reviewer attests “still true”, voided on edit, expiring on a date.
mcpwasm points at that origin, fetches the llms.txt, and verifies every tool.js against its tool_sha256 and its attestation — rejecting any mismatch before loading it.
Each verified skill becomes an MCP tool, and its SKILL.md recipe is served alongside as an MCP resource (with a get_skill_guide fallback) — the agent gets the manual, not just the hammer. Claude, Cursor, any MCP host list and call it like any other tool.
On a call, mcpwasm executes that tool.jsverbatim inside a QuickJS-wasm sandbox — no network or filesystem except the host capabilities it grants (a scoped fetchOrigin back to the site, and search over the site's own content). The result returns to the client.
Neither side has to trust the other's prose: mcpwasm re-derives the hash and checks the signature itself. Static hosting + a verifying runtime = an MCP server with no server to run.
Want to be the publisher side? Start from the GitHub template — a working publisher out of the box (example knowledge bundle, generated skills, validation CI) that this runtime consumes as-is.
Part of a spec, not just a repo
mcpwasm is the reference implementation of two provisional extensions
to the
llms-txt-skills
standard: Executable Skills (v0.5, with origin memory and scopes)
and Skill Attestations (v0.4). Every MUST in those
specs is field-tested in this code — spec and implementation are kept
in sync.