@prontosite/mcp 0.14.5 → 0.14.7
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 +17 -0
- package/api.mjs +10 -6
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/tools.mjs +20 -8
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
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.7 – 2026-10-29
|
|
7
|
+
|
|
8
|
+
- `site_image_set`: neue Option `zoom` (true/false) – Bild per Klick vergrößern (Lightbox der ProntoSite-Runtime, Bildunterschrift
|
|
9
|
+
aus alt/figcaption, Galerie-Blättern in `.ps-gallery`). `zoom` geht auch ohne `mediaId`; `mediaId` ist dafür optional.
|
|
10
|
+
- Instructions 2d: Badge NUR als kanonisches Snippet einbauen (`data-ps="badge"` + `data-ps-variant` auto/light/dark/mono), Footer,
|
|
11
|
+
letzte Zeile, nie manuell stylen – der Server ersetzt beim Ausliefern alles außer der Variante.
|
|
12
|
+
- `site_forms_set`: neues Feld `captcha` (true/false) – Sicherheitscode (Bild-Captcha) je Formular; `site_forms_get` zeigt `captcha`,
|
|
13
|
+
`captchaLive` und `spamAbgewehrt` (abgewehrte Sendungen der letzten 30 Tage je Grund).
|
|
14
|
+
- Versionsnummer im lokalen Server (`index.mjs`) und `package-lock.json` wieder gleich `package.json`.
|
|
15
|
+
|
|
16
|
+
## 0.14.6 – 2026-10-27
|
|
17
|
+
|
|
18
|
+
- Instructions 2c: neues Entwurfs-Limit je Website – 300 Änderungen je Stunde, davon 30 direkt hintereinander (Kunden und
|
|
19
|
+
Betreuer gleich). Bei 429 nicht aufgeben, sondern `retryAfter` Sekunden warten und weitermachen.
|
|
20
|
+
- Fehler mit HTTP 429 tragen `status`, `rateLimited` und `retryAfter` im `structuredContent` (vorher nur der Text);
|
|
21
|
+
Server-Texte mit „erneut versuchen" werden unverändert durchgereicht.
|
|
22
|
+
|
|
6
23
|
## 0.14.5 – 2026-10-24
|
|
7
24
|
|
|
8
25
|
Enthält 0.14.3 und 0.14.4 (nie einzeln veröffentlicht).
|
package/api.mjs
CHANGED
|
@@ -116,7 +116,7 @@ export function friendlyApiError(status, serverError = "") {
|
|
|
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
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.";
|
|
119
|
+
return /wieder möglich in|erneut versuchen/.test(raw) ? raw : "Zu viele Anfragen in kurzer Zeit (Rate-Limit). Bitte einen Moment warten und erneut versuchen, Änderungen bündeln.";
|
|
120
120
|
return raw || `HTTP ${status}`;
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -127,7 +127,11 @@ async function asJson(res) {
|
|
|
127
127
|
if (!res.ok) {
|
|
128
128
|
// Website-Auflösung (Firmenname/Domain): Kandidaten bzw. Vorschläge in die Meldung, damit der Assistent nachfragen kann.
|
|
129
129
|
const list = (body.candidates || body.suggestions || []).map(e => `${e.slug || e.site}${e.name ? ` (${e.name})` : ""}`).join(", ");
|
|
130
|
-
|
|
130
|
+
const err = new Error(friendlyApiError(res.status, body.error) + (list ? ` ${body.candidates ? "Kandidaten" : "Ähnlich"}: ${list}.` : ""));
|
|
131
|
+
// 429: Status + Wartezeit maschinenlesbar mitgeben (structuredContent über asError) – die KI soll warten statt aufgeben.
|
|
132
|
+
err.status = res.status;
|
|
133
|
+
if (res.status === 429) { err.rateLimited = true; err.retryAfter = Number(body.retryAfter) || Number(res.headers?.get?.("retry-after")) || null; }
|
|
134
|
+
throw err;
|
|
131
135
|
}
|
|
132
136
|
return body;
|
|
133
137
|
}
|
|
@@ -528,11 +532,11 @@ export async function apiMediaList(cfg, { site } = {}, fetchImpl = fetch) {
|
|
|
528
532
|
return asJson(await fetchImpl(`${baseUrl(cfg)}/media/${encodeURIComponent(slug)}`, { headers: authHeaders(token, cfg) }));
|
|
529
533
|
}
|
|
530
534
|
/** POST /api/image-set/<site> – Bild einer Seite tauschen (setImage, ein Undo-Schritt). */
|
|
531
|
-
export async function apiImageSet(cfg, { site, page, nodeId = null, marker = null, mediaId, alt = null, baseRev = null } = {}, fetchImpl = fetch) {
|
|
535
|
+
export async function apiImageSet(cfg, { site, page, nodeId = null, marker = null, mediaId = null, alt = null, zoom = null, baseRev = null } = {}, fetchImpl = fetch) {
|
|
532
536
|
const { token, slug } = resolveTarget(cfg, site);
|
|
533
537
|
return asJsonOr409(await fetchImpl(`${baseUrl(cfg)}/image-set/${encodeURIComponent(slug)}`, {
|
|
534
538
|
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" },
|
|
535
|
-
body: JSON.stringify({ page, mediaId, ...(nodeId ? { nodeId } : {}), ...(marker ? { marker } : {}), ...(alt != null ? { alt } : {}), ...(Number.isInteger(baseRev) ? { baseRev } : {}) }) }));
|
|
539
|
+
body: JSON.stringify({ page, ...(mediaId ? { mediaId } : {}), ...(nodeId ? { nodeId } : {}), ...(marker ? { marker } : {}), ...(alt != null ? { alt } : {}), ...(typeof zoom === "boolean" ? { zoom } : {}), ...(Number.isInteger(baseRev) ? { baseRev } : {}) }) }));
|
|
536
540
|
}
|
|
537
541
|
/** GET /api/forms/<site> – Formulare: Konto-Empfänger, Empfänger je Formular, Spam-Modus, Danke-Seiten, erkannte Formulare. */
|
|
538
542
|
export async function apiFormsGet(cfg, { site } = {}, fetchImpl = fetch) {
|
|
@@ -540,9 +544,9 @@ export async function apiFormsGet(cfg, { site } = {}, fetchImpl = fetch) {
|
|
|
540
544
|
return asJson(await fetchImpl(`${baseUrl(cfg)}/forms/${encodeURIComponent(slug)}`, { headers: authHeaders(token, cfg) }));
|
|
541
545
|
}
|
|
542
546
|
/** POST /api/forms/<site> – Empfänger (Konto oder je Formular), Spam-Modus, Danke-Seite setzen. */
|
|
543
|
-
export async function apiFormsSet(cfg, { site, form = null, recipients, spamMode, thanksPage } = {}, fetchImpl = fetch) {
|
|
547
|
+
export async function apiFormsSet(cfg, { site, form = null, recipients, spamMode, thanksPage, captcha } = {}, fetchImpl = fetch) {
|
|
544
548
|
const { token, slug } = resolveTarget(cfg, site);
|
|
545
|
-
const body = { ...(form ? { form } : {}), ...(recipients !== undefined ? { recipients } : {}), ...(spamMode !== undefined ? { spamMode } : {}), ...(thanksPage !== undefined ? { thanksPage } : {}) };
|
|
549
|
+
const body = { ...(form ? { form } : {}), ...(recipients !== undefined ? { recipients } : {}), ...(spamMode !== undefined ? { spamMode } : {}), ...(thanksPage !== undefined ? { thanksPage } : {}), ...(typeof captcha === "boolean" ? { captcha } : {}) };
|
|
546
550
|
return asJson(await fetchImpl(`${baseUrl(cfg)}/forms/${encodeURIComponent(slug)}`, {
|
|
547
551
|
method: "POST", headers: { ...authHeaders(token, cfg), "content-type": "application/json" }, body: JSON.stringify(body) }));
|
|
548
552
|
}
|
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.14.
|
|
24
|
+
const server = new McpServer({ name: "prontosite", version: "0.14.7" }, { 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.14.
|
|
3
|
+
"version": "0.14.7",
|
|
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
|
@@ -30,8 +30,12 @@ const instructionLines = mode => [
|
|
|
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
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
|
-
+ "
|
|
34
|
-
"2d. Badge
|
|
33
|
+
+ "Entwurfs-Limit je Website: 300 Änderungen je Stunde, davon 30 direkt hintereinander (Kunden und Betreuer gleich) – dieselbe Änderung auf vielen Seiten ist EIN Aufruf (site_edit_text scope:\"all\" bzw. site_edit_html). Bei 429 (rateLimited) NICHT aufgeben: retryAfter Sekunden warten, dann weitermachen; bisherige Änderungen bleiben gespeichert.",
|
|
34
|
+
"2d. Badge „Made with ProntoSite\" NUR als dieses Snippet einbauen (Footer, letzte Zeile, bündig mit dem Copyright), nie manuell stylen: "
|
|
35
|
+
+ "<a data-ps=\"badge\" data-ps-variant=\"auto\" href=\"https://prontosite.io/?ref=badge&site=<slug>\" rel=\"noopener noreferrer\">Made with ProntoSite</a> "
|
|
36
|
+
+ "(Variante auto | light für dunkle Footer | dark für helle Flächen | mono). Der Server ersetzt beim Ausliefern alles außer der Variante "
|
|
37
|
+
+ "(Schrift, Größe, Farbe, Icon). Ein vorhandenes Badge nie im HTML ändern, nachbauen oder entfernen (site_edit_text/site_edit_html/"
|
|
38
|
+
+ "site_file_put lehnen das ab; erlaubt ist nur das Wiederherstellen des Original-Markers).",
|
|
35
39
|
"2b. Für Übersetzungen und Textüberarbeitung: site_page_texts → Texte übersetzen/überarbeiten → site_page_texts_set, JE SEITE ein Aufruf "
|
|
36
40
|
+ "(neue Sprachfassung vorher mit site_page_copy, z. B. index.html → en/index.html). NIE HTML über site_file_put umschreiben. "
|
|
37
41
|
+ "Rechtstext-Seiten (Impressum/Datenschutz) sind ausgenommen.",
|
|
@@ -83,7 +87,8 @@ export function toolAnnotations(name, mode = "local") {
|
|
|
83
87
|
export const toStructured = obj => (obj && typeof obj === "object" && !Array.isArray(obj) ? obj : Array.isArray(obj) ? { items: obj } : { value: obj ?? null });
|
|
84
88
|
export const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }], structuredContent: toStructured(obj) });
|
|
85
89
|
export const asError = e => { const msg = e?.message || String(e);
|
|
86
|
-
return { isError: true, content: [{ type: "text", text: "Fehler: " + msg }], structuredContent: { error: msg, ...(e?.status ? { status: e.status } : {})
|
|
90
|
+
return { isError: true, content: [{ type: "text", text: "Fehler: " + msg }], structuredContent: { error: msg, ...(e?.status ? { status: e.status } : {}),
|
|
91
|
+
...(e?.rateLimited ? { rateLimited: true, retryAfter: e.retryAfter ?? null } : {}) } }; };
|
|
87
92
|
|
|
88
93
|
/** Ausgabe-Schema von site_file_get (structuredContent; OpenAI Apps SDK: outputSchema für Tools mit structuredContent). */
|
|
89
94
|
const FILE_GET_OUTPUT = z => z.object({
|
|
@@ -322,16 +327,20 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
322
327
|
"site_image_set",
|
|
323
328
|
"SCHREIBEND (Entwurf): setzt ein Bild auf einer Seite ein (ein Rückgängig-Schritt). Ziel: nodeId des Bild-Knotens oder marker (id- bzw. "
|
|
324
329
|
+ "data-ps-Wert eines Elements; dessen Bild wird getauscht). mediaId aus media_upload_commit oder site_media_list. alt = Bildbeschreibung "
|
|
325
|
-
+ "(für Barrierefreiheit/SEO immer setzen).
|
|
330
|
+
+ "(für Barrierefreiheit/SEO immer setzen). zoom:true = Bild per Klick vergrößern (Lightbox mit Bildunterschrift aus alt/figcaption; "
|
|
331
|
+
+ "Bilder in einem Container mit class=\"ps-gallery\" blättern als Galerie), zoom:false schaltet es ab; zoom geht auch ohne mediaId. "
|
|
332
|
+
+ "Danach site_preview_screenshot prüfen.",
|
|
326
333
|
{ site: z.string().optional().describe(SITE_DESC), page: z.string().describe("Seite (ID, Datei oder Slug)."),
|
|
327
|
-
nodeId: z.string().optional(), marker: z.string().optional(), mediaId: z.string(), alt: z.string().optional(),
|
|
334
|
+
nodeId: z.string().optional(), marker: z.string().optional(), mediaId: z.string().optional(), alt: z.string().optional(),
|
|
335
|
+
zoom: z.boolean().optional().describe("true = per Klick vergrößern (Lightbox), false = aus."),
|
|
328
336
|
baseRev: z.number().int().optional().describe("Optional: draftRev (Konfliktschutz).") },
|
|
329
337
|
async (a) => { try { return asText(await apiImageSet(cfg, a)); } catch (e) { return asError(e); } }
|
|
330
338
|
);
|
|
331
339
|
server.tool(
|
|
332
340
|
"site_forms_get",
|
|
333
341
|
"LESEND: Formulare der Website – Konto-Empfänger (defaultRecipients), je Formular: Art (verwaltetes ProntoSite-Formular/Marker "
|
|
334
|
-
+ "oder eigenes <form> aus dem Import), Seiten, Felder, eigene Empfänger (null = Konto-Empfänger), Danke-Seite
|
|
342
|
+
+ "oder eigenes <form> aus dem Import), Seiten, Felder, eigene Empfänger (null = Konto-Empfänger), Danke-Seite, Sicherheitscode "
|
|
343
|
+
+ "(captcha, captchaLive = schon ausgeliefert), abgewehrte Sendungen der letzten 30 Tage (spamAbgewehrt); Spam-Modus "
|
|
335
344
|
+ "(wirksam, Kunde, Admin-Übersteuerung) und die möglichen Danke-Seiten.",
|
|
336
345
|
{ site: z.string().optional().describe(SITE_DESC) },
|
|
337
346
|
async ({ site }) => { try { return asText(await apiFormsGet(cfg, { site })); } catch (e) { return asError(e); } }
|
|
@@ -343,12 +352,15 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
343
352
|
+ "thanksPage: interne Seite für die Weiterleitung nach dem Absenden ('' = Meldung am Formular), nur mit form (außer es gibt genau "
|
|
344
353
|
+ "eins). NEUE Empfänger-Adressen werden erst aktiv, wenn sie den Bestätigungslink in der Mail an sie anklicken (24 h) – bis dahin "
|
|
345
354
|
+ "bleiben die bisherigen aktiv (Antwort: ausstehend). Die bisherigen Adressen bekommen einen Hinweis mit Zurücksetzen-Link. Vorher "
|
|
346
|
-
+ "beim Nutzer bestätigen, wohin Anfragen künftig gehen."
|
|
355
|
+
+ "beim Nutzer bestätigen, wohin Anfragen künftig gehen. captcha: true/false = Sicherheitscode (Bild mit 5 Zeichen) je Formular, "
|
|
356
|
+
+ "nur mit form (außer es gibt genau eins); erscheint mit dem nächsten Veröffentlichen. Nur einschalten, wenn trotz Spam-Schutz Spam "
|
|
357
|
+
+ "durchkommt, und den Nutzer darauf hinweisen, dass ein Bild-Code für blinde/sehbehinderte Menschen eine Hürde ist.",
|
|
347
358
|
{ site: z.string().optional().describe(SITE_DESC),
|
|
348
359
|
form: z.string().optional().describe("Formular-ID aus site_forms_get (für Empfänger/Danke-Seite je Formular)."),
|
|
349
360
|
recipients: z.array(z.string()).optional().describe("E-Mail-Adressen (max. 10)."),
|
|
350
361
|
spamMode: z.enum(["streng", "normal", "basis"]).optional(),
|
|
351
|
-
thanksPage: z.string().optional().describe("Interner Pfad einer Seite dieser Website, z. B. /danke/ (keine externen URLs).")
|
|
362
|
+
thanksPage: z.string().optional().describe("Interner Pfad einer Seite dieser Website, z. B. /danke/ (keine externen URLs)."),
|
|
363
|
+
captcha: z.boolean().optional().describe("Sicherheitscode (Bild-Captcha) für dieses Formular an/aus.") },
|
|
352
364
|
async (a) => { try { return asText(await apiFormsSet(cfg, a)); } catch (e) { return asError(e); } }
|
|
353
365
|
);
|
|
354
366
|
server.tool(
|