@plitzi/sdk-server 0.32.9 → 0.32.10

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/core/services/mcp.js +8 -6
  3. package/dist/mcp.d.ts +2 -0
  4. package/dist/mcp.js +6 -0
  5. package/dist/modules/mcp/apps/example/view.tsx +70 -0
  6. package/dist/modules/mcp/apps/index.js +12 -0
  7. package/dist/modules/mcp/apps/render/index.js +16 -0
  8. package/dist/modules/mcp/apps/render/view.tsx +108 -0
  9. package/dist/modules/mcp/apps/shared/bundle.js +22 -0
  10. package/dist/modules/mcp/apps/shared/page.js +23 -0
  11. package/dist/modules/mcp/apps/shared/registerApp.js +27 -0
  12. package/dist/modules/mcp/apps/shared/resolve.js +5 -0
  13. package/dist/modules/mcp/apps/shared/shell.ejs +37 -0
  14. package/dist/modules/mcp/apps/shared/zodEnglishOnly.js +23 -0
  15. package/dist/modules/mcp/handler.js +3 -6
  16. package/dist/modules/mcp/resources/register.js +1 -1
  17. package/dist/modules/mcp/server.js +21 -12
  18. package/dist/modules/mcp/tools/apply/index.js +1 -1
  19. package/dist/modules/mcp/tools/render.js +3 -3
  20. package/dist/modules/ssr/registerExternalPlugins.js +1 -1
  21. package/dist/modules/ssr/template.js +1 -1
  22. package/dist/plugins/compile.js +1 -1
  23. package/dist/plugins/copy.js +1 -1
  24. package/dist/plugins/manager.js +1 -1
  25. package/dist/src/core/requestParser.d.ts +3 -0
  26. package/dist/src/index.d.ts +1 -1
  27. package/dist/src/mcp.d.ts +13 -0
  28. package/dist/src/modules/mcp/apps/apps.test.d.ts +1 -0
  29. package/dist/src/modules/mcp/apps/example/index.d.ts +8 -0
  30. package/dist/src/modules/mcp/apps/example/view.d.ts +1 -0
  31. package/dist/src/modules/mcp/apps/index.d.ts +8 -0
  32. package/dist/src/modules/mcp/apps/render/index.d.ts +3 -0
  33. package/dist/src/modules/mcp/apps/render/view.d.ts +1 -0
  34. package/dist/src/modules/mcp/apps/shared/bundle.d.ts +1 -0
  35. package/dist/src/modules/mcp/apps/shared/index.d.ts +2 -0
  36. package/dist/src/modules/mcp/apps/shared/page.d.ts +2 -0
  37. package/dist/src/modules/mcp/apps/shared/registerApp.d.ts +5 -0
  38. package/dist/src/modules/mcp/apps/shared/resolve.d.ts +1 -0
  39. package/dist/src/modules/mcp/apps/shared/zodEnglishOnly.d.ts +4 -0
  40. package/dist/src/modules/mcp/e2e/connector.test.d.ts +1 -0
  41. package/dist/src/modules/mcp/e2e/host.test.d.ts +1 -0
  42. package/dist/src/modules/mcp/e2e/index.d.ts +6 -0
  43. package/dist/src/modules/mcp/e2e/mcpEndpoint.d.ts +20 -0
  44. package/dist/src/modules/mcp/e2e/postMessageChannel.d.ts +22 -0
  45. package/dist/src/modules/mcp/e2e/renderingHost.d.ts +14 -0
  46. package/dist/src/modules/mcp/handler.d.ts +9 -5
  47. package/dist/src/modules/mcp/index.d.ts +1 -0
  48. package/dist/src/modules/mcp/resources/register.d.ts +1 -1
  49. package/dist/src/modules/mcp/resources/renderGuide.d.ts +1 -1
  50. package/dist/src/modules/mcp/server.d.ts +2 -10
  51. package/dist/src/modules/mcp/tools/apply/index.d.ts +3 -3
  52. package/dist/src/modules/mcp/tools/operations/index.d.ts +6 -6
  53. package/dist/src/modules/mcp/tools/operations/schema/operations.d.ts +1 -1
  54. package/dist/src/modules/mcp/tools/operations/schema/settings/patchSettings.d.ts +1 -1
  55. package/dist/src/modules/mcp/tools/operations/style/operations.d.ts +2 -2
  56. package/dist/src/modules/mcp/tools/operations/style/shared.d.ts +1 -1
  57. package/dist/src/modules/mcp/tools/operations/style/variables/deleteStyleVariable.d.ts +1 -1
  58. package/dist/src/modules/mcp/tools/operations/style/variables/upsertStyleVariable.d.ts +1 -1
  59. package/dist/src/modules/mcp/tools/preview.d.ts +3 -3
  60. package/dist/src/modules/mcp/tools/render.d.ts +3 -3
  61. package/dist/src/modules/mcp/tools/screenshot.d.ts +3 -3
  62. package/dist/src/modules/mcp/tools/shared/tool.d.ts +4 -7
  63. package/dist/src/modules/mcp/tools/validate.d.ts +3 -3
  64. package/dist/src/modules/mcp/types/appTypes.d.ts +13 -0
  65. package/dist/src/modules/mcp/types/index.d.ts +1 -0
  66. package/package.json +13 -4
  67. package/dist/modules/mcp/resources/renderApp.js +0 -182
  68. package/dist/server.d.ts +0 -1
  69. package/dist/server.js +0 -107
  70. package/dist/src/modules/mcp/resources/renderApp.d.ts +0 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @plitzi/sdk-server
