@prontosite/mcp 0.9.1 → 0.10.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  Alle nennenswerten Änderungen am MCP-Server für ProntoSite. Der Server ist ein dünner Client um die
4
4
  ProntoSite Deploy-API; die eigentliche Logik lebt serverseitig.
5
5
 
6
+ ## 0.10.0 – 2026-10-03
7
+
8
+ - **Neu: `account_create`** (schreibend, nur mit Betreuer-Token: admin/partner/agentur) – legt ein neues
9
+ Kundenkonto an (Dashboard-Weg „Kunde anlegen"). Mit `email` → eingeladenes Login-Konto (Invite-Mail),
10
+ ohne `email` → verwaltetes Konto (ohne Login). Partner/Agentur: Konto wird dem Betreuer zugeordnet,
11
+ Agentur-Kunden erhalten automatisch den Agentur-Plan (`agentur-kunde`/`billing_offline`). Antwort nennt die
12
+ neue Konto-ID (k-…) + den Hinweis, die erste Website per `site_deploy` mit `site="<k-id>:new"` zu importieren.
13
+ Schutz: Namens-/E-Mail-Dublette → Warnung mit vorhandener Konto-ID statt Doppelanlage (`override:true`
14
+ erzwingt die Namensdublette; eine vergebene E-Mail ist nicht erzwingbar), Rate-Limit 10 Konten/Stunde je Token.
15
+ Die Beschreibung lenkt den Assistenten dorthin, wenn es für einen Kunden noch KEIN Konto gibt (statt `site=new`).
16
+
17
+ ## 0.9.2 – 2026-10-01
18
+
19
+ - **Pflicht-Marker in den Tool-Beschreibungen** (`site_deploy`, `site_legal_options`) + README: Rechtsseiten
20
+ bringen `data-ps="impressum"`/`"datenschutz"` + eine EIGENE `<h1>` mit (kein handgeschriebener Rechtstext) →
21
+ `site_legal_options` mit `showTitle:false` setzen (bei `?markers=auto` automatisch bei erkannter eigener H1);
22
+ Footer IMMER mit allen vier Markern (`link-impressum`, `link-datenschutz`, `cookie-einstellungen`,
23
+ `cookie-richtlinie`), sonst ungestylter Consent-Block; Mobile-Menü rendert als Drawer, Toggle braucht
24
+ `aria-controls`. Reine Beschreibungs-/Doku-Änderung (keine neue Funktion).
25
+
6
26
  ## 0.9.0 – 2026-09-27
7
27
 
8
28
  - **Neu: `site_widgets`** (lesend) – Katalog der verwalteten Widgets einer Website inkl. `aktiv`, `optionen`
package/README.md CHANGED
@@ -82,6 +82,7 @@ sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
82
82
  | Tool | Wirkung |
83
83
  |------|---------|
84
84
  | `sites_list` | Alle erreichbaren Websites (Slug, Plan, letzte Änderung, Publish-Bedarf). **Lesend.** |
85
+ | `account_create({ name, email?, plan?, note?, override? })` | **Nur mit Betreuer-Token.** Legt ein neues Kundenkonto an (Dashboard-Weg „Kunde anlegen"). Mit `email` → eingeladenes Login-Konto (Invite-Mail), ohne → verwaltetes Konto. Partner/Agentur: Konto wird dem Betreuer zugeordnet (Agentur-Kunden automatisch `agentur-kunde`). Antwort nennt die neue Konto-ID (k-…) → danach `site_deploy({ site: "<k-id>:new", zipPath })`. Namens-/E-Mail-Dublette → Warnung mit vorhandener k-ID (`override:true` erzwingt die Namensdublette). **Schreibend.** |
85
86
  | `site_status({ site? })` | Plan, Slug, Live-URL, Publish-Bedarf. **Lesend.** |
86
87
  | `site_export({ site?, which, outDir })` | ZIP (`published`\|`draft`\|`original`) lokal speichern. **Lesend.** |
87
88
  | `site_deploy({ site?, zipPath, publish?, resetOverlay?, legal?, company?, siteOverride? })` | ZIP importieren, optional live; optional Firmendaten im selben Aufruf setzen. Beim lokalen Bauen Bausteine im HTML per `data-ps` markieren (`formular:kontakt`, `impressum`/`datenschutz`, `link-impressum`/`link-datenschutz`) → [Marker-Syntax](https://prontosite.io/docs/marker-shortcodes). **Schreibend.** |
@@ -99,6 +100,21 @@ sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
99
100
  `site_deploy` und `site_publish` sind **schreibende** Aktionen (verändern die Live-Website). Die
100
101
  Tool-Beschreibungen weisen den Assistenten an, vorher rückzufragen. Jeder Aufruf wird serverseitig auditiert.
101
102
 
103
+ ### Pflicht-Marker beim lokalen Bauen
104
+
105
+ Baust du das Template selbst, gehören drei Dinge dazu (volle Spec:
106
+ [Marker-Syntax](https://prontosite.io/docs/marker-shortcodes)):
107
+
108
+ - **Rechtsseiten:** `impressum/index.html` und `datenschutz/index.html` tragen IMMER
109
+ `<div data-ps="impressum">` bzw. `<div data-ps="datenschutz">` als Inhaltsbereich und eine **eigene `<h1>`**
110
+ (kein handgeschriebener Rechtstext). Danach `site_legal_options({ impressum:{ showTitle:false } })` setzen
111
+ (eigene H1 → keine doppelte Überschrift). Beim Erst-Import mit `?markers=auto`/der Rechts-Automatik setzt
112
+ ProntoSite `showTitle:false` automatisch, sobald die Rechtsseite eine eigene H1 hat.
113
+ - **Footer:** IMMER alle vier Marker `link-impressum`, `link-datenschutz`, `cookie-einstellungen`,
114
+ `cookie-richtlinie` – sonst hängt das Consent-Widget einen ungestylten Block an den Seitenfuß (mehrsprachig
115
+ je Sprach-Footer dieselben Marker, Linktext lokalisiert).
116
+ - **Mobile-Menü:** rendert als Drawer (von rechts); der Toggle braucht `aria-controls` auf das Menü.
117
+
102
118
  ## Arbeitsweise: ein Chat je Kunde
103
119
 
104
120
  Für saubere Trennung: pro Kundenwebsite **einen eigenen Claude-Chat**. So bezieht sich der ganze Verlauf auf
@@ -127,6 +143,11 @@ Admin/Partner-Bereich (Panel „Betreuer-Token"). Config dann einfach:
127
143
  zugeordnet ist – keine Config-Pflege). Website je Aufruf per Slug **oder stabiler Site-ID** angeben (kein
128
144
  `"active"` – es sind mehrere Konten). Erst-Import: `site_deploy({ site: "new", customer: "k-…" })`.
129
145
 
146
+ **Neuen Kunden anlegen** (es gibt noch KEIN Konto): zuerst `account_create({ name, email? })` – das Konto wird
147
+ dem Betreuer zugeordnet (Agentur-Kunden automatisch `agentur-kunde`). Die Antwort nennt die Konto-ID (k-…);
148
+ danach die erste Website per `site_deploy({ site: "<k-id>:new", zipPath })` importieren. Also **nicht** `site=new`
149
+ raten, solange die Konto-ID unbekannt ist – erst `account_create`.
150
+
130
151
  Sicherheit: der Zugriff wird bei JEDEM Aufruf serverseitig geprüft (`assertCanManage` – nur betreute Konten,
131
152
  Rollenentzug wirkt sofort); nur Deploy/Status/Export/Veröffentlichen, kein Konto-/Zahlungszugriff; optionaler
132
153
  Ablauf (Default 90 Tage) und optionale IP-Bindung; Widerruf sperrt alle betreuten Sites auf einmal.
package/api.mjs CHANGED
@@ -159,6 +159,22 @@ export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = n
159
159
  return out;
160
160
  }
161
161
 
162
+ /** POST /api/account – Kundenkonto per BETREUER-Token anlegen (Dashboard-Weg „Kunde anlegen"). Mit email →
163
+ * eingeladenes Login-Konto (Invite-Mail), ohne email → verwaltetes Konto (ohne Login). Nutzt das Top-Level-
164
+ * Betreuer-Token (nicht die sites-Map – ein Betreuer-Token gilt konto-übergreifend). */
165
+ export async function apiAccountCreate(cfg, { name, email = null, plan = null, note = null, override = false } = {}, fetchImpl = fetch) {
166
+ if (!name || !String(name).trim()) throw new Error("name ist Pflicht (Anzeigename des Kunden).");
167
+ const token = needToken(cfg.token, "Betreuer-Token");
168
+ const body = { name: String(name).trim() };
169
+ if (email) body.email = String(email).trim();
170
+ if (plan) body.plan = String(plan).trim();
171
+ if (note) body.note = String(note).trim();
172
+ if (override === true) body.override = true;
173
+ const res = await fetchImpl(`${baseUrl(cfg)}/account`, {
174
+ method: "POST", headers: { ...authHeaders(token), "content-type": "application/json" }, body: JSON.stringify(body) });
175
+ return asJson(res);
176
+ }
177
+
162
178
  /** POST /api/publish/<site> – erneut veröffentlichen (ohne ZIP). force=true = „trotzdem veröffentlichen"
163
179
  * (auch ohne erkannte Änderung), analog zum Dashboard-Menüpunkt „Erneut veröffentlichen". */
164
180
  export async function apiPublish(cfg, { site, force = false } = {}, fetchImpl = fetch) {
package/index.mjs CHANGED
@@ -11,13 +11,13 @@
11
11
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { z } from "zod";
14
- import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect, apiEmbeds, apiEmbedConsent, apiWidgets, apiWidgetSet } from "./api.mjs";
14
+ import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList, apiAccountCreate, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect, apiEmbeds, apiEmbedConsent, apiWidgets, apiWidgetSet } from "./api.mjs";
15
15
 
16
16
  const cfg = loadConfig();
17
17
  // Deutliche Startwarnung bei ambiger Konfiguration (Top-Level-Token NEBEN sites-Map). Nach stderr, damit
18
18
  // es im MCP-Log/Terminal auffällt; die endgültige Auflösung (Betreuer-Token-Vorrang vs. Map) klärt sites_list.
19
19
  for (const w of (cfg.warnings || [])) process.stderr.write("[prontosite-mcp] ⚠ " + w + "\n");
20
- const server = new McpServer({ name: "prontosite", version: "0.9.1" });
20
+ const server = new McpServer({ name: "prontosite", version: "0.10.0" });
21
21
  const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] });
