@prontosite/mcp 0.5.0 → 0.8.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 +31 -0
- package/README.md +4 -0
- package/api.mjs +86 -23
- package/index.mjs +59 -2
- package/package.json +24 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,37 @@
|
|
|
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.8.0 – 2026-09-23
|
|
7
|
+
|
|
8
|
+
- **Neu: `site_embeds`** – listet alle externen Einbettungen (Host, Seite(n), Kategorie `gated`/`notwendig`). Lesend.
|
|
9
|
+
- **Neu: `site_embed_consent`** – stuft eine Einbettung als `notwendig` (§ 25 Abs. 2 Nr. 2 TDDDG,
|
|
10
|
+
Kunden-Eigenverantwortung) oder wieder als `gated` (Zwei-Klick) ein. `notwendig` nur mit `confirmed:true`;
|
|
11
|
+
ohne Bestätigung antwortet der Server 400 mit dem Verantwortungs-Text, den der Assistent dem Nutzer
|
|
12
|
+
vorlegen muss. Optional seiten-gebunden (`page`) – so bleibt der Dienst nur dort einwilligungsfrei, wo die
|
|
13
|
+
Funktion gebraucht wird. Datenschutz-Text + Cookie-Richtlinie werden beim nächsten Publish automatisch
|
|
14
|
+
angepasst (technisch erforderlicher Dienst statt Einwilligung). Betreuer-Token gilt für betreute Kunden.
|
|
15
|
+
|
|
16
|
+
## 0.7.0 – 2026-09-23
|
|
17
|
+
|
|
18
|
+
- **Ambige Config erkannt:** Liegt ein Top-Level-Token (env `PRONTOSITE_TOKEN` bzw. `token`) NEBEN einer
|
|
19
|
+
`sites`-Map, warnt der Server beim Start deutlich auf stderr und `sites_list` trägt den Hinweis mit:
|
|
20
|
+
„Beide Konfigurationen gefunden — sites-Map wird genutzt, env-Token ignoriert."
|
|
21
|
+
- **Betreuer-Token hat IMMER Vorrang:** Ist das Top-Level-Token ein Betreuer-Token (scope=manager), nutzt
|
|
22
|
+
`sites_list` dieses (deckt alle Kunden ab) und meldet die daneben liegende `sites`-Map als
|
|
23
|
+
Konfigurationsfehler (Map ignoriert). Ein Einzel-Token wird weiterhin von der Map verdrängt.
|
|
24
|
+
- **`liveUrl` = kanonische Kundendomain**, sobald `customDomains` gesetzt ist (sonst die Slug-Subdomain) –
|
|
25
|
+
in `sites_list` und `site_status`. So wird gedanklich nie „auf die falsche Adresse" deployt; die Slug-URL
|
|
26
|
+
leitet ohnehin per 301 auf die Kundendomain weiter.
|
|
27
|
+
|
|
28
|
+
## 0.6.0 – 2026-09-22
|
|
29
|
+
|
|
30
|
+
- **Neu: `site_domain_status`** – Kundendomain-Status (verbunden/kanonisch/Zertifikat-bis) + offene Jobs;
|
|
31
|
+
mit `check` nur eine Live-DNS-Prüfung (A @+www → Server-IP, keine AAAA) vor dem Verbinden. LESEND.
|
|
32
|
+
- **Neu: `site_domain_connect`** (SCHREIBEND) – eigene Domain verbinden (nginx-Block + HTTPS-Zertifikat
|
|
33
|
+
automatisch, kanonisch `www`/`apex`, inkl. Republish) oder trennen (`action:disconnect`). Voraussetzung:
|
|
34
|
+
DNS zeigt bereits auf den Server (vorher `site_domain_status` mit `check`). Ab Starter-Plan (Betreuer-/
|
|
35
|
+
Operator-Token ausgenommen); eine Domain nur an EINER Website. Läuft asynchron über die Job-Queue.
|
|
36
|
+
|
|
6
37
|
## 0.5.0 – 2026-09-20
|
|
7
38
|
|
|
8
39
|
- **Neu: `site_legal_options`** – Anzeige-Optionen der Rechtstext-Widgets je Rolle (impressum/datenschutz):
|
package/README.md
CHANGED
|
@@ -89,6 +89,10 @@ sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
|
|
|
89
89
|
| `site_company_get({ site? })` | Firmendaten (Impressum/Footer) lesen. **Lesend.** |
|
|
90
90
|
| `site_company_set({ site?, company, siteOverride? })` | Firmendaten setzen/ergänzen (Teil-Update); füllen Impressum/Datenschutz beim Deploy. **Schreibend.** |
|
|
91
91
|
| `site_legal_options({ site?, impressum?, datenschutz? })` | Anzeige-Optionen der Rechtstexte (showTitle, showDate, draftNotice); ohne Rolle nur lesen. **Schreibend.** |
|
|
92
|
+
| `site_domain_status({ site?, check? })` | Kundendomain-Status (verbunden/kanonisch/Zert-bis) + offene Jobs; mit `check` nur Live-DNS-Prüfung. **Lesend.** |
|
|
93
|
+
| `site_domain_connect({ site?, domain, canonical?, action? })` | Eigene Domain verbinden (nginx + HTTPS automatisch, `www`/`apex`) oder trennen; DNS muss auf den Server zeigen; ab Starter-Plan. **Schreibend.** |
|
|
94
|
+
| `site_embeds({ site? })` | Externe Einbettungen der Website: Host, Seite(n), aktuelle Einwilligungs-Kategorie (`gated` = Zwei-Klick \| `notwendig`). **Lesend.** |
|
|
95
|
+
| `site_embed_consent({ site?, host, page?, category, confirmed })` | Einbettung als `notwendig` (§ 25 Abs. 2 Nr. 2 TDDDG, Eigenverantwortung) oder wieder `gated` einstufen; `notwendig` nur mit `confirmed:true` (sonst 400 mit Verantwortungs-Text). Optional seiten-gebunden. Datenschutz/Cookie-Richtlinie passen sich beim nächsten Publish an. **Schreibend.** |
|
|
92
96
|
|
|
93
97
|
`site_deploy` und `site_publish` sind **schreibende** Aktionen (verändern die Live-Website). Die
|
|
94
98
|
Tool-Beschreibungen weisen den Assistenten an, vorher rückzufragen. Jeder Aufruf wird serverseitig auditiert.
|
package/api.mjs
CHANGED
|
@@ -27,13 +27,19 @@ export function loadConfig(env = process.env, home = os.homedir()) {
|
|
|
27
27
|
} catch { /* defekte Datei ignorieren – Env kann trotzdem reichen */ }
|
|
28
28
|
const host = (env.PRONTOSITE_HOST || file.host || DEFAULT_HOST).replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
29
29
|
const sites = (file.sites && typeof file.sites === "object") ? file.sites : null;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
const envToken = env.PRONTOSITE_TOKEN || "";
|
|
31
|
+
const token = envToken || file.token || "";
|
|
32
|
+
const tokenSource = envToken ? "env" : (file.token ? "datei" : null);
|
|
33
|
+
// Ambige Konfiguration: ein Top-Level-Token (Einzel/Betreuer) NEBEN einer sites-Map. Auflösung:
|
|
34
|
+
// Ein Betreuer-Token (scope=manager) hat IMMER Vorrang (die sites-Map ist dann ein Konfigurationsfehler);
|
|
35
|
+
// ein Einzel-Token wird von der Map verdrängt. Ob es ein Betreuer-Token ist, weiß nur der Server →
|
|
36
|
+
// sites_list klärt es per Server-Antdwort; hier nur die generische Startwarnung.
|
|
37
|
+
const warnings = [];
|
|
38
|
+
if (sites && token) warnings.push(
|
|
39
|
+
`Beide Konfigurationen gefunden — sites-Map wird genutzt, ${tokenSource === "env" ? "env-Token" : "Token"} ignoriert. `
|
|
40
|
+
+ `(Ausnahme: ein Betreuer-Token hat Vorrang – eine sites-Map daneben ist ein Konfigurationsfehler; bitte eins entfernen.)`);
|
|
41
|
+
return { host, token, tokenSource, site: env.PRONTOSITE_SITE || file.site || "active", sites,
|
|
42
|
+
defaultSite: env.PRONTOSITE_SITE || file.defaultSite || null, warnings };
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
const needToken = (token, ref) => {
|
|
@@ -162,6 +168,46 @@ export async function apiCompanySet(cfg, { site, company, siteOverride = false }
|
|
|
162
168
|
return asJson(res);
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
/** GET /api/domain/<site> – Kundendomain-Status (verbunden/kanonisch/Zertifikat-bis) + offene Jobs. Mit
|
|
172
|
+
* check=<domain> stattdessen NUR die Live-DNS-Prüfung (A für @ und www → Server-IP, keine AAAA-Leichen). */
|
|
173
|
+
export async function apiDomainStatus(cfg, { site, check } = {}, fetchImpl = fetch) {
|
|
174
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
175
|
+
const qs = check ? `?check=${encodeURIComponent(check)}` : "";
|
|
176
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/domain/${encodeURIComponent(slug)}${qs}`, { headers: authHeaders(token) });
|
|
177
|
+
return asJson(res);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** POST /api/domain/<site> – SCHREIBEND: eigene Domain verbinden (nginx-Block + HTTPS-Zertifikat werden
|
|
181
|
+
* automatisch eingerichtet) oder trennen. Voraussetzung: der DNS zeigt bereits auf den Server – vorher
|
|
182
|
+
* apiDomainStatus mit `check` nutzen. Läuft asynchron über die Job-Queue; Ergebnis via apiDomainStatus. */
|
|
183
|
+
export async function apiDomainConnect(cfg, { site, domain, canonical = "www", action = "connect" } = {}, fetchImpl = fetch) {
|
|
184
|
+
if (!domain) throw new Error("domain erwartet.");
|
|
185
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
186
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/domain/${encodeURIComponent(slug)}`, {
|
|
187
|
+
method: "POST", headers: { ...authHeaders(token), "content-type": "application/json" },
|
|
188
|
+
body: JSON.stringify({ domain, canonical, action }) });
|
|
189
|
+
return asJson(res);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** GET /api/embeds/<site> – erkannte Embeds (Host, Seite(n), aktuelle Kategorie gated|notwendig). LESEND. */
|
|
193
|
+
export async function apiEmbeds(cfg, { site } = {}, fetchImpl = fetch) {
|
|
194
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
195
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/embeds/${encodeURIComponent(slug)}`, { headers: authHeaders(token) });
|
|
196
|
+
return asJson(res);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** PUT /api/embeds/<site> – SCHREIBEND: ein Embed als „notwendig" (technisch erforderlich, § 25 Abs. 2 Nr. 2
|
|
200
|
+
* TDDDG) oder zurück „gated" einstufen. „notwendig" NUR mit confirmed:true – sonst 400 mit dem
|
|
201
|
+
* Verantwortungs-Text. Wirkt beim nächsten Veröffentlichen; Datenschutz-Text passt sich automatisch an. */
|
|
202
|
+
export async function apiEmbedConsent(cfg, { site, host, page, category = "notwendig", confirmed = false } = {}, fetchImpl = fetch) {
|
|
203
|
+
if (!host) throw new Error("host erwartet.");
|
|
204
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
205
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/embeds/${encodeURIComponent(slug)}`, {
|
|
206
|
+
method: "PUT", headers: { ...authHeaders(token), "content-type": "application/json" },
|
|
207
|
+
body: JSON.stringify({ host, page: page || null, category, confirmed: !!confirmed }) });
|
|
208
|
+
return asJson(res);
|
|
209
|
+
}
|
|
210
|
+
|
|
165
211
|
/** GET/PUT /api/legal-options/<site> – Anzeige-Optionen der Rechtstext-Widgets. Ohne impressum/datenschutz
|
|
166
212
|
* nur LESEN; sonst Teil-Update (nur echte Booleans wirken). */
|
|
167
213
|
export async function apiLegalOptions(cfg, { site, impressum, datenschutz } = {}, fetchImpl = fetch) {
|
|
@@ -179,24 +225,41 @@ export async function apiLegalOptions(cfg, { site, impressum, datenschutz } = {}
|
|
|
179
225
|
|
|
180
226
|
/** Alle erreichbaren Sites auflisten (Slug, Plan, Publish-Bedarf, letzte Änderung). Bei einer sites-Map wird
|
|
181
227
|
* jeder Eintrag per Status abgefragt; sonst die eine konfigurierte Site. Fehler je Site werden mitgeführt. */
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// Per-Kunde-Token-Map: jeden Eintrag einzeln abfragen (jeder Token → seine Site).
|
|
185
|
-
const out = await Promise.all(Object.keys(cfg.sites).map(async label => {
|
|
186
|
-
try {
|
|
187
|
-
const s = await apiStatus(cfg, { site: label }, fetchImpl);
|
|
188
|
-
return { label, slug: s.slug || null, id: s.site || null, name: s.name || null, plan: s.plan,
|
|
189
|
-
everPublished: s.everPublished, publishNeeded: s.publishNeeded, lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null };
|
|
190
|
-
} catch (e) { return { label, error: e.message }; }
|
|
191
|
-
}));
|
|
192
|
-
return { count: out.length, sites: out };
|
|
193
|
-
}
|
|
194
|
-
// Einzel-/Betreuer-Token: der Server liefert alle erreichbaren Sites (Betreuer-Token: ALLE betreuten Kunden,
|
|
195
|
-
// ohne Config-Pflege). GET /api/sites.
|
|
196
|
-
const { token } = resolveTarget(cfg, cfg.defaultSite || cfg.site || "active");
|
|
228
|
+
// GET /api/sites mit EINEM Token → normalisierte Liste (Betreuer-Token: alle betreuten Kunden).
|
|
229
|
+
async function apiSitesWithToken(cfg, token, fetchImpl) {
|
|
197
230
|
const body = await asJson(await fetchImpl(`${baseUrl(cfg)}/sites`, { headers: authHeaders(token) }));
|
|
198
231
|
const sites = (body.sites || []).map(s => ({ label: s.slug || s.site, slug: s.slug || null, id: s.site || null,
|
|
199
232
|
name: s.name || null, plan: s.plan, everPublished: s.everPublished, publishNeeded: s.publishNeeded,
|
|
200
233
|
lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null, customer: s.customer }));
|
|
201
|
-
return { count: sites.length, manager: !!body.manager, sites };
|
|
234
|
+
return { count: sites.length, manager: !!body.manager, hint: body.hint || null, sites };
|
|
235
|
+
}
|
|
236
|
+
// sites-Map abfragen: jeden Eintrag einzeln (jeder Token → seine Site).
|
|
237
|
+
async function sitesFromMap(cfg, fetchImpl) {
|
|
238
|
+
const out = await Promise.all(Object.keys(cfg.sites).map(async label => {
|
|
239
|
+
try {
|
|
240
|
+
const s = await apiStatus(cfg, { site: label }, fetchImpl);
|
|
241
|
+
return { label, slug: s.slug || null, id: s.site || null, name: s.name || null, plan: s.plan,
|
|
242
|
+
everPublished: s.everPublished, publishNeeded: s.publishNeeded, lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null };
|
|
243
|
+
} catch (e) { return { label, error: e.message }; }
|
|
244
|
+
}));
|
|
245
|
+
return { count: out.length, sites: out };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function sitesList(cfg, fetchImpl = fetch) {
|
|
249
|
+
// Ambig (Top-Level-Token NEBEN sites-Map): Betreuer-Token hat IMMER Vorrang. Nur der Server kennt den
|
|
250
|
+
// Scope → das Top-Level-Token einmal prüfen: manager:true → dieses nutzen (Map ist Konfigurationsfehler);
|
|
251
|
+
// sonst die Map (Einzel-Token verdrängt).
|
|
252
|
+
if (cfg.sites && cfg.token) {
|
|
253
|
+
let probe = null;
|
|
254
|
+
try { probe = await apiSitesWithToken(cfg, cfg.token, fetchImpl); } catch { /* Token ungültig? → Map-Weg */ }
|
|
255
|
+
if (probe && probe.manager)
|
|
256
|
+
return { ...probe, configError: "Konfigurationsfehler: Betreuer-Token UND sites-Map konfiguriert. "
|
|
257
|
+
+ "Der Betreuer-Token deckt bereits ALLE betreuten Kunden ab und hat Vorrang – die sites-Map wird ignoriert. "
|
|
258
|
+
+ "Bitte die sites-Map (oder den Betreuer-Token) aus ~/.prontosite.json entfernen." };
|
|
259
|
+
return { ...(await sitesFromMap(cfg, fetchImpl)), warning: cfg.warnings[0] || null };
|
|
260
|
+
}
|
|
261
|
+
if (cfg.sites) return sitesFromMap(cfg, fetchImpl);
|
|
262
|
+
// Einzel-/Betreuer-Token ohne Map: der Server liefert alle erreichbaren Sites.
|
|
263
|
+
const { token } = resolveTarget(cfg, cfg.defaultSite || cfg.site || "active");
|
|
264
|
+
return apiSitesWithToken(cfg, token, fetchImpl);
|
|
202
265
|
}
|
package/index.mjs
CHANGED
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
13
|
import { z } from "zod";
|
|
14
|
-
import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList, apiCompanyGet, apiCompanySet, apiLegalOptions } from "./api.mjs";
|
|
14
|
+
import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect, apiEmbeds, apiEmbedConsent } from "./api.mjs";
|
|
15
15
|
|
|
16
16
|
const cfg = loadConfig();
|
|
17
|
-
|
|
17
|
+
// Deutliche Startwarnung bei ambiger Konfiguration (Top-Level-Token NEBEN sites-Map). Nach stderr, damit
|
|
18
|
+
// es im MCP-Log/Terminal auffällt; die endgültige Auflösung (Betreuer-Token-Vorrang vs. Map) klärt sites_list.
|
|
19
|
+
for (const w of (cfg.warnings || [])) process.stderr.write("[prontosite-mcp] ⚠ " + w + "\n");
|
|
20
|
+
const server = new McpServer({ name: "prontosite", version: "0.8.0" });
|
|
18
21
|
const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] });
|
|
19
22
|
const asError = e => ({ isError: true, content: [{ type: "text", text: "Fehler: " + (e?.message || String(e)) }] });
|
|
20
23
|
const SITE_DESC = "Website-Slug (z. B. 'mas-monti'). Ohne Angabe die Standard-Site der Config. Bei Betreuer-"
|
|
@@ -146,5 +149,59 @@ server.tool(
|
|
|
146
149
|
}
|
|
147
150
|
);
|
|
148
151
|
|
|
152
|
+
server.tool(
|
|
153
|
+
"site_domain_status",
|
|
154
|
+
"LESEND: Kundendomain-Status einer Website – verbundene Domains (kanonisch/Weiterleitung), Zertifikat-"
|
|
155
|
+
+ "Gültigkeit und offene Verbindungs-Jobs. Mit `check` stattdessen NUR eine Live-DNS-Prüfung der angegebenen "
|
|
156
|
+
+ "Domain (A-Record für @ und www muss auf den Server zeigen, kein AAAA) – ideal VOR dem Verbinden.",
|
|
157
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
158
|
+
check: z.string().optional().describe("Domain (z. B. ihre-firma.de) NUR auf DNS-Bereitschaft prüfen, nichts verbinden.") },
|
|
159
|
+
async ({ site, check }) => { try { return asText(await apiDomainStatus(cfg, { site, check })); } catch (e) { return asError(e); } }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
server.tool(
|
|
163
|
+
"site_domain_connect",
|
|
164
|
+
"SCHREIBEND: eigene Domain mit der Website verbinden oder trennen. Beim Verbinden werden nginx-Serverblock "
|
|
165
|
+
+ "und HTTPS-Zertifikat (Let's Encrypt) automatisch eingerichtet und die Domain als kanonische Adresse gesetzt "
|
|
166
|
+
+ "(inkl. Republish für Canonical/Sitemap). VORAUSSETZUNG: der DNS zeigt bereits auf den Server – vorher IMMER "
|
|
167
|
+
+ "site_domain_status mit `check` aufrufen und das Ergebnis bestätigen lassen. Nur ab Starter-Plan (Betreuer-/"
|
|
168
|
+
+ "Operator-Token ausgenommen); eine Domain kann nur an EINER Website hängen. Läuft asynchron über eine Job-"
|
|
169
|
+
+ "Queue – danach site_domain_status pollen. action='disconnect' trennt die Domain wieder.",
|
|
170
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
171
|
+
domain: z.string().describe("Die zu verbindende/trennende Domain, z. B. ihre-firma.de (mit oder ohne www)."),
|
|
172
|
+
canonical: z.enum(["www", "apex"]).default("www").describe("Kanonische Adresse: 'www' (mit www) oder 'apex' (ohne www). Nur beim Verbinden relevant."),
|
|
173
|
+
action: z.enum(["connect", "disconnect"]).default("connect").describe("connect = verbinden, disconnect = trennen.") },
|
|
174
|
+
async ({ site, domain, canonical, action }) => {
|
|
175
|
+
try { return asText(await apiDomainConnect(cfg, { site, domain, canonical, action })); } catch (e) { return asError(e); }
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
server.tool(
|
|
180
|
+
"site_embeds",
|
|
181
|
+
"LESEND: erkannte eingebettete Inhalte Dritter (iframes) einer Website – je Embed Host, Seite(n) und "
|
|
182
|
+
+ "aktuelle Consent-Kategorie (`gated` = lädt erst nach Einwilligung / Zwei-Klick, `notwendig` = technisch "
|
|
183
|
+
+ "erforderlich, lädt ohne Einwilligung). Basis für site_embed_consent.",
|
|
184
|
+
{ site: z.string().optional().describe(SITE_DESC) },
|
|
185
|
+
async ({ site }) => { try { return asText(await apiEmbeds(cfg, { site })); } catch (e) { return asError(e); } }
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
server.tool(
|
|
189
|
+
"site_embed_consent",
|
|
190
|
+
"SCHREIBEND: ein Embed als `notwendig` (technisch erforderlich, § 25 Abs. 2 Nr. 2 TDDDG) oder zurück auf "
|
|
191
|
+
+ "`gated` einstufen. Bei `notwendig` erklärt der Kunde/Betreuer, dass der Dienst für die vom Besucher "
|
|
192
|
+
+ "gewünschte Funktion (z. B. Reservierung) UNBEDINGT ERFORDERLICH ist und übernimmt die Verantwortung – "
|
|
193
|
+
+ "das MUSS mit dem Nutzer bestätigt werden (confirmed:true; sonst lehnt der Server mit dem Verantwortungs-"
|
|
194
|
+
+ "Text ab). Dann lädt das Embed OHNE Einwilligung, aber nur auf der angegebenen Seite; der Datenschutz-Text "
|
|
195
|
+
+ "passt sich automatisch an. Greift beim nächsten Veröffentlichen. Betreuer-Token gilt für betreute Kunden.",
|
|
196
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
197
|
+
host: z.string().describe("Embed-Host/Key aus site_embeds (z. B. 'resmio', 'youtube' oder ein Hostname)."),
|
|
198
|
+
page: z.string().optional().describe("Seiten-Datei, auf die die Einstufung beschränkt ist (z. B. 'reservierung.html'); leer = alle Seiten mit diesem Embed."),
|
|
199
|
+
category: z.enum(["notwendig", "gated"]).default("notwendig").describe("notwendig = technisch erforderlich (ohne Einwilligung); gated = zurück hinter das Consent-/Zwei-Klick-Gate."),
|
|
200
|
+
confirmed: z.boolean().default(false).describe("MUSS true sein, um 'notwendig' zu setzen (Verantwortungs-Bestätigung). Vorher den Nutzer explizit fragen.") },
|
|
201
|
+
async ({ site, host, page, category, confirmed }) => {
|
|
202
|
+
try { return asText(await apiEmbedConsent(cfg, { site, host, page, category, confirmed })); } catch (e) { return asError(e); }
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
149
206
|
const transport = new StdioServerTransport();
|
|
150
207
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prontosite/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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
|
-
"bin": {
|
|
6
|
+
"bin": {
|
|
7
|
+
"prontosite-mcp": "index.mjs"
|
|
8
|
+
},
|
|
7
9
|
"main": "index.mjs",
|
|
8
|
-
"files": [
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
"files": [
|
|
11
|
+
"index.mjs",
|
|
12
|
+
"api.mjs",
|
|
13
|
+
"README.md",
|
|
14
|
+
"CHANGELOG.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"prontosite",
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"claude",
|
|
24
|
+
"deploy",
|
|
25
|
+
"website"
|
|
26
|
+
],
|
|
11
27
|
"license": "UNLICENSED",
|
|
12
28
|
"homepage": "https://prontosite.io/docs/mcp",
|
|
13
|
-
"publishConfig": {
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
14
32
|
"dependencies": {
|
|
15
33
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
34
|
"zod": "^3.23.0"
|