three-ws-portal
Health Warn
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Fail
- exec() — Shell command execution in src/layout.js
- process.env — Environment variable access in src/mcp.js
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Turn any website into a walkable 3D world: layout in JS, a Portal API client, a CLI and an MCP server.
@three-ws/portal
Turn any website into a walkable 3D world.
Paste an address; get a place. Every section becomes a building sized by what it
says, every link becomes a door you can step through, every image becomes a
billboard, every code block becomes a monolith. Walk it in the browser, download
it as a .glb, embed it in your own page, or hand it to an agent over MCP.
Quick start · How a page becomes a place · API · CLI · MCP · Manners
Why
Every tool that "visualizes a website" draws a node graph. A graph tells you a
site has 40 pages; it does not tell you which section carries the weight, where
the dead ends are, or what it feels like to arrive. A place does, because
size, distance and direction are things people read without being taught.
Portal turns the page you already have into that place, deterministically, with
no model in the loop: the same URL always builds the same city, so a link you
share opens the world you walked.
Quick start
npm install @three-ws/portal
import { fetchWorld, describeWorld } from '@three-ws/portal';
const { world } = await fetchWorld('example.com');
console.log(describeWorld(world));
// { host: 'example.com', sections: 2, doors: 1, tallest: { name: 'Example Domain', … } }
Put a live, walkable world on your own page:
import { mountPortal } from '@three-ws/portal';
mountPortal(document.querySelector('#stage'), { url: 'example.com', height: 560 });
Or without any JavaScript at all:
<iframe
src="https://three.ws/portal?url=example.com&embed=1"
width="100%" height="520" style="border:0;border-radius:14px"
loading="lazy" title="Walk example.com in 3D"></iframe>
No key, no account, no wallet. fetchWorld reads the public three.ws Portal API,
which fetches the page once and caches the result for an hour.
How a page becomes a place
The mapping is fixed, documented, and testable, which is what makes a world
predictable enough to share:
| On the page | In the world |
|---|---|
| The page itself | A plaza with an obelisk carrying the title |
Each h1/h2/h3 section |
A district with one building |
| How many words a section has | The building's height, on a log scale |
| How many blocks it holds | The building's footprint |
| Each link | A door on the building's wall. Same-site doors rebuild the world where they point; outside links open the page |
| Each image | A billboard beside the building, showing the real image |
| Each code block | A monolith: a tall, dark, emissive slab |
theme-color, or the host name |
The palette, so a site looks like itself |
Layout is a pure function with a seeded PRNG. Call it yourself on any outline:
import { buildWorld, collidersFor } from '@three-ws/portal/layout';
const world = buildWorld(outline); // no network, no DOM, no three.js
const solids = collidersFor(world); // circles, for your own player controller
API
| Export | What it does |
|---|---|
fetchWorld(url, opts?) |
Build (or read from cache) { world, outline, cached, stale } |
fetchOutline(url, opts?) |
The structural read only: headings, weights, links, images |
fetchWorldGlb(url, opts?) |
The world as a glTF binary (ArrayBuffer) |
mountPortal(el, { url, height? }) |
Mount a live walkable world into an element |
embedSnippet(url, opts?) |
The same thing as an HTML string |
describeWorld(world) |
A compact summary: districts, doors, tallest section |
buildWorld(outline, opts?) |
The pure layout function |
collidersFor(world) |
Collision circles for the solids in a world |
paletteFor(outline) |
The deterministic palette for a site |
PortalError |
Every failure, carrying the API's own code |
opts accepts endpoint (default https://three.ws/api/portal), fetch (bring
your own), and signal.
Errors are typed rather than thrown as strings:
import { fetchWorld, PortalError } from '@three-ws/portal';
try {
await fetchWorld('example.com/private');
} catch (err) {
if (err instanceof PortalError && err.code === 'robots_disallowed') {
console.log('That site asks crawlers to stay out, so Portal does.');
}
}
Codes you can branch on: invalid_url, robots_disallowed, blocked_host,not_html, no_structure, too_large, unreachable, upstream_status,rate_limited.
CLI
npx @three-ws/portal three.ws # the world, summarized
npx @three-ws/portal three.ws --glb site.glb # save it as a 3D file
npx @three-ws/portal three.ws --json world.json # save the world document
npx @three-ws/portal three.ws --open # print the walkable link
three.ws
24 districts · 1563 words · 42 doors (38 internal) · 2 billboards · 0 code monoliths
███████████··········· 109w Your 3D agent. In your room.
█████████············· 66w Your agents don't just talk. They trade.
█████················· 20w One tag. Any site.
MCP
An agent that fetches a page gets a wall of text. Over MCP it can ask for the
page's shape instead.
claude mcp add portal -- npx -y @three-ws/portal portal-mcp
| Tool | Returns |
|---|---|
walk_site |
The world: districts, doors (with targets), billboards, plus a walkable link and a GLB URL |
site_shape |
The heading spine with the weight of each section. The cheap read |
Both accept any public URL and honour the same manners as the API.
Manners
Portal is a crawler that runs on a person's request, and behaves like one:
- It identifies itself as
ThreeWSPortalBot/1.0 (+https://three.ws/portal). - It reads robots.txt first and honours a
Disallowfor that token with a
cleanrobots_disallowederror. Rules are matched per RFC 9309: most specific
user-agent group, longest matching rule,Allowwins a tie. - It reads a page once. Worlds are cached for an hour fleet-wide, so a link
shared with a thousand people is one request to the origin. - It is bounded: 3 MB of HTML, a 12 second deadline, and an SSRF guard that
re-resolves the host, refuses private address ranges, and pins the socket to
the address it validated.
License
Apache-2.0. Built by three.ws.
Support the project
If three-ws-portal saves you time, star it on GitHub. Stars are how other developers and AI agents find the repositories worth trusting, and they cost you one click.
Know someone who would use it? Post on X · Share on Bluesky · Share on LinkedIn · Submit to Hacker News · Share on Reddit
Built for AI agents too
Coding agents and LLM tooling can read this repo directly: AGENTS.md, llms.txt, llms-full.txt. Point an agent at https://github.com/nirholas/three-ws-portal and it has the context it needs.
More from the same author
- All repositories by nirholas: the full catalog, grouped by topic
- three.ws: the platform for 3D AI agents with Solana wallets, a skill marketplace and x402 payments
- Questions or ideas: open an issue or start a discussion
Contributors
Star history
Part of three.ws
three.ws is a platform for 3D AI agents with Solana wallets: avatars, a skill marketplace, x402 payments and more than seventy MCP servers. @three-ws/portal is one package from it.
- Documentation site: https://nirholas.github.io/three-ws-portal/
- npm: https://www.npmjs.com/package/@three-ws/portal
- Canonical source: https://github.com/nirholas/three.ws (this repository is a generated mirror of
packages/portal) - Agent-readable summary: llms.txt and AGENTS.md
- Issues and ideas: https://github.com/nirholas/three-ws-portal/issues
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found