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 CHANGED
@@ -294,11 +294,16 @@ The plugin follows the documented Strapi 5 plugin APIs:
294
294
  (`register` / `bootstrap` / `destroy` / `config` / `controllers` / `services` / `routes`).
295
295
  Routes are declared with `type: 'admin'`, so the chat/STT/TTS endpoints require an
296
296
  authenticated admin session.
297
- - **MCP** — tools are registered with `strapi.ai.mcp.registerTool` during `register()`
298
- (the documented extension point), using `z` from `@strapi/utils` for the schemas and
299
- `auth.policies` (content-manager read/update/publish) for RBAC. They're organized in a
300
- modular `server/src/mcp/` (one file per tool in `tools/`, aggregated and looped) following
301
- the structure from [Paul Bratslavsky's MCP tool-extension example](https://github.com/PaulBratslavsky/strapi-mcp-demo-and-tool-extension).
297
+ - **MCP** — tools are **defined** as pure objects with a local typed `defineTool` helper
298
+ (`server/src/mcp/define.ts`) and **registered from an array** via
299
+ `strapi.ai.mcp.registerTool` during `register()`, using `z` from `@strapi/utils` for the
300
+ schemas and `auth.policies` (content-manager read/update/publish) for RBAC. The
301
+ `defineTool`/`defineResource`/`definePrompt` identity functions infer each handler's
302
+ `args` from its input schema (no `any`) and keep the definitions side-effect-free —
303
+ mirroring the direction of Strapi's [PR #26603](https://github.com/strapi/strapi/pull/26603)
304
+ (`ai.mcp.defineTool` + the `import { ai } from "@strapi/strapi"` namespace). When that API
305
+ ships stable, migrating is a one-line import swap; until then the plugin stays on the
306
+ released `registerTool` so it runs on any Strapi ≥ 5.47 (no experimental build required).
302
307
  - **Admin** — `register()` uses only documented APIs (`app.addMenuLink`, `app.registerPlugin`).
303
308
 
304
309
  One intentional deviation: the **global floating chat** is mounted via its own React root
@@ -316,6 +321,27 @@ single spot.
316
321
  token's permissions — scope the token to only what those clients should change.
317
322
  - The agent can edit and publish content — give the plugin only to trusted editors.
318
323
 
324
+ ## Reliability — never degrades or breaks the host Strapi
325
+
326
+ This plugin is built to be a good citizen: installing it must never slow down or take
327
+ down the host app. Concrete guarantees:
328
+
329
+ - **Can't crash boot.** `register()` degrades gracefully if the native MCP server / i18n /
330
+ OpenAI key are absent (logs a warning, disables the feature). MCP tools register inside a
331
+ per-tool `try/catch`, so a single bad tool can never abort the others or the boot.
332
+ `destroy()` is best-effort.
333
+ - **Can't blank the admin.** The global overlay (floating chat + preview) mounts inside a
334
+ React **error boundary** in its own root, after an SSR/double-mount guard; any render
335
+ error just hides the overlay, leaving the Strapi admin fully intact. Lingering preview
336
+ layout styles are reset on load, and screen/mic capture is stopped on unmount.
337
+ - **Provisioning is fail-safe.** Generated schemas are **validated before any file is
338
+ written** (kind/attributes/known types/relation targets) and writes are **all-or-nothing**
339
+ — a malformed manifest can never leave Strapi with a broken, unbootable schema. Generation
340
+ stays additive (never touches existing types), dev-only, with hardened zip-slip protection.
341
+ - **Won't become a bottleneck.** Every outbound call (OpenAI, MCP) has a **timeout** so a
342
+ slow upstream can't hold a request open; recursive content search has depth caps and a
343
+ result cap; tool outputs are size-capped before going back to the model.
344
+
319
345
  ## License
320
346
 
321
347
  [MIT](./LICENSE) © Raul Balestra
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Error boundary que ISOLA as sobreposições do plugin (chat + preview) do resto
3
+ * do admin do Strapi. Se algo dentro renderizar com erro, capturamos aqui e
4
+ * renderizamos `null` — o overlay some, mas o admin do Strapi continua intacto.
5
+ * Sem isto, um erro de render num root React próprio poderia quebrar a página.
6
+ */
7
+ import { Component, type ReactNode } from 'react';
8
+
9
+ type Props = { children: ReactNode };
10
+ type State = { failed: boolean };
11
+
12
+ export class ErrorBoundary extends Component<Props, State> {
13
+ state: State = { failed: false };
14
+
15
+ static getDerivedStateFromError(): State {
16
+ return { failed: true };
17
+ }
18
+
19
+ componentDidCatch(error: unknown) {
20
+ // Apenas loga; nunca propaga para o admin.
21
+ // eslint-disable-next-line no-console
22
+ console.error('[mcp-chat] overlay desativado após erro de render:', error);
23
+ }
24
+
25
+ render() {
26
+ if (this.state.failed) return null;
27
+ return this.props.children;
28
+ }
29
+ }
@@ -113,6 +113,14 @@ export const FloatingChat = ({ previewOn, previewUrl, onTogglePreview, onReply }
113
113
  if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
114
114
  }, [messages, loading]);
