@prontosite/mcp 0.4.0 → 0.7.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 +28 -0
- package/README.md +3 -0
- package/api.mjs +82 -23
- package/index.mjs +52 -2
- package/package.json +24 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
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.7.0 – 2026-09-23
|
|
7
|
+
|
|
8
|
+
- **Ambige Config erkannt:** Liegt ein Top-Level-Token (env `PRONTOSITE_TOKEN` bzw. `token`) NEBEN einer
|
|
9
|
+
`sites`-Map, warnt der Server beim Start deutlich auf stderr und `sites_list` trägt den Hinweis mit:
|
|
10
|
+
„Beide Konfigurationen gefunden — sites-Map wird genutzt, env-Token ignoriert."
|
|
11
|
+
- **Betreuer-Token hat IMMER Vorrang:** Ist das Top-Level-Token ein Betreuer-Token (scope=manager), nutzt
|
|
12
|
+
`sites_list` dieses (deckt alle Kunden ab) und meldet die daneben liegende `sites`-Map als
|
|
13
|
+
Konfigurationsfehler (Map ignoriert). Ein Einzel-Token wird weiterhin von der Map verdrängt.
|
|
14
|
+
- **`liveUrl` = kanonische Kundendomain**, sobald `customDomains` gesetzt ist (sonst die Slug-Subdomain) –
|
|
15
|
+
in `sites_list` und `site_status`. So wird gedanklich nie „auf die falsche Adresse" deployt; die Slug-URL
|
|
16
|
+
leitet ohnehin per 301 auf die Kundendomain weiter.
|
|
17
|
+
|
|
18
|
+
## 0.6.0 – 2026-09-22
|
|
19
|
+
|
|
20
|
+
- **Neu: `site_domain_status`** – Kundendomain-Status (verbunden/kanonisch/Zertifikat-bis) + offene Jobs;
|
|
21
|
+
mit `check` nur eine Live-DNS-Prüfung (A @+www → Server-IP, keine AAAA) vor dem Verbinden. LESEND.
|
|
22
|
+
- **Neu: `site_domain_connect`** (SCHREIBEND) – eigene Domain verbinden (nginx-Block + HTTPS-Zertifikat
|
|
23
|
+
automatisch, kanonisch `www`/`apex`, inkl. Republish) oder trennen (`action:disconnect`). Voraussetzung:
|
|
24
|
+
DNS zeigt bereits auf den Server (vorher `site_domain_status` mit `check`). Ab Starter-Plan (Betreuer-/
|
|
25
|
+
Operator-Token ausgenommen); eine Domain nur an EINER Website. Läuft asynchron über die Job-Queue.
|
|
26
|
+
|
|
27
|
+
## 0.5.0 – 2026-09-20
|
|
28
|
+
|
|
29
|
+
- **Neu: `site_legal_options`** – Anzeige-Optionen der Rechtstext-Widgets je Rolle (impressum/datenschutz):
|
|
30
|
+
`showTitle` (Überschrift), `showDate` (Stand-Datum), `draftNotice` (Entwurfs-Hinweis in der Live-Ausgabe).
|
|
31
|
+
Ohne `impressum`/`datenschutz` nur lesen; sonst Teil-Update. `draftNotice:false` blendet den Entwurfs-Hinweis
|
|
32
|
+
live aus (protokolliert) – Bestätigung empfohlen. Serverseitig: `GET/PUT /api/legal-options/<site>`.
|
|
33
|
+
|
|
6
34
|
## 0.4.0 – 2026-09-20
|
|
7
35
|
|
|
8
36
|
- **Neu: `site_company_get`** – Firmendaten (Impressum/Footer) einer Website lesen (effektiver Stand +
|
package/README.md
CHANGED
|
@@ -88,6 +88,9 @@ sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
|
|
|
88
88
|
| `site_publish({ site? })` | Aktuellen Stand erneut veröffentlichen. **Schreibend.** |
|
|
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
|
+
| `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.** |
|
|
91
94
|
|
|
92
95
|
`site_deploy` und `site_publish` sind **schreibende** Aktionen (verändern die Live-Website). Die
|
|
93
96
|
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,26 +168,79 @@ 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/PUT /api/legal-options/<site> – Anzeige-Optionen der Rechtstext-Widgets. Ohne impressum/datenschutz
|
|
193
|
+
* nur LESEN; sonst Teil-Update (nur echte Booleans wirken). */
|
|
194
|
+
export async function apiLegalOptions(cfg, { site, impressum, datenschutz } = {}, fetchImpl = fetch) {
|
|
195
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
196
|
+
const endpoint = `${baseUrl(cfg)}/legal-options/${encodeURIComponent(slug)}`;
|
|
197
|
+
if (impressum === undefined && datenschutz === undefined) {
|
|
198
|
+
return asJson(await fetchImpl(endpoint, { headers: authHeaders(token) }));
|
|
199
|
+
}
|
|
200
|
+
const body = {};
|
|
201
|
+
if (impressum !== undefined) body.impressum = impressum;
|
|
202
|
+
if (datenschutz !== undefined) body.datenschutz = datenschutz;
|
|
203
|
+
const res = await fetchImpl(endpoint, { method: "PUT", headers: { ...authHeaders(token), "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
204
|
+
return asJson(res);
|
|
205
|
+
}
|
|
206
|
+
|
|
165
207
|
/** Alle erreichbaren Sites auflisten (Slug, Plan, Publish-Bedarf, letzte Änderung). Bei einer sites-Map wird
|
|
166
208
|
* jeder Eintrag per Status abgefragt; sonst die eine konfigurierte Site. Fehler je Site werden mitgeführt. */
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// Per-Kunde-Token-Map: jeden Eintrag einzeln abfragen (jeder Token → seine Site).
|
|
170
|
-
const out = await Promise.all(Object.keys(cfg.sites).map(async label => {
|
|
171
|
-
try {
|
|
172
|
-
const s = await apiStatus(cfg, { site: label }, fetchImpl);
|
|
173
|
-
return { label, slug: s.slug || null, id: s.site || null, name: s.name || null, plan: s.plan,
|
|
174
|
-
everPublished: s.everPublished, publishNeeded: s.publishNeeded, lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null };
|
|
175
|
-
} catch (e) { return { label, error: e.message }; }
|
|
176
|
-
}));
|
|
177
|
-
return { count: out.length, sites: out };
|
|
178
|
-
}
|
|
179
|
-
// Einzel-/Betreuer-Token: der Server liefert alle erreichbaren Sites (Betreuer-Token: ALLE betreuten Kunden,
|
|
180
|
-
// ohne Config-Pflege). GET /api/sites.
|
|
181
|
-
const { token } = resolveTarget(cfg, cfg.defaultSite || cfg.site || "active");
|
|
209
|
+
// GET /api/sites mit EINEM Token → normalisierte Liste (Betreuer-Token: alle betreuten Kunden).
|
|
210
|
+
async function apiSitesWithToken(cfg, token, fetchImpl) {
|
|
182
211
|
const body = await asJson(await fetchImpl(`${baseUrl(cfg)}/sites`, { headers: authHeaders(token) }));
|
|
183
212
|
const sites = (body.sites || []).map(s => ({ label: s.slug || s.site, slug: s.slug || null, id: s.site || null,
|
|
184
213
|
name: s.name || null, plan: s.plan, everPublished: s.everPublished, publishNeeded: s.publishNeeded,
|
|
185
214
|
lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null, customer: s.customer }));
|
|
186
|
-
return { count: sites.length, manager: !!body.manager, sites };
|
|
215
|
+
return { count: sites.length, manager: !!body.manager, hint: body.hint || null, sites };
|
|
216
|
+
}
|
|
217
|
+
// sites-Map abfragen: jeden Eintrag einzeln (jeder Token → seine Site).
|
|
218
|
+
async function sitesFromMap(cfg, fetchImpl) {
|
|
219
|
+
const out = await Promise.all(Object.keys(cfg.sites).map(async label => {
|
|
220
|
+
try {
|
|
221
|
+
const s = await apiStatus(cfg, { site: label }, fetchImpl);
|
|
222
|
+
return { label, slug: s.slug || null, id: s.site || null, name: s.name || null, plan: s.plan,
|
|
223
|
+
everPublished: s.everPublished, publishNeeded: s.publishNeeded, lastChangeAt: s.lastChangeAt || null, liveUrl: s.liveUrl || null };
|
|
224
|
+
} catch (e) { return { label, error: e.message }; }
|
|
225
|
+
}));
|
|
226
|
+
return { count: out.length, sites: out };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function sitesList(cfg, fetchImpl = fetch) {
|
|
230
|
+
// Ambig (Top-Level-Token NEBEN sites-Map): Betreuer-Token hat IMMER Vorrang. Nur der Server kennt den
|
|
231
|
+
// Scope → das Top-Level-Token einmal prüfen: manager:true → dieses nutzen (Map ist Konfigurationsfehler);
|
|
232
|
+
// sonst die Map (Einzel-Token verdrängt).
|
|
233
|
+
if (cfg.sites && cfg.token) {
|
|
234
|
+
let probe = null;
|
|
235
|
+
try { probe = await apiSitesWithToken(cfg, cfg.token, fetchImpl); } catch { /* Token ungültig? → Map-Weg */ }
|
|
236
|
+
if (probe && probe.manager)
|
|
237
|
+
return { ...probe, configError: "Konfigurationsfehler: Betreuer-Token UND sites-Map konfiguriert. "
|
|
238
|
+
+ "Der Betreuer-Token deckt bereits ALLE betreuten Kunden ab und hat Vorrang – die sites-Map wird ignoriert. "
|
|
239
|
+
+ "Bitte die sites-Map (oder den Betreuer-Token) aus ~/.prontosite.json entfernen." };
|
|
240
|
+
return { ...(await sitesFromMap(cfg, fetchImpl)), warning: cfg.warnings[0] || null };
|
|
241
|
+
}
|
|
242
|
+
if (cfg.sites) return sitesFromMap(cfg, fetchImpl);
|
|
243
|
+
// Einzel-/Betreuer-Token ohne Map: der Server liefert alle erreichbaren Sites.
|
|
244
|
+
const { token } = resolveTarget(cfg, cfg.defaultSite || cfg.site || "active");
|
|
245
|
+
return apiSitesWithToken(cfg, token, fetchImpl);
|
|
187
246
|
}
|
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 } from "./api.mjs";
|
|
14
|
+
import { loadConfig, apiStatus, apiExport, apiDeploy, apiPublish, sitesList, apiCompanyGet, apiCompanySet, apiLegalOptions, apiDomainStatus, apiDomainConnect } 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.7.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-"
|
|
@@ -126,5 +129,52 @@ server.tool(
|
|
|
126
129
|
}
|
|
127
130
|
);
|
|
128
131
|
|
|
132
|
+
const legalOptShape = z.object({
|
|
133
|
+
showTitle: z.boolean().optional().describe("Überschrift (H1) des Rechtstextes anzeigen (Default an)."),
|
|
134
|
+
showDate: z.boolean().optional().describe("Stand-Datum anzeigen (Default an; nur wo vorhanden, i. d. R. Datenschutz)."),
|
|
135
|
+
draftNotice: z.boolean().optional().describe("Entwurfs-Hinweis in der VERÖFFENTLICHTEN Seite anzeigen (Default an)."),
|
|
136
|
+
}).describe("Anzeige-Optionen einer Rechtstext-Rolle – Teil-Update, nur gesetzte Booleans wirken.");
|
|
137
|
+
|
|
138
|
+
server.tool(
|
|
139
|
+
"site_legal_options",
|
|
140
|
+
"LESEND/SCHREIBEND: Anzeige-Optionen der Rechtstext-Widgets je Rolle (impressum/datenschutz): showTitle "
|
|
141
|
+
+ "(Überschrift), showDate (Stand-Datum), draftNotice (Entwurfs-Hinweis in der Live-Ausgabe). OHNE impressum/"
|
|
142
|
+
+ "datenschutz nur LESEN. Teil-Update (nur gesetzte Felder). HINWEIS: draftNotice:false blendet den rechtlich "
|
|
143
|
+
+ "empfohlenen Entwurfs-Hinweis live aus (protokolliert) – bitte vorher bestätigen lassen.",
|
|
144
|
+
{ site: z.string().optional().describe(SITE_DESC),
|
|
145
|
+
impressum: legalOptShape.optional(),
|
|
146
|
+
datenschutz: legalOptShape.optional() },
|
|
147
|
+
async ({ site, impressum, datenschutz }) => {
|
|
148
|
+
try { return asText(await apiLegalOptions(cfg, { site, impressum, datenschutz })); } catch (e) { return asError(e); }
|
|
149
|
+
}
|
|
150
|
+
);
|
|
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
|
+
|
|
129
179
|
const transport = new StdioServerTransport();
|
|
130
180
|
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.7.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"
|