Skip to main content
Glama
README.md
# wattpad-mcp

Servidor MCP no oficial que conecta Claude Code con Wattpad: leer tus obras y
borradores, medir estadísticas, revisar comentarios, buscar historias públicas y
—de forma experimental— crear, editar y publicar capítulos.

> **Aviso.** Wattpad no ofrece API pública. Este servidor habla con los endpoints
> internos que usan su web y su app. Pueden cambiar sin previo aviso, y los
> términos de servicio de Wattpad no contemplan el acceso automatizado. Úsalo con
> tu propia cuenta, con un ritmo bajo de peticiones y asumiendo ese riesgo.

## Qué cambió en 0.2.0

Una auditoría de la versión 0.1.0 encontró tres clases de problema: lecturas que
mentían en silencio, un ciclo leer→editar→guardar que destruía el formato, y
escrituras sin red de seguridad. Esta versión los corrige.

- **El formato ya no se pierde.** Los capítulos se leen y se escriben en un
  *markup* que conserva cursivas, negritas, subrayados, imágenes y separadores.
- **El HTML original está disponible** (`wattpad_get_part_text include_html=true`
  y `wattpad_backup_part`), que es la única copia fiel posible.
- **Nada se sobrescribe sin respaldo** en disco del HTML previo.
- **`wattpad_whoami` verifica de verdad la sesión** contra Wattpad, y avisa si la
  cookie pertenece a otra cuenta.
- **Los borradores ausentes se denuncian** en vez de presentarse como una cuenta
  vacía.
- **Los errores son errores**: las herramientas lanzan, y el cliente MCP recibe
  `isError`, en vez de un JSON de éxito que contenía la palabra "error".

## Requisitos

- Python 3.10 o superior
- Una cuenta de Wattpad

## Instalación

```bash
cd wattpad-mcp
python -m venv .venv
.venv/Scripts/activate          # Linux/macOS: source .venv/bin/activate
pip install -e ".[dev]"
pytest
```

Funciona tanto con `mcp` 1.x como con 2.x (donde `FastMCP` pasó a llamarse
`MCPServer`); el servidor detecta cuál hay instalado.

## Autenticación

Dos opciones. **La cookie es la recomendada** porque tu contraseña nunca sale del
navegador.

### Opción A — cookie de sesión (recomendada)

1. Abre `https://www.wattpad.com` con tu sesión iniciada.
2. DevTools → **Application** → **Cookies** → `https://www.wattpad.com`.
3. Copia el valor de la cookie **`token`**.
4. Ponlo en `WATTPAD_TOKEN`.

La cookie caduca cada cierto tiempo; cuando veas errores 401 o 403, vuelve a
copiarla. Trátala como una contraseña: quien la tenga entra en tu cuenta.

### Opción B — usuario y contraseña

Define `WATTPAD_USERNAME` y `WATTPAD_PASSWORD`. El servidor pide un token a
`api.wattpad.com/v4/sessions` y lo guarda **solo en memoria**: no se escribe en
disco ni se envía a ningún otro sitio. Aun así, prefiere la Opción A: guardar una
contraseña en la configuración del cliente MCP es peor que guardar una cookie
revocable.

## Registro en Claude Code

```bash
claude mcp add wattpad --env WATTPAD_TOKEN=tu_cookie_token --env WATTPAD_ALLOW_WRITES=false -- /ruta/a/wattpad-mcp/.venv/bin/python -m wattpad_mcp
```

O a mano en `~/.claude.json`:

```json
{
  "mcpServers": {
    "wattpad": {
      "command": "/ruta/a/wattpad-mcp/.venv/bin/python",
      "args": ["-m", "wattpad_mcp"],
      "env": {
        "WATTPAD_TOKEN": "tu_cookie_token",
        "WATTPAD_ALLOW_WRITES": "false",
        "WATTPAD_MIN_INTERVAL": "0.8"
      }
    }
  }
}
```

`WATTPAD_USERNAME` es opcional: sirve para contrastar que la cookie es de la
cuenta que crees. El usuario real siempre lo resuelve la sesión.

Comprueba que funciona pidiéndole a Claude: *"usa wattpad_whoami"*. Si responde
`"authenticated": false`, la cookie no sirve — y ahora sí te lo dirá.

## El formato de texto (markup)

Los capítulos viajan en un texto con marcas, para que el formato sobreviva al
viaje de ida y vuelta:

| En Wattpad | En el markup |
|---|---|
| `<i>` / `<em>` | `*cursiva*` |
| `<b>` / `<strong>` | `**negrita**` |
| `<u>` | `__subrayado__` |
| párrafo | línea en blanco entre bloques |
| `<br>` | salto de línea simple |
| separador de escena | `---` en su propia línea |
| `<img src="…">` | `[imagen: …]` |

