@prontosite/mcp 0.11.2 → 0.12.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,12 +3,30 @@
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.11.2 – 2026-10-19
6
+ ## 0.12.0 – 2026-10-19
7
+
8
+ - **Arbeitsweise im Server verankert:** Der MCP-Server liefert beim Verbinden `instructions` – Standard ist das Arbeiten direkt
9
+ am Server-Entwurf (`site_status` → `site_edit_text`/`site_file_*` → `site_status`, Vorschau-Link nennen). Export + Deploy nur
10
+ auf ausdrücklichen Wunsch bzw. für größere Umbauten; `site_publish` nur nach Bestätigung im Chat; bei 409 erst `site_status`.
11
+ - **Neu `site_edit_text`** (`find`, `replace`, optional `pages`, `baseRev`): Text über alle Seiten suchen und ersetzen –
12
+ Formatierung bleibt, ein Rückgängig-Schritt, Rechtstexte ausgenommen; bei mehreren Fundstellen ohne `pages` nur eine Liste.
13
+ - Beschreibungen: Datei-Tools „STANDARDWEG", Export/Deploy „AUSNAHME"; `site_publish` ausdrücklich „live für Besucher, nur nach
14
+ Bestätigung".
15
+ - **`baseRev` beim Deploy-Update Pflicht** (F4a): ZIPs aus `site_export` tragen ihn automatisch, sonst `baseRev` aus `site_status`
16
+ mitgeben (neuer Parameter an `site_deploy`); ohne → `428`.
17
+
18
+ ## 0.11.3 – 2026-10-19
19
+
20
+ - Beschreibungen von `site_file_put`/`site_file_delete`/`site_file_rename`: Schreiben geht jetzt auch mit Kunden-Token
21
+ (serverseitig seit 19.10.); Skriptdateien (JavaScript) bleiben Betreuern vorbehalten – Kunden bekommen dort 403.
22
+
23
+ ## 0.11.2 – 2026-10-19 (veröffentlicht 19.10.2026)
7
24
 
8
25
  - **Intern, kein Verhaltenswechsel:** die Tool-Definitionen liegen jetzt in `tools.mjs` – dieselbe Quelle nutzt der neue
9
26
  Remote-Endpunkt `https://app.prontosite.io/mcp` (Streamable HTTP, Bearer-Token; für Cursor und Claude Code ohne lokale
10
27
  Installation). Lokal bleiben `site_export`/`site_deploy` mit Dateipfaden; remote heißen sie Download-Link bzw.
11
28
  `deploy_upload_begin` + `deploy_commit`.
29
+ - Die Config-Prüfung akzeptiert neben `psk_` auch `pso_` (OAuth-Zugriff – nur der Remote-Endpunkt reicht ihn intern weiter; lokal ändert sich nichts).
12
30
  - MCP-SDK mindestens 1.31.0 (Sicherheitshinweis GHSA-6qxp-vccf-f47h betrifft nur die OAuth-Client-Seite, hier ungenutzt).
13
31
 
14
32
  ## 0.11.1 – 2026-10-18
package/api.mjs CHANGED
@@ -43,7 +43,8 @@ export function loadConfig(env = process.env, home = os.homedir()) {
43
43
  }
44
44
 
45
45
  const needToken = (token, ref) => {
46
- if (!token || !String(token).startsWith("psk_"))
46
+ // psk_ = API-Token; pso_ = OAuth-Zugriff (nur der Remote-Endpunkt /mcp reicht ihn an seinen internen Loopback weiter).
47
+ if (!token || !/^(psk_|pso_)/.test(String(token)))
47
48
  throw new Error(`Kein API-Token für „${ref}" konfiguriert. Setze PRONTOSITE_TOKEN oder trage die Site in ~/.prontosite.json (sites) ein.`);
48
49
  return token;
49
50
  };
@@ -155,7 +156,7 @@ export async function apiExport(cfg, { site, which = "published", outDir = "." }
155
156
  }
156
157
 
157
158
  /** POST /api/deploy/<site> – ZIP übergeben (+ optional publish/resetOverlay/legal). site="new" = anlegen. */
