@prontosite/mcp 0.2.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/README.md +117 -0
- package/api.mjs +137 -0
- package/index.mjs +76 -0
- package/package.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# ProntoSite MCP-Server
|
|
2
|
+
|
|
3
|
+
Deploy, Export und Status deiner ProntoSite-Website **direkt aus einem KI-Assistenten** (z. B. Claude Desktop) –
|
|
4
|
+
ohne Terminal. Der Server ist ein **dünner Client** um die bestehende [ProntoSite Deploy-API](../../docs/deploy-api.md);
|
|
5
|
+
er hat keine eigene Logik und keinen Zustand. Die HTTP-API (curl/Terminal, Skripte) bleibt der primäre Weg –
|
|
6
|
+
dies ist nur ein zusätzlicher Client obendrauf.
|
|
7
|
+
|
|
8
|
+
## Voraussetzungen
|
|
9
|
+
|
|
10
|
+
- Node.js ≥ 18
|
|
11
|
+
- Ein ProntoSite **API-Token** (`psk_…`) – im Dashboard unter **Konto → API-Zugang** (oder vom Betreuer erzeugt).
|
|
12
|
+
|
|
13
|
+
## Einrichtung (empfohlen: npx)
|
|
14
|
+
|
|
15
|
+
Kein Klonen, kein Node-Projekt nötig – Claude Desktop startet den Server per `npx`:
|
|
16
|
+
|
|
17
|
+
```jsonc
|
|
18
|
+
// claude_desktop_config.json → "mcpServers"
|
|
19
|
+
{
|
|
20
|
+
"prontosite": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "@prontosite/mcp"],
|
|
23
|
+
"env": { "PRONTOSITE_TOKEN": "psk_…", "PRONTOSITE_SITE": "meine-firma" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
(Repo-Variante ohne npx: `"command": "node", "args": ["/pfad/promptsite/mcp/prontosite/index.mjs"]`. Vorher
|
|
29
|
+
`cd mcp/prontosite && npm install`.)
|
|
30
|
+
|
|
31
|
+
## Konfiguration (Token nie im Chat)
|
|
32
|
+
|
|
33
|
+
Umgebungsvariablen **oder** `~/.prontosite.json`. Zwei Formen:
|
|
34
|
+
|
|
35
|
+
**Wichtig zum Site-Bezeichner:** Nutze die **stabile Site-ID** (`s-…`, steht im Dashboard) oder `"active"` –
|
|
36
|
+
**nicht** den Slug. Der Slug/die Adresse kann sich ändern (eigene Domain, Umbenennung); die Site-ID nie.
|
|
37
|
+
Für ein Konto mit nur einer Website ist `"active"` der robusteste Wert.
|
|
38
|
+
|
|
39
|
+
**Einzelne Website (ein Kunde):**
|
|
40
|
+
```jsonc
|
|
41
|
+
{ "token": "psk_…", "site": "active", "host": "app.prontosite.io" }
|
|
42
|
+
```
|
|
43
|
+
Env-Äquivalent: `PRONTOSITE_TOKEN`, `PRONTOSITE_SITE`, `PRONTOSITE_HOST`.
|
|
44
|
+
|
|
45
|
+
**Mehrere Websites (Betreuer/Agentur) – zwei gleichwertige Wege:** Bis ~5 Kunden am einfachsten je Kunde ein
|
|
46
|
+
eigener `mcpServers`-Eintrag (Token in `env`, terminalfrei) – siehe „Einrichtung (empfohlen: npx)" oben, den
|
|
47
|
+
Block je Kunde mit eigenem Server-Namen wiederholen. Ab ~5 wird die folgende `sites`-Map übersichtlicher (ein
|
|
48
|
+
Server, alle Kunden in einer Datei; `sites_list` zeigt alle auf einmal). Die Map – Schlüssel = frei wählbares
|
|
49
|
+
**Label** (das du im Chat sagst), `site` = **stabile Site-ID**, ein Token je Website:
|
|
50
|
+
```jsonc
|
|
51
|
+
{
|
|
52
|
+
"host": "app.prontosite.io",
|
|
53
|
+
"defaultSite": "mas-monti",
|
|
54
|
+
"sites": {
|
|
55
|
+
"mas-monti": { "site": "s-mth…", "token": "psk_…" },
|
|
56
|
+
"wallboxservice24": { "site": "s-msz…", "token": "psk_…" }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
Jedes Tool nimmt einen optionalen Parameter `site` (Label **oder** Site-ID). Ohne Angabe gilt `defaultSite`
|
|
61
|
+
bzw. die Einzel-Site. So verwaltest du mit **einem** Claude Desktop beliebig viele Kundenseiten – und die
|
|
62
|
+
Zuordnung bricht nicht, wenn ein Kunde seine Domain/Adresse ändert.
|
|
63
|
+
|
|
64
|
+
- `site` – **Site-ID** (`s-…`, empfohlen) oder `"active"`; ein Slug wird zwar noch akzeptiert, kann aber
|
|
65
|
+
veralten. Erst-Import: `site:"new"`.
|
|
66
|
+
- `host` – Standard `app.prontosite.io`.
|
|
67
|
+
|
|
68
|
+
**Datei anlegen/erweitern:** Existiert `~/.prontosite.json` noch nicht, lege sie mit obigem Inhalt an
|
|
69
|
+
(z. B. `cat > ~/.prontosite.json <<'JSON' … JSON` – ⚠ **überschreibt** eine vorhandene Datei!). Ist sie schon
|
|
70
|
+
da, NICHT überschreiben: die Datei öffnen und unter `"sites"` einen weiteren Eintrag ergänzen (vorher
|
|
71
|
+
sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
|
|
72
|
+
|
|
73
|
+
## Tools
|
|
74
|
+
|
|
75
|
+
| Tool | Wirkung |
|
|
76
|
+
|------|---------|
|
|
77
|
+
| `sites_list` | Alle erreichbaren Websites (Slug, Plan, letzte Änderung, Publish-Bedarf). **Lesend.** |
|
|
78
|
+
| `site_status({ site? })` | Plan, Slug, Live-URL, Publish-Bedarf. **Lesend.** |
|
|
79
|
+
| `site_export({ site?, which, outDir })` | ZIP (`published`\|`draft`\|`original`) lokal speichern. **Lesend.** |
|
|
80
|
+
| `site_deploy({ site?, zipPath, publish?, resetOverlay?, legal? })` | ZIP importieren, optional live. **Schreibend.** |
|
|
81
|
+
| `site_publish({ site? })` | Aktuellen Stand erneut veröffentlichen. **Schreibend.** |
|
|
82
|
+
|
|
83
|
+
`site_deploy` und `site_publish` sind **schreibende** Aktionen (verändern die Live-Website). Die
|
|
84
|
+
Tool-Beschreibungen weisen den Assistenten an, vorher rückzufragen. Jeder Aufruf wird serverseitig auditiert.
|
|
85
|
+
|
|
86
|
+
## Arbeitsweise: ein Chat je Kunde
|
|
87
|
+
|
|
88
|
+
Für saubere Trennung: pro Kundenwebsite **einen eigenen Claude-Chat**. So bezieht sich der ganze Verlauf auf
|
|
89
|
+
genau diese Website, und Verwechslungen sind ausgeschlossen. `sites_list` zeigt alle Slugs – so kannst du im
|
|
90
|
+
Chat einfach sagen „deploye mas-monti", ohne Slugs auswendig zu kennen. (Alternativ ein Chat für alles und die
|
|
91
|
+
Website je Aufruf über `site` wählen – die getrennten Chats sind aber übersichtlicher.)
|
|
92
|
+
|
|
93
|
+
## Sicherheit
|
|
94
|
+
|
|
95
|
+
- Tokens liegen nur lokal (Config/Env), werden nie geloggt und nie in Antworten zurückgegeben.
|
|
96
|
+
- **Behandle Tokens wie Passwörter.** Ein durchgesickertes Token erlaubt Deploy/Publish der betroffenen Website;
|
|
97
|
+
im Dashboard jederzeit widerrufbar, dann ein neues erzeugen.
|
|
98
|
+
- Es gelten dieselben serverseitigen Grenzen wie bei der HTTP-API (Plan/Operator-Gate, Rate-Limit, Audit-Log,
|
|
99
|
+
100 MB ZIP, kein Server-Code).
|
|
100
|
+
|
|
101
|
+
## Betreuer-Token (ein Token für ALLE betreuten Kunden)
|
|
102
|
+
|
|
103
|
+
Für Agenturen/Partner: **ein** Betreuer-Token deckt alle betreuten Kunden ab – **ohne `sites`-Map**. Erzeugt im
|
|
104
|
+
Admin/Partner-Bereich (Panel „Betreuer-Token"). Config dann einfach:
|
|
105
|
+
|
|
106
|
+
```jsonc
|
|
107
|
+
{ "token": "psk_…", "host": "app.prontosite.io" } // KEIN sites, KEIN site
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`sites_list` liefert automatisch alle betreuten Kunden-Sites (Neukunde erscheint, sobald er dem Partner
|
|
111
|
+
zugeordnet ist – keine Config-Pflege). Website je Aufruf per Slug **oder stabiler Site-ID** angeben (kein
|
|
112
|
+
`"active"` – es sind mehrere Konten). Erst-Import: `site_deploy({ site: "new", customer: "k-…" })`.
|
|
113
|
+
|
|
114
|
+
Sicherheit: der Zugriff wird bei JEDEM Aufruf serverseitig geprüft (`assertCanManage` – nur betreute Konten,
|
|
115
|
+
Rollenentzug wirkt sofort); nur Deploy/Status/Export/Veröffentlichen, kein Konto-/Zahlungszugriff; optionaler
|
|
116
|
+
Ablauf (Default 90 Tage) und optionale IP-Bindung; Widerruf sperrt alle betreuten Sites auf einmal.
|
|
117
|
+
Alternative bleibt die `sites`-Map oben (kleinere Angriffsfläche je Token, aber Pflege je Kunde).
|
package/api.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProntoSite MCP – HTTP-Kern (ohne MCP-SDK, damit testbar). Dünner Client um die bestehende Deploy-API;
|
|
3
|
+
* KEINE eigene Logik. Token/Site/Host aus lokaler Config (Env > ~/.prontosite.json), nie aus dem Chat.
|
|
4
|
+
* Tokens werden nie geloggt und nie zurückgegeben.
|
|
5
|
+
*
|
|
6
|
+
* Config-Formen:
|
|
7
|
+
* Einzel (ein Kunde): { "token":"psk_…", "site":"active", "host":"app.prontosite.io" }
|
|
8
|
+
* Multi (Betreuer): { "host":"app.prontosite.io", "defaultSite":"mas-monti", "sites": {
|
|
9
|
+
* "mas-monti": { "site":"s-mth…", "token":"psk_…" },
|
|
10
|
+
* "wallboxservice24": { "site":"s-msz…", "token":"psk_…" } } }
|
|
11
|
+
* `site` ist die STABILE Site-ID (s-…) oder "active" – NICHT der Slug (der kann sich ändern: Kundendomain,
|
|
12
|
+
* Umbenennung). Der Map-Schlüssel ist ein frei wählbares Label (das man im Chat sagt). Jedes Tool nimmt
|
|
13
|
+
* optional `site` (Label ODER Site-ID); ohne Angabe gilt defaultSite bzw. die Einzel-Site (sonst „active").
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
const DEFAULT_HOST = "app.prontosite.io";
|
|
20
|
+
|
|
21
|
+
/** Config laden: Umgebungsvariablen haben Vorrang (Einzel-Werte), plus optionale sites-Map aus der Datei. */
|
|
22
|
+
export function loadConfig(env = process.env, home = os.homedir()) {
|
|
23
|
+
let file = {};
|
|
24
|
+
try {
|
|
25
|
+
const p = path.join(home, ".prontosite.json");
|
|
26
|
+
if (fs.existsSync(p)) file = JSON.parse(fs.readFileSync(p, "utf8")) || {};
|
|
27
|
+
} catch { /* defekte Datei ignorieren – Env kann trotzdem reichen */ }
|
|
28
|
+
const host = (env.PRONTOSITE_HOST || file.host || DEFAULT_HOST).replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
29
|
+
const sites = (file.sites && typeof file.sites === "object") ? file.sites : null;
|
|
30
|
+
return {
|
|
31
|
+
host,
|
|
32
|
+
token: env.PRONTOSITE_TOKEN || file.token || "",
|
|
33
|
+
site: env.PRONTOSITE_SITE || file.site || "active",
|
|
34
|
+
sites,
|
|
35
|
+
defaultSite: env.PRONTOSITE_SITE || file.defaultSite || null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const needToken = (token, ref) => {
|
|
40
|
+
if (!token || !String(token).startsWith("psk_"))
|
|
41
|
+
throw new Error(`Kein API-Token für „${ref}" konfiguriert. Setze PRONTOSITE_TOKEN oder trage die Site in ~/.prontosite.json (sites) ein.`);
|
|
42
|
+
return token;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Ziel (Token + Site-Bezeichner für die API) bestimmen. Bei einer sites-Map wird der Eintrag per Label-
|
|
46
|
+
* Schlüssel ODER per stabiler Site-ID (entry.site) gefunden; die API bekommt die stabile ID (entry.site)
|
|
47
|
+
* bzw. den Schlüssel. Sonst der Einzel-Token mit dem übergebenen Bezeichner (ID/„active"). */
|
|
48
|
+
export function resolveTarget(cfg, site) {
|
|
49
|
+
const want = site || cfg.defaultSite || cfg.site || "active";
|
|
50
|
+
if (cfg.sites) {
|
|
51
|
+
const key = cfg.sites[want] ? want : Object.keys(cfg.sites).find(k => cfg.sites[k] && cfg.sites[k].site === want);
|
|
52
|
+
if (key) { const e = cfg.sites[key]; return { token: needToken(e.token, key), slug: e.site || key }; }
|
|
53
|
+
}
|
|
54
|
+
return { token: needToken(cfg.token, want), slug: want };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const baseUrl = cfg => `https://${cfg.host}/api`;
|
|
58
|
+
const authHeaders = token => ({ authorization: `Bearer ${token}` });
|
|
59
|
+
|
|
60
|
+
/** Antwort als JSON lesen; bei Fehlerstatus eine sprechende Exception (ohne Token). */
|
|
61
|
+
async function asJson(res) {
|
|
62
|
+
let body = {};
|
|
63
|
+
try { body = await res.json(); } catch { /* kein JSON */ }
|
|
64
|
+
if (!res.ok) throw new Error(body.error || `HTTP ${res.status}`);
|
|
65
|
+
return body;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** GET /api/status/<site> – Status der Site (Plan, Slug, Live-URL, Publish-Bedarf, letzte Änderung). */
|
|
69
|
+
export async function apiStatus(cfg, { site } = {}, fetchImpl = fetch) {
|
|
70
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
71
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/status/${encodeURIComponent(slug)}`, { headers: authHeaders(token) });
|
|
72
|
+
return asJson(res);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** GET /api/export/<site>?which= – ZIP in einen lokalen Ordner speichern. */
|
|
76
|
+
export async function apiExport(cfg, { site, which = "published", outDir = "." } = {}, fetchImpl = fetch) {
|
|
77
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
78
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/export/${encodeURIComponent(slug)}?which=${encodeURIComponent(which)}`, { headers: authHeaders(token) });
|
|
79
|
+
if (!res.ok) return asJson(res); // Fehler kommen als JSON
|
|
80
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
81
|
+
const cd = res.headers.get("content-disposition") || "";
|
|
82
|
+
const m = /filename="?([^"]+)"?/.exec(cd);
|
|
83
|
+
const name = (m && m[1]) || `prontosite-${slug}-${which}.zip`;
|
|
84
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
85
|
+
const outPath = path.join(outDir, name.replace(/[^a-zA-Z0-9._-]+/g, "_"));
|
|
86
|
+
fs.writeFileSync(outPath, buf);
|
|
87
|
+
return { path: outPath, bytes: buf.length, which, site: slug };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** POST /api/deploy/<site> – ZIP übergeben (+ optional publish/resetOverlay/legal). site="new" = anlegen. */
|
|
91
|
+
export async function apiDeploy(cfg, { site, customer = null, zipPath, publish = false, resetOverlay = false, legal = null } = {}, fetchImpl = fetch) {
|
|
92
|
+
if (!zipPath || !fs.existsSync(zipPath)) throw new Error(`ZIP nicht gefunden: ${zipPath}`);
|
|
93
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
94
|
+
const q = new URLSearchParams();
|
|
95
|
+
if (publish) q.set("publish", "1");
|
|
96
|
+
if (resetOverlay) q.set("resetOverlay", "1");
|
|
97
|
+
if (legal === true) q.set("legal", "1");
|
|
98
|
+
if (legal === false) q.set("legal", "0");
|
|
99
|
+
if (customer) q.set("customer", customer); // Betreuer-Token + site="new": Ziel-Konto für den Erst-Import
|
|
100
|
+
const qs = q.toString() ? `?${q}` : "";
|
|
101
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/deploy/${encodeURIComponent(slug)}${qs}`, {
|
|
102
|
+
method: "POST", headers: { ...authHeaders(token), "content-type": "application/zip" }, body: fs.readFileSync(zipPath) });
|
|
103
|
+
return asJson(res);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** POST /api/publish/<site> – erneut veröffentlichen (ohne ZIP). force=true = „trotzdem veröffentlichen"
|
|
107
|
+
* (auch ohne erkannte Änderung), analog zum Dashboard-Menüpunkt „Erneut veröffentlichen". */
|
|
108
|
+
export async function apiPublish(cfg, { site, force = false } = {}, fetchImpl = fetch) {
|
|
109
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
110
|
+
const qs = force ? "?force=1" : "";
|
|
111
|
+
const res = await fetchImpl(`${baseUrl(cfg)}/publish/${encodeURIComponent(slug)}${qs}`, { method: "POST", headers: authHeaders(token) });
|
|
112
|
+
return asJson(res);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Alle erreichbaren Sites auflisten (Slug, Plan, Publish-Bedarf, letzte Änderung). Bei einer sites-Map wird
|
|
116
|
+
* jeder Eintrag per Status abgefragt; sonst die eine konfigurierte Site. Fehler je Site werden mitgeführt. */
|
|
117
|
+
export async function sitesList(cfg, fetchImpl = fetch) {
|
|
118
|
+
if (cfg.sites) {
|
|
119
|
+
// Per-Kunde-Token-Map: jeden Eintrag einzeln abfragen (jeder Token → seine Site).
|
|
120
|
+
const out = await Promise.all(Object.keys(cfg.sites).map(async label => {
|
|
121
|
+
try {
|
|
122
|
+
const s = await apiStatus(cfg, { site: label }, fetchImpl);
|
|
123
|
+
return { label, slug: s.slug || null, id: s.site || null, name: s.name || null, plan: s.plan,
|
|
124
|
+
everPublished: s.everPublished, publishNeeded: s.publishNeeded, lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null };
|
|
125
|
+
} catch (e) { return { label, error: e.message }; }
|
|
126
|
+
}));
|
|
127
|
+
return { count: out.length, sites: out };
|
|
128
|
+
}
|
|
129
|
+
// Einzel-/Betreuer-Token: der Server liefert alle erreichbaren Sites (Betreuer-Token: ALLE betreuten Kunden,
|
|
130
|
+
// ohne Config-Pflege). GET /api/sites.
|
|
131
|
+
const { token } = resolveTarget(cfg, cfg.defaultSite || cfg.site || "active");
|
|
132
|
+
const body = await asJson(await fetchImpl(`${baseUrl(cfg)}/sites`, { headers: authHeaders(token) }));
|
|
133
|
+
const sites = (body.sites || []).map(s => ({ label: s.slug || s.site, slug: s.slug || null, id: s.site || null,
|
|
134
|
+
name: s.name || null, plan: s.plan, everPublished: s.everPublished, publishNeeded: s.publishNeeded,
|
|
135
|
+
lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null, customer: s.customer }));
|
|
136
|
+
return { count: sites.length, manager: !!body.manager, sites };
|
|
137
|
+
}
|
package/index.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ProntoSite MCP-Server (stdio) für Claude Desktop u. a. KI-Assistenten.
|
|
4
|
+
* DÜNNER Client um die bestehende ProntoSite-Deploy-API – keine eigene Logik, kein Zustand. Die HTTP-API
|
|
5
|
+
* (curl/Terminal) bleibt der primäre Weg; dies ist nur ein zusätzlicher Client. Token/Site/Host aus lokaler
|
|
6
|
+
* Config (Env > ~/.prontosite.json), nie aus dem Chat.
|
|
7
|
+
*
|
|
8
|
+
* Multi-Site (Betreuer): trägt die Config eine `sites`-Map, adressiert jedes Tool per `site`-Parameter die
|
|
9
|
+
* jeweilige Website (eigenes Token je Site). Arbeitsweise: EIN Chat je Kunde. `sites_list` zeigt alle Sites.
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList } from "./api.mjs";
|
|
15
|
+
|
|
16
|
+
const cfg = loadConfig();
|
|
17
|
+
const server = new McpServer({ name: "prontosite", version: "0.2.0" });
|
|
18
|
+
const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] });
|
|
19
|
+
const asError = e => ({ isError: true, content: [{ type: "text", text: "Fehler: " + (e?.message || String(e)) }] });
|
|
20
|
+
const SITE_DESC = "Website-Slug (z. B. 'mas-monti'). Ohne Angabe die Standard-Site der Config. Bei Betreuer-"
|
|
21
|
+
+ "Config (mehrere Sites) hier die gewünschte Website angeben.";
|
|
22
|
+
|
|
23
|
+
server.tool(
|
|
24
|
+
"sites_list",
|
|
25
|
+
"Listet alle erreichbaren ProntoSite-Websites auf (Slug, Plan, letzte Änderung, offener Veröffentlichungs"
|
|
26
|
+
+ "bedarf). Nützlich, um im Chat eine Website beim Namen zu nennen. LESEND.",
|
|
27
|
+
{},
|
|
28
|
+
async () => { try { return asText(await sitesList(cfg)); } catch (e) { return asError(e); } }
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
server.tool(
|
|
32
|
+
"site_status",
|
|
33
|
+
"Status einer ProntoSite-Website: Plan, Slug, Live-URL, letzte Änderungen und offener Veröffentlichungs"
|
|
34
|
+
+ "bedarf. LESEND – ändert nichts.",
|
|
35
|
+
{ site: z.string().optional().describe(SITE_DESC) },
|
|
36
|
+
async ({ site }) => { try { return asText(await apiStatus(cfg, { site })); } catch (e) { return asError(e); } }
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
server.tool(
|
|
40
|
+
"site_export",
|
|
41
|
+
"Exportiert eine Website als ZIP in ein lokales Verzeichnis. which: 'published' (Live-Stand, Standard), "
|
|
42
|
+
+ "'draft' (Entwurf) oder 'original' (flacher Stand ohne ProntoSite-Zutaten). LESEND.",
|
|
43
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
44
|
+
which: z.enum(["published", "draft", "original"]).default("published"),
|
|
45
|
+
outDir: z.string().default(".").describe("Zielordner für die ZIP-Datei (lokal).") },
|
|
46
|
+
async ({ site, which, outDir }) => { try { return asText(await apiExport(cfg, { site, which, outDir })); } catch (e) { return asError(e); } }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
server.tool(
|
|
50
|
+
"site_deploy",
|
|
51
|
+
"SCHREIBEND: Übergibt eine lokale ZIP-Datei an ProntoSite (Import). Mit publish=true wird die Website "
|
|
52
|
+
+ "DIREKT LIVE geschaltet und der bestehende Live-Stand überschrieben. site='new' legt beim Erst-Import "
|
|
53
|
+
+ "(Konto-Token) eine neue Website an. Bitte VOR dem Aufruf bestätigen lassen – und die richtige Website prüfen.",
|
|
54
|
+
{ site: z.string().optional().describe(SITE_DESC + " Oder 'new' zum Anlegen (Erst-Import)."),
|
|
55
|
+
customer: z.string().optional().describe("Nur mit Betreuer-Token + site='new': Ziel-Konto-ID (k-…) für die neue Website."),
|
|
56
|
+
zipPath: z.string().describe("Pfad zur ZIP-Datei der Website (lokal)."),
|
|
57
|
+
publish: z.boolean().default(false).describe("true = nach dem Import direkt veröffentlichen (LIVE)."),
|
|
58
|
+
resetOverlay: z.boolean().default(false).describe("true = bisherige Dashboard-Änderungen verwerfen."),
|
|
59
|
+
legal: z.boolean().optional().describe("Rechts-Automatik (Impressum/Datenschutz): true erzwingt, false schaltet ab.") },
|
|
60
|
+
async ({ site, customer, zipPath, publish, resetOverlay, legal }) => {
|
|
61
|
+
try { return asText(await apiDeploy(cfg, { site, customer, zipPath, publish, resetOverlay, legal })); } catch (e) { return asError(e); }
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
server.tool(
|
|
66
|
+
"site_publish",
|
|
67
|
+
"SCHREIBEND: Veröffentlicht den aktuellen Stand einer Website ERNEUT (ohne ZIP) – z. B. um Plattform-"
|
|
68
|
+
+ "Aktualisierungen live zu bringen. Meldet die Antwort nothing:true (keine erkannte Änderung), aber du "
|
|
69
|
+
+ "willst trotzdem neu ausliefern, setze force:true. Bitte VOR dem Aufruf bestätigen lassen – und die richtige Website prüfen.",
|
|
70
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
71
|
+
force: z.boolean().default(false).describe("true = trotzdem veröffentlichen, auch ohne erkannte Änderung (wie der Menüpunkt Erneut veröffentlichen).") },
|
|
72
|
+
async ({ site, force }) => { try { return asText(await apiPublish(cfg, { site, force })); } catch (e) { return asError(e); } }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const transport = new StdioServerTransport();
|
|
76
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prontosite/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"bin": { "prontosite-mcp": "index.mjs" },
|
|
7
|
+
"main": "index.mjs",
|
|
8
|
+
"files": ["index.mjs", "api.mjs", "README.md"],
|
|
9
|
+
"engines": { "node": ">=18" },
|
|
10
|
+
"keywords": ["prontosite", "mcp", "model-context-protocol", "claude", "deploy", "website"],
|
|
11
|
+
"license": "UNLICENSED",
|
|
12
|
+
"homepage": "https://prontosite.io/mcp-anleitung",
|
|
13
|
+
"publishConfig": { "access": "public" },
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"zod": "^3.23.0"
|
|
17
|
+
}
|
|
18
|
+
}
|