@prontosite/mcp 0.14.1 → 0.14.4
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 +24 -0
- package/README.md +1 -1
- package/api.mjs +9 -4
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/tools.mjs +53 -13
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
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.14.4 – 2026-10-24
|
|
7
|
+
|
|
8
|
+
Enthält 0.14.3 (nie einzeln veröffentlicht).
|
|
9
|
+
- Instructions: „Änderungen sofort live" (Auto-Publish). Meldet `site_status` `autoPublish:true`, nicht nach Veröffentlichung
|
|
10
|
+
fragen und nicht „Entwurf" sagen, sondern „ist live" melden; der Vorschau-Link entfällt, `site_undo` veröffentlicht sofort mit.
|
|
11
|
+
Bestätigung bleibt Pflicht für Domain, Rechtstexte-Einstellungen und das Löschen von Seiten (`autoPublish.state:"held"` →
|
|
12
|
+
nach Bestätigung `site_publish`).
|
|
13
|
+
|
|
14
|
+
## 0.14.3 – 2026-10-24
|
|
15
|
+
|
|
16
|
+
- Instructions + `site_publish`: Änderungen im Entwurf bündeln und am Ende einmal veröffentlichen, nicht nach jedem Schritt
|
|
17
|
+
(weiterhin nur nach Bestätigung des Nutzers). Neues Publish-Limit des Servers: Kunden 30, Betreuer 120 Veröffentlichungen je
|
|
18
|
+
Stunde, je 10 direkt hintereinander; bei 429 `retryAfter` abwarten, der Entwurf bleibt erhalten.
|
|
19
|
+
|
|
20
|
+
## 0.14.2 – 2026-10-24
|
|
21
|
+
|
|
22
|
+
- Alle Tools liefern zusätzlich `structuredContent` (dieselben Daten wie der Text-Block, MCP 2025-06-18 „Structured Content");
|
|
23
|
+
Fehler als `structuredContent.error` (außer bei Tools mit `outputSchema` – dort nur Text). Anlass: Befund ChatGPT, `site_file_get` kam als `{}` an.
|
|
24
|
+
- `site_file_get {path, offset?, length?, outline?}`: liest in Teilen (Standard 60 KB, max. 1 MB je Aufruf). Antwort mit
|
|
25
|
+
`size`, `offset`, `length`, `truncated`, `nextOffset`; Einheit Bytes (UTF-8), Teile enden nie mitten in einem Zeichen, Binär-Teile
|
|
26
|
+
(Base64) lassen sich aneinanderhängen. Beim ersten Teil einer großen Datei bzw. mit `outline:true` eine gekappte Gliederung
|
|
27
|
+
(Überschriften, Abschnitte, IDs, CSS-Regeln mit `line` + `offset`) zum gezielten Nachladen. `outputSchema` für `site_file_get`.
|
|
28
|
+
- REST `GET /api/file` ohne die neuen Parameter liefert weiter die ganze Datei (ältere Clients).
|
|
29
|
+
|
|
6
30
|
## 0.14.1 – 2026-10-22
|
|
7
31
|
|
|
8
32
|
- Instructions + `media_search`: Fotos nie per URL von Unsplash oder anderen Bilddiensten (images.unsplash.com, imgix …) ins
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ sichern: `cp ~/.prontosite.json ~/.prontosite.json.bak`).
|
|
|
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
86
|
| `site_status({ site? })` | Plan, Slug, Live-URL, Publish-Bedarf, `draftRev` + `recentEdits` (wer hat zuletzt wo geändert). **Lesend.** |
|
|
87
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.** |
|
|
88
|
+
| `site_file_get({ site?, path, offset?, length?, outline? })` | Quelltext einer Datei (Text bzw. Base64, Datei max. 5 MB) in Teilen von höchstens `length` Bytes (Standard 60 KB): `truncated` + `nextOffset` zum Weiterlesen, beim ersten Teil bzw. mit `outline:true` eine Gliederung (Abschnitte/IDs mit `line` + `offset`); + `draftRev` + Hinweis auf Dashboard-Änderungen darüber (`overlay`). **Lesend.** |
|
|
89
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
90
|
| `site_file_delete({ site?, path, baseRev, confirmDestructive? })` / `site_file_rename({ site?, from, to, baseRev })` | Text-Datei im Entwurf entfernen bzw. umbenennen; eine Seite löschen/umbenennen (Startseite geschützt, Verweise werden gemeldet). Eine neue `.html` per `site_file_put` legt eine neue Seite an. Undo-fähig, nur Betreuer-Token. **Schreibend.** |
|
|
91
91
|
| `site_lock({ site?, note?, takeover? })` / `site_unlock({ site? })` | Sanfte Sperre „In Bearbeitung durch …“ (2 h, verlängert sich bei jeder Änderung). Blockiert niemanden; `takeover` nur als Betreuer. |
|
package/api.mjs
CHANGED
|
@@ -369,10 +369,15 @@ export async function apiFiles(cfg, { site } = {}, fetchImpl = fetch) {
|
|
|
369
369
|
const { token, slug } = resolveTarget(cfg, site);
|
|
370
370
|
return asJson(await fetchImpl(`${baseUrl(cfg)}/files/${encodeURIComponent(slug)}`, { headers: authHeaders(token, cfg) }));
|
|
371
371
|
}
|
|
372
|
-
/** GET /api/file/<site>?path= – Quelltext einer Datei (Text bzw. Base64) + draftRev + Overlay-Hinweis. */
|
|
373
|
-
export async function apiFileGet(cfg, { site, path: rel } = {}, fetchImpl = fetch) {
|
|
374
|
-
const { token, slug } = resolveTarget(cfg, site);
|
|
375
|
-
|
|
372
|
+
/** GET /api/file/<site>?path=&offset=&length=&outline= – Quelltext einer Datei bzw. ein Teil davon (Text bzw. Base64) + draftRev + Overlay-Hinweis. */
|
|
373
|
+
export async function apiFileGet(cfg, { site, path: rel, offset = null, length = null, outline = false } = {}, fetchImpl = fetch) {
|
|
374
|
+
const { token, slug } = resolveTarget(cfg, site);
|
|
375
|
+
// Teil-Lesen (0.14.2): offset/length in Bytes, outline=1 nur Gliederung – ohne Angaben die ganze Datei (wie bisher).
|
|
376
|
+
const q = new URLSearchParams({ path: rel });
|
|
377
|
+
if (Number.isInteger(offset)) q.set("offset", String(offset));
|
|
378
|
+
if (Number.isInteger(length)) q.set("length", String(length));
|
|
379
|
+
if (outline) q.set("outline", "1");
|
|
380
|
+
return asJson(await fetchImpl(`${baseUrl(cfg)}/file/${encodeURIComponent(slug)}?${q}`, { headers: authHeaders(token, cfg) }));
|
|
376
381
|
}
|
|
377
382
|
/** PUT /api/file/<site>?path= – Text-Inhalt ersetzen (Entwurf). baseRev Pflicht. */
|
|
378
383
|
export async function apiFilePut(cfg, { site, path: rel, content, baseRev, overlay = "keep" } = {}, fetchImpl = fetch) {
|
package/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const cfg = loadConfig();
|
|
|
21
21
|
// es im MCP-Log/Terminal auffällt; die endgültige Auflösung (Betreuer-Token-Vorrang vs. Map) klärt sites_list.
|
|
22
22
|
for (const w of (cfg.warnings || [])) process.stderr.write("[prontosite-mcp] ⚠ " + w + "\n");
|
|
23
23
|
// instructions: Arbeitsweise (Weg B = Standard) – gleicher Text wie am Remote-Endpunkt.
|
|
24
|
-
const server = new McpServer({ name: "prontosite", version: "0.14.
|
|
24
|
+
const server = new McpServer({ name: "prontosite", version: "0.14.4" }, { instructions: instructionsFor("local") });
|
|
25
25
|
// Tool-Definitionen: gemeinsame Quelle mit dem Remote-Endpunkt (tools.mjs, Konzept KI-Anbindung §4.2).
|
|
26
26
|
registerTools(server, { z, cfg, mode: "local" });
|
|
27
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prontosite/mcp",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
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": {
|
package/tools.mjs
CHANGED
|
@@ -35,7 +35,14 @@ const instructionLines = mode => [
|
|
|
35
35
|
"2b. Für Übersetzungen und Textüberarbeitung: site_page_texts → Texte übersetzen/überarbeiten → site_page_texts_set, JE SEITE ein Aufruf "
|
|
36
36
|
+ "(neue Sprachfassung vorher mit site_page_copy, z. B. index.html → en/index.html). NIE HTML über site_file_put umschreiben. "
|
|
37
37
|
+ "Rechtstext-Seiten (Impressum/Datenschutz) sind ausgenommen.",
|
|
38
|
-
"3. Veröffentlichen (site_publish, publish=true) NUR nach ausdrücklicher Bestätigung des Nutzers im Chat – bis dahin bleibt alles im Entwurf."
|
|
38
|
+
"3. Veröffentlichen (site_publish, publish=true) NUR nach ausdrücklicher Bestätigung des Nutzers im Chat – bis dahin bleibt alles im Entwurf. "
|
|
39
|
+
+ "Änderungen im Entwurf BÜNDELN und am Ende EINMAL veröffentlichen, nicht nach jedem Schritt (Vorschau über previewUrl). Limit: Kunden 30, "
|
|
40
|
+
+ "Betreuer 120 Veröffentlichungen je Stunde (je 10 direkt hintereinander); bei 429 retryAfter abwarten, der Entwurf bleibt erhalten.",
|
|
41
|
+
"3a. Wenn site_status autoPublish:true meldet („Änderungen sofort live\"): nicht nach Veröffentlichung fragen und nicht „Entwurf\" sagen – "
|
|
42
|
+
+ "„ist live\" melden (geht nach ca. 5 s automatisch live, Folgen von Änderungen werden gebündelt); Vorschau-Link entfällt; site_undo "
|
|
43
|
+
+ "veröffentlicht sofort mit. Weiterhin NUR nach Bestätigung des Nutzers: Domain verbinden/trennen, Rechtstexte-Einstellungen "
|
|
44
|
+
+ "(Firmendaten, site_legal_options, Impressum/Datenschutz) und Löschen von Seiten. Meldet eine Antwort autoPublish.state \"held\", "
|
|
45
|
+
+ "wartet die Änderung auf Bestätigung → nach Bestätigung site_publish.",
|
|
39
46
|
"4. Konflikt (409, conflict:true): nicht erzwingen. Erst site_status aufrufen (zeigt, wer was geändert hat), Inhalt neu holen, Änderung "
|
|
40
47
|
+ "neu anwenden und mit der neuen draftRev erneut senden.",
|
|
41
48
|
"5a. Bilder: Für neue Bilder media_upload_begin → Upload-Link. Wenn du Dateien lesen kannst, lade die ORIGINALDATEI selbst per HTTP PUT "
|
|
@@ -70,13 +77,37 @@ export function toolAnnotations(name, mode = "local") {
|
|
|
70
77
|
return { readOnlyHint: ro, destructiveHint: !ro && DESTRUCTIVE.has(name), idempotentHint: ro || name === "site_lock" || name === "site_unlock", openWorldHint: OPEN_WORLD.has(name) };
|
|
71
78
|
}
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
// Antworten (MCP 2025-06-18 „Structured Content"): dieselben Daten als structuredContent (JSON-Objekt) UND serialisiert im
|
|
81
|
+
// Text-Block (Rückwärtskompatibilität). ChatGPT wertet structuredContent aus, Claude & Co. den Text (Befund 24.10.2026).
|
|
82
|
+
export const toStructured = obj => (obj && typeof obj === "object" && !Array.isArray(obj) ? obj : Array.isArray(obj) ? { items: obj } : { value: obj ?? null });
|
|
83
|
+
export const asText = obj => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }], structuredContent: toStructured(obj) });
|
|
84
|
+
export const asError = e => { const msg = e?.message || String(e);
|
|
85
|
+
return { isError: true, content: [{ type: "text", text: "Fehler: " + msg }], structuredContent: { error: msg, ...(e?.status ? { status: e.status } : {}) } }; };
|
|
86
|
+
|
|
87
|
+
/** Ausgabe-Schema von site_file_get (structuredContent; OpenAI Apps SDK: outputSchema für Tools mit structuredContent). */
|
|
88
|
+
const FILE_GET_OUTPUT = z => z.object({
|
|
89
|
+
ok: z.boolean().optional(), path: z.string(), type: z.string().optional(), encoding: z.enum(["utf8", "base64"]),
|
|
90
|
+
content: z.string().nullable().describe("Inhalt dieses Teils (utf8 bzw. Base64); null bei outline:true."),
|
|
91
|
+
bytes: z.number().int().optional(), size: z.number().int().describe("Dateigröße in Bytes."),
|
|
92
|
+
offset: z.number().int().describe("Start dieses Teils in Bytes."), length: z.number().int().describe("Länge dieses Teils in Bytes."),
|
|
93
|
+
truncated: z.boolean().describe("true = nicht die ganze Datei."), nextOffset: z.number().int().nullable().describe("Weiterlesen ab hier; null = Ende."),
|
|
94
|
+
outline: z.object({ items: z.array(z.object({ line: z.number().int(), offset: z.number().int() }).passthrough()), total: z.number().int() }).passthrough().optional(),
|
|
95
|
+
hinweis: z.string().optional(), sha: z.string().optional(), source: z.string().optional(), draftRev: z.number().int().optional(),
|
|
96
|
+
page: z.object({ id: z.string(), title: z.string().optional() }).passthrough().optional(), overlay: z.any().optional(),
|
|
97
|
+
}).passthrough(); // weitere Felder erlaubt (additionalProperties) – neue Server-Felder brechen strenge Clients nicht
|
|
75
98
|
|
|
76
99
|
/** Alle Tools am MCP-Server registrieren. */
|
|
77
100
|
export function registerTools(server0, { z, cfg, mode = "local", remote = null }) {
|
|
78
101
|
// Jedes Tool mit Annotationen registrieren (Signatur des SDK: name, description, schema, annotations, handler).
|
|
79
|
-
const server = { tool: (name, desc, schema, cb) => server0.tool(name, desc, schema, toolAnnotations(name, mode), cb)
|
|
102
|
+
const server = { tool: (name, desc, schema, cb) => server0.tool(name, desc, schema, toolAnnotations(name, mode), cb),
|
|
103
|
+
// mit Ausgabe-Schema (outputSchema; SDK prüft structuredContent dagegen) – Fallback ohne, falls der Server nur tool() kennt
|
|
104
|
+
// Fehler ohne structuredContent: Clients (z. B. der SDK-Client) prüfen es sonst gegen das Schema und verwerfen die Fehlermeldung.
|
|
105
|
+
toolWithOutput: (name, desc, schema, outputSchema, cb) => {
|
|
106
|
+
const wrapped = async (...args) => { const r = await cb(...args); if (r?.isError) { const { structuredContent, ...rest } = r; return rest; } return r; };
|
|
107
|
+
return typeof server0.registerTool === "function"
|
|
108
|
+
? server0.registerTool(name, { description: desc, inputSchema: schema, outputSchema, annotations: toolAnnotations(name, mode) }, wrapped)
|
|
109
|
+
: server0.tool(name, desc, schema, toolAnnotations(name, mode), wrapped);
|
|
110
|
+
} };
|
|
80
111
|
const SITE_DESC = mode === "remote"
|
|
81
112
|
? "Website: Slug (z. B. 'mas-monti'), Site-ID (s-…), Firmenname, Domain oder Site-Titel (unscharf, z. B. 'Firat-Deveci'). Ohne Angabe "
|
|
82
113
|
+ "die Website des Tokens bzw. der Freigabe. Betreuer: die gewünschte Website angeben (sites_list zeigt alle mit Firmennamen)."
|
|
@@ -268,7 +299,7 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
268
299
|
const r = await apiMediaSearch(cfg, { site, query, orientation, count });
|
|
269
300
|
const images = (r.results || []).filter(x => x.preview?.data).map(x => ({ type: "image", data: x.preview.data, mimeType: x.preview.mimeType || "image/jpeg" }));
|
|
270
301
|
const text = { ...r, results: (r.results || []).map((x, i) => ({ nr: i + 1, ...x, preview: x.preview ? "(Bild unten)" : null })) };
|
|
271
|
-
return { content: [{ type: "text", text: JSON.stringify(text, null, 2) }, ...images] };
|
|
302
|
+
return { content: [{ type: "text", text: JSON.stringify(text, null, 2) }, ...images], structuredContent: text };
|
|
272
303
|
} catch (e) { return asError(e); }
|
|
273
304
|
}
|
|
274
305
|
);
|
|
@@ -361,9 +392,9 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
361
392
|
try {
|
|
362
393
|
const r = await apiPreviewScreenshot(cfg, { site, page, width });
|
|
363
394
|
if (!r.png) return asText(r);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
395
|
+
const info = { page: r.page, width: r.width, height: r.height, ...(r.truncated ? { abgeschnitten: `Seite ist ${r.fullHeight} px hoch – gezeigt die oberen ${r.height} px` } : {}),
|
|
396
|
+
...(r.overflowX ? { horizontalerUeberlauf: `Inhalt ist ${r.overflowX} px breit (Viewport ${r.width}) – die Seite scrollt seitwärts; meist ein Bild/Element ohne max-width.` } : {}), ms: r.ms };
|
|
397
|
+
return { content: [{ type: "image", data: r.png, mimeType: "image/png" }, { type: "text", text: JSON.stringify(info) }], structuredContent: info };
|
|
367
398
|
} catch (e) { return asError(e); }
|
|
368
399
|
}
|
|
369
400
|
);
|
|
@@ -431,7 +462,8 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
431
462
|
server.tool(
|
|
432
463
|
"site_publish",
|
|
433
464
|
"SCHREIBEND – LIVE FÜR BESUCHER: Veröffentlicht den aktuellen Entwurf einer Website. NUR nach ausdrücklicher Bestätigung "
|
|
434
|
-
+ "des Nutzers im Chat aufrufen (vorher kurz zusammenfassen, was live geht, und die richtige Website prüfen).
|
|
465
|
+
+ "des Nutzers im Chat aufrufen (vorher kurz zusammenfassen, was live geht, und die richtige Website prüfen). Erst alle Änderungen "
|
|
466
|
+
+ "im Entwurf erledigen, dann EINMAL veröffentlichen – nicht nach jedem Schritt (Limit je Stunde, bei 429 retryAfter abwarten). Meldet die Antwort "
|
|
435
467
|
+ "nothing:true (keine erkannte Änderung), aber du willst trotzdem neu ausliefern, setze force:true.",
|
|
436
468
|
{ site: z.string().optional().describe(SITE_DESC),
|
|
437
469
|
force: z.boolean().default(false).describe("true = trotzdem veröffentlichen, auch ohne erkannte Änderung (wie der Menüpunkt Erneut veröffentlichen).") },
|
|
@@ -585,13 +617,21 @@ export function registerTools(server0, { z, cfg, mode = "local", remote = null }
|
|
|
585
617
|
{ site: z.string().optional().describe(SITE_DESC) },
|
|
586
618
|
async ({ site }) => { try { return asText(await apiFiles(cfg, { site })); } catch (e) { return asError(e); } }
|
|
587
619
|
);
|
|
588
|
-
server.
|
|
620
|
+
server.toolWithOutput(
|
|
589
621
|
"site_file_get",
|
|
590
|
-
"LESEND: Liefert den Quelltext einer Datei des Entwurfs (Text; Binär als Base64, max. 5 MB) samt draftRev
|
|
622
|
+
"LESEND: Liefert den Quelltext einer Datei des Entwurfs (Text; Binär als Base64, Datei max. 5 MB) samt draftRev – in Teilen von "
|
|
623
|
+
+ "höchstens length Bytes (Standard 60 KB). truncated:true + nextOffset → mit offset:nextOffset weiterlesen; beim ersten Teil einer "
|
|
624
|
+
+ "großen Datei kommt eine Gliederung (outline: Überschriften/Abschnitte/IDs mit line + offset), outline:true liefert NUR die "
|
|
625
|
+
+ "Gliederung – damit gezielt nachladen. offset/length/nextOffset sind Bytes (UTF-8), Teile enden nie mitten in einem Zeichen. "
|
|
626
|
+
+ "site_file_put ersetzt die GANZE Datei: nie mit einem Teil aufrufen – für gezielte Änderungen site_edit_text/site_edit_html. "
|
|
591
627
|
+ "Der Block overlay nennt Dashboard-Änderungen, die im Entwurf ÜBER diesem Quelltext liegen (sie gewinnen sichtbar) – "
|
|
592
628
|
+ "berücksichtige sie, bevor du Text änderst. Merke dir draftRev: du brauchst sie als baseRev für site_file_put. " + FILE_SMALL,
|
|
593
|
-
{ site: z.string().optional().describe(SITE_DESC), path: z.string().describe("Relativer Pfad, z. B. 'index.html' oder 'css/style.css' (aus site_files).")
|
|
594
|
-
|
|
629
|
+
{ site: z.string().optional().describe(SITE_DESC), path: z.string().describe("Relativer Pfad, z. B. 'index.html' oder 'css/style.css' (aus site_files)."),
|
|
630
|
+
offset: z.number().int().min(0).default(0).describe("Start in Bytes (aus nextOffset bzw. outline[].offset)."),
|
|
631
|
+
length: z.number().int().min(1).max(1048576).default(61440).describe("Höchstens so viele Bytes (Standard 60 KB, max. 1 MB)."),
|
|
632
|
+
outline: z.boolean().optional().describe("true = nur die Gliederung (ohne Inhalt) – für große Dateien zuerst.") },
|
|
633
|
+
FILE_GET_OUTPUT(z),
|
|
634
|
+
async ({ site, path, offset, length, outline }) => { try { return asText(await apiFileGet(cfg, { site, path, offset, length, outline })); } catch (e) { return asError(e); } }
|
|
595
635
|
);
|
|
596
636
|
server.tool(
|
|
597
637
|
"site_file_put",
|