three-ws-portal

mcp
Security Audit
Fail
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.

SUMMARY

Turn any website into a walkable 3D world: layout in JS, a Portal API client, a CLI and an MCP server.

README.md

@three-ws/portal

GitHub stars License Last commit PRs welcome AI agent friendly

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 Disallow for that token with a
    clean robots_disallowed error. Rules are matched per RFC 9309: most specific
    user-agent group, longest matching rule, Allow wins 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

Contributors

Contributors

Star history

Star History Chart

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.

Reviews (0)

No results found