@divlet/core 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/AGENTS.md +3 -2
  2. package/BACKLOG.md +3 -4
  3. package/CHANGELOG.md +11 -2
  4. package/README.md +16 -6
  5. package/dist/core/lib/acceptance.d.ts +18 -0
  6. package/dist/core/lib/acceptance.js +29 -0
  7. package/dist/core/lib/assets.js +1 -1
  8. package/dist/core/lib/browser-board.d.ts +41 -0
  9. package/dist/core/lib/browser-board.js +203 -0
  10. package/dist/core/lib/browser-mcp.d.ts +131 -0
  11. package/dist/core/lib/browser-mcp.js +448 -0
  12. package/dist/core/lib/browser-media-type.d.ts +4 -0
  13. package/dist/core/lib/browser-media-type.js +7 -0
  14. package/dist/core/lib/browser-runtime.d.ts +598 -0
  15. package/dist/core/lib/browser-runtime.js +674 -0
  16. package/dist/core/lib/codec.js +2 -1
  17. package/dist/core/lib/contract.d.ts +1 -152
  18. package/dist/core/lib/contract.js +3 -359
  19. package/dist/core/lib/diff.js +1 -1
  20. package/dist/core/lib/prepare.d.ts +27 -0
  21. package/dist/core/lib/prepare.js +41 -0
  22. package/dist/core/lib/project.js +3 -3
  23. package/dist/core/lib/render.d.ts +2 -9
  24. package/dist/core/lib/render.js +10 -35
  25. package/dist/core/lib/runtime.d.ts +1 -1
  26. package/dist/core/lib/runtime.js +1 -21
  27. package/dist/core/lib/tools.d.ts +154 -0
  28. package/dist/core/lib/tools.js +364 -0
  29. package/dist/core/lib/validation.d.ts +5 -0
  30. package/dist/core/lib/validation.js +21 -0
  31. package/dist/skills/divlet/SKILL.md +7 -1
  32. package/docs/README.md +1 -0
  33. package/docs/agents.md +11 -1
  34. package/docs/architecture.md +15 -7
  35. package/docs/board.md +1 -1
  36. package/docs/browser-mcp.md +108 -0
  37. package/docs/browser-runtime.md +6 -4
  38. package/docs/publishing.md +6 -6
  39. package/docs/testing.md +8 -1
  40. package/examples/README.md +2 -2
  41. package/examples/landing/landing.divlet +1 -1
  42. package/package.json +24 -5
  43. package/skills/divlet/SKILL.md +7 -1