158
- export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = null, name = null, zipPath, publish = false, resetOverlay = false, legal = null, company = null, siteOverride = false } = {}, fetchImpl = fetch) {
159
+ export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = null, name = null, zipPath, baseRev = null, publish = false, resetOverlay = false, legal = null, company = null, siteOverride = false } = {}, fetchImpl = fetch) {
159
160
  if (!zipPath || !fs.existsSync(zipPath)) throw new Error(`ZIP nicht gefunden: ${zipPath}`);
160
161
  const target = resolveTarget(cfg, site);
161
162
  const { token, slug } = target;
@@ -172,7 +173,8 @@ export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = n
172
173
  if (legal === false) q.set("legal", "0");
173
174
  if (effCustomer) q.set("customer", effCustomer); // Betreuer-Token + „new": Ziel-Konto für den Erst-Import
174
175
  if (wishSlug) q.set("slug", String(wishSlug)); // Erst-Import: Wunsch-Adresse (sonst wird sie abgeleitet)
175
- if (isNew && name) q.set("name", String(name)); // Erst-Import: Site-Name explizit setzen (sonst aus der Startseite abgeleitet)
176
+ if (isNew && name) q.set("name", String(name));
177
+ if (!isNew && Number.isInteger(baseRev)) q.set("baseRev", String(baseRev)); // F4a: ab 0.12 Pflicht bei Updates (ZIP aus site_export trägt ihn selbst) // Erst-Import: Site-Name explizit setzen (sonst aus der Startseite abgeleitet)
176
178
  const qs = q.toString() ? `?${q}` : "";
177
179
  const res = await fetchImpl(`${baseUrl(cfg)}/deploy/${encodeURIComponent(slug)}${qs}`, {
178
180
  method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/zip" }, body: fs.readFileSync(zipPath) });
@@ -386,6 +388,14 @@ export async function apiFileRename(cfg, { site, from, to, baseRev } = {}, fetch
386
388
  method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify({ from, to, baseRev }) }));
387
389
  }
388
390
 
391
+ /** POST /api/edit-text/<site> – Text über alle Seiten suchen + ersetzen (Entwurf, eine Undo-Einheit). 409 kommt als Objekt zurück. */
392
+ export async function apiEditText(cfg, { site, find, replace, pages = null, baseRev = null } = {}, fetchImpl = fetch) {
393
+ const { token, slug } = resolveTarget(cfg, site);
394
+ const body = { find, replace, ...(Array.isArray(pages) && pages.length ? { pages } : {}), ...(Number.isInteger(baseRev) ? { baseRev } : {}) };
395
+ return asJsonOr409(await fetchImpl(`${baseUrl(cfg)}/edit-text/${encodeURIComponent(slug)}`, {
396
+ method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify(body) }));
397
+ }
398
+
389
399
  /** GET/POST /api/lock/<site> – sanfte Sperre („in Bearbeitung durch …"); blockiert niemanden. action: lock|unlock|takeover. */
