@webmcp-auto-ui/ui 2.5.36 → 2.5.38

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.
@@ -4,7 +4,7 @@
4
4
  // Consumed by import-modals.ts and left-pane.ts.
5
5
  // ---------------------------------------------------------------------------
6
6
 
7
- import { parseBody } from '@webmcp-auto-ui/sdk';
7
+ import { parseBody, parseWidgetDisplayCall } from '@webmcp-auto-ui/sdk';
8
8
  import { uid, defaultCellContent } from './shared.js';
9
9
  import type { NotebookCell, CellType } from './shared.js';
10
10
 
@@ -80,6 +80,21 @@ export function extractCellFromFence(lang: string, content: string): NotebookCel
80
80
  return { id: uid(), type: 'sql', content: sql.trim(), hideSource: false, hideResult: false };
81
81
  }
82
82
  }
83
+ // widget_display is a local WebMCP tool (autoui), not a remote MCP tool —
84
+ // route it to the JS sandbox `widget()` helper which captures and renders
85
+ // inline. callTool() would fail with "No MCP server exposes tool".
86
+ if (/^(?:[A-Za-z_]\w*_)?widget_display$/.test(name)) {
87
+ const parsed = parseWidgetDisplayCall(trimmed);
88
+ if (parsed) {
89
+ const paramsLiteral = JSON.stringify(parsed.params, null, 2);
90
+ return {
91
+ id: uid(),
92
+ type: 'js',
93
+ content: `return widget('${parsed.name}', ${paramsLiteral})`,
94
+ hideSource: false, hideResult: false,
95
+ };
96
+ }
97
+ }
83
98
  return {
84
99
  id: uid(),
85
100
  type: 'js',
@@ -5,6 +5,7 @@
5
5
  // ---------------------------------------------------------------------------
6
6
 
7
7
  import { encode, buildShortUrl } from '@webmcp-auto-ui/sdk';
8
+ import { canvasVanilla } from '@webmcp-auto-ui/sdk/canvas-vanilla';
8
9
  import type { NotebookState, NotebookCell } from './shared.js';
9
10
 
10
11
  // ---------------------------------------------------------------------------
@@ -27,8 +28,23 @@ export async function shareAsMarkdown(state: NotebookState): Promise<void> {
27
28
  triggerDownload(blob, sanitizeFilename(state.title || 'notebook') + '.md');
28
29
  }
29
30
 
30
- function serializeToMarkdown(state: NotebookState): string {
31
+ /**
32
+ * Serialize a notebook state as a HyperSkill standalone markdown:
33
+ * ---
34
+ * title: "..."
35
+ * description: "..."
36
+ * servers:
37
+ * - name: foo
38
+ * url: https://...
39
+ * ---
40
+ * <body with ```sql / ```js fenced cells>
41
+ *
42
+ * Re-parsable via @webmcp-auto-ui/core::parseFrontmatter + @webmcp-auto-ui/sdk::parseBody.
43
+ */
44
+ export function serializeToMarkdown(state: NotebookState): string {
45
+ const fm = buildFrontmatter(state);
31
46
  const parts: string[] = [];
47
+ if (fm) parts.push(fm);
32
48
  if (state.title) parts.push(`# ${state.title}`, '');
33
49
  for (const cell of state.cells) {
34
50
  if (cell.type === 'md') {
@@ -46,6 +62,79 @@ function serializeToMarkdown(state: NotebookState): string {
46
62
  return parts.join('\n').trim() + '\n';
47
63
  }
48
64
 
65
+ /**
66
+ * Emit YAML frontmatter for HyperSkill format. Reads connected MCP servers from
67
+ * the canvas store. Returns '' when nothing useful to declare (no title, no
68
+ * description, no servers) — caller can skip prepending.
69
+ */
70
+ function buildFrontmatter(state: NotebookState): string {
71
+ const title = (state.title || '').trim();
72
+ const description = extractDescription(state);
73
+ const servers = collectEnabledServers();
74
+ const webmcpServers = collectEnabledWebmcpServers();
75
+ if (!title && !description && servers.length === 0 && webmcpServers.length === 0) return '';
76
+
77
+ const lines: string[] = ['---'];
78
+ if (title) lines.push(`title: ${yamlQuote(title)}`);
79
+ if (description) lines.push(`description: ${yamlQuote(description)}`);
80
+ if (servers.length > 0) {
81
+ lines.push('servers:');
82
+ for (const s of servers) {
83
+ lines.push(` - name: ${yamlQuote(s.name)}`);
84
+ lines.push(` url: ${yamlQuote(s.url)}`);
85
+ }
86
+ }
87
+ if (webmcpServers.length > 0) {
88
+ // YAML flow-style for compactness (registry ids, no spaces).
89
+ lines.push(`webmcp_servers: [${webmcpServers.map(yamlQuote).join(', ')}]`);
90
+ }
91
+ lines.push('---', '');
92
+ return lines.join('\n');
93
+ }
94
+
95
+ /**
96
+ * Read canvas.enabledServerIds — the registry ids (e.g. 'autoui', 'd3',
97
+ * 'observable-plot') of bundled WebMCP servers active in this notebook.
98
+ * The viewer re-instantiates them from @webmcp-auto-ui/servers on load.
99
+ */
100
+ function collectEnabledWebmcpServers(): string[] {
101
+ try {
102
+ const ids = (canvasVanilla as { enabledServerIds?: string[] }).enabledServerIds ?? [];
103
+ return ids.filter((id): id is string => typeof id === 'string' && id.length > 0);
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ function extractDescription(state: NotebookState): string {
110
+ for (const cell of state.cells) {
111
+ if (cell.type !== 'md') continue;
112
+ const text = stripHtml(cell.content).trim();
113
+ if (!text) continue;
114
+ // First non-heading line of the first md cell.
115
+ const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
116
+ const prose = lines.find((l) => !/^#{1,6}\s/.test(l) && !/^[-*]\s/.test(l));
117
+ if (prose) return prose.slice(0, 200);
118
+ }
119
+ return '';
120
+ }
121
+
122
+ function collectEnabledServers(): { name: string; url: string }[] {
123
+ try {
124
+ const servers = canvasVanilla.dataServers ?? [];
125
+ return servers
126
+ .filter((s: any) => s?.enabled && s?.url && s?.name && s.name !== 'autoui' && s.kind !== 'ui' && s.kind !== 'webmcp')
127
+ .map((s: any) => ({ name: String(s.name), url: String(s.url) }));
128
+ } catch {
129
+ return [];
130
+ }
131
+ }
132
+
133
+ /** Quote a YAML scalar safely. Conservative: always double-quote. */
134
+ function yamlQuote(s: string): string {
135
+ return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"';
136
+ }
137
+
49
138
  function stripHtml(s: string): string {
50
139
  if (typeof document === 'undefined') return s;
51
140
  const d = document.createElement('div');