2
2
 
3
+ ## 0.32.10
4
+
5
+ ### Patch Changes
6
+
7
+ - v0.32.10
8
+ - Updated dependencies
9
+ - @plitzi/plitzi-sdk@0.32.10
10
+ - @plitzi/sdk-schema@0.32.10
11
+ - @plitzi/sdk-shared@0.32.10
12
+
3
13
  ## 0.32.9
4
14
 
5
15
  ### Patch Changes
@@ -2,15 +2,17 @@ import { handleMcp } from "../../modules/mcp/handler.js";
2
2
  import { createHttpPreviewClient } from "../../modules/mcp/previewClient.js";
3
3
  import { createHttpScreenshotClient } from "../../modules/mcp/screenshotClient.js";
4
4
  //#region src/core/services/mcp.ts
5
- var mcpPathOf = (path) => path ?? "/";
5
+ var mcpPathOf = (config) => config.mcpAi?.path ?? config.mcp?.path ?? "/mcp";
6
6
  var serveMcp = (ctx) => {
7
- const { previewClient, screenshot } = ctx.config;
8
- const preview = previewClient ? createHttpPreviewClient(previewClient) : void 0;
9
- const screenshotClient = screenshot ? createHttpScreenshotClient(screenshot) : void 0;
10
- return handleMcp(ctx.raw, ctx.rawRes, ctx.req, ctx.config.adapters, preview, screenshotClient, ctx.config.mcpLogger, ctx.config.renderApp);
7
+ const { previewClient, screenshot, adapters, mcpLogger } = ctx.config;
8
+ return handleMcp(ctx.raw, ctx.rawRes, ctx.req, adapters, {
9
+ preview: previewClient ? createHttpPreviewClient(previewClient) : void 0,
10
+ screenshot: screenshot ? createHttpScreenshotClient(screenshot) : void 0,
11
+ logger: mcpLogger
12
+ });
11
13
  };
