@webmcp-auto-ui/sdk
The @webmcp-auto-ui/sdk package provides the application layer between the agent and the UI. It manages reactive canvas state (widgets, chat, MCP connection), the HyperSkill system for serializing and sharing complete configurations as short URLs, and a persistent skills registry backed by localStorage.
It bridges what the agent produces and what the UI displays.
Internal Architecture
Section titled “Internal Architecture”graph TD subgraph "@webmcp-auto-ui/sdk" CS[Canvas Store Svelte 5] --> HS[HyperSkill] CV[Canvas Vanilla] --> HS HS --> ENC[encode / decode] HS --> HASH[hash / diff] HS --> COMP[gzip/brotli compression] SR[Skills Registry] --> LS[localStorage] DEMOS[MCP Demo Servers] end
AGENT["@webmcp-auto-ui/agent"] -->|callbacks| CS UI["@webmcp-auto-ui/ui"] -->|$derived| CS CS -->|buildHyperskillParam| HS CS -->|buildSkillJSON| SRInstallation
Section titled “Installation”// Main import (types + HyperSkill + skills registry)import { encodeHyperSkill, decodeHyperSkill, createSkill } from '@webmcp-auto-ui/sdk';
// Canvas store — Svelte 5 (with $state/$derived runes)import { canvas } from '@webmcp-auto-ui/sdk/canvas';
// Canvas store — vanilla (React, Vue, or plain JS)import { canvas } from '@webmcp-auto-ui/sdk/canvas-vanilla';In an app’s package.json:
{ "devDependencies": { "@webmcp-auto-ui/sdk": "file:../../packages/sdk" }}The package depends on hyperskills (NPM) for HyperSkill encoding/decoding, and on Svelte 5 as a peer dependency for the reactive canvas store.
Canvas Store
Section titled “Canvas Store”The canvas store is the central state store for the application. It manages:
- Displayed widgets (blocks)
- Selected LLM model
- MCP connection (URL, status, tools)
- Chat history (messages)
- Theme overrides
- Generation state (is the agent currently responding?)
Two store versions
Section titled “Two store versions”The SDK exports two versions of the same store with identical APIs:
| Import | Framework | Reactivity |
|---|---|---|
@webmcp-auto-ui/sdk/canvas | Svelte 5 | Runes ($state / $derived) |
@webmcp-auto-ui/sdk/canvas-vanilla | Agnostic | subscribe() / getSnapshot() |
Full API
Section titled “Full API”// -- Widgets -------------------------------------------------------canvas.blocks: Widget[]; // List of displayed widgetscanvas.addWidget(type, data): Widget; // Add a widget, returns the created objectcanvas.removeBlock(id): void; // Remove a widget by IDcanvas.updateBlock(id, data): void; // Update widget datacanvas.moveBlock(fromId, toId): void; // Reorder a widgetcanvas.clearBlocks(): void; // Clear the canvascanvas.setBlocks(widgets): void; // Replace all widgets at once
// -- Mode ----------------------------------------------------------canvas.mode: 'auto' | 'drag' | 'chat';canvas.setMode(mode): void;
// -- LLM -----------------------------------------------------------canvas.llm: 'haiku' | 'sonnet' | 'gemma-e2b' | 'gemma-e4b';canvas.setLlm(model): void;
// -- MCP -----------------------------------------------------------canvas.mcpUrl: string;canvas.setMcpUrl(url): void;canvas.mcpConnected: boolean;canvas.mcpConnecting: boolean;canvas.mcpName: string;canvas.mcpTools: McpToolInfo[];canvas.setMcpConnecting(bool): void;canvas.setMcpConnected(connected, name?, tools?): void;canvas.setMcpError(error): void;
// -- Chat ----------------------------------------------------------canvas.messages: ChatMsg[];canvas.addMsg(role, content, thinking?): ChatMsg;canvas.updateMsg(id, content, thinking?): void;canvas.clearMessages(): void;
// -- Theme ---------------------------------------------------------canvas.themeOverrides: Record<string, string>;canvas.setThemeOverrides(overrides): void;
// -- Derived metrics -----------------------------------------------canvas.blockCount: number;canvas.isEmpty: boolean;canvas.generating: boolean;Svelte 5 example
Section titled “Svelte 5 example”<script lang="ts"> import { canvas } from '@webmcp-auto-ui/sdk/canvas'; import { WidgetRenderer } from '@webmcp-auto-ui/ui';</script>
<select bind:value={canvas.llm}> <option value="haiku">Haiku (remote)</option> <option value="sonnet">Sonnet (remote)</option> <option value="gemma-e2b">Gemma 2B (local)</option></select>
<button onclick={() => canvas.addWidget('stat', { label: 'Sales', value: '1000' })}> Add widget</button>
<div class="grid grid-cols-2 gap-4"> {#each canvas.blocks as widget (widget.id)} <WidgetRenderer type={widget.type} data={widget.data} /> {/each}</div>
<p>{canvas.blockCount} widgets displayed</p>Vanilla example (React, Vue, plain JS)
Section titled “Vanilla example (React, Vue, plain JS)”import { canvas } from '@webmcp-auto-ui/sdk/canvas-vanilla';
const unsubscribe = canvas.subscribe(() => { const snapshot = canvas.getSnapshot(); console.log('Widgets:', snapshot.blocks.length); renderWidgets(snapshot.blocks);});
canvas.addWidget('stat', { label: 'Users', value: '42k' });
unsubscribe();HyperSkill: Experience Serialization
Section titled “HyperSkill: Experience Serialization”The HyperSkill system serializes a complete experience (widgets, theme, MCP connection, history) into a short, shareable URL parameter. This is what makes demos shareable via simple links.
Serialization flow
Section titled “Serialization flow”graph LR SKILL[HyperSkill Object] -->|JSON.stringify| JSON[JSON String] JSON -->|"> 6KB ?"| CHECK{Size} CHECK -->|Yes| GZ[gzip compress] CHECK -->|No| B64[Base64 encode] GZ --> B64_GZ[Base64 encode] B64 --> PARAM["?hs=..."] B64_GZ --> PARAM_GZ["?hs=gz...."]encodeHyperSkill
Section titled “encodeHyperSkill”Serializes a HyperSkill object into a URL parameter. Automatically compresses with gzip when the payload exceeds 6 KB.
import { encodeHyperSkill } from '@webmcp-auto-ui/sdk';
const skill = { meta: { title: 'Q1 Dashboard', llm: 'sonnet', mcp: 'https://mcp.example.com/mcp', tags: ['sales', 'quarterly'], }, content: { blocks: [ { type: 'stat', data: { label: 'Revenue', value: '$42k', trend: 'up' } }, ], },};
const param = await encodeHyperSkill(skill, 'https://demos.hyperskills.net');// "g_qs9K2wZqE..." or "gz.eJzLSM3JyVcozy/KSQEAHmwFpA==" (if compressed)decodeHyperSkill
Section titled “decodeHyperSkill”Deserializes a URL parameter or full URL into a HyperSkill object:
import { decodeHyperSkill } from '@webmcp-auto-ui/sdk';
const skill = await decodeHyperSkill('g_qs9K2wZqE...');const skill2 = await decodeHyperSkill('https://demos.hyperskills.net?hs=g_qs9K2w...');
console.log(skill.meta.title); // 'Q1 Dashboard'console.log(skill.content.blocks); // [{ type: 'stat', ... }]Automatically supports:
- gzip (
gz.prefix) - brotli (
br.prefix) - Plain Base64 (no prefix)
- Full URLs with
?hs=...
HyperSkill interface
Section titled “HyperSkill interface”interface HyperSkill { meta: HyperSkillMeta; content: unknown;}
interface HyperSkillMeta { title?: string; description?: string; version?: string; created?: string; mcp?: string; mcpName?: string; llm?: string; tags?: string[]; theme?: Record<string, string>; hash?: string; previousHash?: string; chatSummary?: string; provenance?: { mcpServers?: string[]; toolsUsed?: string[]; toolCallCount?: number; skillsReferenced?: string[]; llm?: string; exportedAt?: string; };}Raw functions (re-exports)
Section titled “Raw functions (re-exports)”The SDK re-exports raw functions from the hyperskills NPM package:
import { encode, decode, hash, diff, getHsParam } from '@webmcp-auto-ui/sdk';
const param = await encode('https://example.com', jsonString, { compress: 'gz' });const { sourceUrl, content } = await decode(param);const h = await hash('https://example.com', jsonString);const changes = await diff(oldJson, newJson);const hsParam = getHsParam('https://example.com?hs=abc123'); // 'abc123'Hash and versioning
Section titled “Hash and versioning”The hash system enables skill versioning with a linked list of hashes:
import { computeHash, createVersion } from '@webmcp-auto-ui/sdk';
const h = await computeHash('https://example.com', skillContent);
const version = await createVersion(skill, 'https://example.com', previousHash);// { hash, previousHash, timestamp, skill }Compare two skill versions:
import { diffSkills } from '@webmcp-auto-ui/sdk';const changes = await diffSkills(oldSkill, newSkill);Canvas Store x HyperSkill
Section titled “Canvas Store x HyperSkill”The canvas store natively integrates HyperSkill functions for export and import:
import { canvas } from '@webmcp-auto-ui/sdk/canvas';
// Export current state as skill JSONconst skill = canvas.buildSkillJSON();
// Generate a compressed HyperSkill parameterconst param = await canvas.buildHyperskillParam();const url = `https://demos.hyperskills.net?hs=${param}`;
// Import from a parameterawait canvas.loadFromParam(param);
// Import from a full URLawait canvas.loadFromUrl('https://demos.hyperskills.net?hs=g_qs9K2w...');Skills Registry
Section titled “Skills Registry”The skills registry persists configurations in localStorage with a full CRUD API.
Registry API
Section titled “Registry API”import { createSkill, updateSkill, deleteSkill, getSkill, listSkills, clearSkills, loadSkills, loadDemoSkills, onSkillsChange,} from '@webmcp-auto-ui/sdk';interface Skill { id: string; name: string; description?: string; content: any; created: number; updated: number; version?: string; tags?: string[];}
interface SkillBlock { type: string; data: Record<string, unknown>;}CRUD operations
Section titled “CRUD operations”const skill = createSkill('dashboard-q1', { description: 'Quarterly sales dashboard', content: { blocks: [{ type: 'stat', data: { label: 'Revenue', value: '$42k' } }] }, tags: ['sales'],});
const retrieved = getSkill(skill.id);const all = listSkills();updateSkill(skill.id, { name: 'Dashboard Q1 2024' });deleteSkill(skill.id);Batch operations
Section titled “Batch operations”await loadSkills(skillObjects); // Load a set of skills at onceawait loadDemoSkills(); // Load pre-configured demo skillsclearSkills(); // Clear the entire registryReactivity
Section titled “Reactivity”const unsubscribe = onSkillsChange((skills) => { console.log(`${skills.length} skills in registry`);});Auto-save example
Section titled “Auto-save example”<script lang="ts"> import { canvas } from '@webmcp-auto-ui/sdk/canvas'; import { createSkill, updateSkill } from '@webmcp-auto-ui/sdk';
let currentSkillId: string | null = null;
function autoSave() { const json = canvas.buildSkillJSON(); if (!currentSkillId) { const skill = createSkill(`skill_${Date.now()}`, json); currentSkillId = skill.id; } else { updateSkill(currentSkillId, json); } }
$effect(() => { canvas.blocks; // Reactive dependency const timer = setTimeout(autoSave, 5000); return () => clearTimeout(timer); });</script>MCP Demo Servers
Section titled “MCP Demo Servers”List of available MCP demo servers for testing the agent:
import { MCP_DEMO_SERVERS } from '@webmcp-auto-ui/sdk';
interface McpDemoServer { name: string; description: string; url: string; tools: string[];}
MCP_DEMO_SERVERS.forEach(server => { console.log(`${server.name}: ${server.url}`); console.log(` Tools: ${server.tools.join(', ')}`);});This array feeds the <RemoteMCPserversDemo> component from the UI package.
Tutorial: Multi-Skills Application
Section titled “Tutorial: Multi-Skills Application”Step 1: Initialize the canvas
Section titled “Step 1: Initialize the canvas”<script lang="ts"> import { canvas } from '@webmcp-auto-ui/sdk/canvas'; import { listSkills, createSkill, getSkill, loadDemoSkills } from '@webmcp-auto-ui/sdk'; import { WidgetRenderer, LLMSelector } from '@webmcp-auto-ui/ui';
let skills = $state(listSkills()); let selectedId = $state<string | null>(null);
$effect(() => { if (skills.length === 0) { loadDemoSkills().then(() => { skills = listSkills(); }); } });</script>Step 2: Navigate between skills
Section titled “Step 2: Navigate between skills”<aside class="w-64 p-4 bg-gray-100"> <h2 class="font-bold mb-4">Skills</h2> <ul> {#each skills as skill (skill.id)} <li> <button class:bg-blue-500={selectedId === skill.id} onclick={() => { selectedId = skill.id; const s = getSkill(skill.id); if (s?.content?.blocks) canvas.setBlocks(s.content.blocks); if (s?.content?.llm) canvas.setLlm(s.content.llm); }} > {skill.name} </button> </li> {/each} </ul></aside>Step 3: HyperSkill export
Section titled “Step 3: HyperSkill export”<script lang="ts"> async function shareSkill() { const param = await canvas.buildHyperskillParam(); const url = `https://demos.hyperskills.net?hs=${param}`; await navigator.clipboard.writeText(url); alert('Link copied!'); }</script>
<button onclick={shareSkill}>Share</button>Step 4: Import from URL
Section titled “Step 4: Import from URL”<script lang="ts"> import { onMount } from 'svelte'; import { getHsParam } from '@webmcp-auto-ui/sdk';
onMount(async () => { const param = getHsParam(window.location.href); if (param) await canvas.loadFromParam(param); });</script>Integration with Other Packages
Section titled “Integration with Other Packages”graph TD SDK["@webmcp-auto-ui/sdk"] -->|canvas store| UI["@webmcp-auto-ui/ui"] SDK -->|HyperSkill export| AGENT["@webmcp-auto-ui/agent"] AGENT -->|summarizeChat| SDK UI -->|WidgetRenderer| SDK SDK -->|MCP_DEMO_SERVERS| UI SDK -->|"hyperskills (npm)"| EXT[hyperskills package]Best Practices
Section titled “Best Practices”Why are there two canvas store versions?
The Svelte 5 store uses runes ($state/$derived) for fine-grained native reactivity. The vanilla store uses a subscribe/getSnapshot pattern compatible with any framework. Both share the same internal logic.
Is the HyperSkill parameter secure?
The parameter is Base64-encoded (optionally compressed), not encrypted. Don’t put sensitive data in a skill. Chat summaries are automatically anonymized by summarizeChat.
How does compression work?
Above 6 KB, encodeHyperSkill uses gzip via the browser’s CompressionStream API. The gz. prefix tells decodeHyperSkill to decompress. Brotli is also supported (br. prefix), but gzip is the default due to wider support.
Do skills persist across sessions? Yes, the registry uses localStorage. Skills remain available as long as the user doesn’t clear their browser storage. For cross-device sharing, use HyperSkill export.