> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Edge Quickstart

> Run Mirage workspaces and the Mirage server API on an edge runtime with @struktoai/mirage-edge.

`@struktoai/mirage-edge` runs Mirage on an edge runtime: a host with web `Request`/`Response`, no Node and no threads. A workspace runs in the runtime's own isolate: the shell, the commands built on Rust, and `python3` on [Monty](https://docs.mirage.strukto.ai/typescript/runtime/python.md), all in one thread.

The runtime needs:

* Node's `buffer`, `crypto`, `path`, `process` and `util` modules.
* A bundler that loads a `.wasm` import as a compiled `WebAssembly.Module`. Mirage's Rust module and Monty both load this way.

The examples below run on Cloudflare Workers.

## Install

```bash theme={null}
npm install @struktoai/mirage-edge
npm install -D wrangler
```

```toml wrangler.toml theme={null}
name = "mirage"
main = "src/worker.ts"
compatibility_date = "2026-10-01"
compatibility_flags = ["nodejs_compat", "no_handle_cross_request_promise_resolution"]
```

## Run a workspace

```ts src/worker.ts theme={null}
import { MountMode, RAMVFS, Workspace } from '@struktoai/mirage-edge'

export default {
  async fetch(request: Request): Promise<Response> {
    const { command } = (await request.json()) as { command: string }
    const ws = new Workspace(
      { '/data': new RAMVFS() },
      { mode: MountMode.EXEC, runtimes: ['workspace', 'monty'], maxMemory: '32MB' },
    )
    try {
      const result = await ws.shell(command)
      return Response.json({ exit: result.exitCode, stdout: result.stdoutText })
    } finally {
      await ws.close()
    }
  },
}
```

```bash theme={null}
npx wrangler dev
curl -s localhost:8787 -d '{"command": "seq 1 100 | sort -rn | head -3"}'
```

`python3` runs on Monty in the isolate's own thread, where no timer can interrupt it, so Monty's own clock stops a run: at the line's time limit, which ends the line with exit 124, or after 30 seconds with `TimeoutError` if that comes first. A loop that never ends leaves the isolate free for the next line.

For anything else from core, import its subpaths (`@struktoai/mirage-core/vfs/registry`), not its package root: the root also imports optional packages, such as Pyodide, that an edge bundle cannot resolve.

## Serve the server API

`createApp` answers the [HTTP](https://docs.mirage.strukto.ai/home/access/http.md) routes, [MCP](https://docs.mirage.strukto.ai/home/access/mcp.md) and [RPC](https://docs.mirage.strukto.ai/home/access/rpc.md) as the [server](https://docs.mirage.strukto.ai/typescript/access/server.md) does, over the workspaces the isolate holds:

```ts src/worker.ts theme={null}
import { createApp } from '@struktoai/mirage-edge'

export default createApp({ maxWorkspaces: 8 })
```

A workspace outlives the request that created it, so a later request finishes work an earlier one started; `no_handle_cross_request_promise_resolution` above lets it.

It checks each request the way the server does, from the same `MIRAGE_AUTH_MODE`, `MIRAGE_AUTH_TOKEN` and `MIRAGE_JWT_*` [variables](https://docs.mirage.strukto.ai/home/access/http.md#configure-authentication). Without a token or a JWT issuer it serves nothing, since an edge app's URL is public.

```toml wrangler.toml theme={null}
[vars]
MIRAGE_AUTH_MODE = "token"
```

```bash theme={null}
npx wrangler secret put MIRAGE_AUTH_TOKEN
curl -s localhost:8787/v1/workspaces -H "Authorization: Bearer $MIRAGE_AUTH_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"id": "demo", "config": {"mounts": {"/": {"vfs": "ram"}}, "runtimes": ["workspace", "monty"]}}'
curl -s localhost:8787/v1/workspaces/demo/shell -H "Authorization: Bearer $MIRAGE_AUTH_TOKEN" \
  -H 'content-type: application/json' -d '{"command": "python3 -c \"print(6 * 7)\""}'
```

| Option | Meaning |
| - | - |
| `auth` | An `AuthConfig` in place of the env variables: `{ mode: 'token', bearerToken }`, or `{ mode: 'jwt', jwt }`. |
| `maxWorkspaces` | The most workspaces the isolate holds at once. A create past it answers 503. |
| `maxSessions` | The most sessions each workspace holds. A new session past it answers 429. |
| `policies` | The server's own [policies](https://docs.mirage.strukto.ai/home/access/http.md#server-policies). |
| `snapshotStore` | An `S3Config`: the store a snapshot request may name a key in. |

## Limits

* An edge isolate has little memory and no disk; a Cloudflare Worker has 128 MB. Set `max_memory` on each workspace so a large write fails with `Disk quota exceeded` before the isolate runs out.
* Workspaces live in the isolate's memory. A request routed to another isolate does not see them, and they are gone when the isolate is evicted. `store.type` must be `ram`.
* A config's mounts come from core's VFS table: `ram`, `github`, `gdrive`, `gdocs`, `gsheets`, `gslides`, `gcal`, `gmail`, `onedrive`, `sharepoint`, `box`, `airtable`, `linear`, `trello`, `langfuse`, `wandb`, `mem0`, `qdrant` and `dify`. `qdrant` needs `@qdrant/js-client-rest` installed; without it the bundle still builds and the mount refuses. `chroma` loads its package by a name only known at run time, which an edge runtime cannot do.
* Network rules judge each request by host name: the isolate never sees the address a name reaches, so a `denied_domains` entry naming an address or a range is refused, and jwt mode's block on cloud metadata addresses covers only a request made to the address itself.
* SSH, FUSE, policy and script files read by path, and runtimes or CLIs loaded by a `source:Class` reference are the Node server's alone.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.