@prontosite/mcp 0.14.0 → 0.14.2
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 +16 -0
- package/README.md +1 -1
- package/api.mjs +9 -4
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/tools.mjs +47 -13
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
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.2 – 2026-10-24
|
|
7
|
+
|
|
8
|
+
- Alle Tools liefern zusätzlich `structuredContent` (dieselben Daten wie der Text-Block, MCP 2025-06-18 „Structured Content");
|
|
9
|
+
Fehler als `structuredContent.error` (außer bei Tools mit `outputSchema` – dort nur Text). Anlass: Befund ChatGPT, `site_file_get` kam als `{}` an.
|
|
10
|
+
- `site_file_get {path, offset?, length?, outline?}`: liest in Teilen (Standard 60 KB, max. 1 MB je Aufruf). Antwort mit
|
|
11
|
+
`size`, `offset`, `length`, `truncated`, `nextOffset`; Einheit Bytes (UTF-8), Teile enden nie mitten in einem Zeichen, Binär-Teile
|
|
12
|
+
(Base64) lassen sich aneinanderhängen. Beim ersten Teil einer großen Datei bzw. mit `outline:true` eine gekappte Gliederung
|
|
13
|
+
(Überschriften, Abschnitte, IDs, CSS-Regeln mit `line` + `offset`) zum gezielten Nachladen. `outputSchema` für `site_file_get`.
|
|
14
|
+
- REST `GET /api/file` ohne die neuen Parameter liefert weiter die ganze Datei (ältere Clients).
|
|
15
|
+
|
|
16
|
+
## 0.14.1 – 2026-10-22
|
|
17
|
+
|
|
18
|
+
- Instructions + `media_search`: Fotos nie per URL von Unsplash oder anderen Bilddiensten (images.unsplash.com, imgix …) ins
|
|
19
|
+
HTML/CSS schreiben – sonst lädt jeder Besucher das Bild beim Dienst (Besucher-IPs in die USA, Einwilligungspflicht); immer
|
|
20
|
+
`media_import`, ProntoSite liefert das Foto selbst aus.
|
|
21
|
+
|
|
6
22
|
## 0.14.0 – 2026-10-22
|
|
7
23
|
|
|
8
24
|
Enthält 0.13.8 (nie einzeln veröffentlicht).
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
|
|
|
85
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.** |
|
|
86
86
|
| `site_status({ site? })` | Plan, Slug, Live-URL, Publish-Bedarf, `draftRev` + `recentEdits` (wer hat zuletzt wo geändert). **Lesend.** |
|
|
87
87
|
| `site_files({ site? })` | Dateien des Entwurfs (Pfad, Größe, Typ, bearbeitbar) + `draftRev`. **Lesend.** |
|
|
88
|
-
| `site_file_get({ site?, path })` | Quelltext einer Datei (Text bzw. Base64, max. 5 MB) + `draftRev` + Hinweis auf Dashboard-Änderungen darüber (`overlay`). **Lesend.** |
|
|
88
|
+
| `site_file_get({ site?, path, offset?, length?, outline? })` | Quelltext einer Datei (Text bzw. Base64, Datei max. 5 MB) in Teilen von höchstens `length` Bytes (Standard 60 KB): `truncated` + `nextOffset` zum Weiterlesen, beim ersten Teil bzw. mit `outline:true` eine Gliederung (Abschnitte/IDs mit `line` + `offset`); + `draftRev` + Hinweis auf Dashboard-Änderungen darüber (`overlay`). **Lesend.** |
|
|
89
89
|
| `site_file_put({ site?, path, content, baseRev, overlay? })` | Text-Datei im Entwurf ersetzen (nur Betreuer-Token). `baseRev` Pflicht; bei Konflikt `conflict:true` mit Liste statt Überschreiben. Nicht live – danach `site_publish`. **Schreibend.** |
|
|
90
90
|
| `site_file_delete({ site?, path, baseRev, confirmDestructive? })` / `site_file_rename({ site?, from, to, baseRev })` | Text-Datei im Entwurf entfernen bzw. umbenennen; eine Seite löschen/umbenennen (Startseite geschützt, Verweise werden gemeldet). Eine neue `.html` per `site_file_put` legt eine neue Seite an. Undo-fähig, nur Betreuer-Token. **Schreibend.** |
|
|
91
91
|
| `site_lock({ site?, note?, takeover? })` / `site_unlock({ site? })` | Sanfte Sperre „In Bearbeitung durch …“ (2 h, verlängert sich bei jeder Änderung). Blockiert niemanden; `takeover` nur als Betreuer. |
|
package/api.mjs
CHANGED
|
@@ -369,10 +369,15 @@ export async function apiFiles(cfg, { site } = {}, fetchImpl = fetch) {
|
|
|
369
369
|
const { token, slug } = resolveTarget(cfg, site);
|
|
370
370
|
return asJson(await fetchImpl(`${baseUrl(cfg)}/files/${encodeURIComponent(slug)}`, { headers: authHeaders(token, cfg) }));
|
|
371
371
|
}
|
|
372
|
-
/** GET /api/file/<site>?path= – Quelltext einer Datei (Text bzw. Base64) + draftRev + Overlay-Hinweis. */
|
|
373
|
-
export async function apiFileGet(cfg, { site, path: rel } = {}, fetchImpl = fetch) {
|
|
374
|
-
const { token, slug } = resolveTarget(cfg, site);
|
|
375
|
-
|
|
372
|
+
/** GET /api/file/<site>?path=&offset=&length=&outline= – Quelltext einer Datei bzw. ein Teil davon (Text bzw. Base64) + draftRev + Overlay-Hinweis. */
|
|
373
|
+
export async function apiFileGet(cfg, { site, path: rel, offset = null, length = null, outline = false } = {}, fetchImpl = fetch) {
|
|
374
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
375
|
+
// Teil-Lesen (0.14.2): offset/length in Bytes, outline=1 nur Gliederung – ohne Angaben die ganze Datei (wie bisher).
|
|
376
|
+
const q = new URLSearchParams({ path: rel });
|
|
377
|
+
if (Number.isInteger(offset)) q.set("offset", String(offset));
|
|
378
|
+
if (Number.isInteger(length)) q.set("length", String(length));
|
|
379
|
+
if (outline) q.set("outline", "1");
|
|
380
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/file/${encodeURIComponent(slug)}?${q}`, { headers: authHeaders(token, cfg) }));
|
|
376
381
|
}
|
|
377
382
|
/** PUT /api/file/<site>?path= – Text-Inhalt ersetzen (Entwurf). baseRev Pflicht. */
|
|
378
383
|
export async function apiFilePut(cfg, { site, path: rel, content, baseRev, overlay = "keep" } = {}, fetchImpl = fetch) {
|
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.2" }, { 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.2",
|
|
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
|
@@ -44,7 +44,8 @@ const instructionLines = mode => [
|
|
|
44
44
|
...(mode === "local" ? ["5a-lokal. Wenn du einen lokalen Dateipfad hast, nutze media_upload_file; sonst Upload-Link (media_upload_begin). "
|
|
45
45
|
+ "media_upload_file prüft, lädt hoch und übernimmt in einem Schritt und liefert die mediaId."] : []),
|
|
46
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."
|
|
47
|
+
+ "Vorschauen zeigen und wählen lassen, dann media_import + site_image_set + site_preview_screenshot. Fotos NIE per URL von Unsplash "
|
|
48
|
+
+ "oder anderen Bilddiensten (images.unsplash.com, imgix …) ins HTML/CSS schreiben – sonst lädt jeder Besucher beim Dienst; immer media_import.",
|
|
48
49
|
"5b. Design = CSS-Datei ändern (site_file_get/site_file_put), nicht HTML umschreiben. Nach Design- oder Bild-Änderungen "
|
|
49
50
|
+ "site_preview_screenshot (1280 bzw. 390) prüfen, BEVOR du dem Nutzer antwortest.",
|
|
50
51
|
"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 "
|
|
@@ -69,13 +70,37 @@ export function toolAnnotations(name, mode = "local") {
|
|
|
69
70
|
return { readOnlyHint: ro, destructiveHint: !ro && DESTRUCTIVE.has(name), idempotentHint: ro || name === "site_lock" || name === "site_unlock", openWorldHint: OPEN_WORLD.has(name) };
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
// Antworten (MCP 2025-06-18 „Structured Content"): dieselben Daten als structuredContent (JSON-Objekt) UND serialisiert im
|
|
74
|
+
// Text-Block (Rückwärtskompatibilität). ChatGPT wertet structuredContent aus, Claude & Co. den Text (Befund 24.10.2026).
|
|
75
|
+
export const toStructured = obj => (obj && typeof obj === "object" && !Array.isArray(obj) ? obj : Array.isArray(obj) ? { items: obj } : { value: obj ?? null });
|
|
76
|
+
export const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }], structuredContent: toStructured(obj) });
|
|
77
|
+
export const asError = e => { const msg = e?.message || String(e);
|
|
78
|
+
return { isError: true, content: [{ type: "text", text: "Fehler: " + msg }], structuredContent: { error: msg, ...(e?.status ? { status: e.status } : {}) } }; };
|
|
79
|
+
|
|
80
|
+
/** Ausgabe-Schema von site_file_get (structuredContent; OpenAI Apps SDK: outputSchema für Tools mit structuredContent). */
|
|
81
|
+
const FILE_GET_OUTPUT = z => z.object({
|
|
82
|
+
ok: z.boolean().optional(), path: z.string(), type: z.string().optional(), encoding: z.enum(["utf8", "base64"]),
|
|
83
|
+
content: z.string().nullable().describe("Inhalt dieses Teils (utf8 bzw. Base64); null bei outline:true."),
|
|
84
|
+
bytes: z.number().int().optional(), size: z.number().int().describe("Dateigröße in Bytes."),
|
|
85
|
+
offset: z.number().int().describe("Start dieses Teils in Bytes."), length: z.number().int().describe("Länge dieses Teils in Bytes."),
|
|
86
|
+
truncated: z.boolean().describe("true = nicht die ganze Datei."), nextOffset: z.number().int().nullable().describe("Weiterlesen ab hier; null = Ende."),
|
|
87
|
+
outline: z.object({ items: z.array(z.object({ line: z.number().int(), offset: z.number().int() }).passthrough()), total: z.number().int() }).passthrough().optional(),
|
|
88
|
+
hinweis: z.string().optional(), sha: z.string().optional(), source: z.string().optional(), draftRev: z.number().int().optional(),
|
|
89
|
+
page: z.object({ id: z.string(), title: z.string().optional() }).passthrough().optional(), overlay: z.any().optional(),
|
|
90
|
+
}).passthrough(); // weitere Felder erlaubt (additionalProperties) – neue Server-Felder brechen strenge Clients nicht
|
|
74
91
|
|
|
75
92
|
/** Alle Tools am MCP-Server registrieren. */
|
|
76
93
|
export function registerTools(server0, { z, cfg, mode = "local", remote = null }) {
|
|
77
94
|
// Jedes Tool mit Annotationen registrieren (Signatur des SDK: name, description, schema, annotations, handler).
|
|
78
|
-
const server = { tool: (name, desc, schema, cb) => server0.tool(name, desc, schema, toolAnnotations(name, mode), cb)
|
|
95
|
+
const server = { tool: (name, desc, schema, cb) => server0.tool(name, desc, schema, toolAnnotations(name, mode), cb),
|
|
96
|
+
// mit Ausgabe-Schema (outputSchema; SDK prüft structuredContent dagegen) – Fallback ohne, falls der Server nur tool() kennt
|
|
97
|
+
// Fehler ohne structuredContent: Clients (z. B. der SDK-Client) prüfen es sonst gegen das Schema und verwerfen die Fehlermeldung.
|
|
98
|
+
toolWithOutput: (name, desc, schema, outputSchema, cb) => {
|
|
99
|
+
const wrapped = async (...args) => { const r = await cb(...args); if (r?.isError) { const { structuredContent, ...rest } = r; return rest; } return r; };
|
|
100
|
+
return typeof server0.registerTool === "function"
|
|
101
|
+
? server0.registerTool(name, { description: desc, inputSchema: schema, outputSchema, annotations: toolAnnotations(name, mode) }, wrapped)
|
|
102
|
+
: server0.tool(name, desc, schema, toolAnnotations(name, mode), wrapped);
|
|
103
|
+
} };
|
|
79
104
|
const SITE_DESC = mode === "remote"
|
|
80
105
|
? "Website: Slug (z. B. 'mas-monti'), Site-ID (s-…), Firmenname, Domain oder Site-Titel (unscharf, z. B. 'Firat-Deveci'). Ohne Angabe "
|
|
81
106
|
+ "die Website des Tokens bzw. der Freigabe. Betreuer: die gewünschte Website angeben (sites_list zeigt alle mit Firmennamen)."
|
|
@@ -257,7 +282,8 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
257
282
|
"media_search",
|
|
258
283
|
"Kostenlose Fotos von Unsplash suchen (verbraucht ein gemeinsames Stunden-Kontingent; gleiche Suchen kommen 1 h aus dem Cache). "
|
|
259
284
|
+ "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
|
|
285
|
+
+ "englisch und konkret (z. B. 'bakery counter bread'). Dem Nutzer 2–3 Vorschauen zeigen und wählen lassen, dann media_import "
|
|
286
|
+
+ "(nie die Unsplash-URL direkt einbinden).",
|
|
261
287
|
{ site: z.string().optional().describe(SITE_DESC), query: z.string().describe("Suchbegriff (2–120 Zeichen)."),
|
|
262
288
|
orientation: z.enum(["landscape", "portrait", "squarish"]).optional().describe("Ausrichtung: landscape (Hero/Banner), portrait, squarish."),
|
|
263
289
|
count: z.number().int().min(1).max(6).default(3).describe("Anzahl Treffer (max. 6).") },
|
|
@@ -266,7 +292,7 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
266
292
|
const r = await apiMediaSearch(cfg, { site, query, orientation, count });
|
|
267
293
|
const images = (r.results || []).filter(x => x.preview?.data).map(x => ({ type: "image", data: x.preview.data, mimeType: x.preview.mimeType || "image/jpeg" }));
|
|
268
294
|
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] };
|
|
295
|
+
return { content: [{ type: "text", text: JSON.stringify(text, null, 2) }, ...images], structuredContent: text };
|
|
270
296
|
} catch (e) { return asError(e); }
|
|
271
297
|
}
|
|
272
298
|
);
|
|
@@ -359,9 +385,9 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
359
385
|
try {
|
|
360
386
|
const r = await apiPreviewScreenshot(cfg, { site, page, width });
|
|
361
387
|
if (!r.png) return asText(r);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
388
|
+
const info = { page: r.page, width: r.width, height: r.height, ...(r.truncated ? { abgeschnitten: `Seite ist ${r.fullHeight} px hoch – gezeigt die oberen ${r.height} px` } : {}),
|
|
389
|
+
...(r.overflowX ? { horizontalerUeberlauf: `Inhalt ist ${r.overflowX} px breit (Viewport ${r.width}) – die Seite scrollt seitwärts; meist ein Bild/Element ohne max-width.` } : {}), ms: r.ms };
|
|
390
|
+
return { content: [{ type: "image", data: r.png, mimeType: "image/png" }, { type: "text", text: JSON.stringify(info) }], structuredContent: info };
|
|
365
391
|
} catch (e) { return asError(e); }
|
|
366
392
|
}
|
|
367
393
|
);
|
|
@@ -583,13 +609,21 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
583
609
|
{ site: z.string().optional().describe(SITE_DESC) },
|
|
584
610
|
async ({ site }) => { try { return asText(await apiFiles(cfg, { site })); } catch (e) { return asError(e); } }
|
|
585
611
|
);
|
|
586
|
-
server.
|
|
612
|
+
server.toolWithOutput(
|
|
587
613
|
"site_file_get",
|
|
588
|
-
"LESEND: Liefert den Quelltext einer Datei des Entwurfs (Text; Binär als Base64, max. 5 MB) samt draftRev
|
|
614
|
+
"LESEND: Liefert den Quelltext einer Datei des Entwurfs (Text; Binär als Base64, Datei max. 5 MB) samt draftRev – in Teilen von "
|
|
615
|
+
+ "höchstens length Bytes (Standard 60 KB). truncated:true + nextOffset → mit offset:nextOffset weiterlesen; beim ersten Teil einer "
|
|
616
|
+
+ "großen Datei kommt eine Gliederung (outline: Überschriften/Abschnitte/IDs mit line + offset), outline:true liefert NUR die "
|
|
617
|
+
+ "Gliederung – damit gezielt nachladen. offset/length/nextOffset sind Bytes (UTF-8), Teile enden nie mitten in einem Zeichen. "
|
|
618
|
+
+ "site_file_put ersetzt die GANZE Datei: nie mit einem Teil aufrufen – für gezielte Änderungen site_edit_text/site_edit_html. "
|
|
589
619
|
+ "Der Block overlay nennt Dashboard-Änderungen, die im Entwurf ÜBER diesem Quelltext liegen (sie gewinnen sichtbar) – "
|
|
590
620
|
+ "berücksichtige sie, bevor du Text änderst. Merke dir draftRev: du brauchst sie als baseRev für site_file_put. " + FILE_SMALL,
|
|
591
|
-
{ site: z.string().optional().describe(SITE_DESC), path: z.string().describe("Relativer Pfad, z. B. 'index.html' oder 'css/style.css' (aus site_files).")
|
|
592
|
-
|
|
621
|
+
{ site: z.string().optional().describe(SITE_DESC), path: z.string().describe("Relativer Pfad, z. B. 'index.html' oder 'css/style.css' (aus site_files)."),
|
|
622
|
+
offset: z.number().int().min(0).default(0).describe("Start in Bytes (aus nextOffset bzw. outline[].offset)."),
|
|
623
|
+
length: z.number().int().min(1).max(1048576).default(61440).describe("Höchstens so viele Bytes (Standard 60 KB, max. 1 MB)."),
|
|
624
|
+
outline: z.boolean().optional().describe("true = nur die Gliederung (ohne Inhalt) – für große Dateien zuerst.") },
|
|
625
|
+
FILE_GET_OUTPUT(z),
|
|
626
|
+
async ({ site, path, offset, length, outline }) => { try { return asText(await apiFileGet(cfg, { site, path, offset, length, outline })); } catch (e) { return asError(e); } }
|
|
593
627
|
);
|
|
594
628
|
server.tool(
|
|
595
629
|
"site_file_put",
|