115
115
 
116
+ // ── Cleanup ao desmontar: encerra captura de tela / microfone (sem vazar) ──
117
+ useEffect(() => {
118
+ return () => {
119
+ try { streamRef.current?.getTracks().forEach((t) => t.stop()); } catch { /* noop */ }
120
+ try { recorderRef.current?.stop(); } catch { /* noop */ }
121
+ };
122
+ }, []);
123
+
116
124
  // ── Screenshare ───────────────────────────────────────────────────────────
117
125
  const startShare = async () => {
118
126
  setError(null);
@@ -1,6 +1,25 @@
1
1
  import { createRoot } from 'react-dom/client';
2
2
  import { PLUGIN_ID } from './pluginId';
3
3
  import { AdminOverlays } from './components/AdminOverlays';
4
+ import { ErrorBoundary } from './components/ErrorBoundary';
5
+
6
+ // Flag de módulo: trava extra contra duplo-mount (além do guard por id no DOM).
7
+ let mounted = false;
8
+
9
+ /** Limpa estilos inline que o PreviewPanel possa ter deixado no #strapi caso uma
10
+ * sessão anterior tenha sido encerrada de forma abrupta — garante que o admin
11
+ * nunca apareça encolhido/quebrado ao carregar. */
12
+ const resetStrapiRootStyles = () => {
13
+ try {
14
+ const root = document.getElementById('strapi') as HTMLElement | null;
15
+ if (!root) return;
16
+ for (const p of ['width', 'maxWidth', 'transform', 'overflow'] as const) {
17
+ root.style[p] = '';
18
+ }
19
+ } catch {
20
+ /* noop */
21
+ }
22
+ };
4
23
 
5
24
  const PluginIcon = () => (
6
25
  <svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -39,11 +58,26 @@ export default {
39
58
  // tocar na árvore do admin). É o único desvio das APIs documentadas e está
40
59
  // contido a este único ponto. `register()` acima usa só APIs oficiais
41
60
  // (addMenuLink + registerPlugin).
61
+ // Guarda de ambiente: só roda no browser (nunca em SSR/headless sem DOM).
62
+ if (typeof document === 'undefined' || typeof window === 'undefined') return;
42
63
  const ID = 'mcp-chat-fab-root';
43
- if (document.getElementById(ID)) return;
44
- const el = document.createElement('div');
45
- el.id = ID;
46
- document.body.appendChild(el);
47
- createRoot(el).render(<AdminOverlays />);
64
+ if (mounted || document.getElementById(ID)) return;
65
+ mounted = true;
66
+ try {
67
+ resetStrapiRootStyles();
68
+ const el = document.createElement('div');
69
+ el.id = ID;
70
+ document.body.appendChild(el);
71
+ // ErrorBoundary garante que um erro de render do overlay nunca derrube o admin.
72
+ createRoot(el).render(
73
+ <ErrorBoundary>
74
+ <AdminOverlays />
75
+ </ErrorBoundary>
76
+ );
77
+ } catch (e) {
78
+ // Em último caso, falhar em montar o overlay não pode quebrar o admin.
79
+ // eslint-disable-next-line no-console
80
+ console.error('[mcp-chat] falha ao montar overlays (admin segue normal):', e);
81
+ }
48
82
  },
49
83
  };