strapi-plugin-mcp-chat 0.3.1 → 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 +31 -5
- package/admin/src/components/ErrorBoundary.tsx +29 -0
- package/admin/src/components/FloatingChat.tsx +8 -0
- package/admin/src/index.tsx +39 -5
- package/dist/server/index.js +298 -184
- package/package.json +1 -1
- package/server/src/content-tools.ts +16 -4
- 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/write.ts +92 -0
- package/server/src/services/chat.ts +28 -6
package/dist/server/index.js
CHANGED
|
@@ -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)) {
|
|
@@ -2088,8 +2181,11 @@ async function integrateFrontend(strapi, opts) {
|
|
|
2088
2181
|
// server/src/controllers/frontend.ts
|
|
2089
2182
|
var MANIFEST_NAME = "strapi.manifest.json";
|
|
2090
2183
|
function ensureInside2(base, target) {
|
|
2091
|
-
const
|
|
2092
|
-
|
|
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);
|
|
2093
2189
|
}
|
|
2094
2190
|
function toKebab(input) {
|
|
2095
2191
|
const s = (input || "frontend").toLowerCase().replace(/\.zip$/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "");
|
|
@@ -2311,6 +2407,15 @@ var baseHeaders = {
|
|
|
2311
2407
|
"Content-Type": "application/json",
|
|
2312
2408
|
Accept: "application/json, text/event-stream"
|
|
2313
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
|
+
};
|
|
2314
2419
|
var parseSse = (text) => {
|
|
2315
2420
|
const dataLines = text.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).filter(Boolean);
|
|
2316
2421
|
const last = dataLines[dataLines.length - 1];
|
|
@@ -2334,7 +2439,7 @@ var McpClient = class {
|
|
|
2334
2439
|
return h;
|
|
2335
2440
|
}
|
|
2336
2441
|
async init() {
|
|
2337
|
-
const res = await
|
|
2442
|
+
const res = await fetchT(this.url, {
|
|
2338
2443
|
method: "POST",
|
|
2339
2444
|
headers: this.headers(),
|
|
2340
2445
|
body: JSON.stringify({
|
|
@@ -2350,14 +2455,14 @@ var McpClient = class {
|
|
|
2350
2455
|
});
|
|
2351
2456
|
this.sessionId = res.headers.get("mcp-session-id") || void 0;
|
|
2352
2457
|
await res.text();
|
|
2353
|
-
await
|
|
2458
|
+
await fetchT(this.url, {
|
|
2354
2459
|
method: "POST",
|
|
2355
2460
|
headers: this.headers(),
|
|
2356
2461
|
body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
|
|
2357
2462
|
});
|
|
2358
2463
|
}
|
|
2359
2464
|
async rpc(method, params, id) {
|
|
2360
|
-
const res = await
|
|
2465
|
+
const res = await fetchT(this.url, {
|
|
2361
2466
|
method: "POST",
|
|
2362
2467
|
headers: this.headers(),
|
|
2363
2468
|
body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
|
|
@@ -2473,16 +2578,18 @@ function createContentTools(strapi) {
|
|
|
2473
2578
|
);
|
|
2474
2579
|
const attrsOf = (uid) => strapi.contentTypes?.[uid]?.attributes || strapi.components?.[uid]?.attributes || {};
|
|
2475
2580
|
const hasDraftAndPublish = (uid) => strapi.contentTypes?.[uid]?.options?.draftAndPublish === true;
|
|
2476
|
-
const
|
|
2581
|
+
const MAX_DEPTH = 8;
|
|
2582
|
+
const buildPopulate = (attributes, seen = /* @__PURE__ */ new Set(), depth = 0) => {
|
|
2583
|
+
if (depth >= MAX_DEPTH) return {};
|
|
2477
2584
|
const populate = {};
|
|
2478
2585
|
for (const [name, a] of Object.entries(attributes)) {
|
|
2479
2586
|
if (a.type === "component" && a.component) {
|
|
2480
|
-
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);
|
|
2481
2588
|
populate[name] = Object.keys(sub).length ? { populate: sub } : true;
|
|
2482
2589
|
} else if (a.type === "dynamiczone") {
|
|
2483
2590
|
const on = {};
|
|
2484
2591
|
for (const comp of a.components || []) {
|
|
2485
|
-
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);
|
|
2486
2593
|
on[comp] = Object.keys(sub).length ? { populate: sub } : true;
|
|
2487
2594
|
}
|
|
2488
2595
|
populate[name] = { on };
|
|
@@ -2494,6 +2601,7 @@ function createContentTools(strapi) {
|
|
|
2494
2601
|
};
|
|
2495
2602
|
const walkFind = (node, attributes, basePath, needle, collect) => {
|
|
2496
2603
|
if (!node || typeof node !== "object") return;
|
|
2604
|
+
if (basePath.length > 24) return;
|
|
2497
2605
|
for (const [name, a] of Object.entries(attributes)) {
|
|
2498
2606
|
const v = node[name];
|
|
2499
2607
|
if (v == null) continue;
|
|
@@ -2518,11 +2626,13 @@ function createContentTools(strapi) {
|
|
|
2518
2626
|
}
|
|
2519
2627
|
}
|
|
2520
2628
|
};
|
|
2629
|
+
const MAX_MATCHES = 100;
|
|
2521
2630
|
const buscarTexto = async (termo) => {
|
|
2522
2631
|
const needle = String(termo || "").toLowerCase().trim();
|
|
2523
2632
|
if (!needle) return { erro: "termo vazio" };
|
|
2524
2633
|
const matches = [];
|
|
2525
2634
|
for (const ct of apiContentTypes()) {
|
|
2635
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
2526
2636
|
const attributes = ct.attributes || {};
|
|
2527
2637
|
const populate = buildPopulate(attributes);
|
|
2528
2638
|
let entries = [];
|
|
@@ -2549,7 +2659,12 @@ function createContentTools(strapi) {
|
|
|
2549
2659
|
});
|
|
2550
2660
|
}
|
|
2551
2661
|
}
|
|
2552
|
-
|
|
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
|
+
};
|
|
2553
2668
|
};
|
|
2554
2669
|
const sanitizeNode = (node, attributes) => {
|
|
2555
2670
|
if (node == null) return node;
|
|
@@ -3061,7 +3176,10 @@ var chat_default2 = ({ strapi }) => ({
|
|
|
3061
3176
|
if (process.env.PLAYWRIGHT_MCP_URL) {
|
|
3062
3177
|
try {
|
|
3063
3178
|
const client = new McpClient(process.env.PLAYWRIGHT_MCP_URL, "playwright");
|
|
3064
|
-
await
|
|
3179
|
+
await Promise.race([
|
|
3180
|
+
client.init(),
|
|
3181
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 4e3))
|
|
3182
|
+
]);
|
|
3065
3183
|
const list = await client.listTools();
|
|
3066
3184
|
for (const t of list) {
|
|
3067
3185
|
if (mcpByTool[t.name]) continue;
|
|
@@ -3133,11 +3251,22 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
|
|
|
3133
3251
|
}
|
|
3134
3252
|
});
|
|
3135
3253
|
const callOpenAI = async (body) => {
|
|
3136
|
-
const
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
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
|
+
}
|
|
3141
3270
|
if (!res.ok) throw new Error(`OpenAI chat: ${await res.text()}`);
|
|
3142
3271
|
return res.json();
|
|
3143
3272
|
};
|
|
@@ -3176,6 +3305,11 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
|
|
|
3176
3305
|
} catch (e) {
|
|
3177
3306
|
content = `Erro ao chamar a tool ${name}: ${e?.message || e}`;
|
|
3178
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
|
+
}
|
|
3179
3313
|
convo.push({ role: "tool", tool_call_id: call.id, content });
|
|
3180
3314
|
}
|
|
3181
3315
|
continue;
|
|
@@ -3299,196 +3433,166 @@ var routes_default = {
|
|
|
3299
3433
|
|
|
3300
3434
|
// server/src/mcp/tools/buscar-texto.ts
|
|
3301
3435
|
var import_utils2 = require("@strapi/utils");
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
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 };
|
|
3320
3455
|
}
|
|
3321
|
-
};
|
|
3322
|
-
var buscar_texto_default = tool;
|
|
3456
|
+
});
|
|
3323
3457
|
|
|
3324
3458
|
// server/src/mcp/tools/editar-campo.ts
|
|
3325
3459
|
var import_utils3 = require("@strapi/utils");
|
|
3326
|
-
var
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
const r = await createContentTools(strapi).editarCampo(args);
|
|
3351
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3352
|
-
}
|
|
3353
|
-
});
|
|
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 };
|
|
3354
3484
|
}
|
|
3355
|
-
};
|
|
3356
|
-
var editar_campo_default = tool2;
|
|
3485
|
+
});
|
|
3357
3486
|
|
|
3358
3487
|
// server/src/mcp/tools/publicar.ts
|
|
3359
3488
|
var import_utils4 = require("@strapi/utils");
|
|
3360
|
-
var
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
const r = await createContentTools(strapi).publicar(args);
|
|
3377
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3378
|
-
}
|
|
3379
|
-
});
|
|
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 };
|
|
3380
3505
|
}
|
|
3381
|
-
};
|
|
3382
|
-
var publicar_default = tool3;
|
|
3506
|
+
});
|
|
3383
3507
|
|
|
3384
3508
|
// server/src/mcp/tools/listar-locales.ts
|
|
3385
3509
|
var import_utils5 = require("@strapi/utils");
|
|
3386
|
-
var
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
const r = await createContentTools(strapi).listarLocales();
|
|
3401
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3402
|
-
}
|
|
3403
|
-
});
|
|
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 };
|
|
3404
3524
|
}
|
|
3405
|
-
};
|
|
3406
|
-
var listar_locales_default = tool4;
|
|
3525
|
+
});
|
|
3407
3526
|
|
|
3408
3527
|
// server/src/mcp/tools/criar-locale.ts
|
|
3409
3528
|
var import_utils6 = require("@strapi/utils");
|
|
3410
|
-
var
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
const r = await createContentTools(strapi).criarLocale(args);
|
|
3427
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3428
|
-
}
|
|
3429
|
-
});
|
|
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 };
|
|
3430
3545
|
}
|
|
3431
|
-
};
|
|
3432
|
-
var criar_locale_default = tool5;
|
|
3546
|
+
});
|
|
3433
3547
|
|
|
3434
3548
|
// server/src/mcp/tools/traduzir.ts
|
|
3435
3549
|
var import_utils7 = require("@strapi/utils");
|
|
3436
|
-
var
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
const r = await createContentTools(strapi).traduzir(args);
|
|
3458
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3459
|
-
}
|
|
3460
|
-
});
|
|
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 };
|
|
3461
3571
|
}
|
|
3462
|
-
};
|
|
3463
|
-
var traduzir_default = tool6;
|
|
3572
|
+
});
|
|
3464
3573
|
|
|
3465
3574
|
// server/src/mcp/tools/habilitar-i18n.ts
|
|
3466
3575
|
var import_utils8 = require("@strapi/utils");
|
|
3467
|
-
var
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
const r = enableI18n({ strapi, uid: args?.uid, campos: args?.campos });
|
|
3486
|
-
return { content: [{ type: "text", text: JSON.stringify(r) }], structuredContent: r };
|
|
3487
|
-
}
|
|
3488
|
-
});
|
|
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 };
|
|
3489
3594
|
}
|
|
3490
|
-
};
|
|
3491
|
-
var habilitar_i18n_default = tool7;
|
|
3595
|
+
});
|
|
3492
3596
|
|
|
3493
3597
|
// server/src/mcp/tools/index.ts
|
|
3494
3598
|
var tools = [
|
|
@@ -3511,9 +3615,16 @@ var registerMcpTools = (strapi) => {
|
|
|
3511
3615
|
);
|
|
3512
3616
|
return;
|
|
3513
3617
|
}
|
|
3514
|
-
|
|
3515
|
-
for (const
|
|
3516
|
-
|
|
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_*).`);
|
|
3517
3628
|
};
|
|
3518
3629
|
|
|
3519
3630
|
// server/src/register.ts
|
|
@@ -3542,7 +3653,10 @@ var index_default = {
|
|
|
3542
3653
|
}
|
|
3543
3654
|
},
|
|
3544
3655
|
destroy() {
|
|
3545
|
-
|
|
3656
|
+
try {
|
|
3657
|
+
stopFrontend();
|
|
3658
|
+
} catch {
|
|
3659
|
+
}
|
|
3546
3660
|
},
|
|
3547
3661
|
config: {
|
|
3548
3662
|
default: {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-mcp-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AI chat inside the Strapi 5 admin that reads and edits your content (incl. components & dynamic zones) via MCP, with voice and a side-by-side live preview.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|