@@ -0,0 +1,108 @@
1
+ # Browser-hosted MCP
2
+
3
+ **Status: implemented for tools; deployed on GitHub Pages; `--connect` is in the 0.0.3 release candidate.** A static website can be an executable MCP provider: its tools run in the visitor's (or the agent's) browser, and a local agent connects by URL. The divlet GitHub Pages build is the first such provider — the real board, opening the canonical landing project, with the same contract tools Pi and the local MCP adapter offer. No MCP server is deployed for it. From a checkout: `node scripts/source.mjs mcp/index.js --connect <url>`.
4
+
5
+ ```bash
6
+ # Register in any MCP host (Claude Code, an editor, a script). Tools run in a managed browser page, not on this machine.
7
+ npx -y @divlet/mcp --connect https://llblab.github.io/divlet/
8
+ ```
9
+
10
+ A completely unmodified MCP client cannot speak to a static site: something on the agent side must supply the browser runtime and this transport. Here that is `@divlet/mcp --connect` (for any stdio MCP host) or `@divlet/core/browser-mcp` (for a harness that integrates it directly). What disappears is the website author's server, not the agent-side adapter.
11
+
12
+ ## Layers
13
+
14
+ ```text
15
+ agent host (MCP client) ── stdio ──▶ divlet-mcp --connect @divlet/mcp mcp/lib/connect.js
16
+ │ connectBrowserMcp(url) @divlet/core core/lib/browser-mcp.ts
17
+ ▼ page.evaluate(JSON text) (Playwright, isolated context, no bindings)
18
+ static website ──▶ globalThis[Symbol.for('browser-mcp/1')]
19
+ │ createBrowserMcpProvider @divlet/web/mcp web/lib/mcp-provider.ts
20
+ │ defineTools (one registry) ──────────────── web/lib/mcp-tools.ts
21
+ │ registerWebMcpTools (optional) ───────────── web/lib/webmcp.ts
22
+ ▼
23
+ application core: the tool's own operations (divlet: BrowserRuntime + board ports)
24
+ ```
25
+
26
+ - **Tool registry** (`defineTools`): one canonical list of `{ name, title, description, inputSchema, annotations, execute(args, { signal }) }`. Input is validated by a bounded JSON Schema 2020-12 subset (`type`, `properties`, `required`, `additionalProperties`, `items`, `enum`, `const`, `anyOf`, `oneOf`, numeric/length/item bounds); any other keyword is refused when the tool is defined, so a schema never claims validation it does not get. Execution has a deadline, an abort signal and a byte budget for the JSON result. The MCP provider and the WebMCP adapter both call the same `run`.
27
+ - **Browser MCP provider** (`createBrowserMcpProvider`, `exposeBrowserMcp`): a JSON-RPC 2.0 handler for MCP **2026-07-28** and nothing older. Implemented: `server/discover`, `tools/list`, `tools/call`, `notifications/cancelled`. It needs no Node, backend, extension or experimental API, and calls nothing outside the page.
28
+ - **Browser MCP transport** (`connectBrowserMcp`, `browserMcpTransport`): opens the URL in a managed Chromium, finds the endpoint, and exchanges messages by evaluating JSON text in the page. `browserMcpTransport(connection)` adapts a connection to the official TypeScript SDK's `Transport` interface.
29
+ - **WebMCP adapter** (`registerWebMcpTools`): registers the same registry with `document.modelContext.registerTool(tool, { signal })` when the browser provides it (WebMCP Community Group draft, 12 November 2026). Absent API → nothing is installed or emulated, and the MCP provider keeps working. WebMCP defines tools only; MCP resources/prompts are not mapped.
30
+
31
+ ## Protocol revision
32
+
33
+ Revision 2026-07-28 is stateless: there is no `initialize`. Every request carries `_meta["io.modelcontextprotocol/protocolVersion"]` and `_meta["io.modelcontextprotocol/clientCapabilities"]` (missing → `-32602`); another version → `-32022` with `{ supported, requested }`. Results carry `resultType: "complete"` and `_meta["io.modelcontextprotocol/serverInfo"]`; discovery and list results carry `ttlMs: 0` and `cacheScope: "private"` (the tool set belongs to one page instance). Unknown tools are `-32602`; argument and domain refusals are tool results with `isError: true` and `structuredContent.error = { code, message, details? }`. A legacy `initialize` is refused with the supported versions named. Too many concurrent calls → `-31001` (application-defined, outside the reserved range). Cancelled calls get no response. The official SDK client (`@modelcontextprotocol/client` 2.4, `versionNegotiation: { mode: { pin: '2026-07-28' } }`) interoperates through both an in-memory and the browser transport.
34
+
35
+ `divlet-mcp --connect` answers both eras on its stdio side (legacy `initialize` clients and 2026-07-28 clients) and always uses 2026-07-28 toward the page.
36
+
37
+ ## Discovery and connection
38
+
39
+ The discovery contract is project-specific and versioned (`browser-mcp/1`), not an MCP standard:
40
+
41
+ - The page publishes `globalThis[Symbol.for('browser-mcp/1')] = { protocol: 'browser-mcp/1', instance, handle(text) }` — non-enumerable, non-writable, a fresh random `instance` per provider. `handle` takes one JSON-RPC message as text and resolves to the response text (or `null`).
42
+ - An optional `<meta name="browser-mcp" content="browser-mcp/1">` says a provider is coming. With it, the transport waits (bounded, observable polling) for asynchronous initialization; without it, a page that has no endpoint after `load` fails fast with `NO_PROVIDER`. The meta tag carries no tool definitions — the only tool list is the one the live provider returns.
43
+
44
+ `connectBrowserMcp(url)` navigates, requires the final URL to stay on the requested origin (plus explicit `allowedOrigins`), waits for the endpoint, calls `server/discover` (refusing a provider without 2026-07-28 or without tools) and `tools/list`, then admits well-formed tools (name syntax, object input schema, bounded text) through the caller's `allowTools` policy. Repository subpaths work: every URL the application uses is relative.
45
+
46
+ ## Lifecycle
47
+
48
+ | State | Kept separately |
49
+ | --- | --- |
50
+ | Agent state | The MCP host's conversation; never in the page. |
51
+ | Transport state | One connection = one origin + one provider `instance`; pending requests with ids, deadlines and abort signals. |
52
+ | Browser context | An isolated Playwright context per connection by default (no ordinary profile, cookies, permissions or downloads); `userDataDir` opts into a persistent profile. |
53
+ | Application state | Whatever the page keeps (divlet: one in-memory accepted project state, its history and selection). |
54
+ | Persistent storage | The page's own (divlet: IndexedDB in that browser profile). |
55
+
56
+ Any main-frame navigation or reload, or a replaced provider instance, makes the connection **stale**: every pending call is rejected with `PROVIDER_RELOADED` (it may or may not have completed; it is never retried), new calls fail with `STALE`, and `reconnect()` waits for the new provider and lists its tools again. Navigation to an untrusted origin, page close, crash or context loss **closes** the connection (`CLOSED`); authority is never transferred. `--connect` reconnects a stale page on the next call and emits `notifications/tools/list_changed` when the tool set changed. Pending requests are bounded (`maxPending`), each has a timeout and cancellation sends `notifications/cancelled` to the page.
57
+
58
+ ## Security boundaries
59
+
60
+ - **No host privilege from the page.** The transport exposes no binding, function or route to the page; it only evaluates a fixed exchange function and reads its JSON text. Website code cannot call anything on the agent's machine, and the hostile-page test checks that no host functions appear.
61
+ - **Website text is data.** Tool descriptions, discovery instructions, results and errors are untrusted. `--connect` prefixes each description with its origin and "supplied by that website", drops behaviour annotations (claims, not guarantees), returns only text and JSON content (no website-chosen images, links or embedded resources) and caps text size.
62
+ - **Explicit trust.** The URL the user configures is the trusted origin; redirects elsewhere fail with `ORIGIN_CHANGED`; `--allow-tools` narrows what is offered. Per-call consent is the agent host's own tool-approval prompt, which applies to these tools like any other MCP tools.
63
+ - **Bounded traffic.** Request and response text are size-limited in both directions, replies must match the request id and JSON-RPC shape (`PROTOCOL_VIOLATION` otherwise), and `resultType`s other than `complete` are refused.
64
+ - **Isolation.** A fresh browser context per connection by default; nothing from the user's ordinary browser profile is used.
65
+
66
+ The page's own power is the browser's: it can do in its origin what any website can, and its storage is that browser profile's. A `--connect` session therefore shares state with a human only when both use the same page (the window `--connect` shows by default) — not automatically with the user's everyday browser.
67
+
68
+ ## divlet on GitHub Pages
69
+
70
+ `scripts/pages-build.mjs` builds the public set: the real board client (`web/`), a bundled browser host (`core/lib/browser-board.ts`: `BrowserRuntime`, the board's writer/preview/export ports, the MCP provider and WebMCP registration), `project.divlet` (the canonical landing), `styles.json` (CSS compiled at build time by the local engine), a static `preview-bridge.js`, the prepared landing page under `page/`, and `.nojekyll`. `index.html` carries a hash-based meta CSP (Pages cannot send headers) and the `browser-mcp` hint.
71
+
72
+ - **One writer, shared vectors.** `BrowserRuntime` runs the same `read_divlet`, `patch_divlet`, `edit_divlet`, `frame_divlet`, `undo_divlet` and `mark_divlet` from `core/lib/tools.ts` over the shared validation, operations, component expansion, preparation, diff and acceptance modules. `core/tests/browser-runtime.test.js` drives the same calls through it and the Node runtime and requires identical models, pages, refusals, history and serialized projects. Its orchestration still mirrors `runtime.js` rather than sharing one writer class (backlog).
73
+ - **Human and agent edit one state.** Board edits go through the same writer; selection and measured geometry are shared, so an agent's `view:selection` and `view:preview` see what the visitor selected and sees. Agent calls show in the activity plaque and History as `agent` changes.
74
+ - **First visit.** A visitor with no saved view of the project reads the landing frame expanded at the window's width (a phone gets the phone layout); "Edit on the board" goes back to the canvas, whose view is then remembered. The expanded page keeps receiving live renders, so agent edits appear while it is read.
75
+ - **Storage.** The visitor's copy lives in IndexedDB (key: page path + project), tied to the fingerprint of the published source; a new publication starts a fresh copy. "Download project file" saves it as a `.divlet` for the local host. Undo history belongs to the page instance.
76
+ - **Absent here, by design.** Opening other files, save-as, build output, image/font upload and embedded asset bytes (`ASSETS_UNAVAILABLE`), Markdown sites, utility compilation (new utility classes are reported as unstyled; use frame CSS or inline style) and the board conversation (`agent.conversation: false`; the Agent tab offers the `--connect` command instead).
77
+
78
+ Deploy: push to `main`; `.github/workflows/landing.yml` checks the landing source/cache, runs the unit checks and `web/tests/pages.mjs` against the built set on a static server, then copies the set to the `gh-pages` root only when its fingerprint (`.pages-hash`) changed. Configure Pages to serve the `gh-pages` branch root. A different static host works the same way: serve the directory from `node scripts/pages-build.mjs --copy <dir>` under any path.
79
+
80
+ ## Reuse
81
+
82
+ **Another SPA.** Keep your operations; describe them once:
83
+
84
+ ```js
85
+ import { defineTools, createBrowserMcpProvider, exposeBrowserMcp, registerWebMcpTools } from '@divlet/web/mcp';
86
+ const tools = defineTools([{ name: 'todos.add', description: 'Add a todo.', inputSchema: { type: 'object', properties: { text: { type: 'string', minLength: 1 } }, required: ['text'], additionalProperties: false }, execute: ({ text }) => app.addTodo(text) }]);
87
+ exposeBrowserMcp(createBrowserMcpProvider({ name: 'todos', version: '1.0.0', tools }));
88
+ await registerWebMcpTools(tools); // optional; a no-op without document.modelContext
89
+ ```
90
+
91
+ Add `<meta name="browser-mcp" content="browser-mcp/1">` if the provider appears after asynchronous start-up. `web/tests/fixtures/browser-mcp-site/` is a complete example (IndexedDB todo list whose UI and tools share one domain module).
92
+
93
+ **Another agent harness.** Use the connection directly, or hand the SDK-shaped transport to an MCP client:
94
+
95
+ ```js
96
+ import { connectBrowserMcp, browserMcpTransport } from '@divlet/core/browser-mcp';
97
+ const site = await connectBrowserMcp('https://example.github.io/app/', { headless: false, allowTools: ['todos.add'] });
98
+ await site.callTool('todos.add', { text: 'From an agent' });
99
+ // or: await new Client(info, { versionNegotiation: { mode: { pin: '2026-07-28' } } }).connect(browserMcpTransport(site));
100
+ ```
101
+
102
+ `playwright-core` is an optional peer of `@divlet/core` (a dependency of `@divlet/mcp`); the system Chrome is used first, then Playwright's Chromium, then Edge.
103
+
104
+ ## Not supported
105
+
106
+ Resources, prompts, subscriptions (`listChanged` from the page), multi-round-trip input, tasks, progress and logging; pagination (one page of tools); non-Chromium browsers for the transport; attaching to an already running user browser; real WebMCP interoperability beyond a draft-shaped stand-in (no shipping browser was available to test); offline reload of the static board (no Service Worker); multi-tab ownership of one browser copy; and HTTP-reachable MCP for clients that cannot run an adapter.
107
+
108
+ Tests: `web/tests/mcp-provider.test.js` (protocol, SDK interop, bounds, WebMCP), `web/tests/browser-mcp.mjs` (real Chromium against static fixtures: correlation, timeouts, cancellation, reload/close, origin, hostile pages), `core/tests/browser-runtime.test.js` (writer parity), `web/tests/pages.mjs` (the built site under a prefix, agent by URL, persistence) and `mcp/tests/connect.test.js` / `connect.e2e.mjs` (the stdio adapter, both eras, through the SDK's stdio client).
@@ -1,16 +1,16 @@
1
1
  # Browser runtime, offline demo and agent connectivity
2
2
 
3
- **Status: incremental implementation, not a completed browser runtime or a frozen API.** Shared standalone-frame validation, ordinary patches and inspection now execute in native browser ESM as well as Node. The board, full-project/component/resource handling and persistence still require their Node host. This note separates the portable semantic kernel direction, static-hosted demo and browser-agent transports. Implementation proceeds through bounded validated slices, not a wholesale runtime rewrite or a new release prerequisite. [BACKLOG.md](../BACKLOG.md) owns remaining work. The [web-native contract](web-native.md) remains the semantic authority.
3
+ **Status: incremental implementation, not a completed offline editor or a frozen API.** Shared standalone-frame validation, ordinary patches and inspection execute in native browser ESM as well as Node. A browser-local whole-project writer (`core/lib/browser-runtime.ts`: frames, components, structural edits, frame lifecycle, guarded Undo/Redo, work marks) now runs the shared contract tools with Node parity vectors, and the real board runs on it as a static site — the GitHub Pages board, which is also its own MCP provider ([browser-hosted MCP](browser-mcp.md)). Embedded asset bytes, fonts, sites, utility compilation, offline reload and multi-tab ownership still require the Node host or remain open. This note separates the portable semantic kernel direction, static-hosted demo and browser-agent transports. Implementation proceeds through bounded validated slices, not a wholesale runtime rewrite or a new release prerequisite. [BACKLOG.md](../BACKLOG.md) owns remaining work. The [web-native contract](web-native.md) remains the semantic authority.
4
4
 
5
5
  ## Current evidence
6
6
 
7
7
  - Generated project pages already work on ordinary static hosting. That does not mean the editor can run without its host.
8
- - Board bootstrap, state/change subscriptions and cached frame reads now use `BoardReadSession`. `web/lib/host.js` still composes the native HTTP/SSE adapter; edits, auxiliary reads, preview resources and other startup work still use native routes. Copying `web/` to Pages would leave those dependencies unresolved.
8
+ - Board bootstrap, state/change subscriptions and cached frame reads use `BoardReadSession`; writes, previews, exports and component lists use `BoardWriter`, `BoardPreviews`, `BoardExports` and `BoardComponents`. `web/lib/host.js` composes the native HTTP/SSE adapters, or a complete `BoardHostComposition` published before startup (the static browser host), with no fallback to native routes. Native-only affordances (open/save-as, build output, image/font upload, the board conversation) are absent in the browser host by `native`/capability checks, not disabled.
9
9
  - `core/lib/validation.ts` owns shared domain errors, JSON/object/key/text checks and bounded UTF-8 budget admission without runtime imports or Node globals. Existing model-facing error/function identities remain shared.
10
10
  - `core/lib/asset-reference.ts` shares portable declared-name/path checks with the Node byte decoder and implements the explicit `asset:` URI codec and bounded relative dependency resolution. Native-browser ESM runs the same reference witnesses without bundling or import rewriting. The frame model also uses that codec for resource-only attributes, including head icons and SVG `use`, with shared navigation/form/context refusals and tag-change safety. Controlled native-frame preparation followed by declared closure proves real image/SVG-use loading under a prefix. The Node writer now admits and projects the project asset tree, but that decoder still needs its Node host; this is not browser-local project storage or font upload UI.
11
11
  - `model.ts` binds style lowering through `model-styles.ts`, which retains the existing eager Node engine. The package's standard browser file mapping swaps only that port for `browser-styles.ts`, an explicit `NO_STYLE_ENGINE` refusal. Browser validation/native `style` maps do not imply Tailwind compilation or declarative `styles` lowering. No new public subpath or serialized capability field is introduced.
12
12
  - CSS admission uses the same PostCSS grammar parser with source-map interpretation disabled. Browser bundling respects PostCSS's own optional-feature browser mappings; it adds no Node-global polyfill or alternate validator. `scripts/browser-model.mjs` owns the source/installed verification build profile and guards against concrete engine/writer/Tailwind inputs and external imports. It returns bytes in memory, not a shipped standalone browser SDK artifact.
13
- - Remaining host closures include `project.ts` (`node:path`), `codec.ts` (`Buffer`), concrete provider bootstrap and the asset decoder/canonical MIME slice (`node:util`/`Buffer`). Browser compatibility cannot be inferred from TypeScript compilation.
13
+ - `project.ts`, `codec.ts`, `diff.ts`, `ops.ts`, `prepare.ts`, `acceptance.ts` and `tools.ts` no longer reach Node built-ins. Concrete provider bootstrap remains Node-only, and canonical MIME parsing maps to an explicit `ASSETS_UNAVAILABLE` refusal in browser builds (no approximation of Node's WHATWG MIME canonicalization). Browser compatibility cannot be inferred from TypeScript compilation; `scripts/pages-build.mjs` refuses a host bundle that reaches Node/provider modules.
14
14
  - The current style/provider, component and format-transition gaps remain relevant. An offline fork must not silently establish different cascade, override, resource or renderer rules.
15
15
 
16
16
  ## Recommended boundary: portable semantics, explicit hosts
@@ -47,7 +47,7 @@ Preview measurements first update the client's observation cache and local event
47
47
 
48
48
  `web/lib/board-host.ts`, public as `@divlet/web/host`, separately types optional History and source-update services. They can carry native authority (`navigate`, reimport/open) and are never added to the read session. History reads are omitted when its service is absent; restore controls additionally require both the host's restore method and the state's navigation advertisement. A reader-only service may supply audit data without restore controls. Source-change scanning and its entire notification action workflow require the source-update service, so absence produces neither a scan nor an unsupported import prompt. Native adapters keep the same routes, captured History targets, generation guards, scan-failure policy and deduplicated notifications.
49
49
 
50
- These gates are proven with actual activity/project/store modules under fixed test compositions; the canvas/sidebar/chat collaborators in those consumer checks are inert doubles, not a complete board. This is not yet a host-replaceable offline board: preview paths and remaining mutation affordances still need actual host capabilities and semantic ports or absence, not synthetic HTTP routing.
50
+ These gates are proven with actual activity/project/store modules under fixed test compositions. The complete board also runs on the static browser composition (`core/lib/browser-board.ts`), proven in Chromium against the built Pages set under a repository prefix (`web/tests/pages.mjs`). That is a host-replaceable board, not an offline one: there is no Service Worker, so a first visit and every reload need the static files.
51
51
 
52
52
  ## Public prepared-page inspection data
53
53
 
@@ -82,6 +82,8 @@ The core must not import a marketing project. A demo is composition-owned and op
82
82
 
83
83
  ## Service Worker is not a public MCP endpoint
84
84
 
85
+ Implemented route (see [browser-hosted MCP](browser-mcp.md)): the agent side supplies the browser. `divlet-mcp --connect <url>` or `@divlet/core/browser-mcp` opens the static site in a managed Chromium and exchanges MCP 2026-07-28 messages with the page's provider by direct evaluation; nothing listens, relays or is deployed. That is an integrated agent-side adapter, not a generally reachable MCP server, and it does not change the points below.
86
+
85
87
  A Service Worker intercepts requests for its controlled browser clients within its origin/scope. It does not accept arbitrary inbound TCP connections or cause a remote MCP client's requests to GitHub Pages to execute in the visitor's browser. It can emulate a local route for a controlled page; that is not a generally reachable MCP HTTP server. Background lifetime also cannot be assumed.
86
88
 
87
89
  There are two useful, distinct agent routes:
@@ -1,6 +1,6 @@
1
1
  # npm publication
2
2
 
3
- All five **0.0.1** packages are published on npm, with frozen-archive integrity and clean registry installations verified. **0.0.2** is the candidate for the first live CI/OIDC publication; do not treat it as published until all five registry checks and the installed-host CI job pass. This guide owns publication safeguards and the first-publication procedure below; it is not permission to publish again. A prepared tree or versioned changelog is not a completed registry publication. Publishing packages, pushing release refs and deploying the landing page require explicit maintainer authorization. Packaging does not freeze or cut over the native schema.
3
+ All five **0.0.2** packages are published on npm through GitHub Actions/OIDC, with frozen-archive integrity, provenance metadata and clean registry CLI/MCP/Pi installations verified. **0.0.3** is the next candidate; do not treat it as published until all five registry checks and the installed-host CI job pass. This guide owns publication safeguards and the first-publication procedure below; it is not permission to publish again. A prepared tree or versioned changelog is not a completed registry publication. Publishing packages, pushing release refs and deploying the landing page require explicit maintainer authorization. Packaging does not freeze or cut over the native schema.
4
4
 
5
5
  npm refused the original unscoped `divlet` name as too similar to `figlet`. The maintainer approved `@divlet/core` and a separately owned `@divlet/web`; the old four-package archives are historical candidates, not scoped substitutes. Never edit archive manifests in place or retry the rejected publisher.
6
6
 
@@ -22,16 +22,16 @@ npm refused the original unscoped `divlet` name as too similar to `figlet`. The
22
22
 
23
23
  Core's HTTP composition imports web's public asset locator; generic model/format modules do not import web. Web's portable contracts own opaque host payloads, not project validation/writes, and have no type/JSDoc back-edge to core. Browser modules use host HTTP/SSE and preview ports, not imports of the Node runtime. Backend plugins consume core contracts; client plugins use the served web module graph. See [architecture](architecture.md).
24
24
 
25
- The root owns `@divlet/core`, `web/` owns `@divlet/web`, and `cli/`, `mcp/` and `pi/` own the adapters, without wrappers. All five versions match. The 0.0.2 candidate adapters pin `@divlet/core@0.0.2`; core pins `@divlet/web@0.0.2`. Core has no bins, Pi manifest/peer requirements or `./pi` export. Its tarball excludes the web and adapter implementations. Web owns its only client copy and portable declarations. Built-in plugins remain bundled in core, not separately published.
25
+ The root owns `@divlet/core`, `web/` owns `@divlet/web`, and `cli/`, `mcp/` and `pi/` own the adapters, without wrappers. All five versions match. The 0.0.3 candidate adapters pin `@divlet/core@0.0.3`; core pins `@divlet/web@0.0.3`. Core has no bins, Pi manifest/peer requirements or `./pi` export. Its tarball excludes the web and adapter implementations. Web owns its only client copy and portable declarations. Built-in plugins remain bundled in core, not separately published.
26
26
 
27
- Node.js 22.19 or later is required. Pi is not required for CLI/MCP. Shared configurations should pin adapter versions, including `npm:@divlet/pi@0.0.2` after the candidate is published. For MCP:
27
+ Node.js 22.19 or later is required. Pi is not required for CLI/MCP. Shared configurations should pin adapter versions, including `npm:@divlet/pi@0.0.3` after the candidate is published. For MCP:
28
28
 
29
29
  ```json
30
30
  {
31
31
  "mcpServers": {
32
32
  "divlet": {
33
33
  "command": "npx",
34
- "args": ["-y", "@divlet/mcp@0.0.2", "--cwd", "/absolute/path/to/workspace"]
34
+ "args": ["-y", "@divlet/mcp@0.0.3", "--cwd", "/absolute/path/to/workspace"]
35
35
  }
36
36
  }
37
37
  }
@@ -67,7 +67,7 @@ Align the five manifests, exact pins, lockfile and candidate notes, then create
67
67
  Select Actions → **Release** → Run workflow, branch `main`, and supply the tag. The equivalent explicit publication action is:
68
68
 
69
69
  ```bash
70
- gh workflow run release.yml --ref main -f release_tag=v0.0.2
70
+ gh workflow run release.yml --ref main -f release_tag=v0.0.3
71
71
  ```
72
72
 
73
73
  The workflow has three boundaries:
@@ -82,7 +82,7 @@ Keep the `npm-candidate-<run-id>` archive set and `npm-outcome-<run-id>-<attempt
82
82
 
83
83
  Publication jobs **cannot be rerun automatically**: attempts after the first are refused, and a fresh dispatch checks prior runs of this workflow for the same tag, including all job attempts. A previously started publisher requires explicit operator recovery even if no registry version is visible. Do not delete run history or edit the run title to bypass that guard. Runs whose publisher was skipped because validation failed do not count as upload attempts; use a fresh dispatch after fixing validation while preserving the tag/source constraint. A failed registry verification after all uploads is not permission to publish again; rerun only the read-only verification job.
84
84
 
85
- For partial or uncertain outcomes, inspect the original logs, frozen bytes, receipt and fresh registry metadata before choosing an explicit manual recovery or a new aligned version. Do not rebuild a failed release's archives, overwrite accepted versions, infer token rights from `npm whoami`, or run manual and CI publishing concurrently. A local/mock pass proves guard behavior, not a successful live OIDC exchange; activation and the first real CI release remain external checks.
85
+ For partial or uncertain outcomes, inspect the original logs, frozen bytes, receipt and fresh registry metadata before choosing an explicit manual recovery or a new aligned version. Do not rebuild a failed release's archives, overwrite accepted versions, infer token rights from `npm whoami`, or run manual and CI publishing concurrently. A local/mock pass proves guard behavior, not a successful live OIDC exchange. The first real CI release was verified for 0.0.2; changes to workflow identity, environment bindings or account policy still require their own live authorization check.
86
86
 
87
87
  ## Readiness gates
88
88
 
package/docs/testing.md CHANGED
@@ -5,12 +5,15 @@ Development runs source files, not `dist/`. Root `npm test`, start/demo and Pi/M
5
5
  | Check | Command | Covers |
6
6
  | --- | --- | --- |
7
7
  | Unit | `node scripts/source.mjs --test` | Model, project, codec, styles, runtime, server, launcher, import, history, safety, output ownership, static/site builds, the Pi adapter (`pi/tests`, with CLI/MCP tests in their own adapter packages), plugins (`plugins/*/tests`) and package contents. |
8
- | Browser (all) | `npm run test:browser:all` | The 34 real Chromium suites in `web/tests/`: `validation`, `run`, `responsive`, `layout-wrap`, `site`, `fullscreen`, `settings`, `interact`, `selection-completion`, `size-editor`, `controls`, `history`, `features`, `agent-status`, `chat`, `spacing`, `toolbars`, `appearance`, `font-containers`, `reimport`, `chrome`, `fluid`, `snapshots`, `launcher`, `launcher-errors`, `native-collections`, `projects`, `security`, `static`, `native-default`, `landing`, `preview-links`, `text-edit-render`, `toast-priority`. Most have a `test:<name>` script (`test:spacing`, `test:chrome`, …); `appearance`, `reimport` and a few others run only through this command or through a combined script (`test:controls`, `test:launcher`, `test:source`). |
8
+ | Browser (all) | `npm run test:browser:all` | The 36 real Chromium suites in `web/tests/`: `validation`, `run`, `responsive`, `layout-wrap`, `site`, `fullscreen`, `settings`, `interact`, `selection-completion`, `size-editor`, `controls`, `history`, `features`, `agent-status`, `chat`, `spacing`, `toolbars`, `appearance`, `font-containers`, `reimport`, `chrome`, `fluid`, `snapshots`, `launcher`, `launcher-errors`, `native-collections`, `projects`, `security`, `static`, `native-default`, `landing`, `preview-links`, `text-edit-render`, `toast-priority`, `browser-mcp`, `pages`. Most have a `test:<name>` script (`test:spacing`, `test:chrome`, …); `appearance`, `reimport` and a few others run only through this command or through a combined script (`test:controls`, `test:launcher`, `test:source`). |
9
9
  | History | `npm run test:history` | Core point/replay and board HTTP regressions, followed by the dedicated real Chromium whole-project acceptance suite (`history.mjs`); basic row/keyboard/phone/race checks also live in `controls.mjs`. |
10
10
  | PEN fidelity | `npm run test:pen-fidelity -- /absolute/path/to/file.pen` | Renders every root frame of a caller-supplied `.pen` (no authored PEN example is bundled) from the native projection and from the converted Divlet frame in Chromium and requires identical pixels; a flat, single-colour screenshot counts as a failure too, because an identical blank canvas would prove nothing. `PI_DIVLET_FIDELITY_LIMIT=N` samples the first N frames. The 68-frame, 3 195-instance result quoted in the changelog came from a design that is not part of this repository and cannot be reproduced from it. |
11
11
  | Release | `npm run test:release` | Packs five tarballs (`@divlet/web`, `@divlet/core`, `@divlet/cli`, `@divlet/mcp`, `@divlet/pi`) and installs them into an empty directory. Checks the public library, CLI, board/client/plugin serving and installed MCP initialization, tools and board opening **without Pi**, including the installed CLI/MCP font scenario in Chromium before adding Pi peers. Checks the Pi adapter package entry and manifest against the installed product; this is not a real Pi session. Core excludes adapter and web implementation trees; web owns its client/portable ports with no core dependency. Both exclude tests and caches. Exercises the installed opt-in versioned compiler. Needs Chromium and the npm registry (or its cache); publishes nothing. |
12
12
  | Release + real Pi | `npm run test:release:pi` | Adds required real Pi RPC and installed-font Chromium acceptance to `test:release`, using compiled consumer exports and an isolated test profile/mock provider. A missing or broken `pi` CLI fails rather than skips. Requires Chromium and a working local Pi; validated with Pi 1.5.2 on Node 22.19. Installs no global extension and touches no active session or account. |
13
13
  | MCP | `npm run test:mcp` | Spawns the MCP adapter source entry through the source-condition runner and speaks its stdio transport for real: `initialize`, `tools/list`, tool calls with a domain refusal, two clients sharing one board (attachment, not a second writer), concurrent writes refused by revision, shutdown on EOF and board de-registration. |
14
+ | Browser MCP | `npm run test:browser-mcp` | `@divlet/core/browser-mcp` against bundled static fixture pages under a repository prefix in real Chromium: connect by URL, discovery, calls observed in the page UI, the official SDK client over the browser transport, concurrent correlation, timeouts and cancellation reaching the page, reload invalidation and reconnect with IndexedDB persistence, page close, fast failure without a provider and waiting for an asynchronous one, cross-origin redirect/navigation refusal, a hostile page (no host bindings, oversized and mismatched replies, instruction-like descriptions kept as data) and WebMCP through a draft-shaped stand-in. Chromium ships no native WebMCP, so real browser-agent interoperability is not proven. |
15
+ | Pages | `npm run test:pages` | The `scripts/pages-build.mjs` set served by a file-only server under `/divlet/`: the real board opens the landing with only static GETs and no errors, native-only affordances are absent, export runs in the page, an agent connects by URL, edits what the visitor sees, refusals stay tool errors, visitor and agent edits share one writer and History, and the browser copy survives a reload. |
16
+ | Connect | `npm run test:connect` | `divlet-mcp --connect` started by the official SDK stdio client against the built Pages set, in both protocol eras. `mcp/tests/connect.test.js` (in `test:mcp`) covers the stdio side without a browser. |
14
17
  | Contract | `npm run test:contract` | One tool list; the JSON Schema and TypeBox renderings agreeing on required parameters; the agent vocabulary; a board running only the tools it publishes; the board registry (one live owner, stale entries removed). |
15
18
  | Pi RPC | `npm run test:pi` | A real Pi in RPC mode with a scripted model: a board request reaches a divlet tool, changes the project and returns as a reply. Skipped when the `pi` CLI is absent. |
16
19
 
@@ -24,6 +27,10 @@ The installed font scenario resolves only compiled `@divlet/core` public exports
24
27
 
25
28
  `npm run test:toast-priority` uses the browser clock with a connected but idle test host: the delayed agent-start warning must not replace a currently actionable Undo toast, and appears after that toast expires. This regression is included in the full source browser battery.
26
29
 
30
+ ## Browser writer parity
31
+
32
+ `node scripts/source.mjs --test core/tests/browser-runtime.test.js` opens the landing in the Node runtime and in `BrowserRuntime` (with the same style compiler) and drives the same contract tool calls through both: patches, a no-op, stale refusals, inserts including sanitized HTML, move/wrap, invalid operations, components and dependent re-renders, frame create/duplicate/delete refusal, project tokens/CSS, Undo/Redo (global and frame-scoped), marks and reads. Models, rendered pages, component uses, results, refusal codes, history summaries (except the Node-only trash note) and the serialized project must be identical. It also checks what the browser writer refuses (assets, sites, `open_divlet`) and that a failing store keeps the accepted change with a diagnostic. It does not prove asset, font or site parity, which the browser writer does not implement.
33
+
27
34
  ## Portable kernel slice
28
35
 
29
36
  `node scripts/source.mjs --test core/tests/validation.test.js core/tests/model-browser.test.js` checks shared admission/error identity, UTF-16/UTF-8 budgets (with an independent Node `Buffer.byteLength` oracle), full standalone-frame candidate/inspection witnesses, strict HTML round trips and cold default-provider behavior. `npm run test:browser:kernel` runs 38 primitive, 48 UTF-8, 53 frame, 111 declared-asset-reference and 3 immutable snapshot-session witnesses plus 12 actual-store local/shared-selection and 12 optional-service consumer checks as native browser ESM under a repository prefix. Primitive source is type-erased only; the model uses the shared esbuild profile in `scripts/browser-model.mjs` with the package's explicit browser style-port mapping and PostCSS's own optional-feature browser mappings. The actual browser has no Node globals/polyfills, model backend API, SSE route or dependency rescue. It verifies both the shared successful/refused operations and explicit absent declarative lowering. The browser entry is included once in the full battery. The release smoke separately bundles the installed compiled package and evaluates its frame/lowering behavior in Node; that is not installed-bundle browser execution. These checks do not establish full-project/component expansion, shipped native-browser SDK delivery or an offline editor.
@@ -18,9 +18,9 @@ Edit the project through its board or the validated source. CSS, text, literal S
18
18
 
19
19
  ## GitHub Pages
20
20
 
21
- The existing Landing workflow uses Node.js 22 to check the nested source/cache and verify freshness in a sandbox, then copies prepared output to the root of `gh-pages` only when its public fingerprint differs. The shared `scripts/landing-hash.mjs` owns both the file list and the fingerprint: `index.html` and owned portable image/font/CSS resources under `_assets/`, plus hierarchical `assets/` resources when their owned MIME is compatible with conventional static hosting. Missing or incompatible MIME refuses the latter rather than guessing from a name. The native writer supplies this provenance, but this prepared-copy policy does not establish actual headers on every static host. Sidecars, the ownership manifest, private data, locks, trash and unowned files never enter the public set.
21
+ The Pages workflow uses Node.js 22 to check the nested source/cache and verify freshness in a sandbox, tests the built site in Chromium, then copies the Pages set to the root of `gh-pages` only when its fingerprint differs. `scripts/pages-build.mjs` owns that set: the real board as a static application that opens this project in the visitor's browser and is its own MCP provider ([browser-hosted MCP](../docs/browser-mcp.md)), plus the prepared page under `page/`. `scripts/landing-hash.mjs` owns the prepared page's file list: `index.html` and owned portable image/font/CSS resources under `_assets/`, plus hierarchical `assets/` resources when their owned MIME is compatible with conventional static hosting. Missing or incompatible MIME refuses the latter rather than guessing from a name. The native writer supplies this provenance, but this prepared-copy policy does not establish actual headers on every static host. Sidecars, the ownership manifest, private data, locks, trash and unowned files never enter the public set.
22
22
 
23
- Publication adds `.nojekyll` so GitHub serves `_assets/`, plus `.landing-hash` as the branch's fingerprint record. It does not patch the page or compile a second implementation. A metadata-only regeneration with identical public bytes produces no deployment commit. Serve Pages from the `gh-pages` branch root; configuration, remote synchronization and an actual deployment still need maintainer authorization. Preparing this project does not push or deploy it.
23
+ Publication adds `.nojekyll` so GitHub serves `_assets/`, plus `.pages-hash` as the branch's fingerprint record. It does not patch the page or compile a second implementation. A metadata-only regeneration with identical public bytes produces no deployment commit. Serve Pages from the `gh-pages` branch root; configuration, remote synchronization and an actual deployment still need maintainer authorization. Preparing this project does not push or deploy it.
24
24
 
25
25
  The native project and frame use unfrozen `schemaVersion: 1`, including explicit optional `head.description` and `head.icon`. Generic typed binary-resource tables and cache-independent file-asset storage remain unimplemented FORMAT-ASSETS work. Static-host navigation is tested; expanded viewing opens the prepared page directly. The separate scaled-canvas Play preview still has a tracked `<base>` fragment-navigation limitation; a portable export does not prove that editor-preview path.
26
26
 
@@ -1834,7 +1834,7 @@
1834
1834
  "text-188": {
1835
1835
  "id": "text-188",
1836
1836
  "type": "text",
1837
- "content": ", supplies the runtime and plugins, with browser assets in @divlet/web and CLI/MCP/Pi adapters. Requires Node.js 22.19 or later.\n Version 0.0.2 is an early release candidate; the native project format remains a pre-freeze draft.",
1837
+ "content": ", supplies the runtime and plugins, with browser assets in @divlet/web and CLI/MCP/Pi adapters. Requires Node.js 22.19 or later.\n Version 0.0.3 is an early release candidate; the native project format remains a pre-freeze draft.",
1838
1838
  "metadata": {}
1839
1839
  },
1840
1840
  "text-189": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divlet/core",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "A browser layout board for agents: one project file, live frames, reusable components; the same tools through Pi and MCP",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -60,11 +60,17 @@
60
60
  "divlet-source": "./core/lib/engine.ts",
61
61
  "default": "./dist/core/lib/engine.js"
62
62
  },
63
+ "./browser-mcp": {
64
+ "divlet-source": "./core/lib/browser-mcp.ts",
65
+ "default": "./dist/core/lib/browser-mcp.js"
66
+ },
63
67
  "./skill": "./skills/divlet/SKILL.md"
64
68
  },
65
69
  "browser": {
66
70
  "./core/lib/model-styles.ts": "./core/lib/browser-styles.ts",
67
- "./dist/core/lib/model-styles.js": "./dist/core/lib/browser-styles.js"
71
+ "./dist/core/lib/model-styles.js": "./dist/core/lib/browser-styles.js",
72
+ "./core/lib/media-type.ts": "./core/lib/browser-media-type.ts",
73
+ "./dist/core/lib/media-type.js": "./dist/core/lib/browser-media-type.js"
68
74
  },
69
75
  "files": [
70
76
  "dist",
@@ -100,7 +106,7 @@
100
106
  "test:history": "node scripts/source.mjs --test core/tests/history-line.test.js core/tests/history.test.js core/tests/server.test.js && node scripts/source.mjs web/tests/history.mjs",
101
107
  "test:settings": "node scripts/source.mjs web/tests/settings.mjs",
102
108
  "test:defaults": "node scripts/source.mjs web/tests/native-default.mjs",
103
- "test:browser:all": "node scripts/source.mjs web/tests/validation.mjs && node scripts/source.mjs web/tests/run.mjs && node scripts/source.mjs web/tests/responsive.mjs && node scripts/source.mjs web/tests/layout-wrap.mjs && node scripts/source.mjs web/tests/site.mjs && node scripts/source.mjs web/tests/fullscreen.mjs && node scripts/source.mjs web/tests/settings.mjs && node scripts/source.mjs web/tests/interact.mjs && node scripts/source.mjs web/tests/selection-completion.mjs && node scripts/source.mjs web/tests/size-editor.mjs && node scripts/source.mjs web/tests/controls.mjs && node scripts/source.mjs web/tests/history.mjs && node scripts/source.mjs web/tests/features.mjs && node scripts/source.mjs web/tests/agent-status.mjs && node scripts/source.mjs web/tests/chat.mjs && node scripts/source.mjs web/tests/spacing.mjs && node scripts/source.mjs web/tests/toolbars.mjs && node scripts/source.mjs web/tests/appearance.mjs && node scripts/source.mjs web/tests/font-containers.mjs && node scripts/source.mjs web/tests/reimport.mjs && node scripts/source.mjs web/tests/chrome.mjs && node scripts/source.mjs web/tests/fluid.mjs && node scripts/source.mjs web/tests/snapshots.mjs && node scripts/source.mjs web/tests/launcher.mjs && node scripts/source.mjs web/tests/launcher-errors.mjs && node scripts/source.mjs web/tests/native-collections.mjs && node scripts/source.mjs web/tests/projects.mjs && node scripts/source.mjs web/tests/security.mjs && node scripts/source.mjs web/tests/static.mjs && node scripts/source.mjs web/tests/native-default.mjs && node scripts/source.mjs web/tests/landing.mjs && node scripts/source.mjs web/tests/preview-links.mjs && node scripts/source.mjs web/tests/text-edit-render.mjs && node scripts/source.mjs web/tests/toast-priority.mjs",
109
+ "test:browser:all": "node scripts/source.mjs web/tests/validation.mjs && node scripts/source.mjs web/tests/run.mjs && node scripts/source.mjs web/tests/responsive.mjs && node scripts/source.mjs web/tests/layout-wrap.mjs && node scripts/source.mjs web/tests/site.mjs && node scripts/source.mjs web/tests/fullscreen.mjs && node scripts/source.mjs web/tests/settings.mjs && node scripts/source.mjs web/tests/interact.mjs && node scripts/source.mjs web/tests/selection-completion.mjs && node scripts/source.mjs web/tests/size-editor.mjs && node scripts/source.mjs web/tests/controls.mjs && node scripts/source.mjs web/tests/history.mjs && node scripts/source.mjs web/tests/features.mjs && node scripts/source.mjs web/tests/agent-status.mjs && node scripts/source.mjs web/tests/chat.mjs && node scripts/source.mjs web/tests/spacing.mjs && node scripts/source.mjs web/tests/toolbars.mjs && node scripts/source.mjs web/tests/appearance.mjs && node scripts/source.mjs web/tests/font-containers.mjs && node scripts/source.mjs web/tests/reimport.mjs && node scripts/source.mjs web/tests/chrome.mjs && node scripts/source.mjs web/tests/fluid.mjs && node scripts/source.mjs web/tests/snapshots.mjs && node scripts/source.mjs web/tests/launcher.mjs && node scripts/source.mjs web/tests/launcher-errors.mjs && node scripts/source.mjs web/tests/native-collections.mjs && node scripts/source.mjs web/tests/projects.mjs && node scripts/source.mjs web/tests/security.mjs && node scripts/source.mjs web/tests/static.mjs && node scripts/source.mjs web/tests/native-default.mjs && node scripts/source.mjs web/tests/landing.mjs && node scripts/source.mjs web/tests/preview-links.mjs && node scripts/source.mjs web/tests/text-edit-render.mjs && node scripts/source.mjs web/tests/toast-priority.mjs && node scripts/source.mjs web/tests/browser-mcp.mjs && node scripts/source.mjs web/tests/pages.mjs",
104
110
  "test:toast-priority": "node scripts/source.mjs web/tests/toast-priority.mjs",
105
111
  "test:features": "node scripts/source.mjs web/tests/features.mjs",
106
112
  "test:chat": "node scripts/source.mjs web/tests/chat.mjs",
@@ -129,10 +135,14 @@
129
135
  "landing:check": "node scripts/landing-build.mjs --check",
130
136
  "test:landing": "node scripts/source.mjs web/tests/landing.mjs",
131
137
  "build": "node scripts/build.mjs",
132
- "prepack": "npm run build"
138
+ "prepack": "npm run build",
139
+ "test:browser-mcp": "node scripts/source.mjs web/tests/browser-mcp.mjs",
140
+ "test:pages": "node scripts/source.mjs web/tests/pages.mjs",
141
+ "pages:build": "node scripts/source.mjs scripts/pages-build.mjs",
142
+ "test:connect": "node scripts/source.mjs mcp/tests/connect.e2e.mjs"
133
143
  },
134
144
  "dependencies": {
135
- "@divlet/web": "0.0.2",
145
+ "@divlet/web": "0.0.3",
136
146
  "@tailwindcss/node": "^4.1.0",
137
147
  "@xmldom/xmldom": "0.9.12",
138
148
  "lucide-static": "1.67.0",
@@ -144,7 +154,16 @@
144
154
  "tailwindcss": "^4.1.0",
145
155
  "yaml": "^2.9.1"
146
156
  },
157
+ "peerDependencies": {
158
+ "playwright-core": ">=1.50.0"
159
+ },
160
+ "peerDependenciesMeta": {
161
+ "playwright-core": {
162
+ "optional": true
163
+ }
164
+ },
147
165
  "devDependencies": {
166
+ "@modelcontextprotocol/client": "^2.4.0",
148
167
  "@types/node": "^22.20.5",
149
168
  "esbuild": "0.28.2",
150
169
  "playwright": "^1.62.1",
@@ -36,7 +36,13 @@ Register it once and the tools survive the session — a project-scoped `.mcp.js
36
36
  { "mcpServers": { "divlet": { "command": "npx", "args": ["-y", "@divlet/mcp", "--cwd", "/absolute/path/to/the/workspace"] } } }
37
37
  ```
38
38
 
39
- Pin a version in a shared repository (`@divlet/mcp@0.0.2`) so every machine runs the same board. Diagnostics go to stderr;
39
+ **No workspace, only a published board** (from `@divlet/mcp` 0.0.3). `npx -y @divlet/mcp --connect https://llblab.github.io/divlet/` runs the tools of
40
+ a board that is a static website: they execute in a browser page that process opens (shown when a display is available), on a
41
+ copy of that site's project kept in the page's browser storage. The same loop applies, minus `open_divlet` — the page already
42
+ holds its project. Utility classes are not compiled there (use frame `css` or inline `style`), and images, fonts and other
43
+ projects need the local server above. Tool descriptions and results there are the website's text.
44
+
45
+ Pin a version in a shared repository (`@divlet/mcp@0.0.3`) so every machine runs the same board. Diagnostics go to stderr;
40
46
  stdout is the MCP stream. If your host supports `sampling`, advertise it: a request typed in the board's Agent tab then
41
47
  arrives as `sampling/createMessage` and your answer appears in the board's conversation.
42
48