Un asterisco, un guion bajo o una barra invertida literales se escapan con `\`.
El texto entre angulares (`<TRANSMISIÓN INTERRUMPIDA>`) viaja como texto, no como
etiqueta.

**El separador no se escribe como `<hr>`**: comprobado contra el servidor real,
Wattpad lo borra al guardar. Se escribe como un párrafo con `· · ·`, que sí
sobrevive, y se vuelve a leer como `---`.

**Verificado de punta a punta el 30-ago-2026** contra la cuenta real: se escribió
un capítulo con cursiva, negrita, separador y prosa entre angulares, y al releerlo
el markup volvió **idéntico**.

**Lo que el markup no cubre** —enlaces, alineación centrada, tablas— se enumera en
el campo `lost` de la lectura, y `wattpad_update_part` **se niega a guardar** un
capítulo así salvo que pases `force=true`. Es deliberado: es preferible un error
a una pérdida silenciosa.

## Herramientas

### Lectura

| Herramienta | Qué hace |
|---|---|
| `wattpad_whoami` | Verifica la sesión contra Wattpad y avisa si la cuenta no coincide. |
| `wattpad_list_my_works` | Tus historias, incluidos los borradores. Avisa si no pudo verlos. |
| `wattpad_get_story` | Ficha de una historia y tabla de capítulos con sus IDs y métricas. |
| `wattpad_get_part_text` | Texto de un capítulo en markup, con `text_hash` y aviso de pérdida. |
| `wattpad_backup_part` | Guarda el HTML original en disco y devuelve la ruta. |
| `wattpad_get_stats` | Lecturas, votos y comentarios por obra y por capítulo. |
| `wattpad_list_comments` | Comentarios de un capítulo, con paginación. |
| `wattpad_search_stories` | Búsqueda pública por texto o etiqueta. |
| `wattpad_get_user` | Perfil público de cualquier usuario. |

### Escritura (experimental, desactivada por defecto)

| Herramienta | Qué hace |
|---|---|
| `wattpad_create_story` | Crea una historia vacía. |
| `wattpad_create_part` | Crea un capítulo en borrador, con texto opcional. |
| `wattpad_update_part` | Cambia título o texto. **El texto reemplaza al anterior.** |
| `wattpad_publish_part` | Publica un borrador (dispara notificaciones a seguidores). |
| `wattpad_post_comment` | Comenta en un capítulo. |

Todas fallan con un mensaje explicativo mientras `WATTPAD_ALLOW_WRITES` no sea
`true`. Todas aceptan `dry_run=true`, que devuelve la petición completa —método,
URL, codificación y cuerpo, incluido el HTML que se generaría— sin enviarla.

### Las tres barreras de `wattpad_update_part`

1. **Respaldo**: guarda el HTML original en `WATTPAD_BACKUP_DIR` (por defecto
   `~/.wattpad-mcp/backups`) antes de tocar nada.
2. **Concurrencia**: si pasas `expected_text_hash` (el que devolvió la lectura) y
   el capítulo cambió desde entonces, rechaza la escritura. Sin ese parámetro,
   escribe pero deja un aviso.
3. **Fidelidad**: si el capítulo original contiene formato que el markup no
   representa, rechaza la escritura y te dice qué se perdería.

## Estado de la parte de escritura

**Capturado del editor el 30-ago-2026** ([docs/captura-2026-08-30.md](docs/captura-2026-08-30.md)).
La versión 0.1.0 apuntaba a endpoints que **no existen**; ahora el código
reproduce la forma real:

| Acción | Endpoint real |
|---|---|
| Guardar borrador | `POST /apiv2/editstory` (no `savestorytext`) |
| Crear capítulo | `POST /apiv2/newstory` — devuelve `id` y `text_hash` |
| Crear historia | `POST /write/story/new?_data=…` (router del front nuevo), 204 sin cuerpo |
| Publicar | dos peticiones: detalles de la obra + `editstory` con `draft=0&publish=1` |

Tres cosas que cambian cómo se usa:

- **`last_text_hash` no se calcula, se arrastra.** Es un candado optimista: el
  servidor devuelve `text_hash` y el cliente lo reenvía en el siguiente
  guardado. Nuestro `expected_text_hash` ahora lo verifica de verdad.
- **La cabecera `authorization` NO es una credencial.** Es `apiAuthKey`, la clave
  pública del cliente web: Wattpad sirve **la misma a un visitante anónimo**.
  Quien te autentica sigue siendo la cookie `token`, que es httpOnly. El cliente
  la descubre solo leyendo una página pública; `WATTPAD_AUTHORIZATION` solo hace
  falta si ese descubrimiento falla.
- **Publicar exige que la obra tenga al menos una etiqueta.**
- **Los `data-p-id` los genera el servidor** (MD5 del texto plano de cada
  párrafo). No hay que calcularlos.
- **Bastan 11 campos** de los 35 que manda el editor: comprobado contra el
  servidor real, 200 con `error_count: 0` y el texto cambió.

Queda sin verificar solo `post_comment`, que no se capturó.

**Guía completa: [docs/CAPTURA.md](docs/CAPTURA.md).** En resumen:

1. En el editor de Wattpad, DevTools → Network → Fetch/XHR → *Guardar borrador*.
2. Botón derecho sobre la petición → Copy → **Copy as cURL** → pégala en un fichero.
3. ```bash
   python scripts/comparar_captura.py captura.txt
   ```

El script parsea el cURL (bash, cmd o PowerShell), lo compara con lo que envía
este servidor y te dice campo por campo qué difiere: método, ruta, query,
content-type, campos del cuerpo y cabeceras propias de Wattpad. No envía nada a
ningún sitio.

Dos campos que interesa buscar en la captura:

- **`text_hash`** — si Wattpad lo espera, tenemos control de concurrencia real y
  podemos rechazar una escritura sobre un capítulo que cambió desde el móvil,
  en vez de solo avisar.
- **`x-csrf-token`** — si aparece, hay que averiguar de dónde lo saca la web,
  porque un valor copiado a mano caduca.

`client.request()` acepta `headers=` para añadir lo que descubras.
[docs/lo-que-enviamos.md](docs/lo-que-enviamos.md) tiene la petición exacta de
cada operación, generada desde el código por `scripts/capturar_salida.py`.

## Ritmo y límites

`WATTPAD_MIN_INTERVAL` (0.8 s por defecto) separa las peticiones. Si aparecen
errores 429, súbelo a 2.0 y espera unos minutos. No uses este servidor para
descargar obras ajenas en masa.

## Problemas frecuentes

| Síntoma | Causa y solución |
|---|---|
| `whoami` dice `authenticated: false` | Cookie caducada o ausente. Vuelve a copiar `token`. |
| `whoami` avisa de cuenta distinta | La cookie es de otra cuenta que la de `WATTPAD_USERNAME`. |
| `401` o `403` en lectura | Cookie caducada. Vuelve a copiar `token`. |
| `403` en escritura | Puede ser la cookie, o que el endpoint exija cabeceras que no enviamos. Ver "Estado de la parte de escritura". |
| `429` | Demasiadas peticiones. Sube `WATTPAD_MIN_INTERVAL`. |
| "Escritura bloqueada" | Pon `WATTPAD_ALLOW_WRITES=true` y reinicia Claude Code. |
| "el panel de escritura falló" | `writing/v1` no respondió; la lista no incluye borradores. Comprueba la sesión. |
| "contiene formato que el markup no representa" | Revisa el respaldo que indica el error; si aceptas la pérdida, repite con `force=true`. |
| "respondió 200 pero el cuerpo no es JSON" | Wattpad devolvió HTML: suele ser una página de login o una verificación anti-bot. |

## Estructura

```
src/wattpad_mcp/
├── client.py     sesión HTTP, autenticación, ritmo, errores legibles
├── richtext.py   conversión reversible HTML <-> markup
├── api.py        envoltorio de los endpoints internos de Wattpad
└── server.py     las 14 herramientas MCP
tests/                        92 pruebas
├── test_richtext.py          ida y vuelta del formato
├── test_client_api.py        cliente y API con transporte simulado
├── test_regresion_auditoria.py  una por hallazgo de las revisiones
└── test_comparar_captura.py  el parser de cURL
scripts/
├── comparar_captura.py   compara una captura de DevTools con lo que enviamos
└── capturar_salida.py    regenera docs/lo-que-enviamos.md desde el codigo
docs/
├── CAPTURA.md            como verificar los endpoints de escritura
└── lo-que-enviamos.md    la peticion exacta de cada operacion (generado)
```

TDQS

A4.2/5.0

Scored across 15 tools

Disambiguation5/5

Each tool maps to a clear resource+action: user, story, part, comments, session, stats, markup preview, and backup. Although backup_part and get_part_text both read chapter content, their outputs and purposes are distinct enough that an agent should not confuse them.

Naming Consistency5/5

All tools follow the wattpad_<verb>_<noun> snake_case pattern, e.g. get_part_text, create_story, update_part, publish_part. wattpad_whoami is a minor idiomatic exception but still fits the consistent snake_case style.

Tool Count5/5

Fifteen tools is at the upper edge of the ideal range but each covers a distinct part of the Wattpad reading/writing workflow: auth, search, story/part CRUD, comments, stats, backup, and markup validation. No tool feels redundant.

Completeness4/5

The set covers the core workflow: session check, search, list/create/get stories, create/read/update/publish parts, comments, stats, and a safety backup. The main gaps are the absence of story update/delete operations and no way to delete/unpublish a part, but agents can complete the primary write-and-publish loop.

Maintenance

ActivityMaintained
ResponsivenessNo issues