@webskill/sdk
v0.25.0
Published
WebSkill — browser/Node agent skill runtime (skills, tools, MCP, governance, UI)
Downloads
2,789
Readme
@webskill/sdk
WebSkill is an agent skill runtime for Node.js and the browser. Skills are
self-contained directories (SKILL.md + scripts/ + references/ + assets/)
that an LLM agent discovers, reads, and executes through a multi-turn agent loop
with human-in-the-loop interaction, artifacts, tracing, MCP integration, and
governance.
Features
- Skill protocol — Agent Skills compatible discovery, validation, cataloging
(JSON / XML), declarative
dependenciesandallowed-tools, skill packs (multi-skill zip export/import with integrity digests). - Agent loop — multi-turn tool calling with structured errors fed back to the LLM, guardrails, lifecycle events, memory, human interaction (missing-parameter forms, confirmations, authorization prompts), and resumable interrupted runs.
- Script sandbox — two tiers. Isolation-grade: Node
ProcessSandboxExecutor(fork +--permission, real process isolation). (child_process.fork+--permission, experimental; fs scoped to the skill root, workers/child processes/addons denied by default — note the permission model has NO network dimension, sofetch/WebSocketstay patch-enforced and barenode:netimports remain a documented residual), and the browser opaque origin sandbox (sandbox="allow-scripts"iframe hosting a classic Worker —type:"module"workers cannot load in opaque origins, verified). Capability tier (NOT a security boundary):SandboxedScriptExecutor/BrowserWorkerScriptExecutorin blob-Worker mode, with deny-by-default network policy, builtin-module allowlist, forced approval, timeouts. Known proactive escape hatches (process.binding,dlopen,abort, …) are stripped in the worker entry (0.2.3); other host-shared surfaces are NOT defended — useProcessSandboxExecutorfor untrusted skills. navigator.webskill— a browser facade (discover/read/validate/run/install/uninstall) assembled explicitly in one call.- MCP — call page-provided tools and consume page-declared dynamic skills over standard MCP transports (MessageChannel).
- Governance — LLM-generated skill candidates with mandatory review, approval workflows, audit log, versioning/rollback, quarantine, evaluation, and scoring.
- UI — framework-agnostic forms, result rendering (markdown, tables, SVG charts), generative UI adapters (Vercel AI SDK, OpenUI, A2UI), plus React and Vue component primitives.
Installation
npm install @webskill/sdkReact/Vue integrations require their respective peer dependencies
(react + react-dom, or vue), which are optional.
zod ships as a direct dependency: the @webskill/sdk/ui catalog references it
in its static import graph and in its public types, so declaring it optional
would misrepresent the artifact. Hosts do not need to install it themselves.
tar, oxc-parser and @modelcontextprotocol/sdk remain optional peers —
they are loaded lazily and only when the corresponding API is called.
Requirements
- Node.js >= 22.18 (native type stripping for
.tsskill scripts) - Modern browsers for the browser host (OPFS, Web Workers, ES modules)
Quick start (Node)
Runs as-is with the bundled mock LLM — see
examples/quickstart-node for the full project.
import { WebSkillRuntime } from '@webskill/sdk';
import { NodeFS, SandboxedScriptExecutor } from '@webskill/sdk/node';
import { MockLlmClient } from '@webskill/sdk/testing';
const fs = new NodeFS();
const llm = new MockLlmClient([
{ toolCalls: [{ id: 'c1', name: 'read_skill_file', arguments: { skillName: 'greeter' } }] },
{ toolCalls: [{ id: 'c2', name: 'greeter__greet', arguments: { text: 'world' } }] },
{ content: 'Greeted the world.' }
]);
const runtime = new WebSkillRuntime({ fs, roots: ['./skills'], llm, executor: new SandboxedScriptExecutor(fs) });
const catalog = await runtime.discover();
console.log(catalog.entries.map((e) => e.name));
const { output, run } = await runtime.run('Greet the world.');
console.log(output, run.status, run.trace.length);For a real LLM, replace the mock with OpenAiCompatibleClient:
import { OpenAiCompatibleClient } from '@webskill/sdk';
const llm = new OpenAiCompatibleClient({
baseUrl: 'https://your-llm-endpoint/v1',
apiKey: process.env.LLM_API_KEY,
model: 'your-model'
});Subpath exports
@webskill/sdk (main entry)
Core protocol + runtime engine: discovery, catalog, validation, agent loop, LLM
clients, routing, interaction types, memory, artifacts, tracing, network policy,
capability approval, and the environment-agnostic createWebSkillApi facade.
import { SkillDiscovery, validateSkills, createWebSkillApi } from '@webskill/sdk';
const api = createWebSkillApi({ fs, roots: ['./skills'], llm, skillManager });
const report = await api.validate('./skills');
const run = await api.run('Summarize the references of skill greeter.');@webskill/sdk/node
Node host: NodeFS, script executors (in-process, worker_threads sandbox, and
ProcessSandboxExecutor for real process isolation),
file-backed stores, SkillManager (install/export incl. multi-skill packs),
CLI UI bridge, schema inference.
import { NodeFS, SkillManager } from '@webskill/sdk/node';
const manager = new SkillManager({ managedRoot: './.webskill/skills' });
await manager.install({ type: 'http', url: 'https://example.com/greeter.zip' });
await manager.exportPack(['greeter', 'calculator'], { outPath: './pack.zip' });
console.log((await manager.verifyIntegrity('greeter')).ok);@webskill/sdk/browser
Browser host: OPFS provider, opaque origin iframe script sandbox (default on
the main thread; engines running inside a Worker automatically fall back to the
blob Worker form — no iframe is available there, documented), worker runtime
host/client, BrowserSkillManager, and the navigator.webskill assembler. See
examples/quickstart-browser.
import { installWebSkillNavigator } from '@webskill/browser';
const api = installWebSkillNavigator({ roots: ['/skills'], llm });
const catalog = await navigator.webskill.discover('/skills');
const run = await navigator.webskill.run('Greet the world.');@webskill/sdk/mcp
MCP integration: MessageChannel transport, endpoint registry, tool resolver, temporary (page-declared) skills, WebMCP adapter, runtime plugin.
import { MessageChannelTransport, serveSkillAsMcp } from '@webskill/sdk/mcp';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const server = new McpServer({ name: 'page', version: '0.1.0' });
serveSkillAsMcp(server, { name: 'greeter', description: 'Greet someone', body: '# Greeter' });
await server.connect(new MessageChannelTransport(port));@webskill/sdk/ui
Framework-agnostic UI: form models + WebFormBridge, result rendering
(markdown / table / mini-chart SVG), mini markdown, generative UI adapters
(Vercel AI SDK, OpenUI, A2UI).
import { WebFormBridge } from '@webskill/sdk/ui';
const bridge = new WebFormBridge({ mount: document.querySelector('#ui') });
// pass as uiBridge to the runtime: forms, confirmations, and authorization
// prompts render as real DOM, results (incl. charts) render on completion.@webskill/sdk/ui-react
React bridge state + InteractionForm / ResultBlocks / StreamingText
components (requires react + react-dom).
import { ReactBridgeState, InteractionForm, ResultBlocks } from '@webskill/sdk/ui-react';
const bridge = new ReactBridgeState();
// <InteractionForm bridge={bridge} /> <ResultBlocks bridge={bridge} />@webskill/sdk/ui-vue
Vue bridge state + equivalent components (requires vue).
import { VueBridgeState, InteractionForm, ResultBlocks } from '@webskill/sdk/ui-vue';
const bridge = new VueBridgeState();
// <InteractionForm :bridge="bridge" /> <ResultBlocks :bridge="bridge" />@webskill/sdk/governance
Governance: skill candidates with mandatory review, approval workflows, audit log, versioning/rollback, quarantine, evaluation, scoring, dependency graph.
import { DependencyGraph } from '@webskill/sdk/governance';
const graph = DependencyGraph.buildFromCatalog(catalog.entries, documents);
console.log(graph.dependenciesOf('greeter'));@webskill/sdk/testing
Test facilities (moved out of the main entry in 0.1.0): MockLlmClient,
MockUiBridge, InMemoryStore, MemoryArtifactStore.
import { MockLlmClient, MockUiBridge, InMemoryStore, MemoryArtifactStore } from '@webskill/sdk/testing';
const llm = new MockLlmClient([{ content: 'deterministic answer' }]);allowed-tools
A skill may narrow the tool surface it needs by declaring allowed-tools in its
SKILL.md frontmatter. Three kinds of tools can be listed:
allowed-tools:
- render # a script of this skill (scripts/render.ts)
- endpoint:github/create_issue # one tool of an MCP endpoint
- endpoint:github/* # every tool of an MCP endpoint
- mcp#read_file # one page-provided (WebMCP) toolMatching rules:
- Only whole-entry matches and a trailing
/*are supported. Wildcards in any other position are not, and a bareendpoint:githubis not a prefix wildcard — it simply matches nothing. - A bare identifier means a script of the declaring skill only. It never grants access to another skill's script of the same name.
When several skills are active at the same time:
| Situation | Result |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| No active skill declares allowed-tools | Unrestricted |
| At least one active skill declares it | A tool is allowed if it matches any declaring skill's list, or if some active skill declares nothing |
Enforcement level differs per tool kind in 0.3.0:
- Scripts — enforced since 0.0.5: a script outside the list is not
registered, and calling it by name returns
TOOL_NOT_FOUND. - MCP / WebMCP tools — 0.3.0 only records a
run.warningnaming the tool and the entry to add, and still allows the call. This becomes a rejection in 0.4.0, so treat the warning as a build error in your own pipeline.
Stability
This package follows semver starting at 0.1.0. Public symbols carry JSDoc stability tags:
@stable— covered by the semver contract: bug fixes ship in patch, backwards-compatible capabilities in minor, breaking changes only in major (with release notes).@experimental— may change in minor/patch releases. Currently:ExperimentalWebMcpAdapter,resumeRunand theRunSnapshotformat, the A2UI/OpenUI adapters, and theauthorizeinteraction type.
The public API surface of every subpath is pinned by checked-in .d.ts
snapshots (api-snapshots/); any intentional change requires
pnpm api:update and is reviewable in the diff.
License
MIT
