@webmcp-auto-ui/ui 2.5.36 → 2.5.37
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/agent/MCPserversList.svelte +44 -32
- package/src/agent/RecipeBrowser.svelte +54 -18
- package/src/agent/RemoteMCPserversDemo.svelte +7 -7
- package/src/agent/ToolBrowser.svelte +34 -5
- package/src/agent/WebMCPserversList.svelte +85 -40
- package/src/index.ts +1 -0
- package/src/primitives/MarkdownView.svelte +12 -2
- package/src/recipe/RecipeCodeBlock.svelte +10 -2
- package/src/recipe/RecipeRunModal.svelte +245 -0
- package/src/widgets/WidgetRenderer.svelte +2 -0
- package/src/widgets/notebook/executors/sql.ts +9 -6
- package/src/widgets/notebook/import-modal-api.ts +15 -19
- package/src/widgets/notebook/import-modal.svelte +5 -4
- package/src/widgets/notebook/left-pane.ts +3 -3
- package/src/widgets/notebook/notebook.svelte +0 -1
- package/src/widgets/notebook/notebook.ts +5 -36
- package/src/widgets/notebook/share-handlers.ts +71 -1
- package/src/widgets/notebook/shared.ts +81 -69
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Used by the four notebook layout renderers (compact/workspace/document/editorial)
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { serializeToMarkdown } from './share-handlers.js';
|
|
8
8
|
|
|
9
9
|
export const NB_PUBLISH_HOST: string = (() => {
|
|
10
10
|
try {
|
|
@@ -187,6 +187,8 @@ export function createState(initial?: Partial<NotebookState>): NotebookState {
|
|
|
187
187
|
executors: initial?.executors ?? {},
|
|
188
188
|
lastEditAt: initial?.lastEditAt ?? Date.now(),
|
|
189
189
|
autoRun: initial?.autoRun ?? false,
|
|
190
|
+
publishedSlug: initial?.publishedSlug,
|
|
191
|
+
publishedToken: initial?.publishedToken,
|
|
190
192
|
};
|
|
191
193
|
}
|
|
192
194
|
|
|
@@ -260,9 +262,9 @@ export function cellRuntimeStatus(cell: NotebookCell, overlay: RuntimeOverlay |
|
|
|
260
262
|
return 'idle';
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
/** Live-mode whitelist.
|
|
265
|
+
/** Live-mode whitelist. SQL and JS cells are re-executable; markdown stays frozen. */
|
|
264
266
|
export function isReRunnable(cell: NotebookCell): boolean {
|
|
265
|
-
return cell.type === 'sql';
|
|
267
|
+
return cell.type === 'sql' || cell.type === 'js';
|
|
266
268
|
}
|
|
267
269
|
|
|
268
270
|
/**
|
|
@@ -348,22 +350,17 @@ export function lastRefreshedAt(overlay: RuntimeOverlay | null | undefined): num
|
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
/**
|
|
351
|
-
* Build a CellRunner
|
|
352
|
-
* on the connected servers (matching `*_query_sql` then
|
|
353
|
-
* calls it with `{ sql: cell.content }`, parses
|
|
353
|
+
* Build a CellRunner that issues SQL via `callTool`. Discovers a SQL-capable
|
|
354
|
+
* tool on the connected servers (matching `*_query_sql` then
|
|
355
|
+
* `query|run|execute`), calls it with `{ sql: cell.content }`, parses the
|
|
356
|
+
* content-array into a table.
|
|
354
357
|
*
|
|
355
|
-
*
|
|
356
|
-
* to surface this as a 'stale' status, not crash.
|
|
358
|
+
* Returns a runtime error result if no server is reachable / no SQL tool found.
|
|
357
359
|
*/
|
|
358
|
-
export function createBridgeSqlRunner(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
listTools?: (url: string) => Promise<{ name: string }[]>;
|
|
363
|
-
getToolsForUrl?: (url: string) => { name: string }[];
|
|
364
|
-
};
|
|
365
|
-
callTool: (serverName: string, toolName: string, args: unknown) => Promise<unknown>;
|
|
366
|
-
}, getServerDescriptors: () => DataServerDescriptor[]): CellRunner {
|
|
360
|
+
export function createBridgeSqlRunner(
|
|
361
|
+
callTool: (serverName: string, toolName: string, args: unknown) => Promise<unknown>,
|
|
362
|
+
getServerDescriptors: () => DataServerDescriptor[],
|
|
363
|
+
): CellRunner {
|
|
367
364
|
const PATTERN_PRIMARY = /^.*query_sql$/i;
|
|
368
365
|
const PATTERN_FALLBACK = /^(query|run|execute)(_sql)?$/i;
|
|
369
366
|
|
|
@@ -423,20 +420,18 @@ export function createBridgeSqlRunner(bridge: {
|
|
|
423
420
|
if (!hit) {
|
|
424
421
|
return { ok: false, error: 'No SQL tool exposed by reachable servers', errorKind: 'schema', durationMs: 0 };
|
|
425
422
|
}
|
|
426
|
-
const raw = await
|
|
423
|
+
const raw = await callTool(hit.serverName, hit.toolName, { sql: cell.content });
|
|
427
424
|
return parseResult(raw, startedAt);
|
|
428
425
|
};
|
|
429
426
|
}
|
|
430
427
|
|
|
431
428
|
/**
|
|
432
|
-
* High-level bootstrap: auto-connect declared servers, wait for handshake,
|
|
433
|
-
* a
|
|
434
|
-
* mount time when `state.autoRun && state.mode === 'view'`. Returns
|
|
429
|
+
* High-level bootstrap: auto-connect declared servers, wait for handshake,
|
|
430
|
+
* build a SQL runner, fire runAutoRefresh. Safe to call from any layout at
|
|
431
|
+
* mount time when `state.autoRun && state.mode === 'view'`. Returns cleanup.
|
|
435
432
|
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* bridge. If no singleton exists yet we install it here, and only the installer
|
|
439
|
-
* is allowed to stop it on cleanup.
|
|
433
|
+
* Drives connection state through the canvas store; the store's internal
|
|
434
|
+
* sync owns the McpMultiClient and reconciles handshakes on every mutation.
|
|
440
435
|
*/
|
|
441
436
|
export interface BootstrapLiveRefreshOptions {
|
|
442
437
|
state: NotebookState;
|
|
@@ -447,36 +442,54 @@ export interface BootstrapLiveRefreshOptions {
|
|
|
447
442
|
timeoutMs?: number;
|
|
448
443
|
}
|
|
449
444
|
|
|
445
|
+
interface CanvasLike {
|
|
446
|
+
dataServers: { name: string; url: string; enabled?: boolean; connected?: boolean }[];
|
|
447
|
+
callTool: (name: string, toolName: string, args: unknown) => Promise<unknown>;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function waitForEnabledServers(canvas: CanvasLike, timeoutMs: number): Promise<void> {
|
|
451
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
452
|
+
while (Date.now() < deadline) {
|
|
453
|
+
const enabled = (canvas.dataServers ?? []).filter((s) => s.enabled !== false);
|
|
454
|
+
if (enabled.length === 0) return;
|
|
455
|
+
if (enabled.every((s) => s.connected)) return;
|
|
456
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
450
460
|
export function bootstrapLiveRefresh(opts: BootstrapLiveRefreshOptions): () => void {
|
|
451
461
|
const { state, data, overlay, onCellChange, onTick, timeoutMs } = opts;
|
|
452
462
|
const ac = new AbortController();
|
|
453
|
-
let weCreatedBridge = false;
|
|
454
|
-
let bridgeRef: any = null;
|
|
455
463
|
|
|
456
464
|
void (async () => {
|
|
457
465
|
try {
|
|
458
466
|
autoConnectFrontmatterServers(data);
|
|
459
|
-
const canvas
|
|
460
|
-
.__canvasVanilla ?? (globalThis as { canvasVanilla?:
|
|
467
|
+
const canvas = ((globalThis as { __canvasVanilla?: CanvasLike; canvasVanilla?: CanvasLike })
|
|
468
|
+
.__canvasVanilla ?? (globalThis as { canvasVanilla?: CanvasLike }).canvasVanilla) as CanvasLike | undefined;
|
|
461
469
|
if (!canvas) {
|
|
462
470
|
overlay.error = 'No canvas available';
|
|
463
471
|
overlay.finishedAt = Date.now();
|
|
464
472
|
onTick?.(overlay);
|
|
465
473
|
return;
|
|
466
474
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
475
|
+
await waitForEnabledServers(canvas, timeoutMs ?? 5000);
|
|
476
|
+
|
|
477
|
+
const sqlRunner = createBridgeSqlRunner(
|
|
478
|
+
canvas.callTool.bind(canvas),
|
|
479
|
+
() => collectDataServers(data).filter((s) => canvas.dataServers.find((d) => d.name === s.name)?.connected),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
const runner: CellRunner = async (cell, signal) => {
|
|
483
|
+
if (cell.type === 'sql') return sqlRunner(cell, signal);
|
|
484
|
+
if (cell.type === 'js') {
|
|
485
|
+
const exec = state.executors?.js;
|
|
486
|
+
if (!exec) {
|
|
487
|
+
return { ok: false, error: 'No JS executor registered', errorKind: 'runtime', durationMs: 0 };
|
|
488
|
+
}
|
|
489
|
+
return exec({ cell, state, scope: state.scope, signal });
|
|
490
|
+
}
|
|
491
|
+
return { ok: false, error: `Cell type '${cell.type}' is not re-runnable`, errorKind: 'runtime', durationMs: 0 };
|
|
492
|
+
};
|
|
480
493
|
|
|
481
494
|
await runAutoRefresh({ state, overlay, runner, onCellChange, onTick, signal: ac.signal });
|
|
482
495
|
} catch (err) {
|
|
@@ -486,16 +499,7 @@ export function bootstrapLiveRefresh(opts: BootstrapLiveRefreshOptions): () => v
|
|
|
486
499
|
}
|
|
487
500
|
})();
|
|
488
501
|
|
|
489
|
-
return () => {
|
|
490
|
-
ac.abort();
|
|
491
|
-
if (weCreatedBridge && bridgeRef && typeof bridgeRef.stop === 'function') {
|
|
492
|
-
try { bridgeRef.stop(); } catch { /* ignore */ }
|
|
493
|
-
try {
|
|
494
|
-
const g: any = globalThis as any;
|
|
495
|
-
if (g.__multiMcp === bridgeRef) g.__multiMcp = undefined;
|
|
496
|
-
} catch { /* ignore */ }
|
|
497
|
-
}
|
|
498
|
-
};
|
|
502
|
+
return () => { ac.abort(); };
|
|
499
503
|
}
|
|
500
504
|
|
|
501
505
|
export function registerExecutor(state: NotebookState, type: CellType, fn: CellExecutor): void {
|
|
@@ -672,7 +676,12 @@ export interface DataServerRecipe {
|
|
|
672
676
|
}
|
|
673
677
|
|
|
674
678
|
export interface DataServerDescriptor {
|
|
679
|
+
/** Canvas key (= registry id, e.g. 'wikipedia'). Stable identity used for routing. */
|
|
675
680
|
name: string;
|
|
681
|
+
/** Display label (registry label, or URL host for manual entries). */
|
|
682
|
+
label?: string;
|
|
683
|
+
/** Real server name from MCP handshake (initResult.serverInfo.name, aliased). */
|
|
684
|
+
serverName?: string;
|
|
676
685
|
url?: string;
|
|
677
686
|
kind?: string;
|
|
678
687
|
tools?: DataServerTool[];
|
|
@@ -704,6 +713,8 @@ export function collectDataServers(data: Record<string, unknown>): DataServerDes
|
|
|
704
713
|
.filter((s) => !isUiServer(String(s?.name ?? ''), s?.kind))
|
|
705
714
|
.map((s) => ({
|
|
706
715
|
name: String(s.name),
|
|
716
|
+
label: typeof s.label === 'string' ? s.label : undefined,
|
|
717
|
+
serverName: typeof s.serverName === 'string' ? s.serverName : undefined,
|
|
707
718
|
url: s.url ? String(s.url) : undefined,
|
|
708
719
|
recipes: Array.isArray(s.recipes) ? s.recipes : [],
|
|
709
720
|
tools: Array.isArray(s.tools) ? s.tools : [],
|
|
@@ -715,6 +726,8 @@ export function collectDataServers(data: Record<string, unknown>): DataServerDes
|
|
|
715
726
|
.filter((s) => s?.name && !isUiServer(String(s.name), s?.kind))
|
|
716
727
|
.map((s) => ({
|
|
717
728
|
name: String(s.name),
|
|
729
|
+
label: typeof s.label === 'string' ? s.label : undefined,
|
|
730
|
+
serverName: typeof s.serverName === 'string' ? s.serverName : undefined,
|
|
718
731
|
url: s.url ? String(s.url) : undefined,
|
|
719
732
|
recipes: Array.isArray(s.recipes) ? s.recipes : [],
|
|
720
733
|
tools: Array.isArray(s.tools) ? s.tools : [],
|
|
@@ -753,7 +766,7 @@ function publishUrlFor(slug: string): string {
|
|
|
753
766
|
}
|
|
754
767
|
|
|
755
768
|
function publishBtnLabel(state: NotebookState): string {
|
|
756
|
-
return state.publishedSlug ? '
|
|
769
|
+
return state.publishedSlug ? '💾 save' : '📤 publish';
|
|
757
770
|
}
|
|
758
771
|
|
|
759
772
|
function refreshPublishControls(state: NotebookState, controls: PublishControlsHandles): void {
|
|
@@ -761,7 +774,7 @@ function refreshPublishControls(state: NotebookState, controls: PublishControlsH
|
|
|
761
774
|
btn.textContent = publishBtnLabel(state);
|
|
762
775
|
btn.dataset.state = state.publishedSlug ? 'published' : 'draft';
|
|
763
776
|
if (state.publishedSlug) {
|
|
764
|
-
btn.title = `
|
|
777
|
+
btn.title = `Save changes to ${publishUrlFor(state.publishedSlug)}`;
|
|
765
778
|
} else {
|
|
766
779
|
btn.title = 'Publish this notebook';
|
|
767
780
|
}
|
|
@@ -835,21 +848,16 @@ export function createPublishControls(state: NotebookState, opts: PublishControl
|
|
|
835
848
|
const onClick = async () => {
|
|
836
849
|
const prevLabel = btn.textContent ?? '';
|
|
837
850
|
btn.disabled = true;
|
|
838
|
-
btn.textContent = state.publishedSlug ? '…
|
|
851
|
+
btn.textContent = state.publishedSlug ? '… saving' : '… publishing';
|
|
839
852
|
try {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
id: state.id,
|
|
844
|
-
title: state.title,
|
|
845
|
-
mode: state.mode,
|
|
846
|
-
cells: state.cells,
|
|
847
|
-
};
|
|
853
|
+
// HyperSkill standalone markdown — frontmatter (title/description/servers)
|
|
854
|
+
// + body with fenced cells. Re-parsable via parseFrontmatter + parseBody.
|
|
855
|
+
const markdown = serializeToMarkdown(state);
|
|
848
856
|
const res = await fetch(`${NB_PUBLISH_HOST}/api/publish`, {
|
|
849
857
|
method: 'POST',
|
|
850
858
|
headers: { 'content-type': 'application/json' },
|
|
851
859
|
body: JSON.stringify({
|
|
852
|
-
|
|
860
|
+
markdown,
|
|
853
861
|
slug: state.publishedSlug,
|
|
854
862
|
token: state.publishedToken,
|
|
855
863
|
}),
|
|
@@ -859,15 +867,19 @@ export function createPublishControls(state: NotebookState, opts: PublishControl
|
|
|
859
867
|
state.publishedSlug = reply.slug;
|
|
860
868
|
state.publishedToken = reply.token;
|
|
861
869
|
state.lastEditAt = Date.now();
|
|
862
|
-
const
|
|
863
|
-
|
|
870
|
+
const baseUrl: string = reply.url ?? publishUrlFor(String(reply.slug));
|
|
871
|
+
// Author URL embeds the token via `?t=` so the viewer can hydrate it
|
|
872
|
+
// (one-shot — the viewer extracts it into localStorage and cleans the
|
|
873
|
+
// URL). Public visitors share the bare URL without ?t.
|
|
874
|
+
const authorUrl = `${baseUrl}?t=${encodeURIComponent(String(reply.token))}`;
|
|
875
|
+
try { await navigator.clipboard?.writeText?.(authorUrl); } catch { /* ignore */ }
|
|
864
876
|
const updated = Boolean(reply.updated);
|
|
865
877
|
toast(
|
|
866
878
|
updated
|
|
867
|
-
? `
|
|
868
|
-
: `published · ${
|
|
879
|
+
? `saved · ${baseUrl.replace(/^https?:\/\//, '')} (author link copied)`
|
|
880
|
+
: `published · ${baseUrl.replace(/^https?:\/\//, '')} (author link copied)`
|
|
869
881
|
);
|
|
870
|
-
opts.onPublished?.({ slug: String(reply.slug), url, updated });
|
|
882
|
+
opts.onPublished?.({ slug: String(reply.slug), url: baseUrl, updated });
|
|
871
883
|
} catch (err: any) {
|
|
872
884
|
toast(`publish failed · ${String(err?.message ?? err)}`, true);
|
|
873
885
|
btn.textContent = prevLabel;
|