@hcu-lab.me/mcp-device 1.0.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.
Files changed (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +22 -0
  5. package/dist/command-manager.d.ts +8 -0
  6. package/dist/command-manager.js +241 -0
  7. package/dist/config-field-definitions.d.ts +41 -0
  8. package/dist/config-field-definitions.js +37 -0
  9. package/dist/config-manager.d.ts +102 -0
  10. package/dist/config-manager.js +291 -0
  11. package/dist/config.d.ts +6 -0
  12. package/dist/config.js +10 -0
  13. package/dist/custom-stdio.d.ts +51 -0
  14. package/dist/custom-stdio.js +358 -0
  15. package/dist/data/official-app-ca.pem +12 -0
  16. package/dist/data/onboarding-prompts.json +70 -0
  17. package/dist/device/device-state.d.ts +13 -0
  18. package/dist/device/device-state.js +100 -0
  19. package/dist/device/device-status.d.ts +81 -0
  20. package/dist/device/device-status.js +156 -0
  21. package/dist/device/device.d.ts +16 -0
  22. package/dist/device/device.js +190 -0
  23. package/dist/device/execution-engine.d.ts +144 -0
  24. package/dist/device/execution-engine.js +195 -0
  25. package/dist/device/gateway-channel.d.ts +48 -0
  26. package/dist/device/gateway-channel.js +384 -0
  27. package/dist/device/gateway-config.d.ts +40 -0
  28. package/dist/device/gateway-config.js +207 -0
  29. package/dist/device/gateway-identity.d.ts +35 -0
  30. package/dist/device/gateway-identity.js +178 -0
  31. package/dist/device/gateway-pairing.d.ts +25 -0
  32. package/dist/device/gateway-pairing.js +105 -0
  33. package/dist/device/gateway-secure-transport.d.ts +25 -0
  34. package/dist/device/gateway-secure-transport.js +117 -0
  35. package/dist/device/gateway-tool-adapter.d.ts +17 -0
  36. package/dist/device/gateway-tool-adapter.js +284 -0
  37. package/dist/device/gateway-url-policy.d.ts +3 -0
  38. package/dist/device/gateway-url-policy.js +43 -0
  39. package/dist/device/linux-service.d.ts +70 -0
  40. package/dist/device/linux-service.js +268 -0
  41. package/dist/device/official-trust.d.ts +7 -0
  42. package/dist/device/official-trust.js +49 -0
  43. package/dist/device/project-inspection.d.ts +1 -0
  44. package/dist/device/project-inspection.js +180 -0
  45. package/dist/device/runtime-owner.d.ts +41 -0
  46. package/dist/device/runtime-owner.js +177 -0
  47. package/dist/device/windows-dpapi.d.ts +11 -0
  48. package/dist/device/windows-dpapi.js +67 -0
  49. package/dist/device/windows-service.d.ts +56 -0
  50. package/dist/device/windows-service.js +238 -0
  51. package/dist/error-handlers.d.ts +7 -0
  52. package/dist/error-handlers.js +15 -0
  53. package/dist/handlers/edit-search-handlers.d.ts +6 -0
  54. package/dist/handlers/edit-search-handlers.js +6 -0
  55. package/dist/handlers/filesystem-handlers.d.ts +38 -0
  56. package/dist/handlers/filesystem-handlers.js +440 -0
  57. package/dist/handlers/history-handlers.d.ts +12 -0
  58. package/dist/handlers/history-handlers.js +67 -0
  59. package/dist/handlers/index.d.ts +6 -0
  60. package/dist/handlers/index.js +7 -0
  61. package/dist/handlers/process-handlers.d.ts +9 -0
  62. package/dist/handlers/process-handlers.js +15 -0
  63. package/dist/handlers/search-handlers.d.ts +17 -0
  64. package/dist/handlers/search-handlers.js +224 -0
  65. package/dist/handlers/terminal-handlers.d.ts +21 -0
  66. package/dist/handlers/terminal-handlers.js +35 -0
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +138 -0
  69. package/dist/mcp-device.d.ts +2 -0
  70. package/dist/mcp-device.js +7 -0
  71. package/dist/npm-scripts/remote.d.ts +3 -0
  72. package/dist/npm-scripts/remote.js +314 -0
  73. package/dist/search-manager.d.ts +147 -0
  74. package/dist/search-manager.js +828 -0
  75. package/dist/server.d.ts +46 -0
  76. package/dist/server.js +1563 -0
  77. package/dist/terminal-manager.d.ts +104 -0
  78. package/dist/terminal-manager.js +635 -0
  79. package/dist/tools/config.d.ts +96 -0
  80. package/dist/tools/config.js +260 -0
  81. package/dist/tools/edit.d.ts +38 -0
  82. package/dist/tools/edit.js +435 -0
  83. package/dist/tools/feedback.d.ts +8 -0
  84. package/dist/tools/feedback.js +132 -0
  85. package/dist/tools/filesystem.d.ts +79 -0
  86. package/dist/tools/filesystem.js +947 -0
  87. package/dist/tools/fuzzySearch.d.ts +12 -0
  88. package/dist/tools/fuzzySearch.js +74 -0
  89. package/dist/tools/fuzzySearchCore.d.ts +52 -0
  90. package/dist/tools/fuzzySearchCore.js +125 -0
  91. package/dist/tools/improved-process-tools.d.ts +24 -0
  92. package/dist/tools/improved-process-tools.js +627 -0
  93. package/dist/tools/mime-types.d.ts +3 -0
  94. package/dist/tools/mime-types.js +28 -0
  95. package/dist/tools/pdf/extract-images.d.ts +34 -0
  96. package/dist/tools/pdf/extract-images.js +132 -0
  97. package/dist/tools/pdf/index.d.ts +6 -0
  98. package/dist/tools/pdf/index.js +3 -0
  99. package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
  100. package/dist/tools/pdf/lib/pdf2md.js +76 -0
  101. package/dist/tools/pdf/manipulations.d.ts +13 -0
  102. package/dist/tools/pdf/manipulations.js +99 -0
  103. package/dist/tools/pdf/markdown.d.ts +26 -0
  104. package/dist/tools/pdf/markdown.js +275 -0
  105. package/dist/tools/pdf/utils.d.ts +12 -0
  106. package/dist/tools/pdf/utils.js +34 -0
  107. package/dist/tools/process.d.ts +3 -0
  108. package/dist/tools/process.js +56 -0
  109. package/dist/tools/prompts.d.ts +28 -0
  110. package/dist/tools/prompts.js +259 -0
  111. package/dist/tools/schemas.d.ts +454 -0
  112. package/dist/tools/schemas.js +242 -0
  113. package/dist/tools/usage.d.ts +5 -0
  114. package/dist/tools/usage.js +24 -0
  115. package/dist/types.d.ts +83 -0
  116. package/dist/types.js +1 -0
  117. package/dist/ui/config-editor/config-editor-runtime.js +150 -0
  118. package/dist/ui/config-editor/index.html +13 -0
  119. package/dist/ui/config-editor/src/app.d.ts +43 -0
  120. package/dist/ui/config-editor/src/app.js +831 -0
  121. package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
  122. package/dist/ui/config-editor/src/array-modal.js +185 -0
  123. package/dist/ui/config-editor/src/main.d.ts +1 -0
  124. package/dist/ui/config-editor/src/main.js +2 -0
  125. package/dist/ui/config-editor/styles.css +587 -0
  126. package/dist/ui/contracts.d.ts +14 -0
  127. package/dist/ui/contracts.js +21 -0
  128. package/dist/ui/file-preview/index.html +16 -0
  129. package/dist/ui/file-preview/preview-runtime.js +469 -0
  130. package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
  131. package/dist/ui/file-preview/shared/preview-file-types.js +59 -0
  132. package/dist/ui/file-preview/src/app.d.ts +4 -0
  133. package/dist/ui/file-preview/src/app.js +680 -0
  134. package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
  135. package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
  136. package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
  137. package/dist/ui/file-preview/src/components/highlighting.js +54 -0
  138. package/dist/ui/file-preview/src/components/html-renderer.d.ts +5 -0
  139. package/dist/ui/file-preview/src/components/html-renderer.js +47 -0
  140. package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
  141. package/dist/ui/file-preview/src/components/markdown-renderer.js +59 -0
  142. package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
  143. package/dist/ui/file-preview/src/directory-controller.js +240 -0
  144. package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
  145. package/dist/ui/file-preview/src/document-layout.js +109 -0
  146. package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
  147. package/dist/ui/file-preview/src/document-outline.js +97 -0
  148. package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
  149. package/dist/ui/file-preview/src/document-workspace.js +33 -0
  150. package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
  151. package/dist/ui/file-preview/src/file-type-handlers.js +109 -0
  152. package/dist/ui/file-preview/src/host/external-actions.d.ts +8 -0
  153. package/dist/ui/file-preview/src/host/external-actions.js +55 -0
  154. package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
  155. package/dist/ui/file-preview/src/host/selection-context.js +106 -0
  156. package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
  157. package/dist/ui/file-preview/src/image-preview.js +21 -0
  158. package/dist/ui/file-preview/src/main.d.ts +1 -0
  159. package/dist/ui/file-preview/src/main.js +5 -0
  160. package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
  161. package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
  162. package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
  163. package/dist/ui/file-preview/src/markdown/controller.js +1043 -0
  164. package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
  165. package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
  166. package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
  167. package/dist/ui/file-preview/src/markdown/linking.js +228 -0
  168. package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
  169. package/dist/ui/file-preview/src/markdown/outline.js +16 -0
  170. package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
  171. package/dist/ui/file-preview/src/markdown/parser.js +38 -0
  172. package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
  173. package/dist/ui/file-preview/src/markdown/preview.js +20 -0
  174. package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
  175. package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
  176. package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
  177. package/dist/ui/file-preview/src/markdown/utils.js +15 -0
  178. package/dist/ui/file-preview/src/model.d.ts +35 -0
  179. package/dist/ui/file-preview/src/model.js +1 -0
  180. package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
  181. package/dist/ui/file-preview/src/panel-actions.js +182 -0
  182. package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
  183. package/dist/ui/file-preview/src/path-utils.js +64 -0
  184. package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
  185. package/dist/ui/file-preview/src/payload-utils.js +119 -0
  186. package/dist/ui/file-preview/src/types.d.ts +1 -0
  187. package/dist/ui/file-preview/src/types.js +1 -0
  188. package/dist/ui/file-preview/styles.css +1631 -0
  189. package/dist/ui/resources.d.ts +28 -0
  190. package/dist/ui/resources.js +86 -0
  191. package/dist/ui/shared/compact-row.d.ts +11 -0
  192. package/dist/ui/shared/compact-row.js +18 -0
  193. package/dist/ui/shared/escape-html.d.ts +4 -0
  194. package/dist/ui/shared/escape-html.js +11 -0
  195. package/dist/ui/shared/host-context.d.ts +15 -0
  196. package/dist/ui/shared/host-context.js +51 -0
  197. package/dist/ui/shared/tool-bridge.d.ts +30 -0
  198. package/dist/ui/shared/tool-bridge.js +137 -0
  199. package/dist/ui/shared/tool-shell.d.ts +25 -0
  200. package/dist/ui/shared/tool-shell.js +107 -0
  201. package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
  202. package/dist/ui/shared/ui-event-tracker.js +27 -0
  203. package/dist/ui/shared/widget-state.d.ts +33 -0
  204. package/dist/ui/shared/widget-state.js +158 -0
  205. package/dist/utils/ab-test.d.ts +12 -0
  206. package/dist/utils/ab-test.js +104 -0
  207. package/dist/utils/capture.d.ts +35 -0
  208. package/dist/utils/capture.js +481 -0
  209. package/dist/utils/dockerPrompt.d.ts +17 -0
  210. package/dist/utils/dockerPrompt.js +53 -0
  211. package/dist/utils/feature-flags.d.ts +59 -0
  212. package/dist/utils/feature-flags.js +215 -0
  213. package/dist/utils/files/base.d.ts +173 -0
  214. package/dist/utils/files/base.js +5 -0
  215. package/dist/utils/files/binary.d.ts +21 -0
  216. package/dist/utils/files/binary.js +65 -0
  217. package/dist/utils/files/docx.d.ts +47 -0
  218. package/dist/utils/files/docx.js +679 -0
  219. package/dist/utils/files/excel.d.ts +24 -0
  220. package/dist/utils/files/excel.js +437 -0
  221. package/dist/utils/files/factory.d.ts +41 -0
  222. package/dist/utils/files/factory.js +113 -0
  223. package/dist/utils/files/image.d.ts +21 -0
  224. package/dist/utils/files/image.js +78 -0
  225. package/dist/utils/files/index.d.ts +10 -0
  226. package/dist/utils/files/index.js +13 -0
  227. package/dist/utils/files/pdf.d.ts +32 -0
  228. package/dist/utils/files/pdf.js +142 -0
  229. package/dist/utils/files/text.d.ts +63 -0
  230. package/dist/utils/files/text.js +370 -0
  231. package/dist/utils/fuzzySearchLogger.d.ts +30 -0
  232. package/dist/utils/fuzzySearchLogger.js +126 -0
  233. package/dist/utils/lineEndingHandler.d.ts +21 -0
  234. package/dist/utils/lineEndingHandler.js +77 -0
  235. package/dist/utils/logger.d.ts +32 -0
  236. package/dist/utils/logger.js +72 -0
  237. package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
  238. package/dist/utils/mcp-ui-ab-test.js +62 -0
  239. package/dist/utils/open-browser.d.ts +9 -0
  240. package/dist/utils/open-browser.js +42 -0
  241. package/dist/utils/process-detection.d.ts +23 -0
  242. package/dist/utils/process-detection.js +150 -0
  243. package/dist/utils/ripgrep-resolver.d.ts +9 -0
  244. package/dist/utils/ripgrep-resolver.js +74 -0
  245. package/dist/utils/system-info.d.ts +71 -0
  246. package/dist/utils/system-info.js +712 -0
  247. package/dist/utils/toolHistory.d.ts +86 -0
  248. package/dist/utils/toolHistory.js +257 -0
  249. package/dist/utils/trackTools.d.ts +6 -0
  250. package/dist/utils/trackTools.js +54 -0
  251. package/dist/utils/unsupportedParams.d.ts +24 -0
  252. package/dist/utils/unsupportedParams.js +66 -0
  253. package/dist/utils/usageTracker.d.ts +133 -0
  254. package/dist/utils/usageTracker.js +477 -0
  255. package/dist/utils/welcome-onboarding.d.ts +11 -0
  256. package/dist/utils/welcome-onboarding.js +99 -0
  257. package/dist/utils/withTimeout.d.ts +28 -0
  258. package/dist/utils/withTimeout.js +85 -0
  259. package/dist/version.d.ts +1 -0
  260. package/dist/version.js +1 -0
  261. package/package.json +131 -0
@@ -0,0 +1,28 @@
1
+ export declare const FILE_PREVIEW_RESOURCE: {
2
+ uri: string;
3
+ name: string;
4
+ description: string;
5
+ mimeType: string;
6
+ };
7
+ export declare const CONFIG_EDITOR_RESOURCE: {
8
+ uri: string;
9
+ name: string;
10
+ description: string;
11
+ mimeType: string;
12
+ };
13
+ export declare function getFilePreviewResourceText(): Promise<string>;
14
+ export declare function getConfigEditorResourceText(): Promise<string>;
15
+ export declare function listUiResources(): {
16
+ uri: string;
17
+ name: string;
18
+ description: string;
19
+ mimeType: string;
20
+ }[];
21
+ export declare function readUiResource(uri: string): Promise<{
22
+ contents: {
23
+ _meta?: Record<string, unknown> | undefined;
24
+ uri: string;
25
+ mimeType: string;
26
+ text: string;
27
+ }[];
28
+ } | null>;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Server-side UI resource loading helpers for MCP responses. It resolves packaged UI assets, reads files safely, and exposes structured resource payloads to clients.
3
+ */
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { CONFIG_EDITOR_RESOURCE_URI, FILE_PREVIEW_RESOURCE_URI } from './contracts.js';
8
+ const UI_RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app';
9
+ export const FILE_PREVIEW_RESOURCE = {
10
+ uri: FILE_PREVIEW_RESOURCE_URI,
11
+ name: 'MCP Device File Preview',
12
+ description: 'Markdown-first preview surface for read_file structured content.',
13
+ mimeType: UI_RESOURCE_MIME_TYPE
14
+ };
15
+ export const CONFIG_EDITOR_RESOURCE = {
16
+ uri: CONFIG_EDITOR_RESOURCE_URI,
17
+ name: 'MCP Device Config Editor',
18
+ description: 'Interactive editor for MCP Device configuration values.',
19
+ mimeType: UI_RESOURCE_MIME_TYPE
20
+ };
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+ const DIST_FILE_PREVIEW_DIR = path.resolve(__dirname, 'file-preview');
24
+ const DIST_CONFIG_EDITOR_DIR = path.resolve(__dirname, 'config-editor');
25
+ function replaceOrThrow(source, pattern, replacement, context) {
26
+ if (!pattern.test(source)) {
27
+ throw new Error(`UI template is missing expected ${context}`);
28
+ }
29
+ return source.replace(pattern, () => replacement);
30
+ }
31
+ function inlineTemplateAssets(templateHtml, css, runtime) {
32
+ const safeCss = css.replace(/<\/style/gi, '<\\/style');
33
+ const safeRuntime = runtime.replace(/<\/script/gi, '<\\/script');
34
+ const cssInlined = replaceOrThrow(templateHtml, /<link[^>]*href=["']\.\/styles\.css["'][^>]*>/i, `<style>${safeCss}</style>`, 'styles.css link tag');
35
+ const runtimeInlined = replaceOrThrow(cssInlined, /<script[^>]*src=["']\.\/[^"']+["'][^>]*>\s*<\/script>/i, `<script>${safeRuntime}</script>`, 'runtime script tag');
36
+ if (/href=["']\.\/styles\.css["']/i.test(runtimeInlined) ||
37
+ /<script[^>]*src=["']\.\/[^"']+["']/i.test(runtimeInlined)) {
38
+ throw new Error('UI template still contains external static asset references after inlining');
39
+ }
40
+ return runtimeInlined;
41
+ }
42
+ async function readInlinedResourceHtml(distDir, runtimeFileName) {
43
+ const [templateHtml, css, runtime] = await Promise.all([
44
+ fs.readFile(path.join(distDir, 'index.html'), 'utf8'),
45
+ fs.readFile(path.join(distDir, 'styles.css'), 'utf8'),
46
+ fs.readFile(path.join(distDir, runtimeFileName), 'utf8')
47
+ ]);
48
+ return inlineTemplateAssets(templateHtml, css, runtime);
49
+ }
50
+ export async function getFilePreviewResourceText() {
51
+ return readInlinedResourceHtml(DIST_FILE_PREVIEW_DIR, 'preview-runtime.js');
52
+ }
53
+ export async function getConfigEditorResourceText() {
54
+ return readInlinedResourceHtml(DIST_CONFIG_EDITOR_DIR, 'config-editor-runtime.js');
55
+ }
56
+ const READABLE_UI_RESOURCES = {
57
+ [FILE_PREVIEW_RESOURCE_URI]: {
58
+ mimeType: FILE_PREVIEW_RESOURCE.mimeType,
59
+ getText: getFilePreviewResourceText
60
+ },
61
+ [CONFIG_EDITOR_RESOURCE_URI]: {
62
+ mimeType: CONFIG_EDITOR_RESOURCE.mimeType,
63
+ getText: getConfigEditorResourceText
64
+ }
65
+ };
66
+ export function listUiResources() {
67
+ return [FILE_PREVIEW_RESOURCE, CONFIG_EDITOR_RESOURCE];
68
+ }
69
+ export async function readUiResource(uri) {
70
+ const resource = READABLE_UI_RESOURCES[uri];
71
+ if (!resource) {
72
+ return null;
73
+ }
74
+ const resourceText = await resource.getText();
75
+ const resourceMeta = resource.getMeta?.();
76
+ return {
77
+ contents: [
78
+ {
79
+ uri,
80
+ mimeType: resource.mimeType,
81
+ text: resourceText,
82
+ ...(resourceMeta ? { _meta: resourceMeta } : {})
83
+ }
84
+ ]
85
+ };
86
+ }
@@ -0,0 +1,11 @@
1
+ interface RenderCompactRowOptions {
2
+ id?: string;
3
+ label: string;
4
+ filename?: string;
5
+ variant?: 'ready' | 'loading' | 'status';
6
+ expandable?: boolean;
7
+ expanded?: boolean;
8
+ interactive?: boolean;
9
+ }
10
+ export declare function renderCompactRow(options: RenderCompactRowOptions): string;
11
+ export {};
@@ -0,0 +1,18 @@
1
+ import { escapeHtml } from './escape-html.js';
2
+ export function renderCompactRow(options) {
3
+ const variant = options.variant ?? 'ready';
4
+ const classNames = `compact-row compact-row--${variant}`;
5
+ const id = options.id ? ` id="${escapeHtml(options.id)}"` : '';
6
+ const interactive = options.interactive ?? variant === 'ready';
7
+ const role = interactive ? ' role="button" tabindex="0"' : '';
8
+ const ariaExpanded = typeof options.expanded === 'boolean'
9
+ ? ` aria-expanded="${String(options.expanded)}"`
10
+ : '';
11
+ const chevron = options.expandable
12
+ ? '<svg class="compact-chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M10 6l6 6-6 6z"/></svg>'
13
+ : '';
14
+ const filename = options.filename
15
+ ? `<span class="compact-filename">${escapeHtml(options.filename)}</span>`
16
+ : '';
17
+ return `<div class="${classNames}"${id}${role}${ariaExpanded}>${chevron}<span class="compact-label">${escapeHtml(options.label)}</span>${filename}</div>`;
18
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared HTML escaping helper for UI string interpolation.
3
+ */
4
+ export declare function escapeHtml(value: string): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared HTML escaping helper for UI string interpolation.
3
+ */
4
+ export function escapeHtml(value) {
5
+ return value
6
+ .replace(/&/g, '&amp;')
7
+ .replace(/</g, '&lt;')
8
+ .replace(/>/g, '&gt;')
9
+ .replace(/"/g, '&quot;')
10
+ .replace(/'/g, '&#39;');
11
+ }
@@ -0,0 +1,15 @@
1
+ import { App } from '@modelcontextprotocol/ext-apps';
2
+ export interface UiChromeState {
3
+ expanded: boolean;
4
+ hideSummaryRow: boolean;
5
+ compact?: boolean;
6
+ }
7
+ export interface ConnectWithSharedHostContextOptions {
8
+ app: App;
9
+ chrome: UiChromeState;
10
+ onContextApplied?: () => void;
11
+ onConnected?: () => void | Promise<void>;
12
+ }
13
+ export declare function isObjectRecord(value: unknown): value is Record<string, unknown>;
14
+ export declare function applySharedHostContext(context: unknown, chrome: UiChromeState): void;
15
+ export declare function connectWithSharedHostContext(options: ConnectWithSharedHostContextOptions): Promise<void>;
@@ -0,0 +1,51 @@
1
+ import { applyDocumentTheme, applyHostFonts, applyHostStyleVariables } from '@modelcontextprotocol/ext-apps';
2
+ export function isObjectRecord(value) {
3
+ return typeof value === 'object' && value !== null;
4
+ }
5
+ export function applySharedHostContext(context, chrome) {
6
+ if (!isObjectRecord(context)) {
7
+ return;
8
+ }
9
+ if (context.theme === 'light' || context.theme === 'dark') {
10
+ applyDocumentTheme(context.theme);
11
+ }
12
+ const styles = isObjectRecord(context.styles) ? context.styles : null;
13
+ const variables = isObjectRecord(styles?.variables) ? styles.variables : null;
14
+ const css = isObjectRecord(styles?.css) ? styles.css : null;
15
+ const fonts = typeof css?.fonts === 'string' ? css.fonts : null;
16
+ if (variables) {
17
+ applyHostStyleVariables(variables);
18
+ }
19
+ if (fonts) {
20
+ applyHostFonts(fonts);
21
+ }
22
+ if (typeof context.initiallyExpanded === 'boolean') {
23
+ chrome.expanded = context.initiallyExpanded;
24
+ }
25
+ if (typeof context.compact === 'boolean' && typeof chrome.compact === 'boolean') {
26
+ chrome.compact = context.compact;
27
+ }
28
+ if (typeof context.hideSummaryRow === 'boolean') {
29
+ chrome.hideSummaryRow = context.hideSummaryRow;
30
+ if (context.hideSummaryRow) {
31
+ chrome.expanded = true;
32
+ if (typeof chrome.compact === 'boolean') {
33
+ chrome.compact = true;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ export async function connectWithSharedHostContext(options) {
39
+ const { app, chrome, onContextApplied, onConnected } = options;
40
+ app.onhostcontextchanged = (context) => {
41
+ applySharedHostContext(context, chrome);
42
+ onContextApplied?.();
43
+ };
44
+ await app.connect();
45
+ const hostContext = app.getHostContext();
46
+ if (hostContext) {
47
+ applySharedHostContext(hostContext, chrome);
48
+ onContextApplied?.();
49
+ }
50
+ await onConnected?.();
51
+ }
@@ -0,0 +1,30 @@
1
+ type ToolArgs = Record<string, unknown>;
2
+ type ToolHelper = {
3
+ callTool: (name: string, args: ToolArgs) => Promise<unknown> | unknown;
4
+ };
5
+ type MessageEventLike = {
6
+ data: unknown;
7
+ origin?: string;
8
+ source?: unknown;
9
+ };
10
+ type MessageListener = (event: MessageEventLike) => void;
11
+ type MessageTarget = {
12
+ postMessage: (message: unknown, targetOrigin?: string) => void;
13
+ };
14
+ type BridgeHost = {
15
+ openai?: ToolHelper;
16
+ mcp?: ToolHelper;
17
+ parent?: MessageTarget;
18
+ addEventListener?: (type: 'message', listener: MessageListener) => void;
19
+ removeEventListener?: (type: 'message', listener: MessageListener) => void;
20
+ };
21
+ export interface ToolBridgeOptions {
22
+ host?: BridgeHost;
23
+ requestTimeoutMs?: number;
24
+ targetOrigin?: string;
25
+ idPrefix?: string;
26
+ }
27
+ export declare function createToolBridge(options?: ToolBridgeOptions): {
28
+ callTool: (name: string, args?: ToolArgs) => Promise<unknown>;
29
+ };
30
+ export {};
@@ -0,0 +1,137 @@
1
+ const DEFAULT_TIMEOUT_MS = 5000;
2
+ function getDefaultTargetOrigin() {
3
+ if (typeof document === 'undefined') {
4
+ return '*';
5
+ }
6
+ const referrer = document.referrer;
7
+ if (typeof referrer !== 'string' || referrer.trim().length === 0) {
8
+ return '*';
9
+ }
10
+ try {
11
+ return new URL(referrer).origin;
12
+ }
13
+ catch {
14
+ return '*';
15
+ }
16
+ }
17
+ function normalizeTargetOrigin(value) {
18
+ if (value === '*') {
19
+ return value;
20
+ }
21
+ try {
22
+ return new URL(value).origin;
23
+ }
24
+ catch {
25
+ throw new Error(`Invalid targetOrigin: ${value}`);
26
+ }
27
+ }
28
+ function isObject(value) {
29
+ return typeof value === 'object' && value !== null;
30
+ }
31
+ function normalizeToolArgs(args) {
32
+ return args ?? {};
33
+ }
34
+ function extractErrorMessage(error) {
35
+ if (error instanceof Error && error.message) {
36
+ return error.message;
37
+ }
38
+ return String(error);
39
+ }
40
+ function isHelperUnavailableError(error) {
41
+ if (!isObject(error)) {
42
+ return false;
43
+ }
44
+ const code = typeof error.code === 'string' ? error.code.toLowerCase() : '';
45
+ if (code === 'not_implemented' || code === 'not_supported' || code === 'unavailable') {
46
+ return true;
47
+ }
48
+ const message = extractErrorMessage(error).toLowerCase();
49
+ return message.includes('not implemented')
50
+ || message.includes('not supported')
51
+ || message.includes('unavailable')
52
+ || message.includes('not available');
53
+ }
54
+ export function createToolBridge(options = {}) {
55
+ const host = options.host ?? globalThis;
56
+ const timeoutMs = options.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
57
+ const targetOrigin = normalizeTargetOrigin(options.targetOrigin ?? getDefaultTargetOrigin());
58
+ const idPrefix = options.idPrefix ?? 'tool-bridge';
59
+ let requestCounter = 0;
60
+ async function callViaFallback(name, args) {
61
+ if (!host.parent || !host.addEventListener || !host.removeEventListener) {
62
+ throw new Error('JSON-RPC fallback is unavailable in this host environment.');
63
+ }
64
+ const parent = host.parent;
65
+ const addListener = (type, listener) => {
66
+ host.addEventListener?.(type, listener);
67
+ };
68
+ const removeListener = (type, listener) => {
69
+ host.removeEventListener?.(type, listener);
70
+ };
71
+ requestCounter += 1;
72
+ const requestId = `${idPrefix}:${requestCounter}`;
73
+ return new Promise((resolve, reject) => {
74
+ const timeoutHandle = setTimeout(() => {
75
+ removeListener('message', onMessage);
76
+ reject(new Error(`Tool call fallback timed out after ${timeoutMs}ms (request: ${requestId})`));
77
+ }, timeoutMs);
78
+ const onMessage = (event) => {
79
+ const payload = event.data;
80
+ if (event.source !== parent) {
81
+ return;
82
+ }
83
+ if (targetOrigin !== '*' && event.origin !== targetOrigin) {
84
+ return;
85
+ }
86
+ if (!isObject(payload) || payload.id !== requestId) {
87
+ return;
88
+ }
89
+ clearTimeout(timeoutHandle);
90
+ removeListener('message', onMessage);
91
+ if (isObject(payload.error)) {
92
+ const rawMessage = payload.error.message;
93
+ const message = typeof rawMessage === 'string'
94
+ ? rawMessage
95
+ : 'Unknown tools/call fallback error';
96
+ reject(new Error(`Tool call fallback failed: ${message}`));
97
+ return;
98
+ }
99
+ resolve(payload.result);
100
+ };
101
+ addListener('message', onMessage);
102
+ parent.postMessage({
103
+ jsonrpc: '2.0',
104
+ id: requestId,
105
+ method: 'tools/call',
106
+ params: {
107
+ name,
108
+ arguments: args,
109
+ },
110
+ }, targetOrigin);
111
+ });
112
+ }
113
+ async function callTool(name, args) {
114
+ const normalizedArgs = normalizeToolArgs(args);
115
+ const helperCandidates = [host.openai, host.mcp].filter((candidate) => Boolean(candidate?.callTool));
116
+ for (const helper of helperCandidates) {
117
+ try {
118
+ return await helper.callTool(name, normalizedArgs);
119
+ }
120
+ catch (error) {
121
+ if (isHelperUnavailableError(error)) {
122
+ continue;
123
+ }
124
+ throw new Error(`Tool helper call failed: ${extractErrorMessage(error)}`);
125
+ }
126
+ }
127
+ try {
128
+ return await callViaFallback(name, normalizedArgs);
129
+ }
130
+ catch (fallbackError) {
131
+ throw fallbackError;
132
+ }
133
+ }
134
+ return {
135
+ callTool,
136
+ };
137
+ }
@@ -0,0 +1,25 @@
1
+ export interface ToolShellController {
2
+ getExpanded: () => boolean;
3
+ setExpanded: (nextExpanded: boolean) => void;
4
+ toggle: () => void;
5
+ dispose: () => void;
6
+ }
7
+ interface CreateToolShellControllerOptions {
8
+ shell: HTMLElement | null;
9
+ toggleButton: HTMLButtonElement | null;
10
+ initialExpanded: boolean;
11
+ onToggle?: (expanded: boolean) => void;
12
+ onScrollAfterExpand?: () => void;
13
+ onRender?: () => void;
14
+ }
15
+ interface CreateCompactRowShellControllerOptions {
16
+ shell: HTMLElement | null;
17
+ compactRow: HTMLElement | null;
18
+ initialExpanded: boolean;
19
+ onToggle?: (expanded: boolean) => void;
20
+ onScrollAfterExpand?: () => void;
21
+ onRender?: () => void;
22
+ }
23
+ export declare function createToolShellController(options: CreateToolShellControllerOptions): ToolShellController;
24
+ export declare function createCompactRowShellController(options: CreateCompactRowShellControllerOptions): ToolShellController;
25
+ export {};
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Shared shell behavior for collapsible sections and common UI affordances. It keeps repeated interaction patterns consistent across tool apps.
3
+ */
4
+ const EXPAND_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M7 10l5 5 5-5z"></path></svg>';
5
+ const COLLAPSE_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M7 14l5-5 5 5z"></path></svg>';
6
+ function syncExpandButton(toggleButton, expanded) {
7
+ if (!toggleButton) {
8
+ return;
9
+ }
10
+ const label = expanded ? 'Collapse' : 'Expand';
11
+ toggleButton.title = label;
12
+ toggleButton.setAttribute('aria-label', label);
13
+ toggleButton.innerHTML = expanded ? COLLAPSE_ICON : EXPAND_ICON;
14
+ }
15
+ function syncShellClasses(shell, expanded) {
16
+ if (!shell) {
17
+ return;
18
+ }
19
+ shell.classList.toggle('expanded', expanded);
20
+ shell.classList.toggle('collapsed', !expanded);
21
+ }
22
+ export function createToolShellController(options) {
23
+ const { shell, toggleButton, initialExpanded, onToggle, onScrollAfterExpand, onRender } = options;
24
+ let isExpanded = initialExpanded;
25
+ let scrollTrackedForCurrentExpand = false;
26
+ const shouldTrackScroll = typeof onScrollAfterExpand === 'function';
27
+ const applyExpandedState = (nextExpanded) => {
28
+ const wasExpanded = isExpanded;
29
+ isExpanded = nextExpanded;
30
+ syncShellClasses(shell, isExpanded);
31
+ syncExpandButton(toggleButton, isExpanded);
32
+ if (!wasExpanded && isExpanded) {
33
+ scrollTrackedForCurrentExpand = false;
34
+ }
35
+ };
36
+ const toggle = () => {
37
+ applyExpandedState(!isExpanded);
38
+ onToggle?.(isExpanded);
39
+ onRender?.();
40
+ };
41
+ const handleScroll = (event) => {
42
+ if (!isExpanded || scrollTrackedForCurrentExpand) {
43
+ return;
44
+ }
45
+ const targetNode = event?.target instanceof Node ? event.target : null;
46
+ if (targetNode && shell && !shell.contains(targetNode)) {
47
+ return;
48
+ }
49
+ scrollTrackedForCurrentExpand = true;
50
+ onScrollAfterExpand?.();
51
+ };
52
+ applyExpandedState(isExpanded);
53
+ toggleButton?.addEventListener('click', toggle);
54
+ if (shouldTrackScroll) {
55
+ shell?.addEventListener('scroll', handleScroll, { passive: true });
56
+ document.addEventListener('scroll', handleScroll, { passive: true, capture: true });
57
+ }
58
+ return {
59
+ getExpanded: () => isExpanded,
60
+ setExpanded: applyExpandedState,
61
+ toggle,
62
+ dispose: () => {
63
+ toggleButton?.removeEventListener('click', toggle);
64
+ if (shouldTrackScroll) {
65
+ shell?.removeEventListener('scroll', handleScroll);
66
+ document.removeEventListener('scroll', handleScroll, true);
67
+ }
68
+ },
69
+ };
70
+ }
71
+ export function createCompactRowShellController(options) {
72
+ const { shell, compactRow, initialExpanded, onToggle, onScrollAfterExpand, onRender } = options;
73
+ const controller = createToolShellController({
74
+ shell,
75
+ toggleButton: null,
76
+ initialExpanded,
77
+ onToggle: (expanded) => {
78
+ compactRow?.setAttribute('aria-expanded', String(expanded));
79
+ onToggle?.(expanded);
80
+ },
81
+ onScrollAfterExpand,
82
+ onRender,
83
+ });
84
+ compactRow?.setAttribute('aria-expanded', String(initialExpanded));
85
+ const handleCompactClick = () => {
86
+ controller.toggle();
87
+ };
88
+ const handleCompactKeydown = (event) => {
89
+ if (event.key !== 'Enter' && event.key !== ' ') {
90
+ return;
91
+ }
92
+ event.preventDefault();
93
+ controller.toggle();
94
+ };
95
+ compactRow?.addEventListener('click', handleCompactClick);
96
+ compactRow?.addEventListener('keydown', handleCompactKeydown);
97
+ return {
98
+ getExpanded: controller.getExpanded,
99
+ setExpanded: controller.setExpanded,
100
+ toggle: controller.toggle,
101
+ dispose: () => {
102
+ compactRow?.removeEventListener('click', handleCompactClick);
103
+ compactRow?.removeEventListener('keydown', handleCompactKeydown);
104
+ controller.dispose();
105
+ },
106
+ };
107
+ }
@@ -0,0 +1,9 @@
1
+ type UiEventParamValue = string | number | boolean | null;
2
+ export type UiEventParams = Record<string, UiEventParamValue>;
3
+ type ToolCaller = (name: string, args: Record<string, unknown>) => Promise<unknown>;
4
+ export interface UiEventTrackerOptions {
5
+ component: string;
6
+ baseParams?: UiEventParams;
7
+ }
8
+ export declare function createUiEventTracker(callTool: ToolCaller, options: UiEventTrackerOptions): (event: string, params?: Record<string, unknown>) => void;
9
+ export {};
@@ -0,0 +1,27 @@
1
+ function normalizeUiEventParams(params) {
2
+ const normalized = {};
3
+ if (!params) {
4
+ return normalized;
5
+ }
6
+ for (const [key, value] of Object.entries(params)) {
7
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) {
8
+ normalized[key] = value;
9
+ }
10
+ }
11
+ return normalized;
12
+ }
13
+ export function createUiEventTracker(callTool, options) {
14
+ const baseParams = options.baseParams ?? {};
15
+ return (event, params = {}) => {
16
+ void callTool('track_ui_event', {
17
+ event,
18
+ component: options.component,
19
+ params: {
20
+ ...baseParams,
21
+ ...normalizeUiEventParams(params),
22
+ },
23
+ }).catch(() => {
24
+ // UI analytics should never block UI interactions.
25
+ });
26
+ };
27
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Widget state persistence for MCP Apps hosts.
3
+ *
4
+ * ChatGPT has a special extension (window.openai.widgetState) for persisting
5
+ * widget state across page refreshes. Other hosts use the standard MCP Apps
6
+ * pattern where ui/notifications/tool-result is re-sent when needed.
7
+ *
8
+ * This module provides a simple abstraction:
9
+ * - ChatGPT: Uses window.openai.widgetState
10
+ * - Other hosts: No-op (rely on standard ui/notifications/tool-result)
11
+ */
12
+ export interface WidgetStateStorage<T> {
13
+ /** Read persisted state, returns undefined if not found or not supported */
14
+ read(): T | undefined;
15
+ /** Persist state for recovery after refresh (no-op on unsupported hosts) */
16
+ write(state: T): void;
17
+ }
18
+ /**
19
+ * Check if we're running in ChatGPT (has special widget state API)
20
+ */
21
+ export declare function isChatGPT(): boolean;
22
+ /**
23
+ * Create a widget state storage adapter.
24
+ *
25
+ * On ChatGPT: Uses window.openai.widgetState for persistence
26
+ * On other hosts: Uses sessionStorage as a fallback so the preview can survive
27
+ * transient interruptions (page refresh on hosts that don't re-send tool_result,
28
+ * visibility/focus loss, etc.).
29
+ * The fallback cache key is scoped by app pathname and a per-frame widget id
30
+ * persisted in window.name, so different widgets in the same origin/session
31
+ * do not overwrite one another's cached state.
32
+ */
33
+ export declare function createWidgetStateStorage<T>(validator?: (state: unknown) => boolean): WidgetStateStorage<T>;