@prontosite/mcp 0.10.1 → 0.11.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 +34 -0
- package/README.md +19 -1
- package/api.mjs +67 -5
- package/index.mjs +68 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,40 @@
|
|
|
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.0 – 2026-10-18
|
|
7
|
+
|
|
8
|
+
- **Datei-API – kleine Änderungen am Entwurf ohne lokale Kopie:** neue Tools `site_files`, `site_file_get`,
|
|
9
|
+
`site_file_put`, `site_file_delete`, `site_file_rename` (Schreiben nur mit Betreuer-Token). Pflicht-`baseRev`
|
|
10
|
+
(= `draftRev` aus `site_status`/`site_file_get`); bei Konflikt (jemand hat dieselbe Datei geändert) kommt die
|
|
11
|
+
Antwort mit `conflict:true` und der Liste wer/wann/was zurück – nicht als Fehler, damit der Assistent neu aufsetzt.
|
|
12
|
+
Für größere Umbauten bleibt `site_export (which='draft')` → lokal → `site_deploy`.
|
|
13
|
+
- **`site_status`** beschreibt `draftRev` und `recentEdits` (vor Änderungen lesen).
|
|
14
|
+
- **Client-Kennung:** jeder Aufruf sendet `X-ProntoSite-Client: mcp/<version>` – im Verlauf je Site erscheint der Weg
|
|
15
|
+
„MCP" statt „API".
|
|
16
|
+
- Server-seitig (wirkt auch für ältere Versionen): Export-ZIP trägt `.prontosite-export.json` mit `draftRev`;
|
|
17
|
+
`site_deploy` eines exportierten Stands meldet einen zwischenzeitlich geänderten Entwurf mit 409 statt zu überschreiben.
|
|
18
|
+
|
|
19
|
+
## 0.10.3 – 2026-10-14 (veröffentlicht 18.10.2026)
|
|
20
|
+
|
|
21
|
+
- **`site_deploy`:** neuer Parameter `name` (Erst-Import) – setzt den Website-Namen explizit, falls die
|
|
22
|
+
Ableitung aus der Startseite danebenliegt (z. B. „Physio Phoenix – Bad Honnef Süd" → `name="Physio Phoenix"`).
|
|
23
|
+
Beschreibung ergänzt um die **Reihenfolge-Empfehlung** „Konto → Firmendaten → Import".
|
|
24
|
+
- **`site_company_set`:** Beschreibung klärt, dass ein Firmendaten-Set NACH einem Import die Rechts-Automatik
|
|
25
|
+
AUTOMATISCH neu laufen lässt (Entwurf; `legal.refreshed` in der Antwort) – ein zweiter Import ist nicht mehr
|
|
26
|
+
nötig. `apiDeploy` meldet das im `companyNote` entsprechend.
|
|
27
|
+
|
|
28
|
+
## 0.10.2 – 2026-10-14
|
|
29
|
+
|
|
30
|
+
- **Verständliche Fehlermeldungen** statt roher Server-/HTTP-Fehler – zentral in `api.mjs`
|
|
31
|
+
(`friendlyApiError`, greift über `asJson` für JEDES Tool: `site_deploy`, `site_status`, `sites_list`, …):
|
|
32
|
+
- **403** mit „API-Zugang nicht im Tarif" (bzw. alt „Starter-Plan") → *„Dein ProntoSite-Tarif enthält keinen
|
|
33
|
+
API-Zugang. Wähle im Dashboard → Zahlung einen Tarif oder bitte deinen Betreuer um eine API-Freigabe."*
|
|
34
|
+
- **401** (Token ungültig/abgelaufen) → Hinweis, das Token im Dashboard → API-Zugang neu zu erstellen und in
|
|
35
|
+
`~/.prontosite.json` einzutragen.
|
|
36
|
+
- **429** → Hinweis auf das Rate-Limit (kurz warten und erneut versuchen).
|
|
37
|
+
Andere 403 (z. B. „Kein Zugriff auf dieses Konto") bleiben unverändert. Tests mit gemockten Antworten
|
|
38
|
+
(401/403/429 über mehrere Tools) in `tests/mcp-prontosite.test.js`.
|
|
39
|
+
|
|
6
40
|
## 0.10.1 – 2026-10-03
|
|
7
41
|
|
|
8
42
|
- **Fix:** In der `account_create`-Beschreibung beendeten gerade Anführungszeichen (`"`) innerhalb der
|
package/README.md
CHANGED
|
@@ -83,7 +83,11 @@ sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
|
|
|
83
83
|
|------|---------|
|
|
84
84
|
| `sites_list` | Alle erreichbaren Websites (Slug, Plan, letzte Änderung, Publish-Bedarf). **Lesend.** |
|
|
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
|
-
| `site_status({ site? })` | Plan, Slug, Live-URL, Publish-Bedarf. **Lesend.** |
|
|
86
|
+
| `site_status({ site? })` | Plan, Slug, Live-URL, Publish-Bedarf, `draftRev` + `recentEdits` (wer hat zuletzt wo geändert). **Lesend.** |
|
|
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.** |
|
|
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
|
+
| `site_file_delete({ site?, path, baseRev, confirmDestructive? })` / `site_file_rename({ site?, from, to, baseRev })` | Text-Datei im Entwurf entfernen bzw. umbenennen (undo-fähig, nur Betreuer-Token). **Schreibend.** |
|
|
87
91
|
| `site_export({ site?, which, outDir })` | ZIP (`published`\|`draft`\|`original`) lokal speichern. **Lesend.** |
|
|
88
92
|
| `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.** |
|
|
89
93
|
| `site_publish({ site? })` | Aktuellen Stand erneut veröffentlichen. **Schreibend.** |
|
|
@@ -122,6 +126,20 @@ genau diese Website, und Verwechslungen sind ausgeschlossen. `sites_list` zeigt
|
|
|
122
126
|
Chat einfach sagen „deploye mas-monti", ohne Slugs auswendig zu kennen. (Alternativ ein Chat für alles und die
|
|
123
127
|
Website je Aufruf über `site` wählen – die getrennten Chats sind aber übersichtlicher.)
|
|
124
128
|
|
|
129
|
+
## Fehlermeldungen
|
|
130
|
+
|
|
131
|
+
Statt roher HTTP-/Server-Fehler geben **alle** Tools bei den drei häufigen Betriebsfällen eine verständliche
|
|
132
|
+
Meldung zurück (zentral in `api.mjs`):
|
|
133
|
+
|
|
134
|
+
- **Kein API-Zugang im Tarif (403):** „Dein ProntoSite-Tarif enthält keinen API-Zugang. Wähle im
|
|
135
|
+
Dashboard → Zahlung einen Tarif oder bitte deinen Betreuer um eine API-Freigabe."
|
|
136
|
+
- **Token ungültig/abgelaufen (401):** „Dein API-Token ist ungültig oder abgelaufen. Token im
|
|
137
|
+
Dashboard → API-Zugang neu erstellen und in `~/.prontosite.json` eintragen."
|
|
138
|
+
- **Zu viele Anfragen (429):** Hinweis auf das Rate-Limit – einen Moment warten und erneut versuchen.
|
|
139
|
+
|
|
140
|
+
Andere Fehler (z. B. „Kein Zugriff auf dieses Konto", „Website nicht gefunden") werden unverändert mit der
|
|
141
|
+
Server-Meldung durchgereicht.
|
|
142
|
+
|
|
125
143
|
## Sicherheit
|
|
126
144
|
|
|
127
145
|
- Tokens liegen nur lokal (Config/Env), werden nie geloggt und nie in Antworten zurückgegeben.
|
package/api.mjs
CHANGED
|
@@ -92,13 +92,36 @@ export function resolveTarget(cfg, site) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const baseUrl = cfg => `https://${cfg.host}/api`;
|
|
95
|
-
|
|
95
|
+
// Client-Kennung: der Server führt damit im Verlauf je Site den Weg „mcp" (statt „api" für curl).
|
|
96
|
+
let CLIENT_ID = "mcp";
|
|
97
|
+
try { CLIENT_ID = "mcp/" + JSON.parse(fs.readFileSync(new URL("./package.json", import.meta.url), "utf8")).version; } catch { /* egal */ }
|
|
98
|
+
const authHeaders = token => ({ authorization: `Bearer ${token}`, "x-prontosite-client": CLIENT_ID });
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Server-Fehler → VERSTÄNDLICHE Meldung für den Chat. Zentral für ALLE Tools (über asJson). Übersetzt die
|
|
102
|
+
* drei häufigen Betriebsfälle; alles andere behält die Server-Meldung (bzw. „HTTP <status>"). Token werden
|
|
103
|
+
* nie geloggt/zurückgegeben – hier sind ohnehin nur Status + Server-Text im Spiel.
|
|
104
|
+
* 401 Token ungültig/abgelaufen → neues Token erstellen + in ~/.prontosite.json eintragen.
|
|
105
|
+
* 403 API-Gate (Text „API-Zugang nicht im Tarif" bzw. „Starter-Plan") → Tarif ohne API-Zugang.
|
|
106
|
+
* 429 Rate-Limit → kurz warten.
|
|
107
|
+
* Andere 403 (z. B. „Kein Zugriff auf dieses Konto") bleiben unverändert – nur das API-Zugangs-Gate wird gemappt.
|
|
108
|
+
*/
|
|
109
|
+
export function friendlyApiError(status, serverError = "") {
|
|
110
|
+
const raw = String(serverError || "").trim();
|
|
111
|
+
if (status === 401)
|
|
112
|
+
return "Dein API-Token ist ungültig oder abgelaufen. Token im Dashboard → API-Zugang neu erstellen und in ~/.prontosite.json eintragen.";
|
|
113
|
+
if (status === 403 && /API-Zugang nicht im Tarif|Starter-Plan/i.test(raw))
|
|
114
|
+
return "Dein ProntoSite-Tarif enthält keinen API-Zugang. Wähle im Dashboard → Zahlung einen Tarif oder bitte deinen Betreuer um eine API-Freigabe.";
|
|
115
|
+
if (status === 429)
|
|
116
|
+
return "Zu viele Anfragen in kurzer Zeit (Rate-Limit). Bitte einen Moment warten und erneut versuchen.";
|
|
117
|
+
return raw || `HTTP ${status}`;
|
|
118
|
+
}
|
|
96
119
|
|
|
97
120
|
/** Antwort als JSON lesen; bei Fehlerstatus eine sprechende Exception (ohne Token). */
|
|
98
121
|
async function asJson(res) {
|
|
99
122
|
let body = {};
|
|
100
123
|
try { body = await res.json(); } catch { /* kein JSON */ }
|
|
101
|
-
if (!res.ok) throw new Error(body.error
|
|
124
|
+
if (!res.ok) throw new Error(friendlyApiError(res.status, body.error));
|
|
102
125
|
return body;
|
|
103
126
|
}
|
|
104
127
|
|
|
@@ -125,7 +148,7 @@ export async function apiExport(cfg, { site, which = "published", outDir = "." }
|
|
|
125
148
|
}
|
|
126
149
|
|
|
127
150
|
/** POST /api/deploy/<site> – ZIP übergeben (+ optional publish/resetOverlay/legal). site="new" = anlegen. */
|
|
128
|
-
export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = null, zipPath, publish = false, resetOverlay = false, legal = null, company = null, siteOverride = false } = {}, fetchImpl = fetch) {
|
|
151
|
+
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) {
|
|
129
152
|
if (!zipPath || !fs.existsSync(zipPath)) throw new Error(`ZIP nicht gefunden: ${zipPath}`);
|
|
130
153
|
const target = resolveTarget(cfg, site);
|
|
131
154
|
const { token, slug } = target;
|
|
@@ -142,6 +165,7 @@ export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = n
|
|
|
142
165
|
if (legal === false) q.set("legal", "0");
|
|
143
166
|
if (effCustomer) q.set("customer", effCustomer); // Betreuer-Token + „new": Ziel-Konto für den Erst-Import
|
|
144
167
|
if (wishSlug) q.set("slug", String(wishSlug)); // Erst-Import: Wunsch-Adresse (sonst wird sie abgeleitet)
|
|
168
|
+
if (isNew && name) q.set("name", String(name)); // Erst-Import: Site-Name explizit setzen (sonst aus der Startseite abgeleitet)
|
|
145
169
|
const qs = q.toString() ? `?${q}` : "";
|
|
146
170
|
const res = await fetchImpl(`${baseUrl(cfg)}/deploy/${encodeURIComponent(slug)}${qs}`, {
|
|
147
171
|
method: "POST", headers: { ...authHeaders(token), "content-type": "application/zip" }, body: fs.readFileSync(zipPath) });
|
|
@@ -151,9 +175,11 @@ export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = n
|
|
|
151
175
|
// Publish übernimmt sie. Das melden wir als companyApplied/companyNote statt still zu lassen.
|
|
152
176
|
if (hasCompany && isNew && out && (out.slug || out.site)) {
|
|
153
177
|
try {
|
|
154
|
-
await apiCompanySet(cfg, { site: out.slug || out.site, company, siteOverride }, fetchImpl);
|
|
178
|
+
const compRes = await apiCompanySet(cfg, { site: out.slug || out.site, company, siteOverride }, fetchImpl);
|
|
155
179
|
out.companyApplied = true;
|
|
156
|
-
|
|
180
|
+
// Firmendaten-Set lässt serverseitig die Rechts-Automatik neu laufen (Entwurf) – kein zweiter Import nötig.
|
|
181
|
+
if (compRes && compRes.legal && compRes.legal.refreshed && compRes.legal.refreshed.length)
|
|
182
|
+
out.companyNote = `Firmendaten gesetzt – Rechtstexte (${compRes.legal.refreshed.join(", ")}) automatisch neu erzeugt (Entwurf). Zum Live-Schalten veröffentlichen.`;
|
|
157
183
|
} catch (e) { out.companyApplied = false; out.companyError = e.message; }
|
|
158
184
|
}
|
|
159
185
|
return out;
|
|
@@ -316,3 +342,39 @@ export async function sitesList(cfg, fetchImpl = fetch) {
|
|
|
316
342
|
const { token } = resolveTarget(cfg, cfg.defaultSite || cfg.site || "active");
|
|
317
343
|
return apiSitesWithToken(cfg, token, fetchImpl);
|
|
318
344
|
}
|
|
345
|
+
|
|
346
|
+
// ---- Datei-API (Entwurf ohne lokale Kopie; docs/konzept-serverseitig-editieren.md §6/§7) ----
|
|
347
|
+
// 409 (Konflikt) NICHT als Fehler werfen, sondern komplett zurückgeben: der Assistent braucht die Liste
|
|
348
|
+
// „wer hat wann was geändert" (changes/intervening), um sauber neu aufzusetzen.
|
|
349
|
+
async function asJsonOr409(res) {
|
|
350
|
+
if (res.status === 409) { try { return await res.json(); } catch { return { conflict: true, error: "Konflikt (409)." }; } }
|
|
351
|
+
return asJson(res);
|
|
352
|
+
}
|
|
353
|
+
/** GET /api/files/<site> – Dateiliste des Entwurfs (Pfad, Größe, Typ, bearbeitbar) + draftRev. */
|
|
354
|
+
export async function apiFiles(cfg, { site } = {}, fetchImpl = fetch) {
|
|
355
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
356
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/files/${encodeURIComponent(slug)}`, { headers: authHeaders(token) }));
|
|
357
|
+
}
|
|
358
|
+
/** GET /api/file/<site>?path= – Quelltext einer Datei (Text bzw. Base64) + draftRev + Overlay-Hinweis. */
|
|
359
|
+
export async function apiFileGet(cfg, { site, path: rel } = {}, fetchImpl = fetch) {
|
|
360
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
361
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/file/${encodeURIComponent(slug)}?path=${encodeURIComponent(rel)}`, { headers: authHeaders(token) }));
|
|
362
|
+
}
|
|
363
|
+
/** PUT /api/file/<site>?path= – Text-Inhalt ersetzen (Entwurf). baseRev Pflicht. */
|
|
364
|
+
export async function apiFilePut(cfg, { site, path: rel, content, baseRev, overlay = "keep" } = {}, fetchImpl = fetch) {
|
|
365
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
366
|
+
return asJsonOr409(await fetchImpl(`${baseUrl(cfg)}/file/${encodeURIComponent(slug)}?path=${encodeURIComponent(rel)}`, {
|
|
367
|
+
method: "PUT", headers: { ...authHeaders(token), "content-type": "application/json" }, body: JSON.stringify({ content, baseRev, overlay }) }));
|
|
368
|
+
}
|
|
369
|
+
/** POST /api/file/<site>/delete – Datei im Entwurf als gelöscht markieren (undo-fähig). */
|
|
370
|
+
export async function apiFileDelete(cfg, { site, path: rel, baseRev, confirmDestructive = false } = {}, fetchImpl = fetch) {
|
|
371
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
372
|
+
return asJsonOr409(await fetchImpl(`${baseUrl(cfg)}/file/${encodeURIComponent(slug)}/delete`, {
|
|
373
|
+
method: "POST", headers: { ...authHeaders(token), "content-type": "application/json" }, body: JSON.stringify({ path: rel, baseRev, confirmDestructive }) }));
|
|
374
|
+
}
|
|
375
|
+
/** POST /api/file/<site>/rename – Datei umbenennen (Verweise werden NICHT umgeschrieben). */
|
|
376
|
+
export async function apiFileRename(cfg, { site, from, to, baseRev } = {}, fetchImpl = fetch) {
|
|
377
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
378
|
+
return asJsonOr409(await fetchImpl(`${baseUrl(cfg)}/file/${encodeURIComponent(slug)}/rename`, {
|
|
379
|
+
method: "POST", headers: { ...authHeaders(token), "content-type": "application/json" }, body: JSON.stringify({ from, to, baseRev }) }));
|
|
380
|
+
}
|
package/index.mjs
CHANGED
|
@@ -13,13 +13,13 @@ import { realpathSync } from "node:fs";
|
|
|
13
13
|
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
|
-
import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList, apiAccountCreate, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect, apiEmbeds, apiEmbedConsent, apiWidgets, apiWidgetSet } from "./api.mjs";
|
|
16
|
+
import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList, apiAccountCreate, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect, apiEmbeds, apiEmbedConsent, apiWidgets, apiWidgetSet, apiFiles, apiFileGet, apiFilePut, apiFileDelete, apiFileRename } from "./api.mjs";
|
|
17
17
|
|
|
18
18
|
const cfg = loadConfig();
|
|
19
19
|
// Deutliche Startwarnung bei ambiger Konfiguration (Top-Level-Token NEBEN sites-Map). Nach stderr, damit
|
|
20
20
|
// es im MCP-Log/Terminal auffällt; die endgültige Auflösung (Betreuer-Token-Vorrang vs. Map) klärt sites_list.
|
|
21
21
|
for (const w of (cfg.warnings || [])) process.stderr.write("[prontosite-mcp] ⚠ " + w + "\n");
|
|
22
|
-
const server = new McpServer({ name: "prontosite", version: "0.
|
|
22
|
+
const server = new McpServer({ name: "prontosite", version: "0.11.0" });
|
|
23
23
|
const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] });
|
|
24
24
|
const asError = e => ({ isError: true, content: [{ type: "text", text: "Fehler: " + (e?.message || String(e)) }] });
|
|
25
25
|
const SITE_DESC = "Website-Slug (z. B. 'mas-monti'). Ohne Angabe die Standard-Site der Config. Bei Betreuer-"
|
|
@@ -81,8 +81,9 @@ server.tool(
|
|
|
81
81
|
|
|
82
82
|
server.tool(
|
|
83
83
|
"site_status",
|
|
84
|
-
"Status einer ProntoSite-Website: Plan, Slug, Live-URL, letzte Änderungen
|
|
85
|
-
+ "bedarf.
|
|
84
|
+
"Status einer ProntoSite-Website: Plan, Slug, Live-URL, letzte Änderungen, offener Veröffentlichungs"
|
|
85
|
+
+ "bedarf, draftRev und recentEdits (wer hat zuletzt wo geändert). VOR Änderungen lesen – draftRev ist die baseRev für "
|
|
86
|
+
+ "site_file_put. LESEND – ändert nichts.",
|
|
86
87
|
{ site: z.string().optional().describe(SITE_DESC) },
|
|
87
88
|
async ({ site }) => { try { return asText(await apiStatus(cfg, { site })); } catch (e) { return asError(e); } }
|
|
88
89
|
);
|
|
@@ -106,6 +107,11 @@ server.tool(
|
|
|
106
107
|
+ "(„Unter welcher Adresse soll die Website laufen?“, z. B. 'wuerttemberg' → wuerttemberg.prontosite.io) "
|
|
107
108
|
+ "und übergib sie als slug – ohne slug wird die Adresse nur aus Konto-/Seitenname ABGELEITET (die Antwort "
|
|
108
109
|
+ "nennt dann die abgeleitete Adresse; sie ist später änderbar). Bitte VOR dem Aufruf bestätigen lassen. "
|
|
110
|
+
+ "REIHENFOLGE beim Erst-Import: zuerst Konto (account_create) → Firmendaten (company hier mitgeben ODER "
|
|
111
|
+
+ "site_company_set) → Import; so stehen Impressum/Datenschutz sofort korrekt (ein Firmendaten-Set nach dem "
|
|
112
|
+
+ "Import lässt die Rechts-Automatik automatisch neu laufen – kein zweiter Import nötig). "
|
|
113
|
+
+ "WEBSITE-NAME: wird aus der Startseite abgeleitet (og:site_name → Titel-Präfix vor dem ersten Trenner → "
|
|
114
|
+
+ "Header-H1/Logo-alt); mit name=… explizit setzen (Betreuer), falls die Ableitung danebenliegt. "
|
|
109
115
|
+ "MARKER (wenn du das Template lokal baust): markiere im HTML mit dem Attribut data-ps, wo Bausteine "
|
|
110
116
|
+ "erscheinen sollen – data-ps=\"formular:kontakt\" (verwaltetes Kontaktformular), data-ps=\"impressum\" bzw. "
|
|
111
117
|
+ "data-ps=\"datenschutz\" (Rechtstext-Seite) und data-ps=\"link-impressum\" / data-ps=\"link-datenschutz\" "
|
|
@@ -120,6 +126,7 @@ server.tool(
|
|
|
120
126
|
+ "Syntax-Referenz: https://prontosite.io/docs/marker-shortcodes",
|
|
121
127
|
{ 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."),
|
|
122
128
|
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."),
|
|
129
|
+
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."),
|
|
123
130
|
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'."),
|
|
124
131
|
zipPath: z.string().describe("Pfad zur ZIP-Datei der Website (lokal)."),
|
|
125
132
|
publish: z.boolean().default(false).describe("true = nach dem Import direkt veröffentlichen (LIVE)."),
|
|
@@ -127,8 +134,8 @@ server.tool(
|
|
|
127
134
|
legal: z.boolean().optional().describe("Rechts-Automatik (Impressum/Datenschutz): true erzwingt, false schaltet ab."),
|
|
128
135
|
company: companyObject.optional().describe("Optional: Firmendaten VOR dem Deploy setzen (ein Schritt) – Impressum/Datenschutz nutzen sie dann direkt. Teil-Update."),
|
|
129
136
|
siteOverride: z.boolean().default(false).describe("Nur mit company: true = Firmendaten NUR für diese Website (statt konto-weit).") },
|
|
130
|
-
async ({ site, slug, customer, zipPath, publish, resetOverlay, legal, company, siteOverride }) => {
|
|
131
|
-
try { return asText(await apiDeploy(cfg, { site, slug, customer, zipPath, publish, resetOverlay, legal, company, siteOverride })); } catch (e) { return asError(e); }
|
|
137
|
+
async ({ site, slug, name, customer, zipPath, publish, resetOverlay, legal, company, siteOverride }) => {
|
|
138
|
+
try { return asText(await apiDeploy(cfg, { site, slug, name, customer, zipPath, publish, resetOverlay, legal, company, siteOverride })); } catch (e) { return asError(e); }
|
|
132
139
|
}
|
|
133
140
|
);
|
|
134
141
|
|
|
@@ -156,8 +163,11 @@ server.tool(
|
|
|
156
163
|
+ "USt-IdNr., Register, Kammer/Beruf, Aufsicht, § 18 MStV, Social). Diese Daten füllen beim nächsten Deploy "
|
|
157
164
|
+ "automatisch Impressum & Datenschutz. TEIL-UPDATE: nur die übergebenen Felder ändern sich. Du darfst die "
|
|
158
165
|
+ "Angaben aus jeder Vorlage extrahieren (Impressum-Text, Visitenkarte, Freitext) und strukturiert übergeben. "
|
|
159
|
-
+ "Default konto-weit; siteOverride=true nur, wenn DIESE eine Website abweichende Angaben braucht.
|
|
160
|
-
+ "
|
|
166
|
+
+ "Default konto-weit; siteOverride=true nur, wenn DIESE eine Website abweichende Angaben braucht. NACH EINEM "
|
|
167
|
+
+ "IMPORT lässt dieser Aufruf die Rechts-Automatik AUTOMATISCH neu laufen (Entwurf) – Impressum/Datenschutz "
|
|
168
|
+
+ "übernehmen die Firmendaten sofort, ein zweiter Import ist NICHT nötig (die Antwort nennt die neu erzeugten "
|
|
169
|
+
+ "Rechtstexte unter legal.refreshed); danach site_publish zum Live-Schalten. Bitte VOR dem Speichern die "
|
|
170
|
+
+ "erkannten Felder kurz zusammenfassen und bestätigen lassen.",
|
|
161
171
|
{ site: z.string().optional().describe(SITE_DESC),
|
|
162
172
|
company: companyObject,
|
|
163
173
|
siteOverride: z.boolean().default(false).describe("true = Firmendaten NUR für diese Website (statt konto-weit).") },
|
|
@@ -273,6 +283,56 @@ server.tool(
|
|
|
273
283
|
}
|
|
274
284
|
);
|
|
275
285
|
|
|
286
|
+
// ---- Datei-API: kleine Änderungen am Entwurf direkt auf dem Server (Konzept §7) ----
|
|
287
|
+
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. ";
|
|
288
|
+
server.tool(
|
|
289
|
+
"site_files",
|
|
290
|
+
"LESEND: Listet die Dateien des Entwurfs einer Website (Pfad, Größe, Typ, ob bearbeitbar) und die aktuelle draftRev. "
|
|
291
|
+
+ FILE_SMALL + "Ruf VORHER site_status auf; zeigt es kürzliche Änderungen anderer (recentEdits), sag das dem Nutzer, bevor du etwas änderst.",
|
|
292
|
+
{ site: z.string().optional().describe(SITE_DESC) },
|
|
293
|
+
async ({ site }) => { try { return asText(await apiFiles(cfg, { site })); } catch (e) { return asError(e); } }
|
|
294
|
+
);
|
|
295
|
+
server.tool(
|
|
296
|
+
"site_file_get",
|
|
297
|
+
"LESEND: Liefert den Quelltext einer Datei des Entwurfs (Text; Binär als Base64, max. 5 MB) samt draftRev. "
|
|
298
|
+
+ "Der Block overlay nennt Dashboard-Änderungen, die im Entwurf ÜBER diesem Quelltext liegen (sie gewinnen sichtbar) – "
|
|
299
|
+
+ "berücksichtige sie, bevor du Text änderst. Merke dir draftRev: du brauchst sie als baseRev für site_file_put. " + FILE_SMALL,
|
|
300
|
+
{ site: z.string().optional().describe(SITE_DESC), path: z.string().describe("Relativer Pfad, z. B. 'index.html' oder 'css/style.css' (aus site_files).") },
|
|
301
|
+
async ({ site, path }) => { try { return asText(await apiFileGet(cfg, { site, path })); } catch (e) { return asError(e); } }
|
|
302
|
+
);
|
|
303
|
+
server.tool(
|
|
304
|
+
"site_file_put",
|
|
305
|
+
"SCHREIBEND (Entwurf, nicht live; nur Betreuer-Token): Ersetzt den Inhalt einer Text-Datei (HTML einer bestehenden Seite, "
|
|
306
|
+
+ "CSS, SVG, JSON, TXT …). PFLICHT: baseRev = draftRev aus deinem letzten site_file_get/site_status. Antwortet der Server mit "
|
|
307
|
+
+ "conflict:true (409), hat jemand anderes dieselbe Datei inzwischen geändert: NICHT erzwingen – Datei neu holen, Änderung neu "
|
|
308
|
+
+ "anwenden, mit der neuen draftRev erneut senden; die Antwort listet wer/wann/was. Prüfe danach overlay (umgehängte/deaktivierte "
|
|
309
|
+
+ "Dashboard-Änderungen), sanitized und warnings und berichte sie dem Nutzer. Rechtstexte (Impressum/Datenschutz) nicht "
|
|
310
|
+
+ "umformulieren – der Server lehnt das ab. Danach: site_status zeigt publishNeeded, live erst mit site_publish (vorher "
|
|
311
|
+
+ "bestätigen lassen). " + FILE_SMALL,
|
|
312
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
313
|
+
path: z.string().describe("Relativer Pfad der Datei (bestehende Seite oder Text-Asset)."),
|
|
314
|
+
content: z.string().describe("Vollständiger neuer Dateiinhalt (Text, UTF-8, max. 1 MB)."),
|
|
315
|
+
baseRev: z.number().int().describe("draftRev, auf der deine Änderung beruht (aus site_file_get/site_status)."),
|
|
316
|
+
overlay: z.enum(["keep", "yield"]).default("keep").describe("keep = Dashboard-Änderungen bleiben über dem Quelltext; yield = wo deine Datei denselben Inhalt geändert hat, gewinnt die Datei.") },
|
|
317
|
+
async ({ site, path, content, baseRev, overlay }) => { try { return asText(await apiFilePut(cfg, { site, path, content, baseRev, overlay })); } catch (e) { return asError(e); } }
|
|
318
|
+
);
|
|
319
|
+
server.tool(
|
|
320
|
+
"site_file_delete",
|
|
321
|
+
"SCHREIBEND (Entwurf; nur Betreuer-Token): Markiert eine Text-Datei im Entwurf als gelöscht (rückgängig machbar; Seiten "
|
|
322
|
+
+ "löschen geht über das Dashboard). baseRev Pflicht, confirmDestructive:true erst NACH Rückfrage beim Nutzer. Prüfe vorher, "
|
|
323
|
+
+ "ob die Datei noch verlinkt ist. " + FILE_SMALL,
|
|
324
|
+
{ site: z.string().optional().describe(SITE_DESC), path: z.string(), baseRev: z.number().int(),
|
|
325
|
+
confirmDestructive: z.boolean().default(false).describe("true erst nach ausdrücklicher Bestätigung durch den Nutzer.") },
|
|
326
|
+
async ({ site, path, baseRev, confirmDestructive }) => { try { return asText(await apiFileDelete(cfg, { site, path, baseRev, confirmDestructive })); } catch (e) { return asError(e); } }
|
|
327
|
+
);
|
|
328
|
+
server.tool(
|
|
329
|
+
"site_file_rename",
|
|
330
|
+
"SCHREIBEND (Entwurf; nur Betreuer-Token): Benennt eine Text-Datei (kein HTML) um. Verweise in anderen Dateien werden NICHT "
|
|
331
|
+
+ "angepasst – die musst du selbst per site_file_put nachziehen. baseRev Pflicht. " + FILE_SMALL,
|
|
332
|
+
{ site: z.string().optional().describe(SITE_DESC), from: z.string(), to: z.string(), baseRev: z.number().int() },
|
|
333
|
+
async ({ site, from, to, baseRev }) => { try { return asText(await apiFileRename(cfg, { site, from, to, baseRev })); } catch (e) { return asError(e); } }
|
|
334
|
+
);
|
|
335
|
+
|
|
276
336
|
// Den stdio-Server NUR starten, wenn diese Datei direkt ausgeführt wird (als MCP-Server / bin).
|
|
277
337
|
// Beim bloßen Importieren (Smoke-Test / Tests) NICHT verbinden – sonst würde der Import auf stdin blockieren.
|
|
278
338
|
// So lädt der Import die Datei komplett (inkl. aller server.tool-Registrierungen → fängt Syntax-/Beschreibungs-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prontosite/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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": {
|