# WebMCP Playground

Testumgebung der mindshape Online Marketing Unit zum manuellen und agentengestützten Testen von
[WebMCP](https://github.com/webmachinelearning/webmcp). Enthält 11 Testformulare, die sich systematisch in
**Komplexität** und **Codequalität** unterscheiden, einen globalen WebMCP-An/Aus-Toggle sowie ein
Basic-Auth-geschütztes Dashboard zur Auswertung aller Formular-Submits.

Details zu Architektur- und Umsetzungsentscheidungen: siehe [Plan.md](Plan.md).
Anleitung zum Deployment auf die Staging-Domain `webmcp-playground.mindshape-web.de` (Plesk):
siehe [DEPLOYMENT.md](DEPLOYMENT.md).

**Testest du selbst mit den Formularen?** Diese README richtet sich an Entwickler:innen (Setup, Config,
Code). Eine nicht-technische Anleitung speziell für Tester:innen (Bedienung, Testablauf, Dashboard,
Auswertungskriterien) gibt es in [TESTANLEITUNG.md](TESTANLEITUNG.md).

> **Datenschutz-Hinweis:** Dies ist ein reiner Testplayground. Bitte **keine echten Personendaten**
> eingeben - nur Test-/Dummy-Daten. Versehentlich eingegebene reale Daten können über das
> [Dashboard](#dashboard) zeitnah gelöscht werden.

## Inhalt

1. [Setup (lokal, PHP Built-in Server)](#setup-lokal-php-built-in-server)
2. [Setup mit DDEV (empfohlen)](#setup-mit-ddev-empfohlen)
3. [Konfiguration (.env)](#konfiguration-env)
4. [WebMCP-Toggle](#webmcp-toggle)
5. [Formular-Katalog](#formular-katalog)
6. [Dashboard](#dashboard)
7. [Testablauf mit Chrome + WebMCP](#testablauf-mit-chrome--webmcp)
8. [Agent-Erkennung (experimentell)](#agent-erkennung-experimentell)
9. [Bekannte Einschränkungen / Abweichungen vom Plan](#bekannte-einschränkungen--abweichungen-vom-plan)

## Setup (lokal, PHP Built-in Server)

Voraussetzungen: PHP >= 8.1 mit aktivierter `pdo_sqlite`-Extension (Standard in den meisten PHP-Installationen).

```bash
cp .env.example .env
# .env nach Bedarf anpassen (siehe unten), insbesondere DASHBOARD_USER/DASHBOARD_PASS und IP_HASH_PEPPER

php -S localhost:8000 -t public public/index.php
```

Das letzte Argument (`public/index.php`) ist wichtig: Es macht `index.php` zum Router-Skript des PHP-Dev-Servers,
damit auch "schöne" URLs wie `/formulare/kontakt-einfach-sauber` oder `/dashboard/export.csv` funktionieren
(analog zum `.htaccess`-Rewrite in einem echten Apache-Setup). Echte Dateien unter `public/assets/` werden
dabei weiterhin direkt vom Dev-Server ausgeliefert (siehe Weiche in `public/index.php`).

## Setup mit DDEV (empfohlen)

Für ein reproduzierbares, isoliertes lokales Setup (Apache + PHP-FPM + optional MySQL, ohne lokale
PHP-Installation) liegt eine fertige [DDEV](https://ddev.com/)-Konfiguration unter `.ddev/` bereit.

Voraussetzungen: [DDEV](https://ddev.com/get-started/) sowie Docker/OrbStack/Colima o. Ä. installiert.

```bash
ddev start
```

Das war's - beim ersten Start passiert automatisch:

- `.env` wird per Hook aus `.env.example` angelegt, falls noch nicht vorhanden (`hooks.post-start` in
  `.ddev/config.yaml`).
- Die SQLite-Datenbank (`data/playground.sqlite`) wird beim ersten Request automatisch erzeugt (Default,
  siehe [Konfiguration](#konfiguration-env)).

Danach ist die Seite unter der von DDEV ausgegebenen URL erreichbar (Standard:
`https://webmcp-playground.ddev.site`, siehe auch `ddev describe`).

**Wichtige Befehle:**

| Befehl | Zweck |
|---|---|
| `ddev start` / `ddev stop` | Container starten/stoppen |
| `ddev describe` | URL, Ports, Services anzeigen |
| `ddev ssh` | Shell im Web-Container öffnen |
| `ddev exec <cmd>` | Befehl im Web-Container ausführen (z. B. `ddev exec php -v`) |
| `ddev mysql` | MySQL-CLI im DB-Container öffnen |
| `ddev logs -f` | Logs des Web-Containers verfolgen |
| `ddev delete` | Projekt inkl. Datenbank-Volume vollständig entfernen |

**MySQL statt SQLite testen** (Plan.md Kapitel 2/10 sieht MySQL als Alternative vor - DDEV bringt dafür
bereits einen MySQL-8.0-Container mit): in der `.env` einfach umstellen auf

```bash
DB_DRIVER=mysql
DB_MYSQL_HOST=db
DB_MYSQL_PORT=3306
DB_MYSQL_DATABASE=db
DB_MYSQL_USER=db
DB_MYSQL_PASSWORD=db
```

(`db` ist der interne DDEV-Hostname des Datenbank-Containers sowie der Standard-Datenbankname/-Zugang von
DDEV - beides wurde gegen diesen DDEV-Container erfolgreich verifiziert.)

**Hinweis zur PHP-Version:** `.ddev/config.yaml` verwendet `php_version: "8.3"` statt der im Rest der
Doku genannten Mindestversion 8.1. Grund: Das `ddev-webserver`-Image bringt PHP 8.3 vorinstalliert mit;
andere Versionen werden zur Buildzeit über das Sury-APT-Repository nachinstalliert, dessen Signing-Key zum
Zeitpunkt der Einrichtung abgelaufen war, was den Image-Build fehlschlagen ließ. PHP 8.3 ist mit dem
gesamten Code dieses Projekts (inkl. `readonly`-Properties) voll kompatibel; wer eine andere Version
erzwingen möchte, kann `.ddev/config.yaml` anpassen (`ddev config --php-version=8.1`), muss dann aber ggf.
selbst um das Sury-Key-Problem herum arbeiten (z. B. per `webimage_extra_packages`/eigenem
`.ddev/web-build/Dockerfile`).

Anschließend `http://localhost:8000` im Browser öffnen. Die SQLite-Datenbank (`data/playground.sqlite`)
und ihr Schema werden beim ersten Request automatisch angelegt - kein manueller Migrationsschritt nötig.

Composer ist optional: Ist `vendor/autoload.php` vorhanden, wird es genutzt; andernfalls greift ein
einfacher, manueller PSR-4-Autoloader in `public/index.php`. Das Projekt läuft also auch ohne
`composer install` auf einem simplen PHP-Shared-Hosting.

## Konfiguration (.env)

Siehe [.env.example](.env.example) für alle verfügbaren Variablen. Wichtig:

- `DB_DRIVER=sqlite` (Default) oder `mysql` - bei MySQL zusätzlich `DB_MYSQL_*` setzen.
- `DASHBOARD_USER` / `DASHBOARD_PASS` - Zugangsdaten für das Dashboard (HTTP Basic Auth).
- `IP_HASH_PEPPER` - Pepper zum Hashen der Client-IP (es werden nie Klartext-IPs gespeichert).
- `RATE_LIMIT_MAX` / `RATE_LIMIT_WINDOW_SECONDS` - leichtes Rate-Limit auf `/submit/*` pro IP-Hash.

`.env` wird nicht ins Repository eingecheckt (siehe `.gitignore`).

## WebMCP-Toggle

Jede Seite zeigt oben eine Statusleiste mit dem aktuellen WebMCP-Zustand (aktiviert/deaktiviert) und einem
Umschalt-Link. Zusätzlich lässt sich der Zustand deterministisch per URL erzwingen - praktisch für
Agenten-Skripte, die nicht erst auf einen Link klicken sollen:

```
https://.../formulare/kontakt-einfach-sauber?webmcp=off
https://.../formulare/kontakt-einfach-sauber?webmcp=on
```

Der Query-Parameter setzt gleichzeitig ein Cookie (`wm_toggle`), sodass der Zustand für Folge-Requests
erhalten bleibt, bis er erneut geändert wird. Bei deaktiviertem WebMCP ist das HTML jedes Formulars
**identisch**, nur ohne `toolname`/`tooldescription`/`toolparamdescription`/`toolautosubmit` - so lässt
sich 1:1 vergleichen, ob ein Agent dasselbe Formular mit/ohne WebMCP-Auszeichnung zuverlässig ausfüllen
kann. Der Toggle-Zustand wird bei jedem Submit mitgespeichert und ist im Dashboard filterbar.

## Formular-Katalog

| # | Slug | Titel | Komplexität | Qualität | Testfokus |
|---|---|---|---|---|---|
| 1 | `kontakt-einfach-sauber` | Kontaktformular (sauber) | einfach | gut | Referenzimplementierung |
| 2 | `kontakt-einfach-schlecht` | Kontaktformular (schlecht) | einfach | schlecht | Keine Labels, `div`-Button mit `form.submit()` (umgeht das `submit`-Event) |
| 3 | `anfrage-komplex-sauber` | Anfrageformular (sauber) | komplex | gut | `fieldset`/`legend`, viele Felder, saubere Gruppierung |
| 4 | `anfrage-komplex-schlecht` | Anfrageformular (schlecht) | komplex | schlecht | Generische `field1..8`, `div`-Suppe, `requestSubmit()` statt `type=submit` |
| 5 | `ajax-formular` | Ajax-Kontaktformular | mittel | gut | `SubmitEvent.respondWith()` statt Navigation |
| 6 | `wizard-mehrstufig` | Mehrstufiges Anfrageformular | komplex | gemischt | JS-Steps - Grenzfall der Declarative API |
| 7 | `alpine-validierung` | Formular mit reaktiver Validierung | mittel | gut | Alpine.js `x-model`, reaktive Validierung (lifta.de-Fall) |
| 8 | `spamschutz` | Formular mit Honeypot + Time-Trap | einfach | gut | Verstecktes `fax`-Feld + Mindestausfüllzeit |
| 9 | `hidden-fields` | Formular mit vielen Trackingfeldern | mittel | gut | Viele `hidden`-Inputs (UTM, Campaign-ID, ...) |
| 10 | `autosubmit-vergleich` | Gleiche Form, mit/ohne `toolautosubmit` | einfach | gut | Human-in-the-loop vs. Autosubmit |
| 11 | `zwei-formulare-pro-seite` | Seite mit zwei Formularen | einfach | gemischt | Zwei WebMCP-Tools auf einer Seite (PLZ-Suche + Hauptformular) |

Jede Formularseite zeigt zusätzlich sichtbar ihre Testfall-Metadaten (Komplexität, Qualität, Testfokus).

## Dashboard

Route: `/dashboard` (HTTP Basic Auth, Zugangsdaten aus `.env`).

- **Liste** aller Submissions (neueste zuerst) mit Filter nach Formular, WebMCP-Status, Agent-Flag,
  Honeypot-Treffer und Zeitraum.
- **Detailansicht** (`/dashboard/eintrag/{id}`): vollständiger Payload als Key/Value-Tabelle inkl. Hidden
  Fields, plus manuelles Agent/Mensch-Flag und Freitext-Notiz.
- **Löschen**: einzeln, Mehrfachauswahl oder „alle Einträge dieses Formulars“/„alle Einträge“ - jeweils
  mit JS-Bestätigungsdialog.
- **CSV-Export** der aktuell gefilterten Liste über `/dashboard/export.csv`.

## Testablauf mit Chrome + WebMCP

1. Chrome mit aktiviertem WebMCP-Flag starten: `chrome://flags/#enable-webmcp-testing` (oder je nach
   Chrome-Version die entsprechende WebMCP-Origin-Trial-/Flag-Bezeichnung - Flag-Namen ändern sich
   während der Preview-Phase häufiger, ggf. in den aktuellen WebMCP-Docs prüfen).
2. Die [Model Context Tool Inspector Extension](https://github.com/webmachinelearning/webmcp) (oder das
   jeweils aktuelle Inspector-Tool aus dem WebMCP-Repo) installieren, um zu sehen, welche Tools/Schemas pro
   Seite tatsächlich generiert werden.
3. Empfohlener Testablauf pro Formular:
   - Seite mit `?webmcp=on` öffnen, Tool-Schema im Inspector prüfen (Feldnamen, Beschreibungen, ob
     Hidden-/Honeypot-Felder mit auftauchen).
   - Denselben Agenten-Prompt/Task gegen die Seite laufen lassen und beobachten, ob der Submit gelingt.
   - Dieselbe Seite mit `?webmcp=off` erneut testen (identisches HTML ohne WebMCP-Attribute) und
     vergleichen.
   - Ergebnis im [Dashboard](#dashboard) nachschlagen, ggf. manuelles Agent/Mensch-Flag setzen und eine
     Notiz zum Ergebnis hinterlegen (z. B. "Agent hat Honeypot-Feld befüllt" oder "Agent ist an
     Wizard-Schritt 2 gescheitert").
4. Für automatisierte/Skript-Testläufe: `?webmcp=on|off` direkt in der URL setzen, um manuelles
   Klicken auf den Banner-Link zu vermeiden.

## Agent-Erkennung (experimentell)

`SubmitEvent.agentInvoked` ist Teil der WebMCP-Spec, aber early-preview. Ein dokumentweiter
Capture-Listener (`public/assets/js/agent-invoked.js`) prüft per Feature-Detection
(`'agentInvoked' in SubmitEvent.prototype`), ob der Browser das Feature unterstützt, und schreibt den
Wert in ein verstecktes Feld (`__agent_invoked`), bevor der native Submit weiterläuft. Ist das Feature
nicht verfügbar, bleibt der Wert leer -> in der Datenbank `NULL`.

**Das ist bewusst nur ein Best-Effort-Signal.** Die verlässliche Quelle ist immer das manuelle
Agent/Mensch-Flag im Dashboard (siehe oben).

## Bekannte Einschränkungen / Abweichungen vom Plan

- **Phase 5 (Deployment)**: Die Zieldomain `webmcp-playground.mindshape-web.de` steht inzwischen fest
  (Plesk-Hosting), daher existiert jetzt eine Schritt-für-Schritt-Anleitung dafür - siehe
  [DEPLOYMENT.md](DEPLOYMENT.md). Die tatsächliche Ausführung (Plesk-Zugangsdaten, finale Wahl
  Git-Deploy vs. manueller Upload, SSH-Verfügbarkeit) kann nur jemand mit Plesk-Zugriff vornehmen -
  einige der in Plan.md Kapitel 12 offenen Fragen (2., 3., 6.) sind daher weiterhin vor dem ersten
  echten Deploy zu klären.
- Das in der Verzeichnisstruktur der Plan.md genannte `public/dashboard/.htaccess` als *zusätzliche*
  Basic-Auth-Zone entfällt: Das Dashboard läuft komplett über den PHP-Router (`/dashboard/*`), es gibt
  keinen physischen `public/dashboard`-Ordner, den Apache separat schützen müsste. Der Basic-Auth-Schutz
  erfolgt vollständig PHP-seitig in `src/routes.php` (`require_dashboard_auth()`), wie in Plan.md
  Kapitel 2 als Alternative vorgesehen ("Server-Config **oder** PHP-seitig geprüft").
- CSRF-Schutz (`src/Support/Csrf.php`) ist implementiert, aber ausschließlich für die
  zustandsändernden Dashboard-Aktionen (Löschen, Flag/Notiz) aktiv - nicht auf den öffentlichen
  Testformularen selbst (siehe Plan.md Kapitel 8).
- `db/schema.sql`: `form_slug`, `manual_flag` und `ip_hash` sind als `VARCHAR` statt `TEXT` deklariert
  (SQLite behandelt beide Typen identisch). Grund: MySQL/InnoDB verlangt für einen Index auf einer
  TEXT/BLOB-Spalte eine explizite Schlüssellänge (Fehler 1170) - das wurde beim Testen des MySQL-Pfads
  über DDEV entdeckt und mit `VARCHAR` behoben, ohne dass sich am SQLite-Verhalten etwas ändert.
