Part of The Static Agent Web — the verified, serverless agent ecosystem
CI npm

Static MCP: your tools are files, not servers.

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.

terminal
npx -y @rckflr/mcpwasm https://mauricioperera.github.io

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 and tool.js; the gateway fetches and verifies them by SHA-256, loads the tool into a QuickJS-wasm sandbox, the sandbox calls host.fetchOrigin back to the publisher, and the MCP client gets a JSON-RPC response. Publisher site static: R2 / Pages / any host serves /llms.txt + tool.js Gateway discover → verify sha256 load into sandbox MCP client Claude, Cursor, ... POST /mcp (JSON-RPC 2.0) Sandbox QuickJS-wasm, per skill 1 publish 2 verify sha256 3 load 4 fetchOrigin 5 JSON-RPC
  1. A publisher site ships /llms.txt plus per-skill tool.js and SKILL.md.
  2. The gateway downloads llms.txt, fetches each tool.js, and verifies its SHA-256.
  3. Verified tools load into a fresh QuickJS-wasm context, one per skill.
  4. Tool code calls host.fetchOrigin — scoped to the publishing origin only; anything else throws inside the sandbox.
  5. 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.

bash
curl -s -X POST \
  "https://llmstxt-gateway.rckflr.workers.dev/mcp?origin=<url-encoded-origin>" \
  -H "Authorization: Bearer <AUTH_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Local runtime zero infra, both sides

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.

bash
npx -y @rckflr/mcpwasm https://mauricioperera.github.io

Library embed the sandbox

Build your own platform host with the exact isolation the gateway uses.

js
import { AsyncToolHost } from "@rckflr/mcpwasm";

const host = new AsyncToolHost({ allowedOrigin: "https://example.com" });
await host.init();
host.loadToolSource(toolJsSource);
const result = await host.callTool("sum_numbers", { a: 2, b: 40 });
host.dispose();

Browser runtime no Node either

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?" });

Open the live demo Full-stack agent demo

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.

Read the full write-up

The whole stack, as one personal agent

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.

Open tab-agent

Four rings of trust

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.

Try it now

Run any origin's skills locally, no gateway

bash
npx -y @rckflr/mcpwasm https://mauricioperera.github.io

MCP client configuration

json
{
  "mcpServers": {
    "misitio": {
      "command": "npx",
      "args": ["-y", "@rckflr/mcpwasm", "https://mauricioperera.github.io"]
    }
  }
}

Or call the live demo gateway directly

bash
curl -s -X POST \
  "https://llmstxt-gateway.rckflr.workers.dev/mcp?origin=https%3A%2F%2Fllmstxt-demo-site.rckflr.workers.dev" \
  -H "Authorization: Bearer <AUTH_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"sum_numbers","arguments":{"a":2,"b":40}}}'

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 static publisher site serves llms.txt skills and tool.js; mcpwasm fetches and verifies them, then exposes each as an MCP tool that a client can call, running the tool.js sandboxed. Static site llms.txt · ## Skills · tool.js index.json · attestations.json mcpwasm runtime gateway (Workers) or npx local verify + QuickJS sandbox MCP client Claude, Cursor, any MCP host lists and calls the tools 1 fetch + verify 2 serve as MCP tools 3 call tool(args) 4 sandboxed tool.js
  1. 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.
  2. 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.
  3. 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.
  4. On a call, mcpwasm executes that tool.js verbatim 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.