Clone Space MCP
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Clone Space MCPArchive https://example.com for offline replay"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Clone Space MCP
Archive a live web page so it replays offline with the motion intact, and so an AI agent can read how the page is actually built.
Saving a page from a browser gives you a dead skeleton: the markup survives, the behaviour does
not. clone-space-mcp targets the other end — a carousel that still slides, a GSAP timeline that
still runs, a ScrollTrigger that still fires, with the network unplugged. Then it tells you which
line makes it move.
The load-bearing decision is that replay navigates the original URL with the original document HTML, served from a HAR, so the page's real JavaScript re-executes. Serializing the hydrated DOM into a standalone file is the approach this project rejects: it breaks hydration and entry animations, which is exactly the fidelity being chased.
Quick start
bun install
# 1. archive a page
bun run mcp:call capture_page '{"url":"https://example.com/","outDir":"./out/example"}'
# 2. does it run offline?
bun run mcp:call replay_page '{"archive":"./out/example"}'
# → { "aborted": [], "unservable": 0 } ← empty is the exit criterion
# 3. what moves, and which line defines it?
bun run mcp:call extract_behaviour '{"archive":"./out/example"}'
# 4. is the archive intact and complete?
bun run mcp:call inspect_archive '{"path":"./out/example"}'
# and, without an agent at all:
bun run inspect ./out/example -o report.htmlmcp:call runs the same functions the MCP server does, with no transport and no agent — so a bug
is reproducible by hand. That constraint was adopted before any MCP code was written
(#8).
Connecting an agent
The server speaks MCP over stdio. Node, not Bun: three of the four tools drive Playwright (ADR 0001).
{
"mcpServers": {
"clone-space": {
"command": "node",
"args": ["scripts/mcp-server.ts"],
"cwd": "/absolute/path/to/clone-space-mcp"
}
}
}Tool | Runtime | Answers |
| Node | archive this URL |
| Node | does the archive run offline |
| Node | what moves, what drives it, which line |
| Bun | what is in the archive, and is it complete |
The order is not a suggestion. extract_behaviour runs the page in a replay, because a GSAP
timeline is written down nowhere in the archive — it exists only once the page's own JavaScript has
built it. Reading the HAR recovers source text that might create motion; running the page recovers
the motion that did.
What it can actually do
Related MCP server: UI Perception Engine
Capture — src/capture/
An adaptive sweep, bounded so it can always stop. The sweep scrolls and waits for the page to go
quiet, which is what triggers lazy content. An infinite-scroll or polling page can extend a quiet
window forever, so the sweep runs against a termination budget — wall-clock, bytes, nodes,
height and events (budget.ts). termination.json records the outcome and the reason, so a
truncated capture is distinguishable from a complete one. It is incomplete if any request went
unanswered, whatever the sweep's own reason was.
Bounded interaction, with a written refusal policy. A scroll-through only ever triggers what
scrolling triggers; the effects a reader most wants explained are usually behind a click. So capture
discovers candidates, builds a pure plan (interaction.ts — the rules live where bun test can
reach them), and drives it (interaction-drive.ts). The driver refuses anything it judges
consequential — cross-origin, downloads, new browsing contexts, file pickers, navigation, form
submission, and controls whose text reads as authentication or destruction, in English and Thai —
and records the rule and the fact that tripped it, so a refusal is evidence rather than a gap.
The observation layer — what the page does, not what it shipped (instrument.ts). Hooks
installed before any page script runs, at the browser API layer rather than at a library.
That last choice is what makes it hold: on www.chaingpt.org, THREE is not a global — it loads as
an ES module — and every shader was still captured, because everything must pass through WebGL
eventually.
Measured on
https://www.chaingpt.org/, replayed offline: 82,613 characters of GLSL, 9 canvas contexts, 1,510addEventListenerregistrations.
A WebGL shader is the sharpest case: the GLSL is assembled at runtime from strings and uniforms, so it exists in no archived file and no reader of files can ever produce it. The call stack is in the schema from version 1 on purpose — adding it later would leave every earlier observation unresolvable, and re-capturing a site is not free.
An interaction transcript (transcript.ts) — bounded, sequence-numbered, and each scroll names
its container: the window, or a nested or horizontal scroller. Page coordinates alone cannot say
which thing moved.
A target inventory (targets.ts) — which OOPIFs, popups, workers and worklets existed, when they
attached and detached, and how they relate.
An explicit request-normalization policy (request-normalization.ts,
ADR 0007) — the volatile
query keys replay is allowed to treat as identical. It is a named list, not a guess: an ambiguous
normalization is refused rather than resolved.
Transactional publication. Everything above is staged, validated, and only then published;
commit.json holds a SHA-256 of every other file and is written last. It exists only if
publication succeeded, and inspect_archive re-checks every hash against the bytes on disk.
Replay — src/replay/
The original URL, the original JavaScript. routeFromHAR(har, { notFound: 'abort' }) serves the
archive and aborts anything it cannot serve, so a replay that quietly reaches the live network is
impossible. A request the archive has no usable response for is reported as unservable — a fact
about the archive, not about this replay.
restoreTiming holds each response until its recorded offset from the start of the page
load — not its own duration, which is what made an earlier attempt time out. A page that measures
itself without ordering that measurement against its own resources can otherwise settle differently
offline (#187). Off by default: it costs
wall-clock, measured at 825 ms → 4,577 ms on a 146-entry site.
instrument installs the observation layer on the replay, which is how extract_behaviour
recovers a runtime-assembled shader from an archive.
Extract — src/extract/
A behaviour graph, not a list of CSS rules (behaviour.ts). Each node carries its mechanism,
target, name, timing (duration, delay, iterations), easing, library, and — for a
ScrollTrigger — the trigger detail. It also publishes unrepresented: what it saw and could not
represent. A graph that silently omitted those would read as a complete description of a page it did
not fully explain.
Which line, resolved through the archive's own sourcemaps (sourcemap.ts,
archive-sources.ts). Every position the runtime reports is in minified coordinates —
three.module.min.js:12:326662 names line 12 of a file with about fifteen lines. The extractor
follows each script's sourceMappingURL to the response the capture already fetched, and turns
that into a real file, a real line, and the text written on it:
origin cdn.jsdelivr.net/npm/three@v0.151.3/build/three.module.min.js:12:326662
original /npm/three@0.151.2/build/three.module.js:18723:5
18723 | gl.shaderSource( shader, string );Nothing is fetched here. A map the capture did not take cannot be obtained, and the report says so rather than reaching for the network.
Serve — src/serve/
Four tools over stdio, a CLI harness that reaches the same functions with no transport, and a
visual inspector that needs no agent at all (inspector.ts). That last one exists because of a
specific argument: if the only way to see what an archive contains is to ask an agent, then every
question about whether a capture is any good needs an agent in the loop.
The layering is a rule, not a habit: src/serve/mcp.ts registers and maps errors and holds nothing
else, src/serve/tools/ are plain functions a bun test can call, and src/archive/read.ts
beneath them resolves and parses what capture published — owning no MCP, no transport and no
interpretation, so a tool, a test, a CLI harness and the inspector all reach the same data.
Element identity — src/identity/
Capture and replay have to agree on which element they are talking about, across two runs of a
page that rebuilds its own DOM. An injected script assigns a wa: id, and a ตัวจับคู่ reconciles the
two runs by fingerprint — reporting what it could not match rather than guessing.
A wa: id is a handle within one run, never a key across runs
(ADR 0002). Reading it as
a key is how you write a reconciler that compares strings and reports total failure on a good
archive.
The equivalence gate — src/equivalence/
A clone that looks right is not a measurement. bun run equivalence <url> drives the live page and
the replayed archive with the same driver in one session, collects the same digest from both,
and reports:
equivalence FAIL https://labs.chaingpt.org/
residual (1)
layout.scrollHeight live 8544 replay 8486
unstable (0)
baseline live 3 replay 3
network live 248 req / 27 origins replay 248 req / 28 origins
coverage
scroll 100%
motion_settled 100%
stable_fields 100%
interaction 100%
listener_execution 0%Four exit codes, because a boolean folds together things that need opposite responses: 0 PASS
· 1 FAIL, a residual nothing explains · 2 INCOMPLETE, nothing was proven equal · 3 the run
never produced a verdict.
Four properties it holds on purpose:
Coverage is a vector, never a score. A single number averages away exactly the dimension that is weak.
listener_execution 0%above means the run drove no listeners — so that green row is a claim about navigation and scrolling and nothing else.unobservednever counts asequal. A field only one side produced was not compared, and a page whose click listeners are never fired must not read as agreement.The live page is driven more than once, as a control. Fields that disagree with themselves are reported
unstablerather than blamed on the clone — andbaselinePassespublishes how much evidence that control had, because aFAILresting on no replay passes and one resting on three agreeing passes are different claims.--measure-perturbationasks whether instrumenting the page changes the page. A fourth live drive with hooks installed, compared against every plain pass. Comparing against one would blame the hooks for ordinary run-to-run noise.
The network reading is reported and never compared: performance.getEntriesByType("resource")
saturates at a 250-entry buffer, and one real site reads 248. Recorded verdicts for three sites are
in docs/reports/.
Before you point it at anything
Response bodies are not redacted (ADR 0009). Credentials, cookies and known-sensitive request headers are (ADR 0003) — but an archive of a page contains the page, which is the point. Do not capture an authenticated page, an internal tool, or anything behind a login into a directory you would not hand to whoever can read it.
capture_page refuses to publish an archive served from a private address — loopback,
link-local, private, unique-local, unspecified, or the CGNAT-shared 100.64.0.0/10 a Tailscale peer
answers from — unless you pass allowPrivateNetwork. It reads each HAR entry's serverIPAddress, so
it covers a subresource the page fetched itself, not only the URL you asked for. The whole capture
is discarded, not the offending entry. Capturing this repo's own fixture site needs the flag,
because it runs on localhost.
Two things the interaction policy cannot catch, and no structural rule can: a type="button"
whose handler calls requestSubmit(), and an href="#x" whose handler issues a DELETE. Their
consequence lives entirely in JavaScript no attribute describes. Treat an unfamiliar site as
production.
What an archive is
One directory, all files written 0600:
File | What it records |
| Every request and response, bodies attached as separate files |
| The browser and page environment, split into what was requested, what was observed, and what replay must reproduce |
| Four tri-state flags: service worker, WebSocket, closed shadow root, declared sourcemap |
| The explicit volatile-query-key policy replay matches against |
| Which targets existed during capture — OOPIFs, popups, workers — and when |
| What the driver did to the page, and what it refused to do, with the rule that refused it |
| Why the sweep stopped, the budget it stopped against, and how many requests the archive holds no response for |
| The associations binding all of the above to one coherent run |
| A SHA-256 of every other file, written last, after validation |
How it is kept honest
Mechanism | Scale | What it is |
Mutation corpus — | 153 entries | Re-applies a defect that actually happened and requires a named test to go red. A guard no test can fail against is indistinguishable from one that works |
Fixture sites — | 3 origins | Controlled ground truth: motion, capability flags, and a cross-origin surface. Exit criteria are checked here, never on a live site |
ADRs — | 9 records | One per hard-to-reverse decision, including the alternatives rejected |
Metamorphic check — | a metric | Deliberately outside every gate. Correct code still loses matches in some cases, so reporting it as pass/fail would manufacture false confidence in both directions |
Two consequences worth knowing before trusting a green run:
More tests written from the same design add no safety. One defect stayed live while twelve tests passed, because every one of them was written from the design that contained it.
SURVIVEDis a finding, andMUTATION NOT APPLIEDis notSURVIVED. The second means the corpus no longer matches the code and measured nothing at all.
Development
Bun is the package manager and the runtime — except for code that drives a browser, which runs under Node. Playwright's client does not complete its handshake under Bun; see ADR 0001 for the measurements and what was rejected.
Command | Runtime | What it does |
| Bun | lint → typecheck → test → browser tests → build. The ship gate |
| Bun | re-apply the corpus; each entry must be caught by its own test |
| Bun | the baseline metric above |
| Node | capture → replay → diff one page; four exit codes |
| Node | write a visual inspector page; |
| Node | call one tool with no transport and no agent |
| Node | the stdio MCP server an agent connects to |
| Bun | the fixture site, on three origins |
| Node | the CDP measurement harness |
| Bun | is GitHub Actions still refused for billing — a command, not a memory |
| Bun | run verify and post the result to the head SHA as |
Where to read next
You want | Open |
To use the tools well, and know their limits | |
Why something is built the way it is | |
What is open right now | |
What shipped, and what was refuted | |
Measured verdicts on real sites | |
What a term means here |
|
To work on this repo as an agent |
Status
Alpha, and honest about it. All four stages run end to end; what is known-incomplete is tracked rather than restated here where it would rot — see the open issues and the ledger.
Two limits worth carrying off this page:
listener_executionis 0% in every recorded equivalence verdict. No slice drives listeners yet, so no green verdict this project has produced is a claim about them.Two replays of one archive can lay out to different heights (#187) on a page that measures itself without ordering that measurement against its own resources.
restoreTimingremoves it and costs wall-clock.
License
Not yet chosen.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to clone entire websites, download files, manage authentication sessions, and analyze site information with support for JavaScript-heavy SPAs and dynamic content.8Apache 2.0
- FlicenseNot gradedqualityBmaintenanceEnables AI agents to perceive and interact with web interfaces by extracting a unified UI Scene Graph from live URLs, providing tools for navigation, element detection, visual analysis, and state tracking.

siteglass-mcpofficial
AlicenseNot gradedqualityBmaintenanceEnables archival of any public URL as interactive snapshot (rrweb, PDF, PNG) and autonomous web QA for apps (register, scan, generate and run end-to-end flows).60MIT- AlicenseNot gradedqualityCmaintenancewebEmbedding is a source-first website cloning engine for AI coding agents that captures live pages with Playwright, replays network evidence, and self-verifies the result.703MIT
Related MCP Connectors
Web search, page extraction and structured commerce, social and business data for AI agents
Reliable web access for AI agents: smart HTTP, rotating proxies, and full-browser rendering.
Turn the web into structured, reliable, actionable enterprise data for AI Agents
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/xenodeve/clone-space-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server