390
400
  export async function apiLock(cfg, { site, action = null, note = null } = {}, fetchImpl = fetch) {
391
401
  const { token, slug } = resolveTarget(cfg, site);
package/index.mjs CHANGED
@@ -14,13 +14,14 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
15
  import { z } from "zod";
16
16
  import { loadConfig } from "./api.mjs";
17
- import { registerTools } from "./tools.mjs";
17
+ import { registerTools, SERVER_INSTRUCTIONS } from "./tools.mjs";
18
18
 
19
19
  const cfg = loadConfig();
20
20
  // Deutliche Startwarnung bei ambiger Konfiguration (Top-Level-Token NEBEN sites-Map). Nach stderr, damit
21
21
  // es im MCP-Log/Terminal auffällt; die endgültige Auflösung (Betreuer-Token-Vorrang vs. Map) klärt sites_list.
22
22
  for (const w of (cfg.warnings || [])) process.stderr.write("[prontosite-mcp] ⚠ " + w + "\n");
23
- const server = new McpServer({ name: "prontosite", version: "0.11.2" });
23
+ // instructions: Arbeitsweise (Weg B = Standard) – gleicher Text wie am Remote-Endpunkt.
24
+ const server = new McpServer({ name: "prontosite", version: "0.12.0" }, { instructions: SERVER_INSTRUCTIONS });
24
25
  // Tool-Definitionen: gemeinsame Quelle mit dem Remote-Endpunkt (tools.mjs, Konzept KI-Anbindung §4.2).
25
26
  registerTools(server, { z, cfg, mode: "local" });
26
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prontosite/mcp",
3
- "version": "0.11.2",
3
+ "version": "0.12.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": {
package/tools.mjs CHANGED
@@ -9,7 +9,24 @@
9
9
  * site_domain_connect nur mit Betreuer-Token (Entscheidung 9a).
10
10
  * `z` wird vom Aufrufer übergeben (dieselbe zod-Instanz wie sein MCP-SDK).
11
11
  */
12
- import { apiStatus, apiExport, apiExportFetch, apiDeploy, apiPublish, sitesList, apiAccountCreate, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect, apiEmbeds, apiEmbedConsent, apiWidgets, apiWidgetSet, apiFiles, apiFileGet, apiFilePut, apiFileDelete, apiFileRename, apiLock } from "./api.mjs";
12
+ import { apiEditText, apiStatus, apiExport, apiExportFetch, apiDeploy, apiPublish, sitesList, apiAccountCreate, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect, apiEmbeds, apiEmbedConsent, apiWidgets, apiWidgetSet, apiFiles, apiFileGet, apiFilePut, apiFileDelete, apiFileRename, apiLock } from "./api.mjs";
13
+
14
+ /**
15
+ * Arbeitsweise (MCP-`instructions` beim Initialize – lokal UND /mcp): Weg B ist der Standard (Nick 19.10.).
16
+ * Direkt am Server-Entwurf arbeiten; Export + Deploy nur auf ausdrücklichen Wunsch bzw. für Komplett-Umbauten.
17
+ */
18
+ export const SERVER_INSTRUCTIONS = [
19
+ "ProntoSite – so arbeitest du mit einer Website:",
20
+ "1. STANDARD: direkt am Server-Entwurf arbeiten. Ablauf: site_status (Stand, draftRev, wer zuletzt geändert hat) → Änderung mit "
21
+ + "site_edit_text (Text suchen und ersetzen über alle Seiten) bzw. site_file_get/site_file_put (HTML/CSS) → site_status (Kontrolle). "
22
+ + "Danach dem Nutzer sagen, was geändert wurde, und den Vorschau-Link (previewUrl aus site_status) nennen.",
23
+ "2. AUSNAHME: site_export + Deploy (site_deploy bzw. deploy_upload_begin/deploy_commit) NUR, wenn der Nutzer ausdrücklich lokal arbeiten "
24
+ + "möchte oder die Website komplett umgebaut wird.",
25
+ "3. Veröffentlichen (site_publish, publish=true) NUR nach ausdrücklicher Bestätigung des Nutzers im Chat – bis dahin bleibt alles im Entwurf.",
26
+ "4. Konflikt (409, conflict:true): nicht erzwingen. Erst site_status aufrufen (zeigt, wer was geändert hat), Inhalt neu holen, Änderung "
27
+ + "neu anwenden und mit der neuen draftRev erneut senden.",
28
+ "5. Jede Änderung lässt sich rückgängig machen; Rechtstexte (Impressum/Datenschutz) nicht umformulieren – sie kommen aus den Firmendaten.",
29
+ ].join("\n");
13
30
 
14
31
  const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] });
15
32
  const asError = e => ({ isError: true, content: [{ type: "text", text: "Fehler: " + (e?.message || String(e)) }] });
@@ -80,18 +97,36 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
80
97
  }
81
98
  server.tool(
82
99
  "site_status",
83
- "Status einer ProntoSite-Website: Plan, Slug, Live-URL, letzte Änderungen, offener Veröffentlichungs"
84
- + "bedarf, draftRev und recentEdits (wer hat zuletzt wo geändert). VOR Änderungen lesen – draftRev ist die baseRev für "
85
- + "site_file_put. LESEND – ändert nichts.",
100
+ "LESEND – SCHRITT 1 und letzter Schritt jeder Änderung: Status einer ProntoSite-Website: Plan, Slug, Live-URL, Vorschau-Link "
101
+ + "(previewUrl), letzte Änderungen, offener Veröffentlichungsbedarf (publishNeeded), draftRev und recentEdits (wer hat zuletzt "
102
+ + "wo geändert). draftRev ist die baseRev für site_edit_text/site_file_put. Ändert nichts.",
86
103
  { site: z.string().optional().describe(SITE_DESC) },
87
104
  async ({ site }) => { try { return asText(await apiStatus(cfg, { site })); } catch (e) { return asError(e); } }
88
105
  );
