@mcp-html-bridge/ui-engine 0.2.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-html-bridge/ui-engine",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Core UI rendering engine — schema/data → self-contained HTML",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/engine.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // ── Engine Orchestrator: main entry point ──
2
- import type { EngineInput, RenderOptions, JSONSchema } from './types.js';
2
+ import type { EngineInput, RenderIntent, RenderOptions, JSONSchema } from './types.js';
3
3
  import { document as htmlDocument } from './html-builder.js';
4
4
  import { generateThemeCSS } from './theme.js';
5
5
  import { generateBridgeJS } from './bridge.js';
@@ -12,81 +12,52 @@ import { renderMetricsCard, getMetricsCardCSS } from './renderers/metrics-card.j
12
12
  import { renderComposite, getCompositeCSS } from './renderers/composite.js';
13
13
  import { generatePlaygroundHTML, getPlaygroundCSS, getPlaygroundJS } from './playground.js';
14
14
 
15
- /** Render a form from a JSON Schema (for tool input) */
16
- export function renderFromSchema(
17
- schema: JSONSchema,
18
- options: RenderOptions & { toolName?: string; toolDescription?: string } = {}
19
- ): string {
20
- const sniffResult = sniffSchema(schema as Record<string, unknown>);
21
-
22
- const body = renderForm(schema, {
23
- ...sniffResult.metadata,
24
- toolName: options.toolName,
25
- toolDescription: options.toolDescription,
26
- });
27
-
28
- const playground = options.debug ? generatePlaygroundHTML() : '';
29
-
30
- const css = [
31
- generateThemeCSS(),
32
- getFormCSS(),
33
- options.debug ? getPlaygroundCSS() : '',
34
- ].join('\n');
35
-
36
- const js = [
37
- generateBridgeJS(),
38
- options.debug ? getPlaygroundJS() : '',
39
- ].join('\n');
40
-
41
- return htmlDocument({
42
- title: options.title ?? options.toolName ?? 'MCP Tool',
43
- css,
44
- body: body + playground,
45
- js,
46
- });
15
+ /** Extended options that allow explicit renderer selection */
16
+ interface DataRenderOptions extends RenderOptions {
17
+ toolName?: string;
18
+ toolDescription?: string;
19
+ /** Explicitly choose a renderer. Skips auto-detection when set. */
20
+ renderer?: RenderIntent;
47
21
  }
48
22
 
49
- /** Render HTML from tool result data */
50
- export function renderFromData(
23
+ /** Assemble a full HTML document from a chosen renderer's output */
24
+ function assembleDocument(
25
+ intent: RenderIntent,
51
26
  data: unknown,
52
- options: RenderOptions & { toolName?: string; toolDescription?: string } = {}
27
+ metadata: Record<string, unknown>,
28
+ options: DataRenderOptions
53
29
  ): string {
54
- const results = sniff(data);
55
- const best = results[0];
56
-
57
30
  let body: string;
58
31
  const cssParts = [generateThemeCSS()];
59
32
  const jsParts = [generateBridgeJS()];
60
33
 
61
- switch (best.intent) {
34
+ switch (intent) {
62
35
  case 'data-grid':
63
- body = renderDataGrid(data, best.metadata);
36
+ body = renderDataGrid(data, metadata);
64
37
  cssParts.push(getDataGridCSS());
65
38
  jsParts.push(getDataGridJS());
66
39
  break;
67
40
  case 'metrics-card':
68
- body = renderMetricsCard(data, best.metadata);
41
+ body = renderMetricsCard(data, metadata);
69
42
  cssParts.push(getMetricsCardCSS());
70
43
  break;
71
44
  case 'reading-block':
72
- body = renderReadingBlock(data, best.metadata);
45
+ body = renderReadingBlock(data, metadata);
73
46
  cssParts.push(getReadingBlockCSS());
74
47
  break;
75
48
  case 'json-tree':
76
- body = renderJsonTree(data, best.metadata);
49
+ body = renderJsonTree(data, metadata);
77
50
  cssParts.push(getJsonTreeCSS());
78
51
  jsParts.push(getJsonTreeJS());
79
- // Inject tree data for copy-all
80
52
  jsParts.push(`__mcpTreeData = ${JSON.stringify(data)};`);
81
53
  break;
82
54
  case 'composite':
83
- body = renderComposite(data, best.metadata);
84
- // Composite may use all renderers, include all CSS/JS
55
+ body = renderComposite(data, metadata);
85
56
  cssParts.push(getDataGridCSS(), getMetricsCardCSS(), getReadingBlockCSS(), getJsonTreeCSS(), getCompositeCSS());
86
57
  jsParts.push(getDataGridJS(), getJsonTreeJS());
87
58
  break;
88
59
  default:
89
- body = renderJsonTree(data, best.metadata);
60
+ body = renderJsonTree(data, metadata);
90
61
  cssParts.push(getJsonTreeCSS());
91
62
  jsParts.push(getJsonTreeJS());
92
63
  jsParts.push(`__mcpTreeData = ${JSON.stringify(data)};`);
@@ -106,8 +77,67 @@ export function renderFromData(
106
77
  });
107
78
  }
108
79
 
80
+ /** Render a form from a JSON Schema (for tool input) */
81
+ export function renderFromSchema(
82
+ schema: JSONSchema,
83
+ options: DataRenderOptions = {}
84
+ ): string {
85
+ const sniffResult = sniffSchema(schema as Record<string, unknown>);
86
+
87
+ const body = renderForm(schema, {
88
+ ...sniffResult.metadata,
89
+ toolName: options.toolName,
90
+ toolDescription: options.toolDescription,
91
+ });
92
+
93
+ const playground = options.debug ? generatePlaygroundHTML() : '';
94
+
95
+ const css = [
96
+ generateThemeCSS(),
97
+ getFormCSS(),
98
+ options.debug ? getPlaygroundCSS() : '',
99
+ ].join('\n');
100
+
101
+ const js = [
102
+ generateBridgeJS(),
103
+ options.debug ? getPlaygroundJS() : '',
104
+ ].join('\n');
105
+
106
+ return htmlDocument({
107
+ title: options.title ?? options.toolName ?? 'MCP Tool',
108
+ css,
109
+ body: body + playground,
110
+ js,
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Render HTML from tool result data.
116
+ *
117
+ * When `options.renderer` is set, that renderer is used directly —
118
+ * the heuristic sniffer is bypassed entirely. This is the recommended
119
+ * path when an LLM (e.g. Claude in Claude Code) has already decided
120
+ * the best visualization strategy.
121
+ *
122
+ * When `options.renderer` is omitted, falls back to the built-in
123
+ * confidence-scored heuristic sniffer for auto-detection.
124
+ */
125
+ export function renderFromData(
126
+ data: unknown,
127
+ options: DataRenderOptions = {}
128
+ ): string {
129
+ if (options.renderer) {
130
+ // LLM-driven path: explicit renderer, no heuristic
131
+ return assembleDocument(options.renderer, data, {}, options);
132
+ }
133
+
134
+ // Fallback: heuristic auto-detection
135
+ const results = sniff(data);
136
+ return assembleDocument(results[0].intent, data, results[0].metadata, options);
137
+ }
138
+
109
139
  /** Unified API */
110
- export function render(input: EngineInput, options: RenderOptions = {}): string {
140
+ export function render(input: EngineInput, options: DataRenderOptions = {}): string {
111
141
  if (input.mode === 'schema') {
112
142
  return renderFromSchema(input.schema, {
113
143
  ...options,
@@ -121,3 +151,22 @@ export function render(input: EngineInput, options: RenderOptions = {}): string
121
151
  toolDescription: input.toolDescription,
122
152
  });
123
153
  }
154
+
155
+ /**
156
+ * Available renderers — exported for programmatic use.
157
+ * Each renderer takes raw data and returns an HTML fragment (not a full document).
158
+ * Use renderFromData() with `renderer` option for full document output.
159
+ */
160
+ export const renderers = {
161
+ 'data-grid': renderDataGrid,
162
+ 'metrics-card': renderMetricsCard,
163
+ 'json-tree': renderJsonTree,
164
+ 'reading-block': renderReadingBlock,
165
+ 'composite': renderComposite,
166
+ 'form': renderForm,
167
+ } as const;
168
+
169
+ /** List of available renderer names */
170
+ export const availableRenderers: readonly RenderIntent[] = [
171
+ 'data-grid', 'metrics-card', 'json-tree', 'reading-block', 'composite', 'form',
172
+ ];
package/src/index.ts CHANGED
@@ -1,10 +1,28 @@
1
1
  // ── Public API ──
2
- export { render, renderFromSchema, renderFromData } from './engine.js';
2
+ export {
3
+ render,
4
+ renderFromSchema,
5
+ renderFromData,
6
+ renderers,
7
+ availableRenderers,
8
+ } from './engine.js';
9
+
10
+ // Heuristic sniffer (fallback when no LLM is available)
3
11
  export { sniff, sniffSchema } from './data-sniffer.js';
12
+
13
+ // Building blocks (for custom composition)
4
14
  export { generateThemeCSS } from './theme.js';
5
15
  export { generateBridgeJS } from './bridge.js';
6
16
  export { escapeHtml, tag, style, script, document } from './html-builder.js';
7
17
 
18
+ // Individual renderers (for direct LLM-driven usage)
19
+ export { renderDataGrid, getDataGridCSS, getDataGridJS } from './renderers/data-grid.js';
20
+ export { renderMetricsCard, getMetricsCardCSS } from './renderers/metrics-card.js';
21
+ export { renderJsonTree, getJsonTreeCSS, getJsonTreeJS } from './renderers/json-tree.js';
22
+ export { renderReadingBlock, getReadingBlockCSS } from './renderers/reading-block.js';
23
+ export { renderComposite, getCompositeCSS } from './renderers/composite.js';
24
+ export { renderForm, getFormCSS } from './renderers/form.js';
25
+
8
26
  // Re-export types
9
27
  export type {
10
28
  RenderIntent,