@mcp-html-bridge/ui-engine 0.1.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 +1 -1
- package/src/engine.ts +98 -49
- package/src/index.ts +19 -1
package/package.json
CHANGED
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
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
/**
|
|
50
|
-
|
|
23
|
+
/** Assemble a full HTML document from a chosen renderer's output */
|
|
24
|
+
function assembleDocument(
|
|
25
|
+
intent: RenderIntent,
|
|
51
26
|
data: unknown,
|
|
52
|
-
|
|
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 (
|
|
34
|
+
switch (intent) {
|
|
62
35
|
case 'data-grid':
|
|
63
|
-
body = renderDataGrid(data,
|
|
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,
|
|
41
|
+
body = renderMetricsCard(data, metadata);
|
|
69
42
|
cssParts.push(getMetricsCardCSS());
|
|
70
43
|
break;
|
|
71
44
|
case 'reading-block':
|
|
72
|
-
body = renderReadingBlock(data,
|
|
45
|
+
body = renderReadingBlock(data, metadata);
|
|
73
46
|
cssParts.push(getReadingBlockCSS());
|
|
74
47
|
break;
|
|
75
48
|
case 'json-tree':
|
|
76
|
-
body = renderJsonTree(data,
|
|
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,
|
|
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,
|
|
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:
|
|
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 {
|
|
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,
|