@prontosite/mcp 0.13.6 → 0.14.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 +32 -0
- package/api.mjs +35 -2
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/tools.mjs +73 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
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.14.0 – 2026-10-22
|
|
7
|
+
|
|
8
|
+
Enthält 0.13.8 (nie einzeln veröffentlicht).
|
|
9
|
+
- Neu: `media_search {site, query, orientation?, count≤6}` – Unsplash-Fotos suchen; je Treffer eine kleine Vorschau als Bild,
|
|
10
|
+
Fotograf, Lizenz-Hinweis und `resultId` (1 h gültig). Gleiche Suchen kommen 1 h aus dem Cache.
|
|
11
|
+
- Neu: `media_import {site, resultId}` – Treffer übernehmen (Download-Trigger laut Unsplash-Richtlinie, Varianten, Kontingent,
|
|
12
|
+
Fotograf/Quelle in den Medien-Metadaten) → `mediaId` + vorbelegter alt-Text; danach `site_image_set`.
|
|
13
|
+
- Instructions: „Bei ‚passendes Bild für Abschnitt X': site_page_texts → media_search → 2–3 Vorschauen zur Wahl → media_import
|
|
14
|
+
+ site_image_set + Screenshot."
|
|
15
|
+
|
|
16
|
+
## 0.13.8 – 2026-10-21
|
|
17
|
+
|
|
18
|
+
- Neu: `site_edit_html {find, replace, region?, scope?, pages?}` – Markup-Fragment (z. B. Footer) über alle Seiten in EINEM
|
|
19
|
+
Aufruf ersetzen, ein Rückgängig-Schritt; mehrere Fundstellen ohne `scope:"all"` → nur Liste. Rechtstext-Bereich, Skript-Regeln
|
|
20
|
+
und das ProntoSite-Badge geschützt (`skipped`).
|
|
21
|
+
- Badge-Sperre: `site_edit_text`, `site_edit_html` und `site_file_put` ändern, entfernen oder bauen das Badge (`data-ps="badge"`)
|
|
22
|
+
nicht nach – es wird beim Ausliefern erzeugt; Änderungen daneben und das Wiederherstellen des Original-Markers bleiben erlaubt.
|
|
23
|
+
- Rate-Limit (429): Meldung nennt die Rest-Wartezeit und den richtigen Weg (statt allgemeinem Text). Schreib-Limit je Token/Grant
|
|
24
|
+
und Konto: Betreuer 200, Kunden 30 Aufrufe je 10 Minuten.
|
|
25
|
+
- Instructions: Markup über alle Seiten per `site_edit_html`, nie Seite für Seite per `site_file_put`; Badge nie im HTML ändern.
|
|
26
|
+
|
|
27
|
+
## 0.13.7 – 2026-10-20
|
|
28
|
+
|
|
29
|
+
- Neu: `site_forms_get` – Konto-Empfänger, je Formular Art (verwaltetes ProntoSite-Formular oder eigenes `<form>` aus dem
|
|
30
|
+
Import), Seiten, Felder, eigene Empfänger, Danke-Seite; Spam-Modus (wirksam, Kunde, Admin-Übersteuerung).
|
|
31
|
+
- Neu: `site_forms_set {recipients?, form?, spamMode?, thanksPage?}` – Empfänger (Konto oder je Formular, streng geprüft, kein
|
|
32
|
+
CRLF), Spam-Modus `streng|normal|basis`, Danke-Seite nur als interne Seite. Verlauf mit Akteur + Client (alt → neu).
|
|
33
|
+
- `site_status.forms`: Anzahl, Konto-Empfänger, Formulare mit eigenen Empfängern, wirksamer Spam-Modus.
|
|
34
|
+
- Instructions: „Formular-Empfänger, Spam-Stufe, Danke-Seite über site_forms_set, nie per HTML."
|
|
35
|
+
- Neue Empfänger-Adressen über KI-Clients werden erst nach Bestätigung durch die Adresse aktiv (`ausstehend`, 24 h); bisherige
|
|
36
|
+
Adressen bekommen einen Hinweis mit Zurücksetzen-Link.
|
|
37
|
+
|
|
6
38
|
## 0.13.6 – 2026-10-20
|
|
7
39
|
|
|
8
40
|
- Websites beim Namen nennen: `site` darf jetzt auch Firmenname, Domain oder Site-Titel sein (unscharf: Groß/Klein, Umlaute,
|
package/api.mjs
CHANGED
|
@@ -115,8 +115,8 @@ export function friendlyApiError(status, serverError = "") {
|
|
|
115
115
|
return "Dein API-Token ist ungültig oder abgelaufen. Token im Dashboard → API-Zugang neu erstellen und in ~/.prontosite.json eintragen.";
|
|
116
116
|
if (status === 403 && /API-Zugang nicht im Tarif|Starter-Plan/i.test(raw))
|
|
117
117
|
return "Dein ProntoSite-Tarif enthält keinen API-Zugang. Wähle im Dashboard → Zahlung einen Tarif oder bitte deinen Betreuer um eine API-Freigabe.";
|
|
118
|
-
if (status === 429)
|
|
119
|
-
return "Zu viele Anfragen in kurzer Zeit (Rate-Limit). Bitte einen Moment warten und erneut versuchen.";
|
|
118
|
+
if (status === 429) // Server nennt Wartezeit + richtigen Weg → durchreichen; sonst allgemeiner Hinweis
|
|
119
|
+
return /wieder möglich in/.test(raw) ? raw : "Zu viele Anfragen in kurzer Zeit (Rate-Limit). Bitte einen Moment warten und erneut versuchen.";
|
|
120
120
|
return raw || `HTTP ${status}`;
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -402,6 +402,15 @@ export async function apiEditText(cfg, { site, find, replace, scope = null, regi
|
|
|
402
402
|
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify(body) }));
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
+
/** POST /api/edit-html/<site> – Markup-Fragment über alle Seiten ersetzen (z. B. Footer), EIN Undo-Eintrag. 409 als Objekt. */
|
|
406
|
+
export async function apiEditHtml(cfg, { site, find, replace, scope = null, region = null, pages = null, baseRev = null } = {}, fetchImpl = fetch) {
|
|
407
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
408
|
+
const body = { find, replace, ...(scope ? { scope } : {}), ...(region ? { region } : {}),
|
|
409
|
+
...(Array.isArray(pages) && pages.length ? { pages } : {}), ...(Number.isInteger(baseRev) ? { baseRev } : {}) };
|
|
410
|
+
return asJsonOr409(await fetchImpl(`${baseUrl(cfg)}/edit-html/${encodeURIComponent(slug)}`, {
|
|
411
|
+
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify(body) }));
|
|
412
|
+
}
|
|
413
|
+
|
|
405
414
|
/** GET /api/page-texts/<site>?page= – alle Texte einer Seite (Textknoten, reine Text-Links, Attribute, Titel/Beschreibung). */
|
|
406
415
|
export async function apiPageTexts(cfg, { site, page } = {}, fetchImpl = fetch) {
|
|
407
416
|
const { token, slug } = resolveTarget(cfg, site);
|
|
@@ -520,6 +529,18 @@ export async function apiImageSet(cfg, { site, page, nodeId = null, marker = nul
|
|
|
520
529
|
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" },
|
|
521
530
|
body: JSON.stringify({ page, mediaId, ...(nodeId ? { nodeId } : {}), ...(marker ? { marker } : {}), ...(alt != null ? { alt } : {}), ...(Number.isInteger(baseRev) ? { baseRev } : {}) }) }));
|
|
522
531
|
}
|
|
532
|
+
/** GET /api/forms/<site> – Formulare: Konto-Empfänger, Empfänger je Formular, Spam-Modus, Danke-Seiten, erkannte Formulare. */
|
|
533
|
+
export async function apiFormsGet(cfg, { site } = {}, fetchImpl = fetch) {
|
|
534
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
535
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/forms/${encodeURIComponent(slug)}`, { headers: authHeaders(token, cfg) }));
|
|
536
|
+
}
|
|
537
|
+
/** POST /api/forms/<site> – Empfänger (Konto oder je Formular), Spam-Modus, Danke-Seite setzen. */
|
|
538
|
+
export async function apiFormsSet(cfg, { site, form = null, recipients, spamMode, thanksPage } = {}, fetchImpl = fetch) {
|
|
539
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
540
|
+
const body = { ...(form ? { form } : {}), ...(recipients !== undefined ? { recipients } : {}), ...(spamMode !== undefined ? { spamMode } : {}), ...(thanksPage !== undefined ? { thanksPage } : {}) };
|
|
541
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/forms/${encodeURIComponent(slug)}`, {
|
|
542
|
+
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify(body) }));
|
|
543
|
+
}
|
|
523
544
|
/** POST /api/undo|redo/<site> – letzte Änderung(en) des Entwurfs zurücknehmen bzw. wiederherstellen (derselbe Stack wie das Dashboard). */
|
|
524
545
|
export async function apiUndo(cfg, { site, steps = 1, baseRev = null, redo = false } = {}, fetchImpl = fetch) {
|
|
525
546
|
const { token, slug } = resolveTarget(cfg, site);
|
|
@@ -539,6 +560,18 @@ export async function apiMediaRestore(cfg, { site, trashId } = {}, fetchImpl = f
|
|
|
539
560
|
return asJson(await fetchImpl(`${baseUrl(cfg)}/media-restore/${encodeURIComponent(slug)}`, {
|
|
540
561
|
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify({ trashId }) }));
|
|
541
562
|
}
|
|
563
|
+
/** GET /api/media-search/<site>?query=&orientation=&count= – Unsplash-Treffer (Vorschau base64, Fotograf, resultId). */
|
|
564
|
+
export async function apiMediaSearch(cfg, { site, query, orientation = null, count = 3 } = {}, fetchImpl = fetch) {
|
|
565
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
566
|
+
const q = new URLSearchParams({ query: String(query ?? ""), count: String(count ?? 3), ...(orientation ? { orientation } : {}) });
|
|
567
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/media-search/${encodeURIComponent(slug)}?${q}`, { headers: authHeaders(token, cfg) }));
|
|
568
|
+
}
|
|
569
|
+
/** POST /api/media-import/<site> – Unsplash-Treffer übernehmen (Download-Trigger, Varianten, Attribution) → mediaId. */
|
|
570
|
+
export async function apiMediaImport(cfg, { site, resultId } = {}, fetchImpl = fetch) {
|
|
571
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
572
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/media-import/${encodeURIComponent(slug)}`, {
|
|
573
|
+
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify({ resultId }) }));
|
|
574
|
+
}
|
|
542
575
|
/** GET /api/preview-screenshot/<site>?page=&width= – PNG des Entwurfs (Base64 + Maße). */
|
|
543
576
|
export async function apiPreviewScreenshot(cfg, { site, page = "index.html", width = 1280 } = {}, fetchImpl = fetch) {
|
|
544
577
|
const { token, slug } = resolveTarget(cfg, site);
|
package/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const cfg = loadConfig();
|
|
|
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
23
|
// instructions: Arbeitsweise (Weg B = Standard) – gleicher Text wie am Remote-Endpunkt.
|
|
24
|
-
const server = new McpServer({ name: "prontosite", version: "0.
|
|
24
|
+
const server = new McpServer({ name: "prontosite", version: "0.14.0" }, { instructions: instructionsFor("local") });
|
|
25
25
|
// Tool-Definitionen: gemeinsame Quelle mit dem Remote-Endpunkt (tools.mjs, Konzept KI-Anbindung §4.2).
|
|
26
26
|
registerTools(server, { z, cfg, mode: "local" });
|
|
27
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prontosite/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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,7 @@
|
|
|
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 { apiEditText, apiPageTexts, apiPageTextsSet, apiPageCopy, apiMediaUploadBegin, apiMediaUploadFile, apiMediaCommit, apiMediaList, apiImageSet, apiUndo, apiMediaDelete, apiMediaRestore, apiPreviewScreenshot, 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, apiEditHtml, apiPageTexts, apiPageTextsSet, apiPageCopy, apiMediaUploadBegin, apiMediaUploadFile, apiMediaCommit, apiMediaList, apiImageSet, apiUndo, apiMediaDelete, apiMediaRestore, apiFormsGet, apiFormsSet, apiMediaSearch, apiMediaImport, apiPreviewScreenshot, 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
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Arbeitsweise (MCP-`instructions` beim Initialize – lokal UND /mcp): Weg B ist der Standard (Nick 19.10.).
|
|
@@ -29,6 +29,9 @@ const instructionLines = mode => [
|
|
|
29
29
|
"2a. Für Text-, Link- und Kontaktdaten-Änderungen (Telefonnummer, E-Mail, Adresse, Öffnungszeiten) IMMER site_edit_text mit "
|
|
30
30
|
+ "scope:\"all\" (bei Bedarf region:\"footer\"/\"header\") – EIN Aufruf für alle Seiten, inkl. tel:/mailto:-Links. NIE Seite für Seite per "
|
|
31
31
|
+ "site_file_put. Meldet die Antwort company, zusätzlich site_company_set für die Firmendaten (Impressum/Datenschutz/Kontakt).",
|
|
32
|
+
"2c. Für Markup über alle Seiten (z. B. Footer-HTML) site_edit_html mit scope:\"all\" (ggf. region:\"footer\"), nie Seite für Seite per site_file_put. "
|
|
33
|
+
+ "Schreib-Limit je 10 Minuten: Kunden 30, Betreuer 200 Aufrufe – dieselbe Änderung auf vielen Seiten ist EIN Aufruf (site_edit_text scope:\"all\" bzw. site_edit_html).",
|
|
34
|
+
"2d. Badge nie im HTML ändern, nachbauen oder entfernen – es wird beim Ausliefern erzeugt (data-ps=\"badge\"; site_edit_text/site_edit_html/site_file_put lehnen das ab, erlaubt ist nur das Wiederherstellen des Original-Markers).",
|
|
32
35
|
"2b. Für Übersetzungen und Textüberarbeitung: site_page_texts → Texte übersetzen/überarbeiten → site_page_texts_set, JE SEITE ein Aufruf "
|
|
33
36
|
+ "(neue Sprachfassung vorher mit site_page_copy, z. B. index.html → en/index.html). NIE HTML über site_file_put umschreiben. "
|
|
34
37
|
+ "Rechtstext-Seiten (Impressum/Datenschutz) sind ausgenommen.",
|
|
@@ -40,10 +43,13 @@ const instructionLines = mode => [
|
|
|
40
43
|
+ "media_upload_commit und mit site_image_set einsetzen. Nie Varianten selbst bauen, nie ZIP für Medien.",
|
|
41
44
|
...(mode === "local" ? ["5a-lokal. Wenn du einen lokalen Dateipfad hast, nutze media_upload_file; sonst Upload-Link (media_upload_begin). "
|
|
42
45
|
+ "media_upload_file prüft, lädt hoch und übernimmt in einem Schritt und liefert die mediaId."] : []),
|
|
46
|
+
"5e. Bei „passendes Bild für Abschnitt X\": Abschnittstext lesen (site_page_texts), Suchbegriff bilden, media_search, dem Nutzer 2–3 "
|
|
47
|
+
+ "Vorschauen zeigen und wählen lassen, dann media_import + site_image_set + site_preview_screenshot.",
|
|
43
48
|
"5b. Design = CSS-Datei ändern (site_file_get/site_file_put), nicht HTML umschreiben. Nach Design- oder Bild-Änderungen "
|
|
44
49
|
+ "site_preview_screenshot (1280 bzw. 390) prüfen, BEVOR du dem Nutzer antwortest.",
|
|
45
50
|
"5c. Bei „rückgängig“ site_undo (bzw. „doch wieder“ site_redo), nie Dateien manuell zurückbauen. Medien löschen nur mit site_media_delete "
|
|
46
51
|
+ "(Papierkorb, 30 Tage; zurückholen mit site_media_restore).",
|
|
52
|
+
"5d. Formular-Empfänger, Spam-Stufe und Danke-Seite über site_forms_set, nie per HTML (site_forms_get zeigt den Stand).",
|
|
47
53
|
"5. Jede Änderung lässt sich rückgängig machen; Rechtstexte (Impressum/Datenschutz) nicht umformulieren – sie kommen aus den Firmendaten.",
|
|
48
54
|
];
|
|
49
55
|
/** Server-Instructions je Modus ("local" = stdio-Paket mit Dateizugriff, "remote" = /mcp). */
|
|
@@ -53,10 +59,10 @@ export const SERVER_INSTRUCTIONS = instructionsFor("remote");
|
|
|
53
59
|
// Tool-Annotationen (MCP ToolAnnotations): ChatGPT & Co. fragen sonst bei JEDEM Aufruf nach. readOnly = ändert nichts;
|
|
54
60
|
// destructive = live/kundenwirksam, ersetzt Bestand oder nimmt zurück (Bestätigung sinnvoll). Entwurfs-Änderungen sind
|
|
55
61
|
// rückgängig machbar und daher nicht destruktiv. openWorld = wirkt nach außen (öffentliche Website/DNS).
|
|
56
|
-
const READ_ONLY = new Set(["sites_list", "site_status", "site_page_texts", "site_media_list", "site_preview_screenshot", "site_company_get",
|
|
62
|
+
const READ_ONLY = new Set(["sites_list", "site_status", "site_forms_get", "media_search", "site_page_texts", "site_media_list", "site_preview_screenshot", "site_company_get",
|
|
57
63
|
"site_domain_status", "site_embeds", "site_widgets", "site_files", "site_file_get"]);
|
|
58
64
|
const DESTRUCTIVE = new Set(["site_publish", "site_deploy", "deploy_commit", "site_file_delete", "site_media_delete", "site_undo", "site_redo", "site_domain_connect"]);
|
|
59
|
-
const OPEN_WORLD = new Set(["site_publish", "site_deploy", "deploy_commit", "site_domain_connect"]);
|
|
65
|
+
const OPEN_WORLD = new Set(["site_publish", "site_deploy", "deploy_commit", "site_domain_connect", "media_search", "media_import"]);
|
|
60
66
|
export function toolAnnotations(name, mode = "local") {
|
|
61
67
|
// site_export: remote nur ein Download-Link (lesend), lokal schreibt es eine ZIP auf die Platte.
|
|
62
68
|
const ro = READ_ONLY.has(name) || (name === "site_export" && mode === "remote");
|
|
@@ -164,6 +170,22 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
164
170
|
async ({ site, find, replace, scope, region, attributes, pages, baseRev }) => { try { return asText(await apiEditText(cfg, { site, find, replace, scope, region, attributes, pages, baseRev })); } catch (e) { return asError(e); } }
|
|
165
171
|
);
|
|
166
172
|
|
|
173
|
+
server.tool(
|
|
174
|
+
"site_edit_html",
|
|
175
|
+
"SCHREIBEND (Entwurf, nicht live) – Markup-Fragment über ALLE Seiten in EINEM Aufruf ersetzen (z. B. Footer-HTML: neuer Link, "
|
|
176
|
+
+ "Social-Icons, Zeile ergänzen). Sucht `find` wörtlich im Quelltext (wie site_file_get) innerhalb von region (footer | header | "
|
|
177
|
+
+ "main | all) und ersetzt durch `replace` – EIN Rückgängig-Schritt für alle Seiten. scope:\"all\" = alle Fundstellen ersetzen; ohne "
|
|
178
|
+
+ "scope ändert der Server bei mehr als einer Fundstelle NICHTS und liefert die Liste (oder pages:[…]). Für reinen Text nimm "
|
|
179
|
+
+ "site_edit_text. Rechtstext-Bereich, Skripte (Regeln wie site_file_put) und das ProntoSite-Badge sind geschützt (skipped).",
|
|
180
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
181
|
+
find: z.string().describe("Exaktes Markup, wie es im Quelltext steht (aus site_file_get kopieren)."),
|
|
182
|
+
replace: z.string().describe("Neues Markup."),
|
|
183
|
+
region: z.enum(["all", "header", "main", "footer"]).optional().describe("Nur in diesem Bereich suchen (erstes <footer>/<header>/<main> je Seite)."),
|
|
184
|
+
scope: z.enum(["all"]).optional().describe("\"all\" = alle Fundstellen auf allen (bzw. den gewählten) Seiten ersetzen."),
|
|
185
|
+
pages: z.array(z.string()).optional().describe("Nur diese Seiten (ID, Datei oder Slug)."),
|
|
186
|
+
baseRev: z.number().int().optional().describe("Optional: draftRev aus site_status (Konfliktschutz).") },
|
|
187
|
+
async (a) => { try { return asText(await apiEditHtml(cfg, a)); } catch (e) { return asError(e); } }
|
|
188
|
+
);
|
|
167
189
|
server.tool(
|
|
168
190
|
"site_page_texts",
|
|
169
191
|
"LESEND – STANDARDWEG für Übersetzung/Umformulierung: liefert ALLE Texte einer Seite ohne HTML: [{id, role, text, markdown?}] – "
|
|
@@ -231,6 +253,31 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
231
253
|
{ site: z.string().optional().describe(SITE_DESC), uploadId: z.string().describe("uploadId aus media_upload_begin.") },
|
|
232
254
|
async ({ site, uploadId }) => { try { return asText(await apiMediaCommit(cfg, { site, uploadId })); } catch (e) { return asError(e); } }
|
|
233
255
|
);
|
|
256
|
+
server.tool(
|
|
257
|
+
"media_search",
|
|
258
|
+
"Kostenlose Fotos von Unsplash suchen (verbraucht ein gemeinsames Stunden-Kontingent; gleiche Suchen kommen 1 h aus dem Cache). "
|
|
259
|
+
+ "Liefert je Treffer eine kleine Vorschau als Bild, Fotograf, Lizenz-Hinweis und eine resultId (1 h gültig). Suchbegriff am besten "
|
|
260
|
+
+ "englisch und konkret (z. B. 'bakery counter bread'). Dem Nutzer 2–3 Vorschauen zeigen und wählen lassen, dann media_import.",
|
|
261
|
+
{ site: z.string().optional().describe(SITE_DESC), query: z.string().describe("Suchbegriff (2–120 Zeichen)."),
|
|
262
|
+
orientation: z.enum(["landscape", "portrait", "squarish"]).optional().describe("Ausrichtung: landscape (Hero/Banner), portrait, squarish."),
|
|
263
|
+
count: z.number().int().min(1).max(6).default(3).describe("Anzahl Treffer (max. 6).") },
|
|
264
|
+
async ({ site, query, orientation, count }) => {
|
|
265
|
+
try {
|
|
266
|
+
const r = await apiMediaSearch(cfg, { site, query, orientation, count });
|
|
267
|
+
const images = (r.results || []).filter(x => x.preview?.data).map(x => ({ type: "image", data: x.preview.data, mimeType: x.preview.mimeType || "image/jpeg" }));
|
|
268
|
+
const text = { ...r, results: (r.results || []).map((x, i) => ({ nr: i + 1, ...x, preview: x.preview ? "(Bild unten)" : null })) };
|
|
269
|
+
return { content: [{ type: "text", text: JSON.stringify(text, null, 2) }, ...images] };
|
|
270
|
+
} catch (e) { return asError(e); }
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
server.tool(
|
|
274
|
+
"media_import",
|
|
275
|
+
"SCHREIBEND: einen Unsplash-Treffer aus media_search übernehmen – der Server lädt das Foto (Download-Trigger laut Unsplash-Richtlinie), "
|
|
276
|
+
+ "erzeugt Varianten, speichert Fotograf/Quelle in den Medien-Metadaten und liefert mediaId + vorbelegten alt-Text. Zählt aufs "
|
|
277
|
+
+ "Medien-Kontingent. Danach site_image_set {mediaId, alt} und site_preview_screenshot.",
|
|
278
|
+
{ site: z.string().optional().describe(SITE_DESC), resultId: z.string().describe("resultId aus media_search (1 h gültig).") },
|
|
279
|
+
async ({ site, resultId }) => { try { return asText(await apiMediaImport(cfg, { site, resultId })); } catch (e) { return asError(e); } }
|
|
280
|
+
);
|
|
234
281
|
server.tool(
|
|
235
282
|
"site_media_list",
|
|
236
283
|
"LESEND: Medien der Website (mediaId, Name, Typ, Größe, Maße; Uploads und Bilder der importierten Website) und das Medien-Kontingent; trash = Papierkorb (trashId für site_media_restore).",
|
|
@@ -247,6 +294,29 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
247
294
|
baseRev: z.number().int().optional().describe("Optional: draftRev (Konfliktschutz).") },
|
|
248
295
|
async (a) => { try { return asText(await apiImageSet(cfg, a)); } catch (e) { return asError(e); } }
|
|
249
296
|
);
|
|
297
|
+
server.tool(
|
|
298
|
+
"site_forms_get",
|
|
299
|
+
"LESEND: Formulare der Website – Konto-Empfänger (defaultRecipients), je Formular: Art (verwaltetes ProntoSite-Formular/Marker "
|
|
300
|
+
+ "oder eigenes <form> aus dem Import), Seiten, Felder, eigene Empfänger (null = Konto-Empfänger), Danke-Seite; Spam-Modus "
|
|
301
|
+
+ "(wirksam, Kunde, Admin-Übersteuerung) und die möglichen Danke-Seiten.",
|
|
302
|
+
{ site: z.string().optional().describe(SITE_DESC) },
|
|
303
|
+
async ({ site }) => { try { return asText(await apiFormsGet(cfg, { site })); } catch (e) { return asError(e); } }
|
|
304
|
+
);
|
|
305
|
+
server.tool(
|
|
306
|
+
"site_forms_set",
|
|
307
|
+
"SCHREIBEND: Formular-Einstellungen ändern – NIE per HTML. recipients: Empfänger-Adressen (ohne form = Konto-Empfänger für alle "
|
|
308
|
+
+ "Formulare ohne eigene; mit form = nur dieses Formular, [] = zurück auf Konto-Empfänger); spamMode: streng | normal | basis; "
|
|
309
|
+
+ "thanksPage: interne Seite für die Weiterleitung nach dem Absenden ('' = Meldung am Formular), nur mit form (außer es gibt genau "
|
|
310
|
+
+ "eins). NEUE Empfänger-Adressen werden erst aktiv, wenn sie den Bestätigungslink in der Mail an sie anklicken (24 h) – bis dahin "
|
|
311
|
+
+ "bleiben die bisherigen aktiv (Antwort: ausstehend). Die bisherigen Adressen bekommen einen Hinweis mit Zurücksetzen-Link. Vorher "
|
|
312
|
+
+ "beim Nutzer bestätigen, wohin Anfragen künftig gehen.",
|
|
313
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
314
|
+
form: z.string().optional().describe("Formular-ID aus site_forms_get (für Empfänger/Danke-Seite je Formular)."),
|
|
315
|
+
recipients: z.array(z.string()).optional().describe("E-Mail-Adressen (max. 10)."),
|
|
316
|
+
spamMode: z.enum(["streng", "normal", "basis"]).optional(),
|
|
317
|
+
thanksPage: z.string().optional().describe("Interner Pfad einer Seite dieser Website, z. B. /danke/ (keine externen URLs).") },
|
|
318
|
+
async (a) => { try { return asText(await apiFormsSet(cfg, a)); } catch (e) { return asError(e); } }
|
|
319
|
+
);
|
|
250
320
|
server.tool(
|
|
251
321
|
"site_undo",
|
|
252
322
|
"SCHREIBEND (Entwurf): macht die letzte(n) Änderung(en) rückgängig – derselbe Verlauf wie der Rückgängig-Knopf im Dashboard (auch "
|