@prontosite/mcp 0.10.1 → 0.10.3
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 +21 -0
- package/README.md +14 -0
- package/api.mjs +27 -4
- package/index.mjs +14 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
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.10.3 – 2026-10-14
|
|
7
|
+
|
|
8
|
+
- **`site_deploy`:** neuer Parameter `name` (Erst-Import) – setzt den Website-Namen explizit, falls die
|
|
9
|
+
Ableitung aus der Startseite danebenliegt (z. B. „Physio Phoenix – Bad Honnef Süd" → `name="Physio Phoenix"`).
|
|
10
|
+
Beschreibung ergänzt um die **Reihenfolge-Empfehlung** „Konto → Firmendaten → Import".
|
|
11
|
+
- **`site_company_set`:** Beschreibung klärt, dass ein Firmendaten-Set NACH einem Import die Rechts-Automatik
|
|
12
|
+
AUTOMATISCH neu laufen lässt (Entwurf; `legal.refreshed` in der Antwort) – ein zweiter Import ist nicht mehr
|
|
13
|
+
nötig. `apiDeploy` meldet das im `companyNote` entsprechend.
|
|
14
|
+
|
|
15
|
+
## 0.10.2 – 2026-10-14
|
|
16
|
+
|
|
17
|
+
- **Verständliche Fehlermeldungen** statt roher Server-/HTTP-Fehler – zentral in `api.mjs`
|
|
18
|
+
(`friendlyApiError`, greift über `asJson` für JEDES Tool: `site_deploy`, `site_status`, `sites_list`, …):
|
|
19
|
+
- **403** mit „API-Zugang nicht im Tarif" (bzw. alt „Starter-Plan") → *„Dein ProntoSite-Tarif enthält keinen
|
|
20
|
+
API-Zugang. Wähle im Dashboard → Zahlung einen Tarif oder bitte deinen Betreuer um eine API-Freigabe."*
|
|
21
|
+
- **401** (Token ungültig/abgelaufen) → Hinweis, das Token im Dashboard → API-Zugang neu zu erstellen und in
|
|
22
|
+
`~/.prontosite.json` einzutragen.
|
|
23
|
+
- **429** → Hinweis auf das Rate-Limit (kurz warten und erneut versuchen).
|
|
24
|
+
Andere 403 (z. B. „Kein Zugriff auf dieses Konto") bleiben unverändert. Tests mit gemockten Antworten
|
|
25
|
+
(401/403/429 über mehrere Tools) in `tests/mcp-prontosite.test.js`.
|
|
26
|
+
|
|
6
27
|
## 0.10.1 – 2026-10-03
|
|
7
28
|
|
|
8
29
|
- **Fix:** In der `account_create`-Beschreibung beendeten gerade Anführungszeichen (`"`) innerhalb der
|
package/README.md
CHANGED
|
@@ -122,6 +122,20 @@ genau diese Website, und Verwechslungen sind ausgeschlossen. `sites_list` zeigt
|
|
|
122
122
|
Chat einfach sagen „deploye mas-monti", ohne Slugs auswendig zu kennen. (Alternativ ein Chat für alles und die
|
|
123
123
|
Website je Aufruf über `site` wählen – die getrennten Chats sind aber übersichtlicher.)
|
|
124
124
|
|
|
125
|
+
## Fehlermeldungen
|
|
126
|
+
|
|
127
|
+
Statt roher HTTP-/Server-Fehler geben **alle** Tools bei den drei häufigen Betriebsfällen eine verständliche
|
|
128
|
+
Meldung zurück (zentral in `api.mjs`):
|
|
129
|
+
|
|
130
|
+
- **Kein API-Zugang im Tarif (403):** „Dein ProntoSite-Tarif enthält keinen API-Zugang. Wähle im
|
|
131
|
+
Dashboard → Zahlung einen Tarif oder bitte deinen Betreuer um eine API-Freigabe."
|
|
132
|
+
- **Token ungültig/abgelaufen (401):** „Dein API-Token ist ungültig oder abgelaufen. Token im
|
|
133
|
+
Dashboard → API-Zugang neu erstellen und in `~/.prontosite.json` eintragen."
|
|
134
|
+
- **Zu viele Anfragen (429):** Hinweis auf das Rate-Limit – einen Moment warten und erneut versuchen.
|
|
135
|
+
|
|
136
|
+
Andere Fehler (z. B. „Kein Zugriff auf dieses Konto", „Website nicht gefunden") werden unverändert mit der
|
|
137
|
+
Server-Meldung durchgereicht.
|
|
138
|
+
|
|
125
139
|
## Sicherheit
|
|
126
140
|
|
|
127
141
|
- Tokens liegen nur lokal (Config/Env), werden nie geloggt und nie in Antworten zurückgegeben.
|
package/api.mjs
CHANGED
|
@@ -94,11 +94,31 @@ export function resolveTarget(cfg, site) {
|
|
|
94
94
|
const baseUrl = cfg => `https://${cfg.host}/api`;
|
|
95
95
|
const authHeaders = token => ({ authorization: `Bearer ${token}` });
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Server-Fehler → VERSTÄNDLICHE Meldung für den Chat. Zentral für ALLE Tools (über asJson). Übersetzt die
|
|
99
|
+
* drei häufigen Betriebsfälle; alles andere behält die Server-Meldung (bzw. „HTTP <status>"). Token werden
|
|
100
|
+
* nie geloggt/zurückgegeben – hier sind ohnehin nur Status + Server-Text im Spiel.
|
|
101
|
+
* 401 Token ungültig/abgelaufen → neues Token erstellen + in ~/.prontosite.json eintragen.
|
|
102
|
+
* 403 API-Gate (Text „API-Zugang nicht im Tarif" bzw. „Starter-Plan") → Tarif ohne API-Zugang.
|
|
103
|
+
* 429 Rate-Limit → kurz warten.
|
|
104
|
+
* Andere 403 (z. B. „Kein Zugriff auf dieses Konto") bleiben unverändert – nur das API-Zugangs-Gate wird gemappt.
|
|
105
|
+
*/
|
|
106
|
+
export function friendlyApiError(status, serverError = "") {
|
|
107
|
+
const raw = String(serverError || "").trim();
|
|
108
|
+
if (status === 401)
|
|
109
|
+
return "Dein API-Token ist ungültig oder abgelaufen. Token im Dashboard → API-Zugang neu erstellen und in ~/.prontosite.json eintragen.";
|
|
110
|
+
if (status === 403 && /API-Zugang nicht im Tarif|Starter-Plan/i.test(raw))
|
|
111
|
+
return "Dein ProntoSite-Tarif enthält keinen API-Zugang. Wähle im Dashboard → Zahlung einen Tarif oder bitte deinen Betreuer um eine API-Freigabe.";
|
|
112
|
+
if (status === 429)
|
|
113
|
+
return "Zu viele Anfragen in kurzer Zeit (Rate-Limit). Bitte einen Moment warten und erneut versuchen.";
|
|
114
|
+
return raw || `HTTP ${status}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
97
117
|
/** Antwort als JSON lesen; bei Fehlerstatus eine sprechende Exception (ohne Token). */
|
|
98
118
|
async function asJson(res) {
|
|
99
119
|
let body = {};
|
|
100
120
|
try { body = await res.json(); } catch { /* kein JSON */ }
|
|
101
|
-
if (!res.ok) throw new Error(body.error
|
|
121
|
+
if (!res.ok) throw new Error(friendlyApiError(res.status, body.error));
|
|
102
122
|
return body;
|
|
103
123
|
}
|
|
104
124
|
|
|
@@ -125,7 +145,7 @@ export async function apiExport(cfg, { site, which = "published", outDir = "." }
|
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
/** 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) {
|
|
148
|
+
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
149
|
if (!zipPath || !fs.existsSync(zipPath)) throw new Error(`ZIP nicht gefunden: ${zipPath}`);
|
|
130
150
|
const target = resolveTarget(cfg, site);
|
|
131
151
|
const { token, slug } = target;
|
|
@@ -142,6 +162,7 @@ export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = n
|
|
|
142
162
|
if (legal === false) q.set("legal", "0");
|
|
143
163
|
if (effCustomer) q.set("customer", effCustomer); // Betreuer-Token + „new": Ziel-Konto für den Erst-Import
|
|
144
164
|
if (wishSlug) q.set("slug", String(wishSlug)); // Erst-Import: Wunsch-Adresse (sonst wird sie abgeleitet)
|
|
165
|
+
if (isNew && name) q.set("name", String(name)); // Erst-Import: Site-Name explizit setzen (sonst aus der Startseite abgeleitet)
|
|
145
166
|
const qs = q.toString() ? `?${q}` : "";
|
|
146
167
|
const res = await fetchImpl(`${baseUrl(cfg)}/deploy/${encodeURIComponent(slug)}${qs}`, {
|
|
147
168
|
method: "POST", headers: { ...authHeaders(token), "content-type": "application/zip" }, body: fs.readFileSync(zipPath) });
|
|
@@ -151,9 +172,11 @@ export async function apiDeploy(cfg, { site, customer = null, slug: wishSlug = n
|
|
|
151
172
|
// Publish übernimmt sie. Das melden wir als companyApplied/companyNote statt still zu lassen.
|
|
152
173
|
if (hasCompany && isNew && out && (out.slug || out.site)) {
|
|
153
174
|
try {
|
|
154
|
-
await apiCompanySet(cfg, { site: out.slug || out.site, company, siteOverride }, fetchImpl);
|
|
175
|
+
const compRes = await apiCompanySet(cfg, { site: out.slug || out.site, company, siteOverride }, fetchImpl);
|
|
155
176
|
out.companyApplied = true;
|
|
156
|
-
|
|
177
|
+
// Firmendaten-Set lässt serverseitig die Rechts-Automatik neu laufen (Entwurf) – kein zweiter Import nötig.
|
|
178
|
+
if (compRes && compRes.legal && compRes.legal.refreshed && compRes.legal.refreshed.length)
|
|
179
|
+
out.companyNote = `Firmendaten gesetzt – Rechtstexte (${compRes.legal.refreshed.join(", ")}) automatisch neu erzeugt (Entwurf). Zum Live-Schalten veröffentlichen.`;
|
|
157
180
|
} catch (e) { out.companyApplied = false; out.companyError = e.message; }
|
|
158
181
|
}
|
|
159
182
|
return out;
|
package/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ 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.10.
|
|
22
|
+
const server = new McpServer({ name: "prontosite", version: "0.10.3" });
|
|
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-"
|
|
@@ -106,6 +106,11 @@ server.tool(
|
|
|
106
106
|
+ "(„Unter welcher Adresse soll die Website laufen?“, z. B. 'wuerttemberg' → wuerttemberg.prontosite.io) "
|
|
107
107
|
+ "und übergib sie als slug – ohne slug wird die Adresse nur aus Konto-/Seitenname ABGELEITET (die Antwort "
|
|
108
108
|
+ "nennt dann die abgeleitete Adresse; sie ist später änderbar). Bitte VOR dem Aufruf bestätigen lassen. "
|
|
109
|
+
+ "REIHENFOLGE beim Erst-Import: zuerst Konto (account_create) → Firmendaten (company hier mitgeben ODER "
|
|
110
|
+
+ "site_company_set) → Import; so stehen Impressum/Datenschutz sofort korrekt (ein Firmendaten-Set nach dem "
|
|
111
|
+
+ "Import lässt die Rechts-Automatik automatisch neu laufen – kein zweiter Import nötig). "
|
|
112
|
+
+ "WEBSITE-NAME: wird aus der Startseite abgeleitet (og:site_name → Titel-Präfix vor dem ersten Trenner → "
|
|
113
|
+
+ "Header-H1/Logo-alt); mit name=… explizit setzen (Betreuer), falls die Ableitung danebenliegt. "
|
|
109
114
|
+ "MARKER (wenn du das Template lokal baust): markiere im HTML mit dem Attribut data-ps, wo Bausteine "
|
|
110
115
|
+ "erscheinen sollen – data-ps=\"formular:kontakt\" (verwaltetes Kontaktformular), data-ps=\"impressum\" bzw. "
|
|
111
116
|
+ "data-ps=\"datenschutz\" (Rechtstext-Seite) und data-ps=\"link-impressum\" / data-ps=\"link-datenschutz\" "
|
|
@@ -120,6 +125,7 @@ server.tool(
|
|
|
120
125
|
+ "Syntax-Referenz: https://prontosite.io/docs/marker-shortcodes",
|
|
121
126
|
{ 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
127
|
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."),
|
|
128
|
+
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
129
|
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
130
|
zipPath: z.string().describe("Pfad zur ZIP-Datei der Website (lokal)."),
|
|
125
131
|
publish: z.boolean().default(false).describe("true = nach dem Import direkt veröffentlichen (LIVE)."),
|
|
@@ -127,8 +133,8 @@ server.tool(
|
|
|
127
133
|
legal: z.boolean().optional().describe("Rechts-Automatik (Impressum/Datenschutz): true erzwingt, false schaltet ab."),
|
|
128
134
|
company: companyObject.optional().describe("Optional: Firmendaten VOR dem Deploy setzen (ein Schritt) – Impressum/Datenschutz nutzen sie dann direkt. Teil-Update."),
|
|
129
135
|
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); }
|
|
136
|
+
async ({ site, slug, name, customer, zipPath, publish, resetOverlay, legal, company, siteOverride }) => {
|
|
137
|
+
try { return asText(await apiDeploy(cfg, { site, slug, name, customer, zipPath, publish, resetOverlay, legal, company, siteOverride })); } catch (e) { return asError(e); }
|
|
132
138
|
}
|
|
133
139
|
);
|
|
134
140
|
|
|
@@ -156,8 +162,11 @@ server.tool(
|
|
|
156
162
|
+ "USt-IdNr., Register, Kammer/Beruf, Aufsicht, § 18 MStV, Social). Diese Daten füllen beim nächsten Deploy "
|
|
157
163
|
+ "automatisch Impressum & Datenschutz. TEIL-UPDATE: nur die übergebenen Felder ändern sich. Du darfst die "
|
|
158
164
|
+ "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
|
-
+ "
|
|
165
|
+
+ "Default konto-weit; siteOverride=true nur, wenn DIESE eine Website abweichende Angaben braucht. NACH EINEM "
|
|
166
|
+
+ "IMPORT lässt dieser Aufruf die Rechts-Automatik AUTOMATISCH neu laufen (Entwurf) – Impressum/Datenschutz "
|
|
167
|
+
+ "übernehmen die Firmendaten sofort, ein zweiter Import ist NICHT nötig (die Antwort nennt die neu erzeugten "
|
|
168
|
+
+ "Rechtstexte unter legal.refreshed); danach site_publish zum Live-Schalten. Bitte VOR dem Speichern die "
|
|
169
|
+
+ "erkannten Felder kurz zusammenfassen und bestätigen lassen.",
|
|
161
170
|
{ site: z.string().optional().describe(SITE_DESC),
|
|
162
171
|
company: companyObject,
|
|
163
172
|
siteOverride: z.boolean().default(false).describe("true = Firmendaten NUR für diese Website (statt konto-weit).") },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prontosite/mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
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": {
|