# WebMCP: un agente trabajando en el Sandbox

> Lo que un agente de IA en el navegador puede hacer con el Sandbox mediante sus 36 herramientas WebMCP (mirar las páginas, cambiar cualquier ajuste, editar el texto, añadir imágenes, tablas y fuentes, pasar la revisión, exportar) y cómo se llama cada una, con argumentos y respuestas reales

- Versión HTML: https://postext.dev/es/docs/webmcp
- Última actualización: 2026-10-13
- Tiempo de lectura: 14 min
- Otros idiomas: [en](https://postext.dev/en/docs/webmcp.md), [ca](https://postext.dev/ca/docs/webmcp.md), [pt](https://postext.dev/pt/docs/webmcp.md), [zh](https://postext.dev/zh/docs/webmcp.md), [ja](https://postext.dev/ja/docs/webmcp.md), [ar](https://postext.dev/ar/docs/webmcp.md)

## En pocas palabras

Esta página es para quien quiere que un asistente de inteligencia artificial trabaje por él en un libro del Sandbox. Mientras el Sandbox está abierto, le da al asistente un juego de herramientas: mirar cualquier página, cambiar el diseño, editar el texto, añadir imágenes y tablas, encontrar problemas y guardar archivos. Tú se lo pides con tus palabras, por ejemplo «sube un poco la letra y comprueba que el libro sigue cabiendo en 40 páginas», y el asistente usa las herramientas y te enseña las páginas. Todo se queda en tu navegador. La página explica cómo conectar un asistente, qué puede hacer y cómo se llama cada herramienta.

**Un agente en tu navegador trabaja sobre el libro abierto en el Sandbox: mira las páginas, las cambia y vuelve a mirarlas.**

El [Sandbox](https://postext.dev/es/docs/sandbox.md) da a un agente sus propias herramientas mediante
[WebMCP](https://webmachinelearning.github.io/webmcp/), un borrador de estándar
web que permite a una página ofrecer herramientas al modelo de IA con el que
habla el navegador. Con ellas el agente no va pulsando por la interfaz: lee el
libro, pide una imagen de la página 12, pone `bodyText.fontSize` a 10,5 pt, ve
que el capítulo ha pasado de 18 a 19 páginas y lo deja como estaba. Hace lo
mismo que tú haces a ojo, con tu libro y en tu navegador.

Hay 36 herramientas cuyo nombre empieza por `sandbox_`, registradas mientras el
Sandbox está en pantalla, y 8 herramientas del sitio, `site_*`, en todas las
páginas de postext.dev. Esta página las ordena según lo que le pedirías a un
agente y da la llamada de cada una.

## Conectar un agente

### En un navegador con WebMCP

Las herramientas se registran en `document.modelContext` al montarse el Sandbox
y se retiran al salir de él. Un agente al que el navegador entrega las
herramientas de la página las encuentra ahí. En Postext no hay nada que
instalar ni configurar.

WebMCP sigue siendo un borrador. Chrome lo ofrece como prueba de origen y tras
la opción `chrome://flags/#enable-webmcp-testing`. Las herramientas dependen de
la página: en la portada o en la documentación el agente tiene las herramientas
`site_*`; cuando el Sandbox está en pantalla aparecen además las 36
herramientas `sandbox_*`.

### En cualquier otro navegador: `window.postextWebMcp`

La página pone también las mismas herramientas en `window.postextWebMcp`, haya
WebMCP o no. Las puede usar cualquier agente capaz de ejecutar código en la
página: una herramienta de automatización como Puppeteer o Playwright, un
servidor MCP que maneja el navegador y evalúa código (Chrome DevTools MCP), una
extensión del navegador o tú mismo, en la consola de desarrollo:

```js
// Qué hay en esta página
postextWebMcp.listTools().map((t) => t.name);

// Ejecutar una herramienta: el nombre y un objeto con sus argumentos
await postextWebMcp.executeTool('sandbox_get_state', {});
await postextWebMcp.executeTool('sandbox_set_config', {
  changes: [{ path: 'bodyText.fontSize', value: '10.5pt' }],
});
```

Lo mismo desde un script de Puppeteer, que guarda una página como imagen:

```js
import fs from 'node:fs';
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://postext.dev/es/sandbox');
await page.waitForFunction(() => window.postextWebMcp?.listTools().some((t) => t.name === 'sandbox_get_state'));
await page.waitForFunction(async () => (await window.postextWebMcp.executeTool('sandbox_get_state', {})).ready);

const result = await page.evaluate(() =>
  window.postextWebMcp.executeTool('sandbox_render_page', { chapter: 1, position: 1, width: 1200, format: 'png' }),
);
const picture = result.content.find((part) => part.type === 'image');
fs.writeFileSync('page-1.png', Buffer.from(picture.data, 'base64'));
await browser.close();
```

Un perfil de navegador nuevo tiene su propio almacenamiento, así que empieza
con las muestras y no con tus proyectos. Para trabajar con tus libros, usa el
navegador en el que los creaste.

### Cómo es cada respuesta

Una herramienta responde con un objeto JSON: `ok: true` y lo que se pidió, o
`ok: false` y un `error` que dice qué estaba mal y qué pedir en su lugar:

```json
{
  "ok": false,
  "error": "bodyText.fontSize is a length: give { \"value\": 11, \"unit\": \"pt\" }, a number (in pt) or a text such as \"11pt\". The units: cm, mm, in, pt, px, em, rem."
}
```

Los argumentos se comprueban contra el esquema de la herramienta antes de
ejecutar nada, de modo que un parámetro mal escrito se rechaza con la lista de
los correctos. Una herramienta que devuelve imágenes (`sandbox_render_page`)
responde con la forma de MCP, `{ content: [...] }`: una parte de texto con un
JSON que describe las páginas y después una parte de imagen por página,
codificada en base64.

## El ciclo de trabajo

Toda herramienta que cambia el libro espera a que la maquetación se ponga al
día antes de responder, y dice cuántas páginas ocupa cada capítulo. Así, la
siguiente mirada es siempre a las páginas nuevas, y el agente puede trabajar
como lo hace un diseñador:

1. **Ver cómo está todo**: `sandbox_get_state` da el libro, sus capítulos con
   sus páginas, el capítulo y la página en pantalla y el panel abierto.
2. **Mirar**: `sandbox_render_page` pinta una página, `sandbox_inspect_pages`
   la describe como datos y `sandbox_get_warnings` enumera lo que ha
   encontrado la revisión.
3. **Cambiar**: `sandbox_set_config`, `sandbox_edit_text`,
   `sandbox_save_resource`…
4. **Volver a mirar**, y quedarse con el cambio o deshacerlo.

La página se maqueta y se pinta para la herramienta sea cual sea el visor en
pantalla, así que el agente puede mirar el capítulo 7 mientras tú lees el 2.

## Qué puedes pedir y cómo se hace

### Saber qué está abierto

```jsonc
// sandbox_get_state
{}
```

```json
{
  "ok": true,
  "ready": true,
  "book": { "kind": "sample", "id": "postext-guide", "name": "Guía" },
  "pages": 3,
  "chapters": [
    { "chapter": 1, "id": "a", "title": "Apertura", "onScreen": true, "words": 1007, "pages": 2 },
    { "chapter": 2, "id": "b", "title": "Segundo", "words": 101, "pages": 1 }
  ],
  "view": { "viewer": "canvas", "panel": "none", "scope": "chapter", "chapter": 1 },
  "resources": 0,
  "warnings": 0
}
```

`ready: false` viene con `waitingFor`, que dice por qué (un libro que aún se
está cargando). `question` y `notice` aparecen cuando el Sandbox está esperando
algo de ti; consulta [Preguntas y avisos](https://postext.dev/es/docs/webmcp.md#preguntas-y-avisos).

Toda herramienta que recibe un `chapter` acepta su posición en el libro
(empezando por 1), su título o su id. Si se omite, es el capítulo en pantalla.

### Mirar una página

**Una imagen.** «Enséñame el arranque del capítulo 3», «¿cómo queda la doble
página 24-25?»:

```jsonc
// sandbox_render_page: una doble página, 1200 px de ancho por página
{ "chapter": 3, "page": 24, "count": 2, "width": 1200 }
```

`page` es el número impreso en la página; `position` es el lugar de la página
entre las del capítulo (empezando por 1), para las páginas sin número. Sin
ninguno de los dos, es la página en pantalla. `crop` amplía una parte de la
página, en fracciones de su ancho y su alto desde la esquina superior
izquierda, para juzgar de cerca la letra y los espacios:

```jsonc
// sandbox_render_page: el cuarto inferior derecho, ampliado
{ "chapter": 3, "page": 24, "crop": { "x": 0.5, "y": 0.5, "width": 0.5, "height": 0.5 }, "width": 1600 }
```

`printPreview: true` pinta la prueba en pantalla del perfil de impresión con
las guías de corte, sangre y zona de seguridad; `trimmed: true` deja fuera el
margen con las marcas de corte.

**La misma página como datos.** «¿Qué líneas de esta página quedan flojas?»,
«¿está llena la segunda columna?»:

```jsonc
// sandbox_inspect_pages: sin página, una línea por cada página del capítulo
{ "chapter": 1 }
```

```json
{
  "chapter": 1, "title": "Apertura", "chapterPages": 2, "converged": true,
  "pages": [
    { "position": 1, "label": "1", "role": "opener", "blank": false, "lines": 45, "figures": 0, "starts": "Apertura" },
    { "position": 2, "label": "2", "role": "body", "blank": false, "lines": 37, "figures": 0, "starts": "décadas. La tipografía impresa pasó cinco siglos…" }
  ]
}
```

Con `page` o `position` enumera todos los bloques de la página: su tipo, su
columna, su texto, su número de líneas, las líneas que terminan en guion y las
líneas justificadas flojas con el ancho de sus espacios, su caja en la página,
y además cuánto se llena cada columna (`fill: 0.99`).

**Qué hay en un punto.** Cuando el agente ve algo en una imagen, pregunta qué
es con las mismas fracciones que usó para el recorte:

```jsonc
// sandbox_inspect_point
{ "chapter": 3, "page": 24, "x": 0.71, "y": 0.42 }
```

La respuesta da el bloque, la línea y la columna del Markdown fuente en ese
punto (y la palabra), un enlace y la página a la que lleva, o la celda de
tabla o el pie de un recurso. Con `select: true` hace además lo que hace un
clic: se abre el panel Texto con el cursor en ese lugar (`word: true`
selecciona la palabra y `to` selecciona hasta un segundo punto).

### Cambiar el diseño

Cualquier ajuste de la [configuración](https://postext.dev/es/docs/configuration.md) se puede
cambiar por su ruta. Un agente casi nunca conoce la ruta de entrada, así que
la busca:

```jsonc
// sandbox_find_setting
{ "query": "orphans" }
```

```json
{
  "ok": true,
  "paths": [
    { "path": "bodyText.avoidOrphans", "effective": true },
    { "path": "bodyText.avoidOrphansInLists", "effective": true }
  ],
  "sections": [{ "section": "Body text", "group": "Typography", "settingsGroup": "text", "configKeys": ["bodyText"] }]
}
```

`sandbox_get_config` lee un ajuste o una sección entera: `set` es lo que guarda
el libro y `effective` lo que resulta al completar con los valores por
defecto, que muestra también todos los ajustes que tiene la sección.

```jsonc
// sandbox_get_config
{ "path": "headings.levels[level=2]" }
```

Las rutas nombran un elemento de una lista por lo que lo distingue:
`headings.levels[level=2].fontSize`, `header.elements[id=titleOdd].italic`,
`calloutStyles[id=note].title`. Después, `sandbox_set_config` cambia tantos
ajustes como quieras en una sola llamada:

```jsonc
// sandbox_set_config
{
  "changes": [
    { "path": "bodyText.fontSize", "value": "10.5pt" },
    { "path": "page.margins.top", "value": { "value": 2.5, "unit": "cm" } },
    { "path": "headings.levels[level=2].color", "value": "#1f3a5f" },
    { "path": "bodyText.avoidOrphans", "value": true }
  ]
}
```

Una longitud es `"2.5cm"`, `{ "value": 2.5, "unit": "cm" }` o un número en la
unidad del ajuste; un color es `"#1f3a5f"`. Un valor `null` quita el ajuste y
vuelve el valor del diseño. La respuesta sirve para juzgar el cambio y para
deshacerlo:

```json
{
  "ok": true,
  "changed": [
    {
      "path": "bodyText.fontSize",
      "from": null,
      "to": { "value": 10.5, "unit": "pt" },
      "effective": { "from": { "value": 10, "unit": "pt" }, "to": { "value": 10.5, "unit": "pt" } }
    }
  ],
  "followed": [
    { "path": "captionStyle.fontSize.value", "from": 10, "to": 10.5 },
    { "path": "tableStyle.bodyFontSize.value", "from": 10, "to": 10.5 }
  ],
  "pagesBefore": 212,
  "pagesAfter": 219,
  "chapters": [{ "chapter": 1, "title": "Apertura", "pages": 19, "pagesBefore": 18 }]
}
```

`from` es el valor guardado antes (aquí `null`: el libro no lo fijaba), así que
la misma llamada con `from` como valor lo deshace. `followed` enumera los
ajustes que dependen de este y se han movido con él. Aparece una `note` cuando
el motor ha resuelto algo distinto de lo pedido (un límite, un ajuste que
depende de otro), y un valor con una forma equivocada, o una configuración que
el motor no puede resolver, se rechaza sin cambiar nada.

Para cambios más grandes, `patch` combina un objeto con la configuración (los
objetos se combinan, `null` quita y las listas se sustituyen enteras) y
`replace` pone una configuración completa en lugar de la del libro, como la de
un `postext-config.json` exportado. `sandbox_reset_to_original` con
`part: "config"` restaura el diseño con el que empezó el libro.

Las fuentes también son ajustes: `sandbox_list_fonts` con `search: "Garamond"`
busca familias en el catálogo abierto, y escribir el nombre en un ajuste
`fontFamily` la carga.

### Editar el texto

El texto de cada capítulo es Markdown con las extensiones de Postext (consulta
[Formato del documento](https://postext.dev/es/docs/document-format.md)). El agente lo lee con
números de línea, localiza un pasaje y sustituye pasajes exactos:

```jsonc
// sandbox_read_text: las líneas 40 a 80 del capítulo 2
{ "chapter": 2, "fromLine": 40, "toLine": 80 }
```

```jsonc
// sandbox_search_text: dónde aparece un pasaje y en qué página
{ "query": "retícula base", "pages": true }
```

```jsonc
// sandbox_edit_text
{
  "chapter": 2,
  "edits": [
    { "find": "Las últimas palabras de la apertura.", "replace": "Las palabras con que se cierra la apertura." },
    { "find": "obscuro", "replace": "oscuro", "all": true }
  ]
}
```

Cada `find` tiene que coincidir exactamente con el texto fuente y aparecer una
sola vez, salvo que se indique `all`. O se aplican todas las ediciones o
ninguna: un pasaje que no está se rechaza con el consejo de volver a leer el
capítulo. La llamada entera es un solo paso para `sandbox_undo_text`, que la
deshace (`redo: true` la rehace). `sandbox_write_text` sustituye un capítulo
entero, a partir de `markdown` o de un archivo `.md`.

Para enseñarte un lugar del texto, `sandbox_select_text` abre el panel Texto
con ese pasaje seleccionado (por `text`, `line` u `offset`), igual que un clic
en un hallazgo del panel Revisión.

### Organizar los capítulos

`sandbox_manage_chapters` añade, renombra, mueve, divide, fusiona y muestra
capítulos:

```jsonc
// añadir un capítulo en tercer lugar
{ "action": "add", "title": "Epílogo", "markdown": "# Epílogo\n\nFin.\n", "position": 3 }

// dividir un capítulo donde empieza un pasaje
{ "action": "split", "chapter": "Apertura", "before": "## Segunda parte" }

// un capítulo por cada título de nivel 1
{ "action": "split_at_headings", "chapter": 1 }
```

`sandbox_remove_chapter` borra uno, y eso no se puede deshacer.

### Añadir imágenes, tablas, vídeos y fuentes

`sandbox_save_resource` añade un recurso o cambia uno, como el panel Recursos.
Un archivo llega por `url` (una dirección `https:` que la página pueda
descargar, o una URL `data:`) o en `base64`:

```jsonc
// una imagen con su pie y su texto alternativo
{
  "id": "harbour",
  "image": { "url": "https://example.org/harbour.jpg" },
  "caption": "El puerto al amanecer",
  "altText": "Barcas de pesca amarradas a un muelle de piedra, con niebla sobre el agua",
  "placement": { "position": "top", "span": "page" }
}
```

```jsonc
// una tabla a partir de filas de texto; la primera fila es la cabecera
{
  "id": "prices",
  "caption": "Precios en 1850",
  "table": { "rows": [["Artículo", "Precio"], ["Pan", "2 d"], ["Tinta", "6 d"]], "columnWidths": [2, 1] }
}
```

```jsonc
// un vídeo de YouTube: su portada, el tamaño del fotograma y el título se descargan
{ "id": "interview", "video": { "url": "https://www.youtube.com/watch?v=…" }, "caption": "El autor trabajando" }
```

La respuesta da el registro guardado y la línea que lo coloca,
`::resource{id="prices"}`, que el agente escribe después en el texto con
`sandbox_edit_text`. Sobre un `id` que ya existe, la llamada cambia solo lo que
indica: un pie, las celdas de una tabla, la colocación, la zona segura de una
imagen. `newId` renombra un recurso y, por defecto, todas sus referencias en
el libro. `sandbox_list_resources` los enumera y, con un `id`, da uno completo
(las celdas de una tabla con su formato, el código de un SVG).

Las fuentes que se suben al libro pasan por `sandbox_save_font`:

```json
{ "action": "add", "family": "My Serif", "weight": 700, "style": "italic", "file": { "url": "https://example.org/MySerif-BoldItalic.woff2" } }
```

### Revisar el libro

```jsonc
// sandbox_get_warnings: todos los capítulos
{ "scope": "book" }
```

La respuesta es lo que dice el panel Revisión, para cualquier capítulo o para
el libro entero, esté lo que esté en pantalla: fuentes e imágenes que faltan,
marcado que el motor no ha podido leer, elementos de diseño que no caben,
texto demasiado pálido para su fondo, líneas flojas, viudas, cajas que
desbordan y el preflight de impresión. Cada aviso trae su categoría, su
capítulo, su línea y sus desplazamientos, su página y el ajuste o el recurso
que nombra:

```json
{
  "ok": true,
  "scope": "book",
  "total": 3,
  "byCategory": { "typesetting": 2, "design": 1 },
  "warnings": [
    {
      "kind": "headerFooterMetadataMissing",
      "category": "design",
      "title": "Placeholder metadata missing",
      "detail": "header · {title} — The metadata referenced by this placeholder is not set — it will render empty."
    }
  ]
}
```

Desde un aviso el agente puede ir a su lugar (`sandbox_select_text` con
`offset` y `toOffset`, o `sandbox_go_to_page`), mirar la página y cambiar el
texto o el ajuste. `category` acota la lista; en un libro largo,
`scope: "book"` maqueta todos los capítulos y tarda un rato.

### Mostrar al lector

Lo que cambia el agente, lo ves tú. Para señalarte algo:

```jsonc
// sandbox_set_view: el Canvas con páginas enfrentadas y las líneas flojas resaltadas
{ "viewer": "canvas", "pages": "spread", "fit": "height", "guides": { "looseLines": true, "baselineGrid": true } }
```

```jsonc
// sandbox_go_to_page: donde está colocada una figura
{ "anchor": "harbour" }
```

`sandbox_set_view` elige además el visor (`canvas`, `pdf`, `folio`, `html`,
`epub`), el panel lateral, capítulo o libro entero, el zoom, el tamaño de
letra del lector, la vista previa de impresión y el grupo del panel Diseño.
`sandbox_go_to_page` pasa páginas por `step`, `page`, `position`, un pasaje
`text`, un `anchor` o una entrada `contents` de un EPUB. `sandbox_run_viewer`
pulsa un botón de la barra de herramientas: `regenerate` vuelve a escribir el
PDF o el EPUB y espera a que termine, `print` abre el diálogo de impresión
para que lo confirmes tú y `play_video` reproduce un vídeo de una página.

### Páginas de cómic

`sandbox_inspect_comic` da las viñetas, las líneas de división y los bocadillos
de una página de cómic con sus posiciones; `sandbox_edit_comic` hace las
ediciones que una persona hace con el puntero y las escribe en el texto
fuente:

```jsonc
// mover el centro de un bocadillo a un punto de la imagen de su viñeta, en porcentaje
{ "comic": 1, "action": "move_balloon", "balloon": 2, "at": { "x": 30, "y": 18 } }

// mover la primera línea de división al 45 % de su celda
{ "comic": 1, "action": "move_splitter", "splitter": 1, "start": 45 }
```

La respuesta avisa cuando un bocadillo cae sobre una cara o se sale de su
viñeta. El formato está en [Cómics](https://postext.dev/es/docs/comics.md).

### Libros, archivos y Word

- **Abrir y guardar libros.** `sandbox_list_books` enumera las muestras y tus
  proyectos; `sandbox_open_book` abre uno (`{ "kind": "sample", "id": "postext-guide", "language": "es" }`);
  `sandbox_save_book` con `action: "copy_current"` guarda el libro en
  pantalla como proyecto tuyo, y `new_blank` empieza uno vacío.
- **Exportar.** `sandbox_export` escribe `pdf`, `epub`, `postext`, `docx`,
  `markdown` o `config`. Por defecto el navegador descarga el archivo; con
  `deliver: "data"`, un PDF o un EPUB vuelve al agente codificado en base64:

  ```json
  { "format": "pdf", "scope": "book", "deliver": "data" }
  ```

- **Importar un archivo `.postext`** con `sandbox_import_book` (`inspect: true`
  dice qué idiomas trae sin importarlo).
- **Traer un manuscrito de Word** con `sandbox_import_word`. Ejecútalo primero
  con `dryRun: true`: no cambia nada y responde con el informe de calidad, cada
  estilo de Word con aquello en lo que se convertiría y los capítulos y
  recursos que crearía. Después corrige la asignación y ejecútalo de verdad:

  ```json
  {
    "file": { "url": "https://example.org/manuscript.docx" },
    "destination": "book",
    "mapping": { "paragraphs": { "Epígrafe": { "kind": "quote" }, "Título de capítulo": { "kind": "chapter" } } },
    "saveTemplateAs": "Mi editorial"
  }
  ```

  `sandbox_word_templates` enumera, lee, guarda y borra las asignaciones
  guardadas, que también usa la exportación a Word.

### Preguntas y avisos

Cuando un enlace abre un libro del que ya tienes tu propia versión, el Sandbox
pregunta si sustituir tu copia, y `sandbox_get_state` lo indica como
`question` con `ready: false`. `sandbox_answer` responde: `replaceCopy: false`
conserva la tuya y `true` la sustituye por el libro publicado y borra tu copia.
`dismissNotice: true` cierra el mensaje del panel Libros.

## Tres sesiones de principio a fin

**«Sube un poco la letra, pero que el libro no pase de 220 páginas.»**
`sandbox_get_state` (212 páginas) → `sandbox_set_config` con
`bodyText.fontSize` a `"10.5pt"` (219 páginas) → `sandbox_render_page` de una
página densa para juzgarla → `sandbox_get_warnings` con
`category: "typesetting"` por si hay viudas nuevas → si algo ha salido mal,
`sandbox_set_config` otra vez con los valores `from` de la primera respuesta.

**«Corrige lo que ha encontrado la revisión en el capítulo 4.»**
`sandbox_get_warnings` con `chapter: 4` → para cada aviso, `sandbox_read_text`
alrededor de su `line` y `sandbox_render_page` de su `page` →
`sandbox_edit_text` o `sandbox_set_config` → `sandbox_get_warnings` de nuevo,
hasta que vuelva vacío.

**«Haz un libro con este archivo de Word y dame el PDF.»** `sandbox_save_book`
con `action: "new_blank"` → `sandbox_import_word` con `dryRun: true` →
`mapping` corregido y la importación real con `destination: "book"` →
`sandbox_get_warnings` con `scope: "book"` → `sandbox_export` con
`format: "pdf"` y `scope: "book"`.

## Qué pide tu consentimiento

Seis herramientas hacen cosas que no se pueden deshacer y van marcadas como
consecuentes (`consequentialHint`), para que el navegador pueda preguntarte
antes de ejecutarlas: `sandbox_remove_chapter`, `sandbox_delete_resource`,
`sandbox_delete_book`, `sandbox_reset_to_original`, `sandbox_import_word`
(puede sustituir el libro entero) y `sandbox_answer` (puede borrar tu copia de
un libro). Si tienes dudas, pide antes al agente que exporte una copia
`.postext`.

Las herramientas que solo leen van marcadas con `readOnlyHint`, y las que
devuelven texto que no ha escrito la página (el propio texto del libro, un
archivo descargado), con `untrustedContentHint`, para que el navegador pueda
evitar que ese texto se tome por instrucciones.

## Límites

- **Todo se queda en tu navegador.** Las herramientas no alcanzan nada que no
  alcance el propio Sandbox: tus libros están en el almacenamiento de este
  navegador, y un archivo indicado por `url` lo descarga la página, así que su
  servidor tiene que permitirlo (CORS). Una URL `data:` o `base64` funciona
  siempre.
- **El trabajo largo lleva tiempo.** Una herramienta espera a la maquetación
  antes de responder; `wait: false` responde enseguida. El PDF o el EPUB de un
  libro largo tarda minutos.
- **Lo que deshace `sandbox_undo_text`**: las ediciones de texto de las
  herramientas (una llamada cada vez) y las ediciones hechas sobre las vistas
  previas. No deshace lo que escribes en el editor del panel Texto (eso lo hace
  su propio deshacer), los capítulos añadidos o movidos ni los cambios en el
  diseño y en los recursos: para eso está el `from` de cada respuesta. La
  lista se pierde al recargar la página.
- **El libro en pantalla.** Las herramientas trabajan sobre el libro abierto.
  Solo `sandbox_export` de un archivo `.postext`, `sandbox_save_book` con
  `duplicate` y `sandbox_reset_to_original` con `sample` llegan a otro libro
  sin abrirlo.

## Las herramientas del sitio

En todas las páginas de postext.dev, también en el Sandbox:

- `site_get_context`: la página en pantalla, su idioma y su tema, las
  secciones y todas las páginas de la documentación.
- `site_search`: la documentación y el Recetario, por palabras, por nombres de
  ajustes (`bodyText.avoidOrphans`) o por directivas (`::resource`).
- `site_read_page`: una página del sitio como Markdown, sin salir de la página
  en pantalla.
- `site_open_page`: mostrar una página, con `#título` para llegar a una
  sección.
- `site_list_recipes` y `site_open_recipe_in_sandbox`: encontrar una receta
  del Recetario y abrirla en el Sandbox como un libro que puedes editar.
- `site_lookup_term`: un término del glosario (viuda, kerning, kinsoku…).
- `site_set_preferences`: el idioma y el tema del sitio.

Un agente que empieza en la portada puede, por tanto, buscar en el Recetario,
abrir una receta en el Sandbox y seguir allí con las herramientas `sandbox_*`.

## Todas las herramientas

Agrupadas como las registra el Sandbox. La descripción completa y el esquema
de cada una se obtienen con `postextWebMcp.listTools()`.

- **Lo que está abierto**: `sandbox_get_state`.
- **Las páginas**: `sandbox_render_page`, `sandbox_inspect_pages`,
  `sandbox_inspect_point`.
- **El texto**: `sandbox_read_text`, `sandbox_write_text`, `sandbox_edit_text`,
  `sandbox_undo_text`, `sandbox_select_text`, `sandbox_search_text`,
  `sandbox_manage_chapters`, `sandbox_remove_chapter`.
- **El diseño**: `sandbox_get_config`, `sandbox_set_config`,
  `sandbox_find_setting`, `sandbox_reset_to_original`.
- **Cómo se muestra**: `sandbox_set_view`, `sandbox_go_to_page`,
  `sandbox_run_viewer`.
- **Recursos y fuentes**: `sandbox_list_resources`, `sandbox_save_resource`,
  `sandbox_delete_resource`, `sandbox_list_fonts`, `sandbox_save_font`.
- **Cómics**: `sandbox_inspect_comic`, `sandbox_edit_comic`.
- **Libros y archivos**: `sandbox_list_books`, `sandbox_open_book`,
  `sandbox_save_book`, `sandbox_delete_book`, `sandbox_import_book`,
  `sandbox_export`.
- **Word**: `sandbox_import_word`, `sandbox_word_templates`.
- **Preguntas**: `sandbox_answer`.
- **La revisión**: `sandbox_get_warnings`.

## Al incrustar el Sandbox

`<PostextSandbox>` registra las herramientas mientras está montado;
`webmcp={false}` las deja fuera (consulta [Props](https://postext.dev/es/docs/sandbox.md#props)).
Una página anfitriona puede añadir herramientas propias con el mismo módulo:

```ts
import { registerWebMcpTools } from 'postext-sandbox/webmcp';

const controller = new AbortController();
registerWebMcpTools([
  {
    name: 'shop_list_titles',
    description: 'The titles of this shop, with the id of the Postext project of each one.',
    inputSchema: { type: 'object', properties: {}, additionalProperties: false },
    annotations: { readOnlyHint: true },
    execute: async () => ({ titles: await fetchTitles() }),
  },
], controller.signal);
// controller.abort() las retira
```

Se registran en WebMCP donde el navegador lo ofrece y siempre en
`window.postextWebMcp`, con sus argumentos comprobados contra `inputSchema`.
