strapi-plugin-mcp-chat 0.1.0 → 0.5.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 +91 -9
- package/admin/src/components/AdminOverlays.tsx +35 -1
- package/admin/src/components/ErrorBoundary.tsx +29 -0
- package/admin/src/components/FloatingChat.tsx +22 -0
- package/admin/src/components/LangSwitcher.tsx +24 -0
- package/admin/src/components/Onboarding.tsx +192 -0
- package/admin/src/components/PreviewPanel.tsx +22 -1
- package/admin/src/components/StackLogos.tsx +60 -0
- package/admin/src/i18n.ts +214 -0
- package/admin/src/index.tsx +39 -5
- package/admin/src/pages/HomePage.tsx +55 -24
- package/admin/src/pages/ProvisionPage.tsx +54 -59
- package/dist/server/index.js +358 -200
- package/package.json +1 -1
- package/server/src/content-tools.ts +42 -8
- package/server/src/controllers/chat.ts +2 -2
- package/server/src/controllers/frontend.ts +7 -2
- package/server/src/index.ts +6 -1
- package/server/src/mcp/define.ts +72 -0
- package/server/src/mcp/index.ts +19 -6
- package/server/src/mcp/tools/buscar-texto.ts +18 -24
- package/server/src/mcp/tools/criar-locale.ts +20 -26
- package/server/src/mcp/tools/editar-campo.ts +29 -35
- package/server/src/mcp/tools/habilitar-i18n.ts +23 -29
- package/server/src/mcp/tools/listar-locales.ts +17 -23
- package/server/src/mcp/tools/publicar.ts +21 -27
- package/server/src/mcp/tools/traduzir.ts +26 -32
- package/server/src/mcp/types.ts +12 -9
- package/server/src/mcp-client.ts +15 -3
- package/server/src/provision/integrate.ts +15 -1
- package/server/src/provision/write.ts +92 -0
- package/server/src/services/chat.ts +56 -14
package/dist/server/index.js
CHANGED
|
@@ -36,12 +36,12 @@ module.exports = __toCommonJS(index_exports);
|
|
|
36
36
|
// server/src/controllers/chat.ts
|
|
37
37
|
var chat_default = ({ strapi }) => ({
|
|
38
38
|
async message(ctx) {
|
|
39
|
-
const { messages, image, lang, previewUrl } = ctx.request.body || {};
|
|
39
|
+
const { messages, image, lang, previewUrl, autoPublish } = ctx.request.body || {};
|
|
40
40
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
41
41
|
return ctx.badRequest('Campo "messages" (array) \xE9 obrigat\xF3rio.');
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
|
-
const result = await strapi.plugin("mcp-chat").service("chat").chat({ messages, image, lang, previewUrl });
|
|
44
|
+
const result = await strapi.plugin("mcp-chat").service("chat").chat({ messages, image, lang, previewUrl, autoPublish });
|
|
45
45
|
ctx.body = result;
|
|
46
46
|
} catch (e) {
|
|
47
47
|
strapi.log.error(`[mcp-chat] ${e?.message || e}`);
|
|
@@ -412,6 +412,68 @@ var import_node_path = __toESM(require("node:path"));
|
|
|
412
412
|
function isDev() {
|
|
413
413
|
return process.env.NODE_ENV === "development";
|
|
414
414
|
}
|
|
415
|
+
var KNOWN_ATTR_TYPES = /* @__PURE__ */ new Set([
|
|
416
|
+
"string",
|
|
417
|
+
"text",
|
|
418
|
+
"richtext",
|
|
419
|
+
"blocks",
|
|
420
|
+
"email",
|
|
421
|
+
"password",
|
|
422
|
+
"uid",
|
|
423
|
+
"enumeration",
|
|
424
|
+
"json",
|
|
425
|
+
"integer",
|
|
426
|
+
"biginteger",
|
|
427
|
+
"decimal",
|
|
428
|
+
"float",
|
|
429
|
+
"date",
|
|
430
|
+
"time",
|
|
431
|
+
"datetime",
|
|
432
|
+
"timestamp",
|
|
433
|
+
"boolean",
|
|
434
|
+
"media",
|
|
435
|
+
"relation",
|
|
436
|
+
"component",
|
|
437
|
+
"dynamiczone"
|
|
438
|
+
]);
|
|
439
|
+
function validateApi(api) {
|
|
440
|
+
const errs = [];
|
|
441
|
+
const rel = Object.keys(api.files).find((r) => r.endsWith("schema.json"));
|
|
442
|
+
if (!rel) {
|
|
443
|
+
errs.push(`${api.singularName}: schema.json ausente nos arquivos gerados`);
|
|
444
|
+
return errs;
|
|
445
|
+
}
|
|
446
|
+
let schema;
|
|
447
|
+
try {
|
|
448
|
+
schema = JSON.parse(api.files[rel]);
|
|
449
|
+
} catch (e) {
|
|
450
|
+
errs.push(`${api.singularName}: schema.json n\xE3o \xE9 JSON v\xE1lido (${e?.message ?? e})`);
|
|
451
|
+
return errs;
|
|
452
|
+
}
|
|
453
|
+
if (schema?.kind !== "collectionType" && schema?.kind !== "singleType") {
|
|
454
|
+
errs.push(`${api.singularName}: "kind" inv\xE1lido (${schema?.kind})`);
|
|
455
|
+
}
|
|
456
|
+
if (!schema?.info?.singularName || !schema?.info?.pluralName) {
|
|
457
|
+
errs.push(`${api.singularName}: info.singularName/pluralName obrigat\xF3rios`);
|
|
458
|
+
}
|
|
459
|
+
const attrs = schema?.attributes;
|
|
460
|
+
if (!attrs || typeof attrs !== "object") {
|
|
461
|
+
errs.push(`${api.singularName}: "attributes" ausente ou inv\xE1lido`);
|
|
462
|
+
} else {
|
|
463
|
+
for (const [name, a] of Object.entries(attrs)) {
|
|
464
|
+
if (!a || typeof a !== "object" || !a.type) {
|
|
465
|
+
errs.push(`${api.singularName}.${name}: atributo sem "type"`);
|
|
466
|
+
} else if (!KNOWN_ATTR_TYPES.has(a.type)) {
|
|
467
|
+
errs.push(`${api.singularName}.${name}: type desconhecido "${a.type}"`);
|
|
468
|
+
} else if (a.type === "relation" && !a.target) {
|
|
469
|
+
errs.push(`${api.singularName}.${name}: relation sem "target"`);
|
|
470
|
+
} else if (a.type === "component" && !a.component) {
|
|
471
|
+
errs.push(`${api.singularName}.${name}: component sem "component"`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return errs;
|
|
476
|
+
}
|
|
415
477
|
function writeApis(apis, opts) {
|
|
416
478
|
const result = {
|
|
417
479
|
ok: false,
|
|
@@ -431,6 +493,37 @@ function writeApis(apis, opts) {
|
|
|
431
493
|
result.errors.push(`apiRoot deve ser um caminho absoluto: ${opts.apiRoot}`);
|
|
432
494
|
return result;
|
|
433
495
|
}
|
|
496
|
+
const toWrite = apis.filter((api) => !import_node_fs.default.existsSync(import_node_path.default.join(opts.apiRoot, api.singularName)));
|
|
497
|
+
const knownSingulars = /* @__PURE__ */ new Set([
|
|
498
|
+
...apis.map((a) => a.singularName),
|
|
499
|
+
...import_node_fs.default.existsSync(opts.apiRoot) ? import_node_fs.default.readdirSync(opts.apiRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : []
|
|
500
|
+
]);
|
|
501
|
+
const validationErrors = [];
|
|
502
|
+
for (const api of toWrite) {
|
|
503
|
+
validationErrors.push(...validateApi(api));
|
|
504
|
+
const rel = Object.keys(api.files).find((r) => r.endsWith("schema.json"));
|
|
505
|
+
if (rel) {
|
|
506
|
+
try {
|
|
507
|
+
const attrs = JSON.parse(api.files[rel])?.attributes || {};
|
|
508
|
+
for (const [name, a] of Object.entries(attrs)) {
|
|
509
|
+
if (a?.type === "relation" && typeof a.target === "string") {
|
|
510
|
+
const tgt = a.target.split("::")[1]?.split(".")[0];
|
|
511
|
+
if (tgt && !knownSingulars.has(tgt)) {
|
|
512
|
+
validationErrors.push(`${api.singularName}.${name}: relation aponta para "${a.target}" inexistente`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (validationErrors.length) {
|
|
521
|
+
result.errors.push(
|
|
522
|
+
"Schema gerado inv\xE1lido \u2014 nada foi escrito (provis\xE3o abortada com seguran\xE7a):",
|
|
523
|
+
...validationErrors
|
|
524
|
+
);
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
434
527
|
for (const api of apis) {
|
|
435
528
|
const apiDir = import_node_path.default.join(opts.apiRoot, api.singularName);
|
|
436
529
|
if (import_node_fs.default.existsSync(apiDir)) {
|
|
@@ -1712,6 +1805,18 @@ export function __getLocale(): string {
|
|
|
1712
1805
|
} catch {}
|
|
1713
1806
|
return __defaultLocale;
|
|
1714
1807
|
}
|
|
1808
|
+
/** Status ativo: ?preview=1 ou ?status=draft na URL \u2192 rascunho (preview do
|
|
1809
|
+
* mcp-chat em modo Draft). Caso contr\xE1rio, publicado. S\xF3 no cliente; no SSR
|
|
1810
|
+
* cai para "published" (ver a nota de draft preview no README). */
|
|
1811
|
+
export function __getStatus(): "draft" | "published" {
|
|
1812
|
+
try {
|
|
1813
|
+
if (typeof window !== "undefined") {
|
|
1814
|
+
const sp = new URL(window.location.href).searchParams;
|
|
1815
|
+
if (sp.get("preview") === "1" || sp.get("status") === "draft") return "draft";
|
|
1816
|
+
}
|
|
1817
|
+
} catch {}
|
|
1818
|
+
return "published";
|
|
1819
|
+
}
|
|
1715
1820
|
|
|
1716
1821
|
${mapperCode}
|
|
1717
1822
|
|
|
@@ -1719,11 +1824,13 @@ const __store: Record<string, any> = {};
|
|
|
1719
1824
|
export function hydrate(d: any) { if (d) for (const k of Object.keys(d)) __store[k] = d[k]; }
|
|
1720
1825
|
|
|
1721
1826
|
export async function loadAllData(opts: { locale?: string; status?: "draft" | "published" } = {}) {
|
|
1827
|
+
// Sem status expl\xEDcito, herda do flag de preview na URL (?preview=1 \u2192 draft).
|
|
1828
|
+
const __opts = { locale: opts.locale, status: opts.status || __getStatus() };
|
|
1722
1829
|
const raw: Record<string, any> = {};
|
|
1723
1830
|
await Promise.all(
|
|
1724
1831
|
__cts.map(async (c: any) => {
|
|
1725
1832
|
try {
|
|
1726
|
-
raw[c.s] = c.k === "singleType" ? await fetchSingle(c.s,
|
|
1833
|
+
raw[c.s] = c.k === "singleType" ? await fetchSingle(c.s, __opts) : await fetchCollection(c.p, __opts);
|
|
1727
1834
|
} catch {
|
|
1728
1835
|
raw[c.s] = c.k === "singleType" ? null : [];
|
|
1729
1836
|
}
|
|
@@ -2074,8 +2181,11 @@ async function integrateFrontend(strapi, opts) {
|
|
|
2074
2181
|
// server/src/controllers/frontend.ts
|
|
2075
2182
|
var MANIFEST_NAME = "strapi.manifest.json";
|
|
2076
2183
|
function ensureInside2(base, target) {
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2184
|
+
const b = import_node_path7.default.resolve(base);
|
|
2185
|
+
const t = import_node_path7.default.resolve(target);
|
|
2186
|
+
if (t === b) return true;
|
|
2187
|
+
const rel = import_node_path7.default.relative(b, t);
|
|
2188
|
+
return !!rel && !rel.startsWith("..") && !import_node_path7.default.isAbsolute(rel);
|
|
2079
2189
|
}
|
|
2080
2190
|
function toKebab(input) {
|
|
2081
2191
|
const s = (input || "frontend").toLowerCase().replace(/\.zip$/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "");
|
|
@@ -2297,6 +2407,15 @@ var baseHeaders = {
|
|
|
2297
2407
|
"Content-Type": "application/json",
|
|
2298
2408
|
Accept: "application/json, text/event-stream"
|
|
2299
2409
|
};
|
|
2410
|
+
var fetchT = async (url, opts, timeoutMs = 8e3) => {
|
|
2411
|
+
const ctrl = new AbortController();
|
|
2412
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
2413
|
+
try {
|
|
2414
|
+
return await fetch(url, { ...opts, signal: ctrl.signal });
|
|
2415
|
+
} finally {
|
|
2416
|
+
clearTimeout(t);
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2300
2419
|
var parseSse = (text) => {
|
|
2301
2420
|
const dataLines = text.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).filter(Boolean);
|
|
2302
2421
|
const last = dataLines[dataLines.length - 1];
|
|
@@ -2320,7 +2439,7 @@ var McpClient = class {
|
|
|
2320
2439
|
return h;
|
|
2321
2440
|
}
|
|
2322
2441
|
async init() {
|
|
2323
|
-
const res = await
|
|
2442
|
+
const res = await fetchT(this.url, {
|
|
2324
2443
|
method: "POST",
|
|
2325
2444
|
headers: this.headers(),
|
|
2326
2445
|
body: JSON.stringify({
|
|
@@ -2336,14 +2455,14 @@ var McpClient = class {
|
|
|
2336
2455
|
});
|
|
2337
2456
|
this.sessionId = res.headers.get("mcp-session-id") || void 0;
|
|
2338
2457
|
await res.text();
|
|
2339
|
-
await
|
|
2458
|
+
await fetchT(this.url, {
|
|
2340
2459
|
method: "POST",
|
|
2341
2460
|
headers: this.headers(),
|
|
2342
2461
|
body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
|
|
2343
2462
|
});
|
|
2344
2463
|
}
|
|
2345
2464
|
async rpc(method, params, id) {
|
|
2346
|
-
const res = await
|
|
2465
|
+
const res = await fetchT(this.url, {
|
|
2347
2466
|
method: "POST",
|
|
2348
2467
|
headers: this.headers(),
|
|
2349
2468
|
body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
|
|
@@ -2458,16 +2577,19 @@ function createContentTools(strapi) {
|
|
|
2458
2577
|
(ct) => ct.uid?.startsWith("api::")
|
|
2459
2578
|
);
|
|
2460
2579
|
const attrsOf = (uid) => strapi.contentTypes?.[uid]?.attributes || strapi.components?.[uid]?.attributes || {};
|
|
2461
|
-
const
|
|
2580
|
+
const hasDraftAndPublish = (uid) => strapi.contentTypes?.[uid]?.options?.draftAndPublish === true;
|
|
2581
|
+
const MAX_DEPTH = 8;
|
|
2582
|
+
const buildPopulate = (attributes, seen = /* @__PURE__ */ new Set(), depth = 0) => {
|
|
2583
|
+
if (depth >= MAX_DEPTH) return {};
|
|
2462
2584
|
const populate = {};
|
|
2463
2585
|
for (const [name, a] of Object.entries(attributes)) {
|
|
2464
2586
|
if (a.type === "component" && a.component) {
|
|
2465
|
-
const sub = seen.has(a.component) ? {} : buildPopulate(attrsOf(a.component), new Set(seen).add(a.component));
|
|
2587
|
+
const sub = seen.has(a.component) ? {} : buildPopulate(attrsOf(a.component), new Set(seen).add(a.component), depth + 1);
|
|
2466
2588
|
populate[name] = Object.keys(sub).length ? { populate: sub } : true;
|
|
2467
2589
|
} else if (a.type === "dynamiczone") {
|
|
2468
2590
|
const on = {};
|
|
2469
2591
|
for (const comp of a.components || []) {
|
|
2470
|
-
const sub = seen.has(comp) ? {} : buildPopulate(attrsOf(comp), new Set(seen).add(comp));
|
|
2592
|
+
const sub = seen.has(comp) ? {} : buildPopulate(attrsOf(comp), new Set(seen).add(comp), depth + 1);
|
|
2471
2593
|
on[comp] = Object.keys(sub).length ? { populate: sub } : true;
|
|
2472
2594
|
}
|
|
2473
2595
|
populate[name] = { on };
|
|
@@ -2479,6 +2601,7 @@ function createContentTools(strapi) {
|
|
|
2479
2601
|
};
|
|
2480
2602
|
const walkFind = (node, attributes, basePath, needle, collect) => {
|
|
2481
2603
|
if (!node || typeof node !== "object") return;
|
|
2604
|
+
if (basePath.length > 24) return;
|
|
2482
2605
|
for (const [name, a] of Object.entries(attributes)) {
|
|
2483
2606
|
const v = node[name];
|
|
2484
2607
|
if (v == null) continue;
|
|
@@ -2503,11 +2626,13 @@ function createContentTools(strapi) {
|
|
|
2503
2626
|
}
|
|
2504
2627
|
}
|
|
2505
2628
|
};
|
|
2629
|
+
const MAX_MATCHES = 100;
|
|
2506
2630
|
const buscarTexto = async (termo) => {
|
|
2507
2631
|
const needle = String(termo || "").toLowerCase().trim();
|
|
2508
2632
|
if (!needle) return { erro: "termo vazio" };
|
|
2509
2633
|
const matches = [];
|
|
2510
2634
|
for (const ct of apiContentTypes()) {
|
|
2635
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
2511
2636
|
const attributes = ct.attributes || {};
|
|
2512
2637
|
const populate = buildPopulate(attributes);
|
|
2513
2638
|
let entries = [];
|
|
@@ -2517,6 +2642,7 @@ function createContentTools(strapi) {
|
|
|
2517
2642
|
} catch {
|
|
2518
2643
|
continue;
|
|
2519
2644
|
}
|
|
2645
|
+
const dp = hasDraftAndPublish(ct.uid);
|
|
2520
2646
|
for (const e of entries) {
|
|
2521
2647
|
walkFind(e, attributes, [], needle, (path9, campo, valor) => {
|
|
2522
2648
|
matches.push({
|
|
@@ -2525,12 +2651,20 @@ function createContentTools(strapi) {
|
|
|
2525
2651
|
documentId: e.documentId,
|
|
2526
2652
|
path: path9,
|
|
2527
2653
|
campo,
|
|
2528
|
-
valor_atual: valor.length > 300 ? valor.slice(0, 300) + "\u2026" : valor
|
|
2654
|
+
valor_atual: valor.length > 300 ? valor.slice(0, 300) + "\u2026" : valor,
|
|
2655
|
+
// draftAndPublish=false → não há rascunho; a edição já é o conteúdo
|
|
2656
|
+
// vivo e não há o que publicar (a IA deve avisar o usuário).
|
|
2657
|
+
draftAndPublish: dp
|
|
2529
2658
|
});
|
|
2530
2659
|
});
|
|
2531
2660
|
}
|
|
2532
2661
|
}
|
|
2533
|
-
|
|
2662
|
+
const truncated = matches.length > MAX_MATCHES;
|
|
2663
|
+
return {
|
|
2664
|
+
total: matches.length,
|
|
2665
|
+
resultados: matches.slice(0, MAX_MATCHES),
|
|
2666
|
+
...truncated ? { truncado: true, nota: `mostrando ${MAX_MATCHES} de ${matches.length} resultados; refine o termo` } : {}
|
|
2667
|
+
};
|
|
2534
2668
|
};
|
|
2535
2669
|
const sanitizeNode = (node, attributes) => {
|
|
2536
2670
|
if (node == null) return node;
|
|
@@ -2572,7 +2706,7 @@ function createContentTools(strapi) {
|
|
|
2572
2706
|
const loc = locale ? { locale } : {};
|
|
2573
2707
|
if (p.length === 1 && ad && TEXTUAL.includes(ad.type)) {
|
|
2574
2708
|
const updated2 = await strapi.documents(uid).update({ documentId, ...loc, data: { [topAttr]: novo_valor } });
|
|
2575
|
-
return { ok: true, uid, documentId: updated2?.documentId || documentId, path: p, novo_valor, locale };
|
|
2709
|
+
return { ok: true, uid, documentId: updated2?.documentId || documentId, path: p, novo_valor, locale, draftAndPublish: hasDraftAndPublish(uid) };
|
|
2576
2710
|
}
|
|
2577
2711
|
const populate = buildPopulate(attributes);
|
|
2578
2712
|
const entry = await strapi.documents(uid).findOne({ documentId, status: "draft", ...loc, populate });
|
|
@@ -2586,13 +2720,22 @@ function createContentTools(strapi) {
|
|
|
2586
2720
|
cur[p[p.length - 1]] = novo_valor;
|
|
2587
2721
|
const data = { [topAttr]: sanitizeAttr(entry[topAttr], ad) };
|
|
2588
2722
|
const updated = await strapi.documents(uid).update({ documentId, ...loc, data });
|
|
2589
|
-
return { ok: true, uid, documentId: updated?.documentId || documentId, path: p, novo_valor, locale };
|
|
2723
|
+
return { ok: true, uid, documentId: updated?.documentId || documentId, path: p, novo_valor, locale, draftAndPublish: hasDraftAndPublish(uid) };
|
|
2590
2724
|
};
|
|
2591
2725
|
const publicar = async ({
|
|
2592
2726
|
uid,
|
|
2593
2727
|
documentId,
|
|
2594
2728
|
locale
|
|
2595
2729
|
}) => {
|
|
2730
|
+
if (!hasDraftAndPublish(uid)) {
|
|
2731
|
+
return {
|
|
2732
|
+
ok: true,
|
|
2733
|
+
uid,
|
|
2734
|
+
documentId,
|
|
2735
|
+
status: "no-draft-publish",
|
|
2736
|
+
nota: "Esta content-type n\xE3o tem Draft & Publish; n\xE3o h\xE1 rascunho a publicar \u2014 a altera\xE7\xE3o j\xE1 est\xE1 no ar."
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2596
2739
|
await strapi.documents(uid).publish({ documentId, ...locale ? { locale } : {} });
|
|
2597
2740
|
return { ok: true, uid, documentId, status: "published", locale };
|
|
2598
2741
|
};
|
|
@@ -2722,7 +2865,7 @@ function createContentTools(strapi) {
|
|
|
2722
2865
|
if (!Object.keys(data).length) continue;
|
|
2723
2866
|
await strapi.documents(ct.uid).update({ documentId: e.documentId, locale: tgt, data });
|
|
2724
2867
|
documentos += 1;
|
|
2725
|
-
if (publish) {
|
|
2868
|
+
if (publish && hasDraftAndPublish(ct.uid)) {
|
|
2726
2869
|
await strapi.documents(ct.uid).publish({ documentId: e.documentId, locale: tgt });
|
|
2727
2870
|
publicados += 1;
|
|
2728
2871
|
}
|
|
@@ -2779,7 +2922,7 @@ var openAiToolSpecs = [
|
|
|
2779
2922
|
type: "function",
|
|
2780
2923
|
function: {
|
|
2781
2924
|
name: "publicar",
|
|
2782
|
-
description: 'Publica a entrada (torna a altera\xE7\xE3o vis\xEDvel no site p\xFAblico). Passe "locale" para publicar um idioma espec\xEDfico, ou "*" para todos.',
|
|
2925
|
+
description: 'Publica a entrada (torna a altera\xE7\xE3o vis\xEDvel no site p\xFAblico). Passe "locale" para publicar um idioma espec\xEDfico, ou "*" para todos. Se a content-type N\xC3O tiver Draft & Publish, n\xE3o h\xE1 o que publicar: retorna status "no-draft-publish" (a edi\xE7\xE3o j\xE1 est\xE1 no ar) \u2014 avise o usu\xE1rio em vez de tentar publicar de novo.',
|
|
2783
2926
|
parameters: {
|
|
2784
2927
|
type: "object",
|
|
2785
2928
|
properties: {
|
|
@@ -2955,9 +3098,9 @@ Ferramentas de conte\xFAdo:
|
|
|
2955
3098
|
Fluxo padr\xE3o quando o usu\xE1rio pede uma mudan\xE7a no site (por texto, voz ou mostrando a tela):
|
|
2956
3099
|
1. Use buscar_texto com um trecho distintivo do texto a alterar (sem r\xF3tulos de status).
|
|
2957
3100
|
2. Se houver mais de um resultado, escolha o mais prov\xE1vel pelo contexto (e diga qual escolheu); se amb\xEDguo de verdade, pergunte.
|
|
2958
|
-
3. editar_campo passando o mesmo uid, documentId e path do resultado, com o novo valor.
|
|
2959
|
-
4.
|
|
2960
|
-
5. Confirme em 1 frase o que foi alterado
|
|
3101
|
+
3. editar_campo passando o mesmo uid, documentId e path do resultado, com o novo valor. Isso salva como RASCUNHO (n\xE3o publica).
|
|
3102
|
+
4. Decida se publica ou n\xE3o conforme a POL\xCDTICA DE PUBLICA\xC7\xC3O indicada mais abaixo.
|
|
3103
|
+
5. Confirme em 1 frase o que foi alterado (content-type, campo, antes \u2192 depois) e se ficou como rascunho ou foi publicado.
|
|
2961
3104
|
|
|
2962
3105
|
Ferramentas de tradu\xE7\xE3o / idiomas (i18n):
|
|
2963
3106
|
- listar_locales(): mostra os idiomas configurados e o default.
|
|
@@ -2971,6 +3114,8 @@ Fluxo quando o usu\xE1rio pede tradu\xE7\xE3o (ex.: "quero o site todo em pt-BR"
|
|
|
2971
3114
|
3. Ap\xF3s o restart, ao repetir, traduzir funciona e localiza tudo.
|
|
2972
3115
|
4. Confirme em 1 frase: idiomas, quantos documentos e campos foram traduzidos/publicados (use o resumo retornado, n\xE3o despeje o conte\xFAdo).
|
|
2973
3116
|
|
|
3117
|
+
Draft & Publish: cada resultado de buscar_texto traz "draftAndPublish". Se for false, aquele tipo N\xC3O tem rascunho no Strapi \u2014 a edi\xE7\xE3o j\xE1 \xE9 o conte\xFAdo vivo e N\xC3O h\xE1 o que publicar; nesse caso, ao confirmar, avise que "esse conte\xFAdo n\xE3o tem rascunho, a altera\xE7\xE3o j\xE1 est\xE1 no ar" e N\xC3O chame publicar.
|
|
3118
|
+
|
|
2974
3119
|
Se o usu\xE1rio compartilhar a tela, uma imagem \xE9 anexada \xE0 \xFAltima mensagem \u2014 use-a para entender exatamente o que ele est\xE1 vendo e qual texto quer trocar.
|
|
2975
3120
|
|
|
2976
3121
|
Seja objetivo e acion\xE1vel. Responda SEMPRE em portugu\xEAs.`,
|
|
@@ -2984,9 +3129,9 @@ Content tools:
|
|
|
2984
3129
|
Default flow when the user asks for a site change (by text, voice or by showing their screen):
|
|
2985
3130
|
1. Use buscar_texto with a distinctive snippet of the text to change (no status labels).
|
|
2986
3131
|
2. If there is more than one result, pick the most likely from context (and say which); if truly ambiguous, ask.
|
|
2987
|
-
3. editar_campo passing the same uid, documentId and path from the result, with the new value.
|
|
2988
|
-
4.
|
|
2989
|
-
5. Confirm in one sentence what was changed
|
|
3132
|
+
3. editar_campo passing the same uid, documentId and path from the result, with the new value. This saves a DRAFT (does not publish).
|
|
3133
|
+
4. Decide whether to publish based on the PUBLISH POLICY stated below.
|
|
3134
|
+
5. Confirm in one sentence what was changed (content-type, field, before \u2192 after) and whether it stayed a draft or was published.
|
|
2990
3135
|
|
|
2991
3136
|
Translation / language tools (i18n):
|
|
2992
3137
|
- listar_locales(): shows configured languages and the default.
|
|
@@ -3000,12 +3145,14 @@ Flow when the user asks for translation (e.g. "I want the whole site in pt-BR"):
|
|
|
3000
3145
|
3. After the restart, repeating the request makes traduzir localize everything.
|
|
3001
3146
|
4. Confirm in one sentence: languages, how many documents and fields were translated/published (use the returned summary, don't dump the content).
|
|
3002
3147
|
|
|
3148
|
+
Draft & Publish: each buscar_texto result includes "draftAndPublish". If it is false, that type has NO draft in Strapi \u2014 the edit IS the live content and there is nothing to publish; in that case, when confirming, warn that "this content has no draft, the change is already live" and do NOT call publicar.
|
|
3149
|
+
|
|
3003
3150
|
If the user shares their screen, an image is attached to the last message \u2014 use it to understand exactly what they see and which text they want to change.
|
|
3004
3151
|
|
|
3005
3152
|
Be concise and actionable. ALWAYS answer in English.`
|
|
3006
3153
|
};
|
|
3007
3154
|
var chat_default2 = ({ strapi }) => ({
|
|
3008
|
-
async chat({ messages, image, lang = "pt", previewUrl }) {
|
|
3155
|
+
async chat({ messages, image, lang = "pt", previewUrl, autoPublish = false }) {
|
|
3009
3156
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
3010
3157
|
if (!apiKey) {
|
|
3011
3158
|
throw new Error(
|
|
@@ -3029,7 +3176,10 @@ var chat_default2 = ({ strapi }) => ({
|
|
|
3029
3176
|
if (process.env.PLAYWRIGHT_MCP_URL) {
|
|
3030
3177
|
try {
|
|
3031
3178
|
const client = new McpClient(process.env.PLAYWRIGHT_MCP_URL, "playwright");
|
|
3032
|
-
await
|
|
3179
|
+
await Promise.race([
|
|
3180
|
+
client.init(),
|
|
3181
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 4e3))
|
|
3182
|
+
]);
|
|
3033
3183
|
const list = await client.listTools();
|
|
3034
3184
|
for (const t of list) {
|
|
3035
3185
|
if (mcpByTool[t.name]) continue;
|
|
@@ -3062,7 +3212,19 @@ Voc\xEA tamb\xE9m controla um navegador real via ferramentas browser_* (Playwrig
|
|
|
3062
3212
|
|
|
3063
3213
|
You also control a real browser via browser_* tools (Playwright), pointed at the STRAPI ADMIN at ${adminBase} (the backend \u2014 this is where content actually changes, NOT the public site). You can navigate (browser_navigate), click, type, scroll, take your own screenshots (browser_take_screenshot) and inspect console/errors. Always prefer your direct tools (buscar_texto/editar_campo/publicar) to change content; use the browser to VERIFY in the admin that the edit/publish landed, or for admin UI flows the direct tools don't cover.`
|
|
3064
3214
|
};
|
|
3065
|
-
const
|
|
3215
|
+
const PUBLISH_POLICY = {
|
|
3216
|
+
pt: autoPublish ? `
|
|
3217
|
+
|
|
3218
|
+
POL\xCDTICA DE PUBLICA\xC7\xC3O: AUTO-PUBLICAR est\xE1 LIGADO. Depois de editar_campo, chame publicar para deixar a mudan\xE7a no ar. Em traduzir, use publish:true (default).` : `
|
|
3219
|
+
|
|
3220
|
+
POL\xCDTICA DE PUBLICA\xC7\xC3O: MODO RASCUNHO (auto-publicar DESLIGADO). N\xC3O chame publicar a menos que o usu\xE1rio pe\xE7a explicitamente ("publica", "p\xF5e no ar", "publish"). Depois de editar_campo, PARE e avise que a altera\xE7\xE3o foi salva como RASCUNHO para revis\xE3o (ela j\xE1 aparece no preview em modo rascunho, mas ainda n\xE3o no site p\xFAblico). Em traduzir, passe publish:false. Se o usu\xE1rio pedir para publicar, a\xED sim use publicar (ou traduzir com publish:true).`,
|
|
3221
|
+
en: autoPublish ? `
|
|
3222
|
+
|
|
3223
|
+
PUBLISH POLICY: AUTO-PUBLISH is ON. After editar_campo, call publicar to make the change live. For traduzir, use publish:true (default).` : `
|
|
3224
|
+
|
|
3225
|
+
PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the user explicitly asks ("publish", "make it live", "publica"). After editar_campo, STOP and tell them the change was saved as a DRAFT for review (it already shows in the preview when in draft mode, but not on the public site yet). For traduzir, pass publish:false. If the user asks to publish, then use publicar (or traduzir with publish:true).`
|
|
3226
|
+
};
|
|
3227
|
+
const systemContent = SYSTEM[language] + (hasBrowser ? BROWSER_NOTE[language] : "") + PUBLISH_POLICY[language];
|
|
3066
3228
|
const convo = [{ role: "system", content: systemContent }];
|
|
3067
3229
|
const pageNote = previewUrl ? language === "en" ? `
|
|
3068
3230
|
|
|
@@ -3089,11 +3251,22 @@ You also control a real browser via browser_* tools (Playwright), pointed at the
|
|
|
3089
3251
|
}
|
|
3090
3252
|
});
|
|
3091
3253
|
const callOpenAI = async (body) => {
|
|
3092
|
-
const
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3254
|
+
const ctrl = new AbortController();
|
|
3255
|
+
const timer = setTimeout(() => ctrl.abort(), 6e4);
|
|
3256
|
+
let res;
|
|
3257
|
+
try {
|
|
3258
|
+
res = await fetch(OPENAI_URL4, {
|
|
3259
|
+
method: "POST",
|
|
3260
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
3261
|
+
body: JSON.stringify(body),
|
|
3262
|
+
signal: ctrl.signal
|
|
3263
|
+
});
|
|
3264
|
+
} catch (e) {
|
|
3265
|
+
if (e?.name === "AbortError") throw new Error("OpenAI chat: tempo limite excedido (60s).");
|
|
3266
|
+
throw e;
|
|
3267
|
+
} finally {
|
|
3268
|
+
clearTimeout(timer);
|
|
3269
|
+
}
|
|
3097
3270
|
if (!res.ok) throw new Error(`OpenAI chat: ${await res.text()}`);
|
|
3098
3271
|
return res.json();
|
|
3099
3272
|
};
|
|
@@ -3132,6 +3305,11 @@ You also control a real browser via browser_* tools (Playwright), pointed at the
|
|
|
3132
3305
|
} catch (e) {
|
|
3133
3306
|
content = `Erro ao chamar a tool ${name}: ${e?.message || e}`;
|
|
3134
3307
|
}
|
|
3308
|
+
const MAX_TOOL_CHARS = 12e3;
|
|
3309
|
+
if (content.length > MAX_TOOL_CHARS) {
|
|
3310
|
+
content = content.slice(0, MAX_TOOL_CHARS) + `
|
|
3311
|
+
\u2026[resultado truncado: ${content.length} chars]`;
|
|
3312
|
+
}
|
|
3135
3313
|
convo.push({ role: "tool", tool_call_id: call.id, content });
|
|
3136
3314
|
}
|
|
3137
3315
|
continue;
|
|
@@ -3255,196 +3433,166 @@ var routes_default = {
|
|
|
3255
3433
|
|
|
3256
3434
|
// server/src/mcp/tools/buscar-texto.ts
|
|
3257
3435
|
var import_utils2 = require("@strapi/utils");
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3436
|
+
|
|
3437
|
+
// server/src/mcp/define.ts
|
|
3438
|
+
var defineTool = (def) => def;
|
|
3439
|
+
|
|
3440
|
+
// server/src/mcp/tools/buscar-texto.ts
|
|
3441
|
+
var buscar_texto_default = defineTool({
|
|
3442
|
+
name: "mcp_chat_buscar_texto",
|
|
3443
|
+
title: "Search text across content (deep)",
|
|
3444
|
+
description: 'Search a phrase across ALL content-types, single types, components and dynamic zones (recursive, substring). Returns matches with a `path` (e.g. ["dynamic_zone",2,"heading"]) to pass to mcp_chat_editar_campo.',
|
|
3445
|
+
resolveInputSchema: () => import_utils2.z.object({ termo: import_utils2.z.string() }),
|
|
3446
|
+
resolveOutputSchema: () => import_utils2.z.object({
|
|
3447
|
+
total: import_utils2.z.number().optional(),
|
|
3448
|
+
resultados: import_utils2.z.array(import_utils2.z.any()).optional(),
|
|
3449
|
+
erro: import_utils2.z.string().optional()
|
|
3450
|
+
}),
|
|
3451
|
+
auth: { policies: [{ action: "plugin::content-manager.explorer.read" }] },
|
|
3452
|
+
createHandler: (strapi) => async ({ args }) => {
|
|
3453
|
+
const r = await createContentTools(strapi).buscarTexto(args.termo);
|
|
3454
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3276
3455
|
}
|
|
3277
|
-
};
|
|
3278
|
-
var buscar_texto_default = tool;
|
|
3456
|
+
});
|
|
3279
3457
|
|
|
3280
3458
|
// server/src/mcp/tools/editar-campo.ts
|
|
3281
3459
|
var import_utils3 = require("@strapi/utils");
|
|
3282
|
-
var
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
const r = await createContentTools(strapi).editarCampo(args);
|
|
3307
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3308
|
-
}
|
|
3309
|
-
});
|
|
3460
|
+
var editar_campo_default = defineTool({
|
|
3461
|
+
name: "mcp_chat_editar_campo",
|
|
3462
|
+
title: "Edit a (possibly nested) field",
|
|
3463
|
+
description: "Edit a field value (saved as draft), including text nested in components/dynamic zones. Pass the `path` exactly as returned by mcp_chat_buscar_texto; for a simple top-level field you may use `campo`.",
|
|
3464
|
+
resolveInputSchema: () => import_utils3.z.object({
|
|
3465
|
+
uid: import_utils3.z.string(),
|
|
3466
|
+
documentId: import_utils3.z.string(),
|
|
3467
|
+
path: import_utils3.z.array(import_utils3.z.union([import_utils3.z.string(), import_utils3.z.number()])).optional(),
|
|
3468
|
+
campo: import_utils3.z.string().optional(),
|
|
3469
|
+
novo_valor: import_utils3.z.string(),
|
|
3470
|
+
locale: import_utils3.z.string().optional()
|
|
3471
|
+
}),
|
|
3472
|
+
resolveOutputSchema: () => import_utils3.z.object({
|
|
3473
|
+
ok: import_utils3.z.boolean().optional(),
|
|
3474
|
+
uid: import_utils3.z.string().optional(),
|
|
3475
|
+
documentId: import_utils3.z.string().optional(),
|
|
3476
|
+
path: import_utils3.z.array(import_utils3.z.any()).optional(),
|
|
3477
|
+
novo_valor: import_utils3.z.string().optional(),
|
|
3478
|
+
erro: import_utils3.z.string().optional()
|
|
3479
|
+
}),
|
|
3480
|
+
auth: { policies: [{ action: "plugin::content-manager.explorer.update" }] },
|
|
3481
|
+
createHandler: (strapi) => async ({ args }) => {
|
|
3482
|
+
const r = await createContentTools(strapi).editarCampo(args);
|
|
3483
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3310
3484
|
}
|
|
3311
|
-
};
|
|
3312
|
-
var editar_campo_default = tool2;
|
|
3485
|
+
});
|
|
3313
3486
|
|
|
3314
3487
|
// server/src/mcp/tools/publicar.ts
|
|
3315
3488
|
var import_utils4 = require("@strapi/utils");
|
|
3316
|
-
var
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
const r = await createContentTools(strapi).publicar(args);
|
|
3333
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3334
|
-
}
|
|
3335
|
-
});
|
|
3489
|
+
var publicar_default = defineTool({
|
|
3490
|
+
name: "mcp_chat_publicar",
|
|
3491
|
+
title: "Publish an entry",
|
|
3492
|
+
description: 'Publish an entry by uid + documentId, making the change visible on the site. Pass `locale` to publish a specific language, or "*" for all. For content-types without Draft & Publish there is nothing to publish (returns status "no-draft-publish") \u2014 the edit is already live.',
|
|
3493
|
+
resolveInputSchema: () => import_utils4.z.object({ uid: import_utils4.z.string(), documentId: import_utils4.z.string(), locale: import_utils4.z.string().optional() }),
|
|
3494
|
+
resolveOutputSchema: () => import_utils4.z.object({
|
|
3495
|
+
ok: import_utils4.z.boolean().optional(),
|
|
3496
|
+
uid: import_utils4.z.string().optional(),
|
|
3497
|
+
documentId: import_utils4.z.string().optional(),
|
|
3498
|
+
status: import_utils4.z.string().optional(),
|
|
3499
|
+
locale: import_utils4.z.string().optional()
|
|
3500
|
+
}),
|
|
3501
|
+
auth: { policies: [{ action: "plugin::content-manager.explorer.publish" }] },
|
|
3502
|
+
createHandler: (strapi) => async ({ args }) => {
|
|
3503
|
+
const r = await createContentTools(strapi).publicar(args);
|
|
3504
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3336
3505
|
}
|
|
3337
|
-
};
|
|
3338
|
-
var publicar_default = tool3;
|
|
3506
|
+
});
|
|
3339
3507
|
|
|
3340
3508
|
// server/src/mcp/tools/listar-locales.ts
|
|
3341
3509
|
var import_utils5 = require("@strapi/utils");
|
|
3342
|
-
var
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
const r = await createContentTools(strapi).listarLocales();
|
|
3357
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3358
|
-
}
|
|
3359
|
-
});
|
|
3510
|
+
var listar_locales_default = defineTool({
|
|
3511
|
+
name: "mcp_chat_listar_locales",
|
|
3512
|
+
title: "List i18n locales",
|
|
3513
|
+
description: "List the configured locales (languages) and which one is the default.",
|
|
3514
|
+
resolveInputSchema: () => import_utils5.z.object({}),
|
|
3515
|
+
resolveOutputSchema: () => import_utils5.z.object({
|
|
3516
|
+
default: import_utils5.z.string().optional(),
|
|
3517
|
+
locales: import_utils5.z.array(import_utils5.z.any()).optional(),
|
|
3518
|
+
erro: import_utils5.z.string().optional()
|
|
3519
|
+
}),
|
|
3520
|
+
auth: { policies: [{ action: "plugin::content-manager.explorer.read" }] },
|
|
3521
|
+
createHandler: (strapi) => async () => {
|
|
3522
|
+
const r = await createContentTools(strapi).listarLocales();
|
|
3523
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3360
3524
|
}
|
|
3361
|
-
};
|
|
3362
|
-
var listar_locales_default = tool4;
|
|
3525
|
+
});
|
|
3363
3526
|
|
|
3364
3527
|
// server/src/mcp/tools/criar-locale.ts
|
|
3365
3528
|
var import_utils6 = require("@strapi/utils");
|
|
3366
|
-
var
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
const r = await createContentTools(strapi).criarLocale(args);
|
|
3383
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3384
|
-
}
|
|
3385
|
-
});
|
|
3529
|
+
var criar_locale_default = defineTool({
|
|
3530
|
+
name: "mcp_chat_criar_locale",
|
|
3531
|
+
title: "Create an i18n locale",
|
|
3532
|
+
description: 'Create a locale (language). `code` must be a valid ISO code (e.g. "pt-BR", "es"). Idempotent: returns ok if it already exists.',
|
|
3533
|
+
resolveInputSchema: () => import_utils6.z.object({ code: import_utils6.z.string(), name: import_utils6.z.string().optional() }),
|
|
3534
|
+
resolveOutputSchema: () => import_utils6.z.object({
|
|
3535
|
+
ok: import_utils6.z.boolean().optional(),
|
|
3536
|
+
code: import_utils6.z.string().optional(),
|
|
3537
|
+
name: import_utils6.z.string().optional(),
|
|
3538
|
+
existed: import_utils6.z.boolean().optional(),
|
|
3539
|
+
erro: import_utils6.z.string().optional()
|
|
3540
|
+
}),
|
|
3541
|
+
auth: { policies: [{ action: "plugin::i18n.locale.create" }] },
|
|
3542
|
+
createHandler: (strapi) => async ({ args }) => {
|
|
3543
|
+
const r = await createContentTools(strapi).criarLocale(args);
|
|
3544
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3386
3545
|
}
|
|
3387
|
-
};
|
|
3388
|
-
var criar_locale_default = tool5;
|
|
3546
|
+
});
|
|
3389
3547
|
|
|
3390
3548
|
// server/src/mcp/tools/traduzir.ts
|
|
3391
3549
|
var import_utils7 = require("@strapi/utils");
|
|
3392
|
-
var
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
const r = await createContentTools(strapi).traduzir(args);
|
|
3414
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3415
|
-
}
|
|
3416
|
-
});
|
|
3550
|
+
var traduzir_default = defineTool({
|
|
3551
|
+
name: "mcp_chat_traduzir",
|
|
3552
|
+
title: "Translate localized content",
|
|
3553
|
+
description: "Translate localized content into one or more languages. Creates missing locales, translates field by field (long text is split and reassembled, never overflows) and publishes (only on content-types with Draft & Publish). Without uid/documentId, translates ALL localized content-types. Handles many locales at once.",
|
|
3554
|
+
resolveInputSchema: () => import_utils7.z.object({
|
|
3555
|
+
target_locales: import_utils7.z.array(import_utils7.z.string()).min(1),
|
|
3556
|
+
source_locale: import_utils7.z.string().optional(),
|
|
3557
|
+
uid: import_utils7.z.string().optional(),
|
|
3558
|
+
documentId: import_utils7.z.string().optional(),
|
|
3559
|
+
publish: import_utils7.z.boolean().optional()
|
|
3560
|
+
}),
|
|
3561
|
+
resolveOutputSchema: () => import_utils7.z.object({
|
|
3562
|
+
ok: import_utils7.z.boolean().optional(),
|
|
3563
|
+
source: import_utils7.z.string().optional(),
|
|
3564
|
+
por_locale: import_utils7.z.array(import_utils7.z.any()).optional(),
|
|
3565
|
+
erro: import_utils7.z.string().optional()
|
|
3566
|
+
}),
|
|
3567
|
+
auth: { policies: [{ action: "plugin::content-manager.explorer.update" }] },
|
|
3568
|
+
createHandler: (strapi) => async ({ args }) => {
|
|
3569
|
+
const r = await createContentTools(strapi).traduzir(args);
|
|
3570
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3417
3571
|
}
|
|
3418
|
-
};
|
|
3419
|
-
var traduzir_default = tool6;
|
|
3572
|
+
});
|
|
3420
3573
|
|
|
3421
3574
|
// server/src/mcp/tools/habilitar-i18n.ts
|
|
3422
3575
|
var import_utils8 = require("@strapi/utils");
|
|
3423
|
-
var
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
const r = enableI18n({ strapi, uid: args?.uid, campos: args?.campos });
|
|
3442
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3443
|
-
}
|
|
3444
|
-
});
|
|
3576
|
+
var habilitar_i18n_default = defineTool({
|
|
3577
|
+
name: "mcp_chat_habilitar_i18n",
|
|
3578
|
+
title: "Enable i18n on a content-type",
|
|
3579
|
+
description: 'Enable translation on content-types not localized yet: marks the content-type and its textual fields/components as localized. Required before translating content provisioned without i18n. Omit `uid` (or pass "*") to enable ALL content-types at once. Edits the schema (dev-only); Strapi restarts.',
|
|
3580
|
+
resolveInputSchema: () => import_utils8.z.object({ uid: import_utils8.z.string().optional(), campos: import_utils8.z.array(import_utils8.z.string()).optional() }),
|
|
3581
|
+
resolveOutputSchema: () => import_utils8.z.object({
|
|
3582
|
+
ok: import_utils8.z.boolean().optional(),
|
|
3583
|
+
uid: import_utils8.z.string().optional(),
|
|
3584
|
+
campos: import_utils8.z.array(import_utils8.z.string()).optional(),
|
|
3585
|
+
contentTypes: import_utils8.z.array(import_utils8.z.any()).optional(),
|
|
3586
|
+
total: import_utils8.z.number().optional(),
|
|
3587
|
+
restart: import_utils8.z.boolean().optional(),
|
|
3588
|
+
erro: import_utils8.z.string().optional()
|
|
3589
|
+
}),
|
|
3590
|
+
auth: { policies: [{ action: "plugin::content-type-builder.read" }] },
|
|
3591
|
+
createHandler: (strapi) => async ({ args }) => {
|
|
3592
|
+
const r = enableI18n({ strapi, uid: args.uid, campos: args.campos });
|
|
3593
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3445
3594
|
}
|
|
3446
|
-
};
|
|
3447
|
-
var habilitar_i18n_default = tool7;
|
|
3595
|
+
});
|
|
3448
3596
|
|
|
3449
3597
|
// server/src/mcp/tools/index.ts
|
|
3450
3598
|
var tools = [
|
|
@@ -3467,9 +3615,16 @@ var registerMcpTools = (strapi) => {
|
|
|
3467
3615
|
);
|
|
3468
3616
|
return;
|
|
3469
3617
|
}
|
|
3470
|
-
|
|
3471
|
-
for (const
|
|
3472
|
-
|
|
3618
|
+
let registered = 0;
|
|
3619
|
+
for (const tool of tools) {
|
|
3620
|
+
try {
|
|
3621
|
+
mcp.registerTool(tool);
|
|
3622
|
+
registered += 1;
|
|
3623
|
+
} catch (e) {
|
|
3624
|
+
strapi.log.warn(`[mcp-chat] tool "${tool?.name}" falhou ao registrar: ${e?.message ?? e}`);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
strapi.log.info(`[mcp-chat] ${registered}/${tools.length} tools registradas no MCP nativo (mcp_chat_*).`);
|
|
3473
3628
|
};
|
|
3474
3629
|
|
|
3475
3630
|
// server/src/register.ts
|
|
@@ -3498,7 +3653,10 @@ var index_default = {
|
|
|
3498
3653
|
}
|
|
3499
3654
|
},
|
|
3500
3655
|
destroy() {
|
|
3501
|
-
|
|
3656
|
+
try {
|
|
3657
|
+
stopFrontend();
|
|
3658
|
+
} catch {
|
|
3659
|
+
}
|
|
3502
3660
|
},
|
|
3503
3661
|
config: {
|
|
3504
3662
|
default: {},
|