22
22
  const asError = e => ({ isError: true, content: [{ type: "text", text: "Fehler: " + (e?.message || String(e)) }] });
23
23
  const SITE_DESC = "Website-Slug (z. B. 'mas-monti'). Ohne Angabe die Standard-Site der Config. Bei Betreuer-"
@@ -54,6 +54,29 @@ server.tool(
54
54
  async () => { try { return asText(await sitesList(cfg)); } catch (e) { return asError(e); } }
55
55
  );
56
56
 
57
+ server.tool(
58
+ "account_create",
59
+ "SCHREIBEND (nur mit Betreuer-Token: admin/partner/agentur): Legt ein NEUES Kundenkonto an – der Dashboard-"
60
+ + "Weg „Kunde anlegen". Nutze dieses Tool, BEVOR du eine Website importierst, wenn es für den Kunden noch "
61
+ + "KEIN Konto gibt (z. B. sites_list zeigt den Kunden nicht, oder der Nutzer sagt „für Kunde X gibt es noch "
62
+ + "kein Konto"). Erst Konto anlegen, dann mit der zurückgegebenen Konto-ID (k-…) per site_deploy und "
63
+ + "site=\"<k-id>:new\" die erste Website importieren. Richte KEIN site=new/Erst-Import ein, solange die "
64
+ + "Konto-ID unbekannt ist – dann zuerst dieses Tool. Mit email wird ein Login-Konto angelegt und eine "
65
+ + "Einladungs-Mail verschickt; ohne email ein verwaltetes Konto (ohne Login, Betrieb über den Betreuer). "
66
+ + "Partner/Agentur: das Konto wird DIR zugeordnet; Agentur-Kunden erhalten automatisch den Agentur-Plan "
67
+ + "(Abrechnung über die Agentur). Schutz: bei gleichem Namen/gleicher E-Mail kommt eine Warnung mit der "
68
+ + "bereits vorhandenen Konto-ID statt einer Doppelanlage – mit override:true trotzdem anlegen. "
69
+ + "Bitte VOR dem Aufruf bestätigen lassen (es wird ggf. eine Mail an den Kunden gesendet).",
70
+ { name: z.string().describe("Pflicht: Anzeigename des Kunden (z. B. Firmenname)."),
71
+ email: z.string().optional().describe("Optional: E-Mail des Kunden. Gesetzt → eingeladenes Login-Konto (Invite-Mail); weggelassen → verwaltetes Konto ohne Login."),
72
+ plan: z.enum(["free", "agentur-kunde"]).optional().describe("Optional. Standard richtet sich nach dem Betreuer-Konto (Agentur → agentur-kunde, sonst free). Nur angeben, wenn du den Standard bestätigen willst."),
73
+ note: z.string().optional().describe("Optionale interne Notiz – erscheint im Konto-Verlauf des neuen Kontos."),
74
+ override: z.boolean().default(false).describe("true = eine Namensdublette-Warnung übergehen und trotzdem ein weiteres Konto anlegen. Eine bereits vergebene E-Mail lässt sich NICHT erzwingen.") },
75
+ async ({ name, email, plan, note, override }) => {
76
+ try { return asText(await apiAccountCreate(cfg, { name, email, plan, note, override })); } catch (e) { return asError(e); }
77
+ }
78
+ );
79
+
57
80
  server.tool(
58
81
  "site_status",
59
82
  "Status einer ProntoSite-Website: Plan, Slug, Live-URL, letzte Änderungen und offener Veröffentlichungs"
@@ -85,6 +108,13 @@ server.tool(
85
108
  + "erscheinen sollen – data-ps=\"formular:kontakt\" (verwaltetes Kontaktformular), data-ps=\"impressum\" bzw. "
86
109
  + "data-ps=\"datenschutz\" (Rechtstext-Seite) und data-ps=\"link-impressum\" / data-ps=\"link-datenschutz\" "
87
110
  + "(Footer-Links auf die Rechtstexte). Beim Import legt ProntoSite die Bausteine automatisch an. "
111
+ + "PFLICHT-MARKER: (a) Rechtsseiten impressum/index.html + datenschutz/index.html tragen IMMER den "
112
+ + "data-ps-Marker als Inhaltsbereich UND eine EIGENE <h1>; schreib KEINEN Rechtstext von Hand hinein. Weil "
113
+ + "die Seite ihre eigene H1 hat, die Widget-Überschrift ausschalten – site_legal_options mit showTitle:false "
114
+ + "(bei ?markers=auto setzt ProntoSite das automatisch, sobald die Rechtsseite eine eigene H1 hat). "
115
+ + "(b) Footer IMMER mit allen vier Markern link-impressum, link-datenschutz, cookie-einstellungen, "
116
+ + "cookie-richtlinie (sonst hängt das Consent-Widget einen ungestylten Block an den Seitenfuß; mehrsprachig "
117
+ + "je Sprach-Footer dieselben Marker). (c) Mobile-Menü-Toggle braucht aria-controls (Menü rendert als Drawer). "
88
118
  + "Syntax-Referenz: https://prontosite.io/docs/marker-shortcodes",
89
119
  { site: z.string().optional().describe(SITE_DESC + " Erst-Import: 'new' (Einzel) bzw. '<label>:new' (Multi-Site). Betreuer-Token: '<Konto-ID>:new' (k-…) legt im betreuten Konto an – die Konto-ID im Label ersetzt den customer-Parameter. sites_list zeigt betreute Konten OHNE Website (needsFirstImport) samt ihrer k-ID."),
90
120
  slug: z.string().optional().describe("Erst-Import: gewünschte Web-Adresse (Subdomain-Label, z. B. 'wuerttemberg' → wuerttemberg.prontosite.io). Beim Erst-Import den Nutzer danach FRAGEN; wird sie weggelassen, leitet ProntoSite eine Adresse ab. Bei Updates ignoriert."),
@@ -135,7 +165,7 @@ server.tool(
135
165
  );
136
166
 
137
167
  const legalOptShape = z.object({
138
- showTitle: z.boolean().optional().describe("Überschrift (H1) des Rechtstextes anzeigen (Default an)."),
168
+ showTitle: z.boolean().optional().describe("Widget-Überschrift des Rechtstextes anzeigen (Default an). Bringt die Rechtsseite ihre EIGENE <h1> mit (Pflicht-Marker-Konvention), hier showTitle:false setzen, sonst doppelte Überschrift."),
139
169
  showDate: z.boolean().optional().describe("Stand-Datum anzeigen (Default an; nur wo vorhanden, i. d. R. Datenschutz)."),
140
170
  draftNotice: z.boolean().optional().describe("Entwurfs-Hinweis in der VERÖFFENTLICHTEN Seite anzeigen (Default an)."),
141
171
  }).describe("Anzeige-Optionen einer Rechtstext-Rolle – Teil-Update, nur gesetzte Booleans wirken.");
@@ -146,7 +176,9 @@ server.tool(
146
176
  + "(Überschrift), showDate (Stand-Datum), draftNotice (Entwurfs-Hinweis in der Live-Ausgabe). OHNE impressum/"
147
177
  + "datenschutz nur LESEN. Teil-Update (nur gesetzte Felder). HINWEIS: draftNotice:false blendet den rechtlich "
148
178
  + "empfohlenen Entwurfs-Hinweis live aus (protokolliert) – bitte vorher bestätigen lassen. "
149
- + "MARKER (beim lokalen Bauen): Rechtstext-Seiten und -Links im Template mit data-ps markieren – "
179
+ + "WICHTIG: Rechtsseiten sollen laut Konvention ihre EIGENE <h1> mitbringen (Marker data-ps=\"impressum\"/"
180
+ + "\"datenschutz\" als Inhaltsbereich) – dann hier showTitle:false setzen, damit keine doppelte Überschrift "
181
+ + "entsteht. MARKER (beim lokalen Bauen): Rechtstext-Seiten und -Links im Template mit data-ps markieren – "
150
182
  + "data-ps=\"impressum\" / data-ps=\"datenschutz\" (Seite) bzw. data-ps=\"link-impressum\" / "
151
183
  + "data-ps=\"link-datenschutz\" (Footer-Links). Syntax-Referenz: https://prontosite.io/docs/marker-shortcodes",
152
184
  { site: z.string().optional().describe(SITE_DESC),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prontosite/mcp",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "MCP-Server für ProntoSite – Website-Status, Export, Deploy und Veröffentlichen direkt aus einem KI-Assistenten (Claude Desktop). Dünner Client um die ProntoSite Deploy-API.",
5
5
  "type": "module",
6
6
  "bin": {