pmx-canvas 0.1.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/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/Readme.md +865 -0
- package/dist/canvas/global.css +3173 -0
- package/dist/canvas/index.js +183 -0
- package/dist/json-render/index.css +2 -0
- package/dist/json-render/index.js +389 -0
- package/dist/types/cli/agent.d.ts +13 -0
- package/dist/types/cli/index.d.ts +2 -0
- package/dist/types/cli/watch.d.ts +5 -0
- package/dist/types/client/App.d.ts +1 -0
- package/dist/types/client/canvas/AttentionHistory.d.ts +1 -0
- package/dist/types/client/canvas/AttentionToast.d.ts +1 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +8 -0
- package/dist/types/client/canvas/CanvasViewport.d.ts +8 -0
- package/dist/types/client/canvas/CommandPalette.d.ts +4 -0
- package/dist/types/client/canvas/ContextMenu.d.ts +24 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +1 -0
- package/dist/types/client/canvas/ContextPinHud.d.ts +1 -0
- package/dist/types/client/canvas/DockedNode.d.ts +4 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +8 -0
- package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -0
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +1 -0
- package/dist/types/client/canvas/Minimap.d.ts +23 -0
- package/dist/types/client/canvas/SelectionBar.d.ts +1 -0
- package/dist/types/client/canvas/ShortcutOverlay.d.ts +3 -0
- package/dist/types/client/canvas/SnapshotPanel.d.ts +7 -0
- package/dist/types/client/canvas/snap-guides.d.ts +23 -0
- package/dist/types/client/canvas/use-node-drag.d.ts +15 -0
- package/dist/types/client/canvas/use-node-resize.d.ts +15 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +16 -0
- package/dist/types/client/ext-app/bridge.d.ts +161 -0
- package/dist/types/client/icons.d.ts +70 -0
- package/dist/types/client/index.d.ts +1 -0
- package/dist/types/client/nodes/ContextNode.d.ts +34 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +18 -0
- package/dist/types/client/nodes/FileNode.d.ts +5 -0
- package/dist/types/client/nodes/GroupNode.d.ts +6 -0
- package/dist/types/client/nodes/ImageNode.d.ts +10 -0
- package/dist/types/client/nodes/InlineFormatBar.d.ts +7 -0
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +14 -0
- package/dist/types/client/nodes/LedgerNode.d.ts +4 -0
- package/dist/types/client/nodes/MarkdownNode.d.ts +6 -0
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -0
- package/dist/types/client/nodes/MdFormatBar.d.ts +8 -0
- package/dist/types/client/nodes/PromptNode.d.ts +5 -0
- package/dist/types/client/nodes/ResponseNode.d.ts +5 -0
- package/dist/types/client/nodes/StatusNode.d.ts +4 -0
- package/dist/types/client/nodes/StatusSummary.d.ts +4 -0
- package/dist/types/client/nodes/TraceNode.d.ts +4 -0
- package/dist/types/client/nodes/WebpageNode.d.ts +5 -0
- package/dist/types/client/nodes/image-warnings.d.ts +6 -0
- package/dist/types/client/nodes/inline-editor-commands.d.ts +11 -0
- package/dist/types/client/nodes/md-format.d.ts +25 -0
- package/dist/types/client/state/attention-bridge.d.ts +3 -0
- package/dist/types/client/state/attention-store.d.ts +25 -0
- package/dist/types/client/state/canvas-store.d.ts +74 -0
- package/dist/types/client/state/intent-bridge.d.ts +158 -0
- package/dist/types/client/state/sse-bridge.d.ts +5 -0
- package/dist/types/client/theme/tokens.d.ts +27 -0
- package/dist/types/client/types.d.ts +40 -0
- package/dist/types/client/utils/ext-app-tool-result.d.ts +1 -0
- package/dist/types/client/utils/placement.d.ts +1 -0
- package/dist/types/client/utils/platform.d.ts +2 -0
- package/dist/types/json-render/catalog.d.ts +815 -0
- package/dist/types/json-render/charts/components.d.ts +54 -0
- package/dist/types/json-render/charts/definitions.d.ts +103 -0
- package/dist/types/json-render/charts/extra-components.d.ts +58 -0
- package/dist/types/json-render/charts/extra-definitions.d.ts +181 -0
- package/dist/types/json-render/renderer/index.d.ts +16 -0
- package/dist/types/json-render/schema.d.ts +46 -0
- package/dist/types/json-render/server.d.ts +55 -0
- package/dist/types/mcp/server.d.ts +22 -0
- package/dist/types/server/agent-context.d.ts +21 -0
- package/dist/types/server/artifact-paths.d.ts +3 -0
- package/dist/types/server/canvas-operations.d.ts +154 -0
- package/dist/types/server/canvas-provenance.d.ts +13 -0
- package/dist/types/server/canvas-schema.d.ts +49 -0
- package/dist/types/server/canvas-serialization.d.ts +25 -0
- package/dist/types/server/canvas-state.d.ts +174 -0
- package/dist/types/server/canvas-validation.d.ts +33 -0
- package/dist/types/server/chart-template.d.ts +29 -0
- package/dist/types/server/code-graph.d.ts +67 -0
- package/dist/types/server/context-cards.d.ts +24 -0
- package/dist/types/server/diagram-presets.d.ts +28 -0
- package/dist/types/server/ext-app-call-registry.d.ts +16 -0
- package/dist/types/server/ext-app-tool-result.d.ts +1 -0
- package/dist/types/server/file-watcher.d.ts +16 -0
- package/dist/types/server/index.d.ts +243 -0
- package/dist/types/server/mcp-app-candidate.d.ts +25 -0
- package/dist/types/server/mcp-app-host.d.ts +65 -0
- package/dist/types/server/mcp-app-runtime.d.ts +47 -0
- package/dist/types/server/mutation-history.d.ts +105 -0
- package/dist/types/server/placement.d.ts +37 -0
- package/dist/types/server/server.d.ts +103 -0
- package/dist/types/server/spatial-analysis.d.ts +87 -0
- package/dist/types/server/trace-manager.d.ts +48 -0
- package/dist/types/server/web-artifacts.d.ts +50 -0
- package/dist/types/server/webpage-node.d.ts +25 -0
- package/dist/types/shared/auto-arrange.d.ts +29 -0
- package/dist/types/shared/ext-app-tool-result.d.ts +9 -0
- package/dist/types/shared/placement.d.ts +26 -0
- package/dist/types/shared/semantic-attention.d.ts +97 -0
- package/package.json +109 -0
- package/skills/data-analysis/SKILL.md +324 -0
- package/skills/doc-coauthoring/SKILL.md +375 -0
- package/skills/frontend-design/SKILL.md +45 -0
- package/skills/json-render-codegen/SKILL.md +112 -0
- package/skills/json-render-core/SKILL.md +265 -0
- package/skills/json-render-ink/SKILL.md +273 -0
- package/skills/json-render-mcp/SKILL.md +132 -0
- package/skills/json-render-react/SKILL.md +264 -0
- package/skills/json-render-shadcn/SKILL.md +159 -0
- package/skills/playwright-cli/SKILL.md +67 -0
- package/skills/pmx-canvas/SKILL.md +668 -0
- package/skills/pmx-canvas/evals/evals.json +186 -0
- package/skills/pmx-canvas-testing/SKILL.md +78 -0
- package/skills/published-consumer-e2e/SKILL.md +43 -0
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +241 -0
- package/skills/web-artifacts-builder/SKILL.md +80 -0
- package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +167 -0
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +425 -0
- package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/skills/web-design-guidelines/SKILL.md +39 -0
- package/src/cli/agent.ts +2144 -0
- package/src/cli/index.ts +622 -0
- package/src/cli/watch.ts +88 -0
- package/src/client/App.tsx +507 -0
- package/src/client/canvas/AttentionHistory.tsx +81 -0
- package/src/client/canvas/AttentionToast.tsx +19 -0
- package/src/client/canvas/CanvasNode.tsx +363 -0
- package/src/client/canvas/CanvasViewport.tsx +590 -0
- package/src/client/canvas/CommandPalette.tsx +302 -0
- package/src/client/canvas/ContextMenu.tsx +601 -0
- package/src/client/canvas/ContextPinBar.tsx +25 -0
- package/src/client/canvas/ContextPinHud.tsx +22 -0
- package/src/client/canvas/DockedNode.tsx +66 -0
- package/src/client/canvas/EdgeLayer.tsx +280 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +260 -0
- package/src/client/canvas/FocusFieldLayer.tsx +107 -0
- package/src/client/canvas/Minimap.tsx +301 -0
- package/src/client/canvas/SelectionBar.tsx +69 -0
- package/src/client/canvas/ShortcutOverlay.tsx +69 -0
- package/src/client/canvas/SnapshotPanel.tsx +236 -0
- package/src/client/canvas/snap-guides.ts +170 -0
- package/src/client/canvas/use-node-drag.ts +51 -0
- package/src/client/canvas/use-node-resize.ts +59 -0
- package/src/client/canvas/use-pan-zoom.ts +191 -0
- package/src/client/ext-app/bridge.ts +542 -0
- package/src/client/icons.tsx +424 -0
- package/src/client/index.tsx +7 -0
- package/src/client/nodes/ContextNode.tsx +412 -0
- package/src/client/nodes/ExtAppFrame.tsx +509 -0
- package/src/client/nodes/FileNode.tsx +256 -0
- package/src/client/nodes/GroupNode.tsx +39 -0
- package/src/client/nodes/ImageNode.tsx +160 -0
- package/src/client/nodes/InlineFormatBar.tsx +169 -0
- package/src/client/nodes/InlineMarkdownEditor.tsx +123 -0
- package/src/client/nodes/LedgerNode.tsx +37 -0
- package/src/client/nodes/MarkdownNode.tsx +359 -0
- package/src/client/nodes/McpAppNode.tsx +85 -0
- package/src/client/nodes/MdFormatBar.tsx +109 -0
- package/src/client/nodes/PromptNode.tsx +597 -0
- package/src/client/nodes/ResponseNode.tsx +153 -0
- package/src/client/nodes/StatusNode.tsx +84 -0
- package/src/client/nodes/StatusSummary.tsx +38 -0
- package/src/client/nodes/TraceNode.tsx +120 -0
- package/src/client/nodes/WebpageNode.tsx +288 -0
- package/src/client/nodes/image-warnings.ts +95 -0
- package/src/client/nodes/inline-editor-commands.ts +37 -0
- package/src/client/nodes/md-format.ts +206 -0
- package/src/client/state/attention-bridge.ts +328 -0
- package/src/client/state/attention-store.ts +73 -0
- package/src/client/state/canvas-store.ts +631 -0
- package/src/client/state/intent-bridge.ts +315 -0
- package/src/client/state/sse-bridge.ts +965 -0
- package/src/client/theme/global.css +3173 -0
- package/src/client/theme/tokens.ts +72 -0
- package/src/client/types-shims.d.ts +5 -0
- package/src/client/types.ts +81 -0
- package/src/client/utils/ext-app-tool-result.ts +4 -0
- package/src/client/utils/placement.ts +4 -0
- package/src/client/utils/platform.ts +2 -0
- package/src/json-render/catalog.ts +256 -0
- package/src/json-render/charts/components.tsx +198 -0
- package/src/json-render/charts/definitions.ts +81 -0
- package/src/json-render/charts/extra-components.tsx +267 -0
- package/src/json-render/charts/extra-definitions.ts +145 -0
- package/src/json-render/renderer/index.css +174 -0
- package/src/json-render/renderer/index.tsx +86 -0
- package/src/json-render/schema.ts +62 -0
- package/src/json-render/server.ts +597 -0
- package/src/mcp/server.ts +1377 -0
- package/src/server/agent-context.ts +242 -0
- package/src/server/artifact-paths.ts +17 -0
- package/src/server/canvas-operations.ts +1279 -0
- package/src/server/canvas-provenance.ts +243 -0
- package/src/server/canvas-schema.ts +432 -0
- package/src/server/canvas-serialization.ts +95 -0
- package/src/server/canvas-state.ts +1134 -0
- package/src/server/canvas-validation.ts +114 -0
- package/src/server/chart-template.ts +449 -0
- package/src/server/code-graph.ts +370 -0
- package/src/server/context-cards.ts +31 -0
- package/src/server/diagram-presets.ts +71 -0
- package/src/server/ext-app-call-registry.ts +77 -0
- package/src/server/ext-app-tool-result.ts +4 -0
- package/src/server/file-watcher.ts +121 -0
- package/src/server/index.ts +647 -0
- package/src/server/mcp-app-candidate.ts +174 -0
- package/src/server/mcp-app-host.ts +814 -0
- package/src/server/mcp-app-runtime.ts +459 -0
- package/src/server/mutation-history.ts +350 -0
- package/src/server/placement.ts +125 -0
- package/src/server/server.ts +3846 -0
- package/src/server/spatial-analysis.ts +356 -0
- package/src/server/trace-manager.ts +333 -0
- package/src/server/web-artifacts/scripts/bundle-artifact.sh +167 -0
- package/src/server/web-artifacts/scripts/init-artifact.sh +426 -0
- package/src/server/web-artifacts/scripts/shadcn-components.tar.gz +0 -0
- package/src/server/web-artifacts.ts +442 -0
- package/src/server/webpage-node.ts +328 -0
- package/src/shared/auto-arrange.ts +439 -0
- package/src/shared/ext-app-tool-result.ts +76 -0
- package/src/shared/placement.ts +81 -0
- package/src/shared/semantic-attention.ts +598 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import type {
|
|
5
|
+
CallToolResult,
|
|
6
|
+
ClientCapabilities,
|
|
7
|
+
ListPromptsResult,
|
|
8
|
+
ListResourcesResult,
|
|
9
|
+
ListResourceTemplatesResult,
|
|
10
|
+
ListToolsResult,
|
|
11
|
+
ReadResourceResult,
|
|
12
|
+
TextResourceContents,
|
|
13
|
+
Tool,
|
|
14
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
+
import {
|
|
16
|
+
EXTENSION_ID,
|
|
17
|
+
RESOURCE_MIME_TYPE,
|
|
18
|
+
} from '@modelcontextprotocol/ext-apps/server';
|
|
19
|
+
import type {
|
|
20
|
+
McpUiClientCapabilities,
|
|
21
|
+
McpUiResourceCsp,
|
|
22
|
+
McpUiResourceMeta,
|
|
23
|
+
} from '@modelcontextprotocol/ext-apps';
|
|
24
|
+
import { getToolUiResourceUri } from '@modelcontextprotocol/ext-apps/app-bridge';
|
|
25
|
+
import { normalizeExtAppToolResult } from './ext-app-tool-result.js';
|
|
26
|
+
|
|
27
|
+
export interface ExternalMcpHttpTransportConfig {
|
|
28
|
+
type: 'http';
|
|
29
|
+
url: string;
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ExternalMcpStdioTransportConfig {
|
|
34
|
+
type: 'stdio';
|
|
35
|
+
command: string;
|
|
36
|
+
args?: string[];
|
|
37
|
+
cwd?: string;
|
|
38
|
+
env?: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ExternalMcpTransportConfig =
|
|
42
|
+
| ExternalMcpHttpTransportConfig
|
|
43
|
+
| ExternalMcpStdioTransportConfig;
|
|
44
|
+
|
|
45
|
+
export interface OpenMcpAppInput {
|
|
46
|
+
transport: ExternalMcpTransportConfig;
|
|
47
|
+
toolName: string;
|
|
48
|
+
toolArguments?: Record<string, unknown>;
|
|
49
|
+
serverName?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface OpenMcpAppResult {
|
|
53
|
+
sessionId: string;
|
|
54
|
+
serverName: string;
|
|
55
|
+
toolName: string;
|
|
56
|
+
tool: Tool;
|
|
57
|
+
toolInput: Record<string, unknown>;
|
|
58
|
+
toolResult: CallToolResult;
|
|
59
|
+
resourceUri: string;
|
|
60
|
+
html: string;
|
|
61
|
+
resourceMeta?: McpUiResourceMeta;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ExtAppModelContextUpdateInput {
|
|
65
|
+
content?: unknown[];
|
|
66
|
+
structuredContent?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type RuntimeTransport = StdioClientTransport | StreamableHTTPClientTransport;
|
|
70
|
+
|
|
71
|
+
interface McpAppSession {
|
|
72
|
+
id: string;
|
|
73
|
+
serverName: string;
|
|
74
|
+
client: Client;
|
|
75
|
+
transport: RuntimeTransport;
|
|
76
|
+
tools: Tool[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const uiCapabilities: McpUiClientCapabilities = {
|
|
80
|
+
mimeTypes: [RESOURCE_MIME_TYPE],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const clientCapabilities: ClientCapabilities & {
|
|
84
|
+
extensions: Record<string, unknown>;
|
|
85
|
+
} = {
|
|
86
|
+
extensions: {
|
|
87
|
+
[EXTENSION_ID]: uiCapabilities,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const sessions = new Map<string, McpAppSession>();
|
|
92
|
+
const STORAGE_SHIM_SOURCE = `<script>
|
|
93
|
+
(function() {
|
|
94
|
+
function createStorage() {
|
|
95
|
+
const data = new Map();
|
|
96
|
+
return {
|
|
97
|
+
getItem(key) {
|
|
98
|
+
const normalized = String(key);
|
|
99
|
+
return data.has(normalized) ? data.get(normalized) : null;
|
|
100
|
+
},
|
|
101
|
+
setItem(key, value) {
|
|
102
|
+
data.set(String(key), String(value));
|
|
103
|
+
},
|
|
104
|
+
removeItem(key) {
|
|
105
|
+
data.delete(String(key));
|
|
106
|
+
},
|
|
107
|
+
clear() {
|
|
108
|
+
data.clear();
|
|
109
|
+
},
|
|
110
|
+
key(index) {
|
|
111
|
+
const keys = Array.from(data.keys());
|
|
112
|
+
return typeof index === 'number' && index >= 0 && index < keys.length ? keys[index] : null;
|
|
113
|
+
},
|
|
114
|
+
get length() {
|
|
115
|
+
return data.size;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function installStorage(name) {
|
|
121
|
+
const storage = createStorage();
|
|
122
|
+
|
|
123
|
+
function installOn(target) {
|
|
124
|
+
try {
|
|
125
|
+
Object.defineProperty(target, name, {
|
|
126
|
+
configurable: true,
|
|
127
|
+
enumerable: true,
|
|
128
|
+
get() {
|
|
129
|
+
return storage;
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return true;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
void window[name];
|
|
140
|
+
return;
|
|
141
|
+
} catch {}
|
|
142
|
+
|
|
143
|
+
if (installOn(window)) return;
|
|
144
|
+
installOn(Object.getPrototypeOf(window));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
installStorage('localStorage');
|
|
148
|
+
installStorage('sessionStorage');
|
|
149
|
+
})();
|
|
150
|
+
</script>`;
|
|
151
|
+
|
|
152
|
+
function randomId(prefix: string): string {
|
|
153
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
157
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeHeaders(value: unknown): Record<string, string> | undefined {
|
|
161
|
+
if (!isRecord(value)) return undefined;
|
|
162
|
+
const entries = Object.entries(value)
|
|
163
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === 'string')
|
|
164
|
+
.map(([key, headerValue]) => [key, headerValue.trim()] as const)
|
|
165
|
+
.filter(([, headerValue]) => headerValue.length > 0);
|
|
166
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function defaultServerName(transport: ExternalMcpTransportConfig): string {
|
|
170
|
+
if (transport.type === 'http') {
|
|
171
|
+
try {
|
|
172
|
+
return new URL(transport.url).hostname;
|
|
173
|
+
} catch {
|
|
174
|
+
return 'mcp-http';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const parts = transport.command.split(/[\\/]/);
|
|
179
|
+
return parts[parts.length - 1] || 'mcp-stdio';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function normalizeServerName(raw: string | undefined, transport: ExternalMcpTransportConfig): string {
|
|
183
|
+
const trimmed = String(raw || '').trim();
|
|
184
|
+
return trimmed.length > 0 ? trimmed : defaultServerName(transport);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildTransport(config: ExternalMcpTransportConfig): RuntimeTransport {
|
|
188
|
+
if (config.type === 'http') {
|
|
189
|
+
return new StreamableHTTPClientTransport(new URL(config.url), {
|
|
190
|
+
requestInit: {
|
|
191
|
+
headers: normalizeHeaders(config.headers),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return new StdioClientTransport({
|
|
197
|
+
command: config.command,
|
|
198
|
+
...(Array.isArray(config.args) ? { args: config.args } : {}),
|
|
199
|
+
...(typeof config.cwd === 'string' && config.cwd.trim().length > 0 ? { cwd: config.cwd } : {}),
|
|
200
|
+
env: Object.fromEntries(
|
|
201
|
+
Object.entries({ ...process.env, ...(config.env ?? {}) }).filter(
|
|
202
|
+
(entry): entry is [string, string] => typeof entry[1] === 'string',
|
|
203
|
+
),
|
|
204
|
+
),
|
|
205
|
+
stderr: 'pipe',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function createSession(
|
|
210
|
+
transportConfig: ExternalMcpTransportConfig,
|
|
211
|
+
serverName?: string,
|
|
212
|
+
): Promise<McpAppSession> {
|
|
213
|
+
const transport = buildTransport(transportConfig);
|
|
214
|
+
const client = new Client(
|
|
215
|
+
{ name: 'pmx-canvas-app-host', version: '0.1.0' },
|
|
216
|
+
{ capabilities: clientCapabilities },
|
|
217
|
+
);
|
|
218
|
+
await client.connect(transport);
|
|
219
|
+
|
|
220
|
+
const toolList = await client.listTools();
|
|
221
|
+
const session: McpAppSession = {
|
|
222
|
+
id: randomId('mcp-app-session'),
|
|
223
|
+
serverName: normalizeServerName(serverName, transportConfig),
|
|
224
|
+
client,
|
|
225
|
+
transport,
|
|
226
|
+
tools: toolList.tools,
|
|
227
|
+
};
|
|
228
|
+
sessions.set(session.id, session);
|
|
229
|
+
return session;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function closeSession(session: McpAppSession): Promise<void> {
|
|
233
|
+
sessions.delete(session.id);
|
|
234
|
+
await session.transport.close();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function sessionById(sessionId: string): McpAppSession {
|
|
238
|
+
const session = sessions.get(sessionId);
|
|
239
|
+
if (!session) {
|
|
240
|
+
throw new Error(`MCP app session "${sessionId}" not found.`);
|
|
241
|
+
}
|
|
242
|
+
return session;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function refreshTools(session: McpAppSession): Promise<Tool[]> {
|
|
246
|
+
const toolList = await session.client.listTools();
|
|
247
|
+
session.tools = toolList.tools;
|
|
248
|
+
return session.tools;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function findTool(session: McpAppSession, toolName: string): Promise<Tool> {
|
|
252
|
+
const direct = session.tools.find((tool) => tool.name === toolName);
|
|
253
|
+
if (direct) return direct;
|
|
254
|
+
const refreshed = await refreshTools(session);
|
|
255
|
+
const found = refreshed.find((tool) => tool.name === toolName);
|
|
256
|
+
if (!found) {
|
|
257
|
+
throw new Error(`Tool "${toolName}" not found on MCP server "${session.serverName}".`);
|
|
258
|
+
}
|
|
259
|
+
return found;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function toolVisibility(tool: Tool): string[] {
|
|
263
|
+
const uiMeta = isRecord(tool._meta) && isRecord(tool._meta.ui) ? tool._meta.ui : null;
|
|
264
|
+
const visibility = uiMeta?.visibility;
|
|
265
|
+
if (!Array.isArray(visibility)) return ['model', 'app'];
|
|
266
|
+
return visibility.filter((value): value is string => typeof value === 'string');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function assertToolVisibleToApp(tool: Tool): void {
|
|
270
|
+
if (!toolVisibility(tool).includes('app')) {
|
|
271
|
+
throw new Error(`Tool "${tool.name}" is not app-callable.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function contentMeta(content: { _meta?: Record<string, unknown> }): McpUiResourceMeta | undefined {
|
|
276
|
+
if (!isRecord(content._meta) || !isRecord(content._meta.ui)) return undefined;
|
|
277
|
+
return content._meta.ui as McpUiResourceMeta;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function resourceMetaFromReadResult(result: ReadResourceResult): McpUiResourceMeta | undefined {
|
|
281
|
+
for (const content of result.contents) {
|
|
282
|
+
const meta = contentMeta(content);
|
|
283
|
+
if (meta) return meta;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function htmlContentFromReadResult(result: ReadResourceResult, resourceUri: string): string {
|
|
289
|
+
for (const content of result.contents) {
|
|
290
|
+
if ('text' in content && typeof content.text === 'string') {
|
|
291
|
+
const textContent = content as TextResourceContents;
|
|
292
|
+
if (textContent.uri === resourceUri || String(textContent.mimeType || '').startsWith('text/html')) {
|
|
293
|
+
return textContent.text;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
throw new Error(`Resource "${resourceUri}" did not return HTML content.`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function cspSources(values: string[] | undefined, fallback: string): string {
|
|
301
|
+
if (!values || values.length === 0) return fallback;
|
|
302
|
+
return values.join(' ');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function buildCspContent(csp: McpUiResourceCsp | undefined): string | null {
|
|
306
|
+
if (!csp) return null;
|
|
307
|
+
const resources = cspSources(csp.resourceDomains, `'none'`);
|
|
308
|
+
const connects = cspSources(csp.connectDomains, `'none'`);
|
|
309
|
+
const frames = cspSources(csp.frameDomains, `'none'`);
|
|
310
|
+
const baseUri = cspSources(csp.baseUriDomains, `'self'`);
|
|
311
|
+
return [
|
|
312
|
+
`default-src 'none'`,
|
|
313
|
+
`script-src 'unsafe-inline' ${resources}`,
|
|
314
|
+
`style-src 'unsafe-inline' ${resources}`,
|
|
315
|
+
`img-src data: blob: ${resources}`,
|
|
316
|
+
`font-src data: ${resources}`,
|
|
317
|
+
`media-src blob: data: ${resources}`,
|
|
318
|
+
`connect-src ${connects}`,
|
|
319
|
+
`frame-src ${frames}`,
|
|
320
|
+
`worker-src blob:`,
|
|
321
|
+
`base-uri ${baseUri}`,
|
|
322
|
+
`form-action 'none'`,
|
|
323
|
+
].join('; ');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function injectIntoHead(html: string, injected: string): string {
|
|
327
|
+
const headMatch = html.match(/<head[^>]*>/i);
|
|
328
|
+
if (headMatch) {
|
|
329
|
+
const index = headMatch.index ?? 0;
|
|
330
|
+
const head = headMatch[0];
|
|
331
|
+
const insertAt = index + head.length;
|
|
332
|
+
return `${html.slice(0, insertAt)}${injected}${html.slice(insertAt)}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (/<html[^>]*>/i.test(html)) {
|
|
336
|
+
return html.replace(/<html[^>]*>/i, (match) => `${match}<head>${injected}</head>`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return `<!doctype html><html><head>${injected}</head><body>${html}</body></html>`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function prepareResourceHtml(html: string, meta: McpUiResourceMeta | undefined): string {
|
|
343
|
+
const injections = [STORAGE_SHIM_SOURCE];
|
|
344
|
+
const cspContent = buildCspContent(meta?.csp);
|
|
345
|
+
if (cspContent) {
|
|
346
|
+
const escaped = cspContent.replaceAll('&', '&').replaceAll('"', '"');
|
|
347
|
+
injections.unshift(`<meta http-equiv="Content-Security-Policy" content="${escaped}">`);
|
|
348
|
+
}
|
|
349
|
+
return injectIntoHead(html, injections.join(''));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export async function openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult> {
|
|
353
|
+
const session = await createSession(input.transport, input.serverName);
|
|
354
|
+
try {
|
|
355
|
+
const tool = await findTool(session, input.toolName);
|
|
356
|
+
const resourceUri = getToolUiResourceUri(tool);
|
|
357
|
+
if (!resourceUri) {
|
|
358
|
+
throw new Error(`Tool "${input.toolName}" does not declare an MCP App resource.`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const toolInput = isRecord(input.toolArguments) ? input.toolArguments : {};
|
|
362
|
+
const rawToolResult = await session.client.callTool({
|
|
363
|
+
name: tool.name,
|
|
364
|
+
arguments: toolInput,
|
|
365
|
+
});
|
|
366
|
+
const toolResult = normalizeExtAppToolResult({ result: rawToolResult });
|
|
367
|
+
const readResult = await session.client.readResource({ uri: resourceUri });
|
|
368
|
+
const resourceMeta = resourceMetaFromReadResult(readResult);
|
|
369
|
+
const html = prepareResourceHtml(htmlContentFromReadResult(readResult, resourceUri), resourceMeta);
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
sessionId: session.id,
|
|
373
|
+
serverName: session.serverName,
|
|
374
|
+
toolName: tool.name,
|
|
375
|
+
tool,
|
|
376
|
+
toolInput,
|
|
377
|
+
toolResult,
|
|
378
|
+
resourceUri,
|
|
379
|
+
html,
|
|
380
|
+
...(resourceMeta ? { resourceMeta } : {}),
|
|
381
|
+
};
|
|
382
|
+
} catch (error) {
|
|
383
|
+
void closeSession(session).catch((closeError) => {
|
|
384
|
+
console.debug('[mcp-app-runtime] failed to close openMcpApp session after error', {
|
|
385
|
+
sessionId: session.id,
|
|
386
|
+
error: closeError instanceof Error ? closeError.message : String(closeError),
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export async function callMcpAppTool(
|
|
394
|
+
sessionId: string,
|
|
395
|
+
toolName: string,
|
|
396
|
+
args?: Record<string, unknown>,
|
|
397
|
+
): Promise<CallToolResult> {
|
|
398
|
+
const session = sessionById(sessionId);
|
|
399
|
+
const tool = await findTool(session, toolName);
|
|
400
|
+
assertToolVisibleToApp(tool);
|
|
401
|
+
const rawResult = await session.client.callTool({
|
|
402
|
+
name: tool.name,
|
|
403
|
+
arguments: isRecord(args) ? args : {},
|
|
404
|
+
});
|
|
405
|
+
return normalizeExtAppToolResult({ result: rawResult });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export async function readMcpAppResource(sessionId: string, uri: string): Promise<ReadResourceResult> {
|
|
409
|
+
const session = sessionById(sessionId);
|
|
410
|
+
return session.client.readResource({ uri });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export async function listMcpAppTools(sessionId: string): Promise<ListToolsResult> {
|
|
414
|
+
const session = sessionById(sessionId);
|
|
415
|
+
const tools = await refreshTools(session);
|
|
416
|
+
return {
|
|
417
|
+
tools: tools.filter((tool) => toolVisibility(tool).includes('app')),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export async function listMcpAppResources(sessionId: string): Promise<ListResourcesResult> {
|
|
422
|
+
const session = sessionById(sessionId);
|
|
423
|
+
return session.client.listResources();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export async function listMcpAppResourceTemplates(sessionId: string): Promise<ListResourceTemplatesResult> {
|
|
427
|
+
const session = sessionById(sessionId);
|
|
428
|
+
return session.client.listResourceTemplates();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export async function listMcpAppPrompts(sessionId: string): Promise<ListPromptsResult> {
|
|
432
|
+
const session = sessionById(sessionId);
|
|
433
|
+
return session.client.listPrompts();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function closeMcpAppSession(sessionId: string): void {
|
|
437
|
+
const session = sessions.get(sessionId);
|
|
438
|
+
if (!session) return;
|
|
439
|
+
void closeSession(session).catch((error) => {
|
|
440
|
+
console.debug('[mcp-app-runtime] session close failed', {
|
|
441
|
+
sessionId,
|
|
442
|
+
error: error instanceof Error ? error.message : String(error),
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function closeAllMcpAppSessions(): void {
|
|
448
|
+
for (const sessionId of [...sessions.keys()]) {
|
|
449
|
+
closeMcpAppSession(sessionId);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function hasMcpAppSession(sessionId: string): boolean {
|
|
454
|
+
return sessions.has(sessionId);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function listMcpAppSessionIds(): string[] {
|
|
458
|
+
return [...sessions.keys()];
|
|
459
|
+
}
|