12
14
  var mcpStage = async (ctx) => {
13
- if (!ctx.req.path.startsWith(mcpPathOf(ctx.config.mcp?.path))) return false;
15
+ if (!ctx.req.path.startsWith(mcpPathOf(ctx.config))) return false;
14
16
  await serveMcp(ctx);
15
17
  return true;
16
18
  };
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './src/mcp.js'
2
+ export {}
package/dist/mcp.js ADDED
@@ -0,0 +1,6 @@
1
+ import { createMcpServer } from "./modules/mcp/server.js";
2
+ import { handleMcp, readMcpBody, serveMcp } from "./modules/mcp/handler.js";
3
+ import { createHttpPreviewClient } from "./modules/mcp/previewClient.js";
4
+ import { createHttpScreenshotClient } from "./modules/mcp/screenshotClient.js";
5
+ import { createMCPServer } from "./core/server/mcpServer.js";
6
+ export { createHttpPreviewClient, createHttpScreenshotClient, createMCPServer, createMcpServer, handleMcp, readMcpBody, serveMcp };
@@ -0,0 +1,70 @@
1
+ /* eslint-disable react-refresh/only-export-components -- one bundled entry: components cannot move out. */
2
+ import { useApp, useHostStyles } from '@modelcontextprotocol/ext-apps/react';
3
+ import { useState } from 'react';
4
+ import { createRoot } from 'react-dom/client';
5
+
6
+ import type { McpUiHostContext } from '@modelcontextprotocol/ext-apps';
7
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
8
+
9
+ /** The smallest MCP App view there is — copy this folder to start a new one. It shows the whole contract:
10
+ * connect, receive the tool call and its result, call a tool back, and inherit the host's look. Everything it
11
+ * imports is bundled into the page, so a view that stays this small stays a few KB. */
12
+
13
+ // Host CSS variables: the chat's own palette and fonts, applied by useHostStyles below.
14
+ const panel = {
15
+ padding: 16,
16
+ font: 'var(--font-sans, system-ui, sans-serif)',
17
+ color: 'var(--color-text-primary, #0f172a)',
18
+ background: 'var(--color-background-secondary, #f8fafc)',
19
+ borderRadius: 'var(--border-radius-md, 8px)'
20
+ } as const;
21
+
22
+ const textOf = (result: CallToolResult): string => result.content.find(entry => entry.type === 'text')?.text ?? '';
23
+
24
+ const ExampleApp = () => {
25
+ const [result, setResult] = useState<CallToolResult | null>(null);
26
+ const [context, setContext] = useState<McpUiHostContext | undefined>(undefined);
27
+
28
+ // useApp creates the App, runs onAppCreated so every handler is in place BEFORE the handshake, and connects.
29
+ const { app, error } = useApp({
30
+ appInfo: { name: 'Plitzi Example', version: '1.0.0' },
31
+ capabilities: {},
32
+ onAppCreated: instance => {
33
+ // The result of the tool that opened this view. `ontoolinput` gives its arguments instead.
34
+ instance.ontoolresult = setResult;
35
+ instance.onhostcontextchanged = params => setContext(previous => ({ ...previous, ...params }));
36
+ instance.onteardown = () => ({});
37
+ }
38
+ });
39
+
40
+ useHostStyles(app, context);
41
+
42
+ if (error) {
43
+ return <div style={panel}>Could not connect to the host: {error.message}</div>;
44
+ }
45
+
46
+ if (!app) {
47
+ return null;
48
+ }
49
+
50
+ const insets = context?.safeAreaInsets;
51
+ const message = result ? textOf(result) : 'Waiting for the tool result…';
52
+
53
+ return (
54
+ <div style={{ ...panel, margin: `${insets?.top ?? 0}px ${insets?.right ?? 0}px` }}>
55
+ <p>{message}</p>
56
+ {/* An app can call the server's tools on its own — each call is a round-trip, so plan for latency. */}
57
+ <button
58
+ type="button"
59
+ onClick={() => void app.callServerTool({ name: 'plitzi_search', arguments: {} }).then(setResult)}
60
+ >
61
+ Call plitzi_search
62
+ </button>
63
+ </div>
64
+ );
65
+ };
66
+
67
+ const root = document.getElementById('app');
68
+ if (root) {
69
+ createRoot(root).render(<ExampleApp />);
70
+ }
@@ -0,0 +1,12 @@
1
+ import { registerApp } from "./shared/registerApp.js";
2
+ import { RENDER_APP_URI, renderApp } from "./render/index.js";
3
+ //#region src/modules/mcp/apps/index.ts
4
+ /** Every MCP App this server serves. A new one is a folder beside `render/` (its definition + its view) and one
5
+ * line here — the tool that opens it points at its `uri` through `ui: { resourceUri }`. Start from `example/`,
6
+ * which is that same shape stripped to the minimum. */
7
+ var apps = [renderApp];
8
+ var registerApps = (server) => {
9
+ for (const app of apps) registerApp(server, app);
10
+ };
11
+ //#endregion
12
+ export { RENDER_APP_URI, apps, registerApps };
@@ -0,0 +1,16 @@
1
+ import { require } from "../shared/resolve.js";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region src/modules/mcp/apps/render/index.ts
5
+ var RENDER_APP_URI = "ui://plitzi/render.html";
6
+ var HERE = path.dirname(fileURLToPath(import.meta.url));
7
+ var renderApp = {
8
+ uri: RENDER_APP_URI,
9
+ name: "plitzi-render-app",
10
+ title: "Plitzi widget",
11
+ description: "Interactive view that renders a plitzi_render widget with the Plitzi SDK.",
12
+ entry: path.join(HERE, "view.tsx"),
13
+ styles: () => [path.join(path.dirname(require.resolve("@plitzi/plitzi-sdk")), "plitzi-sdk.css")]
14
+ };
15
+ //#endregion
16
+ export { RENDER_APP_URI, renderApp };
@@ -0,0 +1,108 @@
1
+ /* eslint-disable react-refresh/only-export-components -- one bundled entry: components cannot move out. */
2
+ import { useApp, useHostStyles } from '@modelcontextprotocol/ext-apps/react';
3
+ import PlitziSdk from '@plitzi/plitzi-sdk';
4
+ import { Component, useEffect, useState } from 'react';
5
+ import { createRoot } from 'react-dom/client';
6
+
7
+ import type { McpUiHostContext } from '@modelcontextprotocol/ext-apps';
8
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
9
+ import type { OfflineDataRaw } from '@plitzi/sdk-shared';
10
+ import type { ReactNode } from 'react';
11
+
12
+ /** The view for plitzi_render: the entry the server bundles (React, the MCP Apps runtime and the Plitzi SDK
13
+ * included) and inlines in the shared ui:// page shell — which is what lets it run in a sandbox that can fetch
14
+ * nothing. It mounts on `#app`, the shell's root. */
15
+
16
+ const panelStyle = { padding: 16, font: '13px/1.5 system-ui, sans-serif', color: '#b91c1c' } as const;
17
+
18
+ // Every failure ends up here: an iframe left on its placeholder would hide the reason in the sandbox's console.
19
+ const ErrorPanel = ({ title, details }: { title: string; details: string }) => (
20
+ <div style={panelStyle}>
21
+ <strong>{title}</strong>
22
+ <pre style={{ margin: '8px 0 0', whiteSpace: 'pre-wrap', color: '#7f1d1d', fontSize: 12 }}>{details}</pre>
23
+ </div>
24
+ );
25
+
26
+ class RenderBoundary extends Component<{ children: ReactNode }, { error?: Error }> {
27
+ state: { error?: Error } = {};
28
+
29
+ static getDerivedStateFromError(error: Error) {
30
+ return { error };
31
+ }
32
+
33
+ render() {
34
+ if (this.state.error) {
35
+ return <ErrorPanel title="Widget failed to render" details={this.state.error.message} />;
36
+ }
37
+
38
+ return this.props.children;
39
+ }
40
+ }
41
+
42
+ const RenderApp = () => {
43
+ const [result, setResult] = useState<CallToolResult | null>(null);
44
+ const [cancelled, setCancelled] = useState<string | undefined>(undefined);
45
+ const [context, setContext] = useState<McpUiHostContext | undefined>(undefined);
46
+
47
+ // useApp creates the App, runs onAppCreated so every handler is in place BEFORE the handshake, and connects.
48
+ const { app, error } = useApp({
49
+ appInfo: { name: 'Plitzi Widget', version: '1.0.0' },
50
+ capabilities: {},
51
+ onAppCreated: instance => {
52
+ instance.ontoolresult = setResult;
53
+ instance.ontoolcancelled = params => setCancelled(params.reason ?? 'The host cancelled the render.');
54
+ instance.onhostcontextchanged = params => setContext(previous => ({ ...previous, ...params }));
55
+ instance.onteardown = () => ({});
56
+ }
57
+ });
58
+
59
+ // The context the host sent in its initialize result predates any change notification.
60
+ useEffect(() => {
61
+ if (app) {
62
+ setContext(app.getHostContext());
63
+ }
64
+ }, [app]);
65
+
66
+ useHostStyles(app, context);
67
+
68
+ const failure = error?.message ?? cancelled;
69
+ if (failure) {
70
+ return <ErrorPanel title="Could not render the widget" details={failure} />;
71
+ }
72
+
73
+ // Still connecting, or connected and waiting for the result: the page's CSS placeholder covers both.
74
+ if (!result) {
75
+ return null;
76
+ }
77
+
78
+ // The payload rides in structuredContent, which the protocol types as unknown-valued; plitzi_render is the one
79
+ // guaranteeing the shape, and the SDK validates it again when it renders.
80
+ const offlineData = result.structuredContent?.offlineData as OfflineDataRaw | undefined;
81
+ if (!offlineData) {
82
+ // A failed render answers with its (already compact) reasons as the tool's JSON text summary.
83
+ const text = result.content.find(entry => entry.type === 'text');
84
+
85
+ return <ErrorPanel title="Render failed" details={text?.text ?? 'The tool returned no widget data.'} />;
86
+ }
87
+
88
+ const insets = context?.safeAreaInsets;
89
+
90
+ return (
91
+ <div
92
+ style={{
93
+ padding: `${insets?.top ?? 0}px ${insets?.right ?? 0}px ${insets?.bottom ?? 0}px ${insets?.left ?? 0}px`
94
+ }}
95
+ >
96
+ <RenderBoundary>
97
+ <PlitziSdk offlineData={offlineData} offlineMode environment="main" renderMode="raw" />
98
+ </RenderBoundary>
99
+ </div>
100
+ );
101
+ };
102
+
103
+ const root = document.getElementById('app');
104
+ if (root) {
105
+ // The SDK styles its tree from this container class down.
106
+ root.className = 'plitzi-root-container';
107
+ createRoot(root).render(<RenderApp />);
108
+ }
@@ -0,0 +1,22 @@
1
+ import { require } from "./resolve.js";
2
+ import { zodEnglishOnly } from "./zodEnglishOnly.js";
3
+ import { build } from "esbuild";
4
+ //#region src/modules/mcp/apps/shared/bundle.ts
5
+ var bundle = async (entry) => {
6
+ return (await build({
7
+ entryPoints: [entry],
8
+ bundle: true,
9
+ write: false,
10
+ format: "iife",
11
+ platform: "browser",
12
+ target: "es2022",
13
+ jsx: "automatic",
14
+ minify: true,
15
+ alias: { "@plitzi/plitzi-sdk": require.resolve("@plitzi/plitzi-sdk") },
16
+ define: { "process.env.NODE_ENV": "\"production\"" },
17
+ plugins: [zodEnglishOnly],
18
+ logLevel: "silent"
19
+ })).outputFiles[0].text;
20
+ };
21
+ //#endregion
22
+ export { bundle };
@@ -0,0 +1,23 @@
1
+ import { bundle } from "./bundle.js";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { readFileSync } from "node:fs";
5
+ import ejs from "ejs";
6
+ //#region src/modules/mcp/apps/shared/page.ts
7
+ var SHELL = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "shell.ejs");
8
+ var template = () => ejs.compile(readFileSync(SHELL, "utf-8"), { filename: SHELL });
9
+ var pages = /* @__PURE__ */ new Map();
10
+ var page = (app) => {
11
+ let html = pages.get(app.uri);
12
+ if (!html) {
13
+ html = bundle(app.entry).then((script) => template()({
14
+ title: app.title,
15
+ app: script,
16
+ css: (app.styles?.() ?? []).map((file) => readFileSync(file, "utf-8")).join("\n")
17
+ }));
18
+ pages.set(app.uri, html);
19
+ }
20
+ return html;
21
+ };
22
+ //#endregion
23
+ export { page };
@@ -0,0 +1,27 @@
1
+ import { page } from "./page.js";
2
+ import { RESOURCE_MIME_TYPE, registerAppResource } from "@modelcontextprotocol/ext-apps/server";
3
+ //#region src/modules/mcp/apps/shared/registerApp.ts
4
+ var DEFAULT_CSP = {
5
+ resourceDomains: [
6
+ "*",
7
+ "data:",
8
+ "blob:"
9
+ ],
10
+ connectDomains: ["*"]
11
+ };
12
+ /** Serves the app as a self-contained page: no import map, no asset mounts, no cross-origin fetches, so the
13
+ * strictest host sandbox runs it and no deployment has to serve anything extra. */
14
+ var registerApp = (server, app) => {
15
+ const meta = { ui: { csp: app.csp ?? DEFAULT_CSP } };
16
+ registerAppResource(server, app.name, app.uri, {
17
+ description: app.description,
18
+ _meta: meta
19
+ }, async () => ({ contents: [{
20
+ uri: app.uri,
21
+ mimeType: RESOURCE_MIME_TYPE,
22
+ text: await page(app),
23
+ _meta: meta
24
+ }] }));
25
+ };
26
+ //#endregion
27
+ export { registerApp };
@@ -0,0 +1,5 @@
1
+ import { createRequire } from "node:module";
2
+ //#region src/modules/mcp/apps/shared/resolve.ts
3
+ var require = createRequire(import.meta.url);
4
+ //#endregion
5
+ export { require };
@@ -0,0 +1,37 @@
1
+ <%#
2
+ The page shell every MCP App shares. The host renders it in a sandboxed iframe on ITS OWN origin: the app and
3
+ its styles are inlined, so the page fetches NOTHING and runs under a deny-by-default sandbox CSP.
4
+ -%>
5
+ <!doctype html>
6
+ <html lang="en">
7
+ <head>
8
+ <meta charset="utf-8" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
10
+ <title><%= title %></title>
11
+
12
+ <style>
13
+ html,
14
+ body {
15
+ margin: 0;
16
+ }
17
+ /* The app renders nothing until the host pushes the tool result in. */
18
+ #app:empty::after {
19
+ content: 'Rendering…';
20
+ display: block;
21
+ padding: 16px;
22
+ font: 14px system-ui, sans-serif;
23
+ color: #64748b;
24
+ }
25
+ </style>
26
+ <style>
27
+ <%- css %>
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <div id="app"></div>
32
+
33
+ <script>
34
+ <%- app %>
35
+ </script>
36
+ </body>
37
+ </html>
@@ -0,0 +1,23 @@
1
+ //#region src/modules/mcp/apps/shared/zodEnglishOnly.ts
2
+ var NAMESPACE = "zod-locales-stub";
3
+ var ZOD_PACKAGE = /node_modules[/\\]zod[/\\]/u;
4
+ /** Drops zod's ~40 translations (194 KB): they are re-exported as a namespace, which no bundler can tree-shake.
5
+ * English survives because zod imports `locales/en.js` directly, not through this index. */
6
+ var zodEnglishOnly = {
7
+ name: "zod-english-only",
8
+ setup(builder) {
9
+ builder.onResolve({ filter: /locales[/\\]index\.js$/ }, (args) => ZOD_PACKAGE.test(args.importer) ? {
10
+ path: args.path,
11
+ namespace: NAMESPACE
12
+ } : void 0);
13
+ builder.onLoad({
14
+ filter: /.*/,
15
+ namespace: NAMESPACE
16
+ }, () => ({
17
+ contents: "export {};",
18
+ loader: "js"
19
+ }));
20
+ }
21
+ };
22
+ //#endregion
23
+ export { zodEnglishOnly };
@@ -1,5 +1,5 @@
1
1
  import { createMcpServer } from "./server.js";
