@plitzi/sdk-server 0.32.8 → 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.
- package/CHANGELOG.md +20 -0
- package/dist/core/services/mcp.js +8 -6
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +6 -0
- package/dist/modules/mcp/apps/example/view.tsx +70 -0
- package/dist/modules/mcp/apps/index.js +12 -0
- package/dist/modules/mcp/apps/render/index.js +16 -0
- package/dist/modules/mcp/apps/render/view.tsx +108 -0
- package/dist/modules/mcp/apps/shared/bundle.js +22 -0
- package/dist/modules/mcp/apps/shared/page.js +23 -0
- package/dist/modules/mcp/apps/shared/registerApp.js +27 -0
- package/dist/modules/mcp/apps/shared/resolve.js +5 -0
- package/dist/modules/mcp/apps/shared/shell.ejs +37 -0
- package/dist/modules/mcp/apps/shared/zodEnglishOnly.js +23 -0
- package/dist/modules/mcp/handler.js +3 -6
- package/dist/modules/mcp/resources/register.js +1 -1
- package/dist/modules/mcp/server.js +21 -12
- package/dist/modules/mcp/tools/apply/index.js +1 -1
- package/dist/modules/mcp/tools/render.js +3 -3
- package/dist/modules/mcp/tools/shared/validator/index.js +2 -1
- package/dist/modules/ssr/registerExternalPlugins.js +1 -1
- package/dist/modules/ssr/template.js +1 -1
- package/dist/plugins/compile.js +1 -1
- package/dist/plugins/copy.js +1 -1
- package/dist/plugins/manager.js +1 -1
- package/dist/src/core/requestParser.d.ts +3 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/mcp.d.ts +13 -0
- package/dist/src/modules/mcp/apps/apps.test.d.ts +1 -0
- package/dist/src/modules/mcp/apps/example/index.d.ts +8 -0
- package/dist/src/modules/mcp/apps/example/view.d.ts +1 -0
- package/dist/src/modules/mcp/apps/index.d.ts +8 -0
- package/dist/src/modules/mcp/apps/render/index.d.ts +3 -0
- package/dist/src/modules/mcp/apps/render/view.d.ts +1 -0
- package/dist/src/modules/mcp/apps/shared/bundle.d.ts +1 -0
- package/dist/src/modules/mcp/apps/shared/index.d.ts +2 -0
- package/dist/src/modules/mcp/apps/shared/page.d.ts +2 -0
- package/dist/src/modules/mcp/apps/shared/registerApp.d.ts +5 -0
- package/dist/src/modules/mcp/apps/shared/resolve.d.ts +1 -0
- package/dist/src/modules/mcp/apps/shared/zodEnglishOnly.d.ts +4 -0
- package/dist/src/modules/mcp/e2e/connector.test.d.ts +1 -0
- package/dist/src/modules/mcp/e2e/host.test.d.ts +1 -0
- package/dist/src/modules/mcp/e2e/index.d.ts +6 -0
- package/dist/src/modules/mcp/e2e/mcpEndpoint.d.ts +20 -0
- package/dist/src/modules/mcp/e2e/postMessageChannel.d.ts +22 -0
- package/dist/src/modules/mcp/e2e/renderingHost.d.ts +14 -0
- package/dist/src/modules/mcp/handler.d.ts +9 -5
- package/dist/src/modules/mcp/index.d.ts +1 -0
- package/dist/src/modules/mcp/resources/register.d.ts +1 -1
- package/dist/src/modules/mcp/resources/renderGuide.d.ts +1 -1
- package/dist/src/modules/mcp/server.d.ts +2 -10
- package/dist/src/modules/mcp/tools/apply/index.d.ts +3 -3
- package/dist/src/modules/mcp/tools/operations/index.d.ts +6 -6
- package/dist/src/modules/mcp/tools/operations/schema/operations.d.ts +1 -1
- package/dist/src/modules/mcp/tools/operations/schema/settings/patchSettings.d.ts +1 -1
- package/dist/src/modules/mcp/tools/operations/style/operations.d.ts +2 -2
- package/dist/src/modules/mcp/tools/operations/style/shared.d.ts +1 -1
- package/dist/src/modules/mcp/tools/operations/style/variables/deleteStyleVariable.d.ts +1 -1
- package/dist/src/modules/mcp/tools/operations/style/variables/upsertStyleVariable.d.ts +1 -1
- package/dist/src/modules/mcp/tools/preview.d.ts +3 -3
- package/dist/src/modules/mcp/tools/render.d.ts +3 -3
- package/dist/src/modules/mcp/tools/screenshot.d.ts +3 -3
- package/dist/src/modules/mcp/tools/shared/tool.d.ts +4 -7
- package/dist/src/modules/mcp/tools/validate.d.ts +3 -3
- package/dist/src/modules/mcp/types/appTypes.d.ts +13 -0
- package/dist/src/modules/mcp/types/index.d.ts +1 -0
- package/package.json +13 -4
- package/dist/modules/mcp/resources/renderApp.js +0 -174
- package/dist/server.d.ts +0 -1
- package/dist/server.js +0 -107
- package/dist/src/modules/mcp/resources/renderApp.d.ts +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
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
|
+
|
|
13
|
+
## 0.32.9
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- v0.32.9
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @plitzi/plitzi-sdk@0.32.9
|
|
20
|
+
- @plitzi/sdk-schema@0.32.9
|
|
21
|
+
- @plitzi/sdk-shared@0.32.9
|
|
22
|
+
|
|
3
23
|
## 0.32.8
|
|
4
24
|
|
|
5
25
|
### 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 = (
|
|
5
|
+
var mcpPathOf = (config) => config.mcpAi?.path ?? config.mcp?.path ?? "/mcp";
|
|
6
6
|
var serveMcp = (ctx) => {
|
|
7
|
-
const { previewClient, screenshot } = ctx.config;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
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,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,
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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.
|
|
46
|
+
version: "0.32.10"
|
|
46
47
|
}, { instructions: serverInstructions });
|
|
47
48
|
registerResources(server, getSpace, MCP_ENV, log);
|
|
48
|
-
|
|
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
|
-
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import { BUILTIN_COMPONENTS } from "../../../catalogs/builtinComponents.js";
|
|
1
2
|
import { observedDataSources, observedInteractionActions } from "../../../catalogs/observed.js";
|
|
2
3
|
import { buildTypeRegistry } from "../../../catalogs/registry.js";
|
|
3
4
|
import { findElementByRef, findFolderByRef, findPageByRef, folderAncestorIds, getPageElements, pageFoldersOf, pageRefOf, resolveRef, routeParamNames } from "../../../helpers/space.js";
|
|
@@ -40,7 +41,7 @@ var buildValidationCtx = (space, ops) => {
|
|
|
40
41
|
errors: [],
|
|
41
42
|
warnings: [],
|
|
42
43
|
warned: /* @__PURE__ */ new Set(),
|
|
43
|
-
knownTypes: new Set(Object.keys(registry.types)),
|
|
44
|
+
knownTypes: /* @__PURE__ */ new Set([...Object.keys(registry.types), ...Object.keys(BUILTIN_COMPONENTS)]),
|
|
44
45
|
typeProps: new Map(Object.entries(registry.types).map(([type, info]) => [type, new Set(Object.keys(info.props))])),
|
|
45
46
|
typeMeta: buildTypeMeta(space.catalog),
|
|
46
47
|
elementType: (ref) => (findElementByRef(space.schema, ref) ?? findPageByRef(space.schema, ref))?.definition.type,
|
|
@@ -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();
|
package/dist/plugins/compile.js
CHANGED
package/dist/plugins/copy.js
CHANGED
package/dist/plugins/manager.js
CHANGED
|
@@ -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>;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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 {};
|