89
106
 
107
+ server.tool(
108
+ "site_edit_text",
109
+ "SCHREIBEND (Entwurf, nicht live) – STANDARDWEG für Textänderungen (Telefonnummer, Öffnungszeiten, Preise, Sätze): sucht `find` "
110
+ + "im sichtbaren Text ALLER Seiten und ersetzt ihn durch `replace` – als eine rückgängig machbare Änderung, Formatierung bleibt. "
111
+ + "Gibt es mehr als eine Fundstelle und du hast keine pages angegeben, ändert der Server NICHTS und liefert nur die Liste "
112
+ + "(Seite, Knoten, Kontext): zeig sie dem Nutzer bzw. entscheide anhand des Kontexts und ruf das Tool erneut mit pages:[…] auf "
113
+ + "(alle Fundstellen dieser Seiten werden ersetzt). Rechtstexte und geschützte Bereiche werden nie geändert (skipped). "
114
+ + "linkHints nennt tel:/mailto:-Links mit dem alten Wert – die ändert dieses Tool nicht (ggf. site_file_put). "
115
+ + "Vorher site_status, danach site_status; live erst nach Bestätigung mit site_publish.",
116
+ { site: z.string().optional().describe(SITE_DESC),
117
+ find: z.string().describe("Genau der Text, wie er auf der Website steht (z. B. '02242 123456')."),
118
+ replace: z.string().describe("Neuer Text (reiner Text)."),
119
+ pages: z.array(z.string()).optional().describe("Optional: Seiten-IDs (aus der Fundstellen-Liste), auf denen ersetzt werden soll. Ohne Angabe nur bei genau EINER Fundstelle."),
120
+ baseRev: z.number().int().optional().describe("Optional: draftRev aus site_status (Konfliktschutz, 409 bei zwischenzeitlicher Änderung derselben Seite).") },
121
+ async ({ site, find, replace, pages, baseRev }) => { try { return asText(await apiEditText(cfg, { site, find, replace, pages, baseRev })); } catch (e) { return asError(e); } }
122
+ );
123
+
90
124
  if (mode === "local") {
91
125
  server.tool(
92
126
  "site_export",
93
- "Exportiert eine Website als ZIP in ein lokales Verzeichnis. which: 'published' (Live-Stand, Standard), "
94
- + "'draft' (Entwurf) oder 'original' (flacher Stand ohne ProntoSite-Zutaten). LESEND.",
127
+ "AUSNAHME – nur wenn der Nutzer ausdrücklich lokal arbeiten möchte oder für größere Umbauten (sonst site_edit_text/site_file_*): "
128
+ + "Exportiert eine Website als ZIP in ein lokales Verzeichnis. which: 'published' (Live-Stand, Standard), "
129
+ + "'draft' (Entwurf – zum Weiterbearbeiten) oder 'original' (flacher Stand ohne ProntoSite-Zutaten). LESEND.",
95
130
  { site: z.string().optional().describe(SITE_DESC),
96
131
  which: z.enum(["published", "draft", "original"]).default("published"),
97
132
  outDir: z.string().default(".").describe("Zielordner für die ZIP-Datei (lokal).") },
@@ -101,7 +136,9 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
101
136
 
102
137
  server.tool(
103
138
  "site_deploy",
104
- "SCHREIBEND: Übergibt eine lokale ZIP-Datei an ProntoSite (Import). Mit publish=true wird die Website "
139
+ "SCHREIBEND – AUSNAHME: nur für Erst-Import, ausdrücklichen Wunsch nach lokaler Arbeit oder größere Umbauten (kleine Änderungen: "
140
+ + "site_edit_text/site_file_put). Übergibt eine lokale ZIP-Datei an ProntoSite (Import). Bei Updates PFLICHT: baseRev – exportiere "
141
+ + "vorher mit site_export (which='draft'), die ZIP trägt ihren Stand dann automatisch; sonst baseRev = draftRev aus site_status. Mit publish=true wird die Website "
105
142
  + "DIREKT LIVE geschaltet und der bestehende Live-Stand überschrieben. ERST-IMPORT (neue Website): "
106
143
  + "Einzel-Config site='new'; Multi-Site/Betreuer site='<label>:new' (legt im Konto DES LABELS an). "
107
144
  + "WICHTIG beim ERST-IMPORT: Frag den Nutzer VOR dem Deploy nach der gewünschten Adresse "
@@ -130,13 +167,14 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
130
167
  name: z.string().optional().describe("Erst-Import: expliziter Website-Name (überschreibt die Ableitung aus der Startseite). Nützlich, wenn der Titel einen Ortszusatz trägt (z. B. 'Physio Phoenix – Bad Honnef Süd' → name='Physio Phoenix'). Bei Updates ignoriert."),
131
168
  customer: z.string().optional().describe("Betreuer-Token + Erst-Import: Ziel-Konto-ID (k-…) für die neue Website. Alternativ direkt im site-Argument als '<Konto-ID>:new'."),
132
169
  zipPath: z.string().describe("Pfad zur ZIP-Datei der Website (lokal)."),
170
+ baseRev: z.number().int().optional().describe("Update ohne Export-ZIP: draftRev aus site_status (sonst lehnt der Server mit 428 ab). Bei ZIPs aus site_export nicht nötig."),
133
171
  publish: z.boolean().default(false).describe("true = nach dem Import direkt veröffentlichen (LIVE)."),
134
172
  resetOverlay: z.boolean().default(false).describe("true = bisherige Dashboard-Änderungen verwerfen."),
135
173
  legal: z.boolean().optional().describe("Rechts-Automatik (Impressum/Datenschutz): true erzwingt, false schaltet ab."),
136
174
  company: companyObject.optional().describe("Optional: Firmendaten VOR dem Deploy setzen (ein Schritt) – Impressum/Datenschutz nutzen sie dann direkt. Teil-Update."),
137
175
  siteOverride: z.boolean().default(false).describe("Nur mit company: true = Firmendaten NUR für diese Website (statt konto-weit).") },
138
- async ({ site, slug, name, customer, zipPath, publish, resetOverlay, legal, company, siteOverride }) => {
139
- try { return asText(await apiDeploy(cfg, { site, slug, name, customer, zipPath, publish, resetOverlay, legal, company, siteOverride })); } catch (e) { return asError(e); }
176
+ async ({ site, slug, name, customer, zipPath, baseRev, publish, resetOverlay, legal, company, siteOverride }) => {
177
+ try { return asText(await apiDeploy(cfg, { site, slug, name, customer, zipPath, baseRev, publish, resetOverlay, legal, company, siteOverride })); } catch (e) { return asError(e); }
140
178
  }
141
179
  );
142
180
 
@@ -145,9 +183,9 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
145
183
  }
146
184
  server.tool(
147
185
  "site_publish",
148
- "SCHREIBEND: Veröffentlicht den aktuellen Stand einer Website ERNEUT (ohne ZIP) – z. B. um Plattform-"
149
- + "Aktualisierungen live zu bringen. Meldet die Antwort nothing:true (keine erkannte Änderung), aber du "
150
- + "willst trotzdem neu ausliefern, setze force:true. Bitte VOR dem Aufruf bestätigen lassen – und die richtige Website prüfen.",
186
+ "SCHREIBEND – LIVE FÜR BESUCHER: Veröffentlicht den aktuellen Entwurf einer Website. NUR nach ausdrücklicher Bestätigung "
187
+ + "des Nutzers im Chat aufrufen (vorher kurz zusammenfassen, was live geht, und die richtige Website prüfen). Meldet die Antwort "
188
+ + "nothing:true (keine erkannte Änderung), aber du willst trotzdem neu ausliefern, setze force:true.",
151
189
  { site: z.string().optional().describe(SITE_DESC),
152
190
  force: z.boolean().default(false).describe("true = trotzdem veröffentlichen, auch ohne erkannte Änderung (wie der Menüpunkt Erneut veröffentlichen).") },
153
191
  async ({ site, force }) => { try { return asText(await apiPublish(cfg, { site, force })); } catch (e) { return asError(e); } }
@@ -290,7 +328,9 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
290
328
  );
291
329
 
292
330
  // ---- Datei-API: kleine Änderungen am Entwurf direkt auf dem Server (Konzept §7) ----
293
- const FILE_SMALL = "Für kleine Änderungen am Entwurf ohne lokale Kopie; für größere Umbauten site_export (which='draft') → lokal bearbeiten → site_deploy. ";
331
+ const FILE_SMALL = "STANDARDWEG: Änderungen direkt am Server-Entwurf (für reine Textänderungen am einfachsten site_edit_text); "
332
+ + "Export + Deploy nur als Ausnahme für größere Umbauten oder auf ausdrücklichen Wunsch. "
333
+ + "Skriptdateien (JavaScript) ändern nur Betreuer und nur bei JavaScript-Freigabe der Website – Kunden bekommen dort 403. ";
294
334
  server.tool(
295
335
  "site_files",
296
336
  "LESEND: Listet die Dateien des Entwurfs einer Website (Pfad, Größe, Typ, ob bearbeitbar) und die aktuelle draftRev. "
@@ -308,7 +348,7 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
308
348
  );
309
349
  server.tool(
310
350
  "site_file_put",
311
- "SCHREIBEND (Entwurf, nicht live; nur Betreuer-Token): Ersetzt den Inhalt einer Text-Datei (HTML einer bestehenden Seite, "
351
+ "SCHREIBEND (Entwurf, nicht live): Ersetzt den Inhalt einer Text-Datei (HTML einer bestehenden Seite, "
312
352
  + "CSS, SVG, JSON, TXT …). PFLICHT: baseRev = draftRev aus deinem letzten site_file_get/site_status. Antwortet der Server mit "
313
353
  + "conflict:true (409), hat jemand anderes dieselbe Datei inzwischen geändert: NICHT erzwingen – Datei neu holen, Änderung neu "
314
354
  + "anwenden, mit der neuen draftRev erneut senden; die Antwort listet wer/wann/was. Prüfe danach overlay (umgehängte/deaktivierte "
@@ -324,7 +364,7 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
324
364
  );
325
365
  server.tool(
326
366
  "site_file_delete",
327
- "SCHREIBEND (Entwurf; nur Betreuer-Token): Markiert eine Text-Datei im Entwurf als gelöscht (rückgängig machbar; Seiten "
367
+ "SCHREIBEND (Entwurf): Markiert eine Text-Datei im Entwurf als gelöscht (rückgängig machbar; Seiten "
328
368
  + "löschen geht über das Dashboard). baseRev Pflicht, confirmDestructive:true erst NACH Rückfrage beim Nutzer. Prüfe vorher, "
329
369
  + "ob die Datei noch verlinkt ist. " + FILE_SMALL,
330
370
  { site: z.string().optional().describe(SITE_DESC), path: z.string(), baseRev: z.number().int(),
@@ -333,7 +373,7 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
333
373
  );
334
374
  server.tool(
335
375
  "site_file_rename",
336
- "SCHREIBEND (Entwurf; nur Betreuer-Token): Benennt eine Text-Datei (kein HTML) um. Verweise in anderen Dateien werden NICHT "
376
+ "SCHREIBEND (Entwurf): Benennt eine Text-Datei (kein HTML) um. Verweise in anderen Dateien werden NICHT "
337
377
  + "angepasst – die musst du selbst per site_file_put nachziehen. baseRev Pflicht. " + FILE_SMALL,
338
378
  { site: z.string().optional().describe(SITE_DESC), from: z.string(), to: z.string(), baseRev: z.number().int() },
339
379
  async ({ site, from, to, baseRev }) => { try { return asText(await apiFileRename(cfg, { site, from, to, baseRev })); } catch (e) { return asError(e); } }
@@ -362,7 +402,8 @@ export function registerTools(server, { z, cfg, mode = "local", remote = null })
362
402
  function registerRemoteTransferTools(server, { z, cfg, remote, SITE_DESC, asText, asError }) {
363
403
  server.tool(
364
404
  "site_export",
365
- "LESEND: Exportiert eine Website als ZIP und liefert einen DOWNLOAD-LINK (15 Minuten gültig, nur EINMAL abrufbar). "
405
+ "AUSNAHME – nur wenn der Nutzer ausdrücklich lokal arbeiten möchte oder für größere Umbauten (sonst site_edit_text/site_file_*). "
406
+ + "LESEND: Exportiert eine Website als ZIP und liefert einen DOWNLOAD-LINK (15 Minuten gültig, nur EINMAL abrufbar). "
366
407
  + "which: 'published' (Live-Stand, Standard), 'draft' (Entwurf – für lokales Bearbeiten und späteres deploy_upload_begin) oder "
367
408
  + "'original' (flacher Stand ohne ProntoSite-Zutaten). Den Link mit curl/Browser laden, z. B. curl -o site.zip '<downloadUrl>'.",
368
409
  { site: z.string().optional().describe(SITE_DESC),
@@ -376,7 +417,8 @@ function registerRemoteTransferTools(server, { z, cfg, remote, SITE_DESC, asText
376
417
  );
377
418
  server.tool(
378
419
  "deploy_upload_begin",
379
- "SCHREIBEND (Schritt 1 von 2): ZIP-Deploy vorbereiten. Liefert einen UPLOAD-LINK (HTTP PUT, 15 Minuten, einmalig, max. "
420
+ "SCHREIBEND – AUSNAHME: nur für Erst-Import, ausdrücklichen Wunsch nach lokaler Arbeit oder größere Umbauten (kleine Änderungen: "
421
+ + "site_edit_text/site_file_put). Schritt 1 von 2: ZIP-Deploy vorbereiten. Liefert einen UPLOAD-LINK (HTTP PUT, 15 Minuten, einmalig, max. "
380
422
  + "100 MB) für die ZIP-Datei der Website, z. B. curl -T site.zip '<uploadUrl>'. Danach deploy_commit mit der uploadId. "
381
423
  + "Ein Deploy ersetzt das Original der Website (rückholbar über die Re-Import-Zeitleiste); mit publish=true geht der Stand "
382
424
  + "DIREKT LIVE. Ab dem Starter-Tarif. Bitte VOR dem Aufruf bestätigen lassen – und die richtige Website prüfen. "
@@ -387,9 +429,10 @@ function registerRemoteTransferTools(server, { z, cfg, remote, SITE_DESC, asText
387
429
  name: z.string().optional().describe("Erst-Import: expliziter Website-Name. Bei Updates ignoriert."),
388
430
  publish: z.boolean().default(false).describe("true = nach dem Import direkt veröffentlichen (LIVE)."),
389
431
  resetOverlay: z.boolean().default(false).describe("true = bisherige Dashboard-Änderungen verwerfen."),
390
- legal: z.boolean().optional().describe("Rechts-Automatik (Impressum/Datenschutz): true erzwingt, false schaltet ab.") },
391
- async ({ site, slug, name, publish, resetOverlay, legal }) => {
392
- try { return asText(remote.beginUpload({ site, slug, name, publish, resetOverlay, legal })); } catch (e) { return asError(e); }
432
+ legal: z.boolean().optional().describe("Rechts-Automatik (Impressum/Datenschutz): true erzwingt, false schaltet ab."),
433
+ baseRev: z.number().int().optional().describe("Update ohne Export-ZIP: draftRev aus site_status (Pflicht, sonst 428). ZIPs aus site_export tragen ihn automatisch.") },
434
+ async ({ site, slug, name, publish, resetOverlay, legal, baseRev }) => {
435
+ try { return asText(remote.beginUpload({ site, slug, name, publish, resetOverlay, legal, baseRev })); } catch (e) { return asError(e); }
393
436
  }
394
437
  );
395
438
  server.tool(
@@ -404,7 +447,7 @@ function registerRemoteTransferTools(server, { z, cfg, remote, SITE_DESC, asText
404
447
  up = remote.takeUpload(uploadId);
405
448
  const o = up.options;
406
449
  return asText(await apiDeploy(cfg, { site: o.site, slug: o.slug, name: o.name, zipPath: up.path, publish: o.publish,
407
- resetOverlay: o.resetOverlay, legal: o.legal }));
450
+ resetOverlay: o.resetOverlay, legal: o.legal, baseRev: o.baseRev }));
408
451
  } catch (e) { return asError(e); } finally { if (up) up.done(); }
409
452
  }
410
453
  );