2
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp";
2
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
3
  //#region src/modules/mcp/handler.ts
4
4
  var readMcpBody = (req) => new Promise((resolve, reject) => {
5
5
  let raw = "";
@@ -64,13 +64,10 @@ var serveMcp = async (raw, res, server) => {
64
64
  await transport.close();
65
65
  }
66
66
  };
67
- var handleMcp = (raw, res, req, adapters, preview, screenshot, logger, renderApp) => serveMcp(raw, res, createMcpServer({
67
+ var handleMcp = (raw, res, req, adapters, options = {}) => serveMcp(raw, res, createMcpServer({
68
68
  adapters,
69
69
  getSpaceId: () => adapters.getSpaceId?.(req) ?? Promise.resolve(void 0),
70
- preview,
71
- screenshot,
72
- logger,
73
- renderApp
70
+ ...options
74
71
  }));
75
72
  //#endregion
76
73
  export { createMcpServer, handleMcp, readMcpBody, serveMcp };
@@ -4,7 +4,7 @@ import { resourceErrorMessage } from "./canonical.js";
4
4
  import { envelope, jsonContents } from "./envelope.js";
5
5
  import { readResource } from "./router.js";
6
6
  import { registerRenderResources } from "./renderGuide.js";
7
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp";
7
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  //#region src/modules/mcp/resources/register.ts
9
9
  /** Register every resource on the MCP server: fixed listings plus templated per-item reads. The space is
10
10
  * loaded lazily via getSpace, so listing resources never touches the store — only reading one does. */
@@ -1,18 +1,19 @@
1
+ import { registerApps } from "./apps/index.js";
1
2
  import { emptySpace, emptySpaceMessage, unauthorizedSpaceMessage } from "./helpers/space.js";
2
3
  import { serverInstructions } from "./helpers/guide.js";
3
4
  import { createMcpLog } from "./helpers/log.js";
4
5
  import { registerResources } from "./resources/register.js";
5
- import { registerRenderApp } from "./resources/renderApp.js";
6
6
  import { tools } from "./tools/index.js";
7
7
  import { isCallToolResult } from "../ai/toolkit.js";
8
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";
8
+ import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
10
  //#region src/modules/mcp/server.ts
10
11
  var MCP_ENV = "main";
11
12
  var asText = (data) => ({ content: [{
12
13
  type: "text",
13
14
  text: JSON.stringify(data)
14
15
  }] });
15
- var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger, renderApp }) => {
16
+ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger }) => {
16
17
  const log = createMcpLog(logger);
17
18
  let spaceIdPromise;
18
19
  const requireSpaceId = () => spaceIdPromise ??= getSpaceId().then((id) => {
@@ -42,10 +43,10 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger, rend
42
43
  const getSpace = () => spacePromise ??= loadSpace();
43
44
  const server = new McpServer({
44
45
  name: "plitzi-mcp",
45
- version: "0.32.9"
46
+ version: "0.32.10"
46
47
  }, { instructions: serverInstructions });
47
48
  registerResources(server, getSpace, MCP_ENV, log);
48
- if (renderApp) registerRenderApp(server, renderApp.sdkBase, renderApp.devMode ?? false);
49
+ registerApps(server);
49
50
  const toolContext = async () => ({
50
51
  space: await getSpace(),
51
52
  env: MCP_ENV,
@@ -63,12 +64,7 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger, rend
63
64
  });
64
65
  for (const tool of tools) {
65
66
  if (tool.requires === "screenshot" && !screenshot) continue;
66
- server.registerTool(tool.name, {
67
- title: tool.title,
68
- description: tool.description,
69
- inputSchema: tool.inputShape,
70
- ...tool.ui && renderApp ? { _meta: { ui: tool.ui } } : {}
71
- }, async (args) => {
67
+ const run = async (args) => {
72
68
  const start = performance.now();
73
69
  try {
74
70
  const result = await tool.execute(args, tool.spaceless ? spacelessContext() : await toolContext());
@@ -78,7 +74,20 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger, rend
78
74
  log.toolCall(tool.name, args, performance.now() - start, error);
79
75
  throw error;
80
76
  }
81
- });
77
+ };
78
+ const config = {
79
+ title: tool.title,
80
+ description: tool.description,
81
+ inputSchema: tool.inputShape
82
+ };
83
+ if (tool.ui) {
84
+ registerAppTool(server, tool.name, {
85
+ ...config,
86
+ _meta: { ui: tool.ui }
87
+ }, run);
88
+ continue;
89
+ }
90
+ server.registerTool(tool.name, config, run);
82
91
  }
83
92
  return server;
84
93
  };
@@ -6,7 +6,7 @@ import { defineTool } from "../shared/tool.js";
6
6
  import { validateOperations } from "../shared/validator/index.js";
7
7
  import { auditResources } from "../shared/validator/audit.js";
8
8
  import { z } from "zod";
9
- import { validateSchema } from "@plitzi/sdk-schema";
9
+ import { validateSchema } from "@plitzi/sdk-schema/helpers/schemaValidator";
10
10
  //#region src/modules/mcp/tools/apply/index.ts
11
11
  var applyShape = {
12
12
  environment,
@@ -1,12 +1,12 @@
1
+ import { RENDER_APP_URI } from "../apps/render/index.js";
1
2
  import { emptySpace } from "../helpers/space.js";
2
- import { RENDER_APP_URI } from "../resources/renderApp.js";
3
3
  import { operations } from "./operations/index.js";
4
4
  import { applyOperations } from "./apply/dispatch.js";
5
5
  import { defineTool } from "./shared/tool.js";
6
6
  import { validateOperations } from "./shared/validator/index.js";
7
7
  import { auditResources } from "./shared/validator/audit.js";
8
- import { validateSchema } from "@plitzi/sdk-schema";
9
- import { generateCache } from "@plitzi/sdk-style";
8
+ import { validateSchema } from "@plitzi/sdk-schema/helpers/schemaValidator";
9
+ import { generateCache } from "@plitzi/sdk-style/StyleHelper";
10
10
  //#region src/modules/mcp/tools/render.ts
11
11
  var HOST_PAGE_REF = "render";
12
12
  var seedSpace = () => {
@@ -1,6 +1,6 @@
1
+ import path from "node:path";
1
2
  import crypto from "node:crypto";
2
3
  import fs from "node:fs/promises";
3
- import path from "node:path";
4
4
  //#region src/modules/ssr/registerExternalPlugins.ts
5
5
  var MANIFEST_TTL_MS = 600 * 1e3;
6
6
  var manifestCache = /* @__PURE__ */ new Map();
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
- import fs from "node:fs";
3
2
  import { fileURLToPath } from "node:url";
3
+ import fs from "node:fs";
4
4
  import ejs from "ejs";
5
5
  //#region src/modules/ssr/template.ts
6
6
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -1,6 +1,6 @@
1
- import fs from "node:fs/promises";
2
1
  import path from "node:path";
3
2
  import esbuild from "esbuild";
3
+ import fs from "node:fs/promises";
4
4
  //#region src/plugins/compile.ts
5
5
  var EXTERNAL = [
6
6
  "react",
@@ -1,5 +1,5 @@
1
- import fs from "node:fs/promises";
2
1
  import path from "node:path";
2
+ import fs from "node:fs/promises";
3
3
  //#region src/plugins/copy.ts
4
4
  var copyPlugin = async (src, destDir, filename) => {
5
5
  const dest = path.join(destDir, filename);
@@ -1,8 +1,8 @@
1
1
  import { compilePlugin } from "./compile.js";
2
2
  import { copyPlugin } from "./copy.js";
3
3
  import { detectAction, isComponentSource } from "./detect.js";
4
- import fs from "node:fs/promises";
5
4
  import path from "node:path";
5
+ import fs from "node:fs/promises";
6
6
  //#region src/plugins/manager.ts
7
7
  var META_FILE = "meta.json";
8
8
  var DEFAULT_TTL_MS = 10080 * 60 * 1e3;
@@ -1,4 +1,7 @@
1
1
  import { SSRRequest } from '@plitzi/sdk-shared';
2
2
  import { IncomingMessage } from 'node:http';
3
3
  export declare const parseRequest: (raw: IncomingMessage) => SSRRequest;
4
+ /** The public origin the request was addressed to (proxy-aware via x-forwarded-proto, port included), or an empty
5
+ * string when the request carries no usable authority. */
6
+ export declare const requestOrigin: (req: SSRRequest) => string;
4
7
  export declare const readRawBody: (raw: IncomingMessage) => Promise<string>;
@@ -8,7 +8,7 @@ export { createPreview, createMemoryDraftStore, PREVIEW_TOKEN_PARAM } from './mo
8
8
  export { apply, search, read, validate, applyShape, searchShape, readShape, validateShape, operation, tools } from './modules/mcp/tools';
9
9
  export type { JsonAdaptersConfig } from './adapters/jsonAdapters';
10
10
  export type { ResolvedServices } from './core/createServer';
11
- export type { McpServerContext, PreviewClient, PreviewRequestBody, PreviewResult, HttpPreviewClientConfig, ScreenshotClient, ScreenshotImage, ScreenshotResult, Viewport, HttpScreenshotClientConfig } from './modules/mcp';
11
+ export type { McpServerContext, McpRequestOptions, PreviewClient, PreviewRequestBody, PreviewResult, HttpPreviewClientConfig, ScreenshotClient, ScreenshotImage, ScreenshotResult, Viewport, HttpScreenshotClientConfig } from './modules/mcp';
12
12
  export type { ApplyInput, SearchInput, SearchResponse, SearchHit, ReadInput, ReadResponse, ReadHit, ValidateInput, WriteResponse, WriteElement, Persisters, Operation, ToolDef, ToolContext } from './modules/mcp/tools';
13
13
  export type { Space } from './modules/mcp/helpers';
14
14
  export type { Env } from './modules/mcp/types';
@@ -0,0 +1,13 @@
1
+ /** The MCP role's entry point: everything an MCP deployment wires, and nothing else.
2
+ *
3
+ * Import it as `@plitzi/sdk-server/mcp`. The package root is a barrel over every service (SSR, RSC, plugins, the
4
+ * AI engine…), so pulling `createMCPServer` from there makes the process load that whole graph — including the
5
+ * React element packages its types come from — where this entry loads only the MCP server. */
6
+ export { createMCPServer } from './core/server/mcpServer';
7
+ export { createMcpServer, handleMcp, serveMcp, readMcpBody } from './modules/mcp/handler';
8
+ export { createHttpPreviewClient } from './modules/mcp/previewClient';
9
+ export { createHttpScreenshotClient } from './modules/mcp/screenshotClient';
10
+ export type { McpRequestOptions } from './modules/mcp/handler';
11
+ export type { McpServerContext } from './modules/mcp/server';
12
+ export type { PreviewClient, ScreenshotClient } from './modules/mcp/types';
13
+ export type { HealthIdentity } from './core/health';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { McpApp } from '../../types';
2
+ /** The reference app: the smallest definition that works, kept as the template a new app is copied from.
3
+ *
4
+ * It is deliberately NOT in the `apps` list — nothing renders it, since no tool points at it — so it costs a
5
+ * real deployment nothing while the test suite still builds it, which is what keeps it from rotting. To turn it
6
+ * into a real app: add it to `apps` in ../index.ts and give a tool `ui: { resourceUri: EXAMPLE_APP_URI }`. */
7
+ export declare const EXAMPLE_APP_URI = "ui://plitzi/example.html";
8
+ export declare const exampleApp: McpApp;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { McpApp } from '../types';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ /** Every MCP App this server serves. A new one is a folder beside `render/` (its definition + its view) and one
4
+ * line here — the tool that opens it points at its `uri` through `ui: { resourceUri }`. Start from `example/`,
5
+ * which is that same shape stripped to the minimum. */
6
+ export declare const apps: McpApp[];
7
+ export declare const registerApps: (server: McpServer) => void;
8
+ export { RENDER_APP_URI } from './render';