Deploy to macOS as easily as you deploy to Vercel. Bundle your Next.js
app into a .harness file; your users open it with the WebHarness Player —
no Apple Developer account, no Xcode, no notarization wait.
Status: v0.2.0-beta.2 (changelog, release notes).
npx @mentu/webharness-cli init my-app
cd my-app
npx @mentu/webharness-cli devThree packages, all on 0.2.0-beta.2:
@mentu/webharness-cli— scaffold, dev-loop, build, sign, bundle. Reference →@mentu/webharness-sdk— TypeScript bindings for 22 native capabilities. API reference →@mentu/webharness-mcp— MCP aggregator that lets Claude Code / Cursor / Zed drive every running app. README →
Your web app (React, Vue, Svelte, vanilla JS)
↕ @mentu/webharness-sdk (TypeScript)
WebHarness shell (WKWebView + ControlServer)
↕ capability bridge
Vault · SQLite · Perception · MCP plugins · ANE · file system
Your web app runs inside a native WKWebView. The @mentu/webharness-sdk gives it
typed access to macOS capabilities through a zero-config bridge. No Swift
required.
WebHarness uses the MCP protocol as its plugin system. Any MCP server is
a WebHarness plugin — Claude Desktop plugins, Cursor plugins, and Anthropic
reference servers all work without modification. Declare what you need in
mcp.servers; the runtime proxies calls.
# webharness.yml
mcp:
servers:
- name: github
transport:
type: bundled
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
credentials: github_token # vault key — injected as GITHUB_TOKEN
- name: filesystem
transport:
type: bundled
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]Then from your app:
import { mcp } from '@mentu/webharness-sdk'
const repos = await mcp.call('list_repos', { user: 'me' })
const found = await mcp.discover('read_file') // any tool from any pluginThis is why the plugin ecosystem is the npm registry, not a private WebHarness store.
- Pack Your Own —
webharness packproduces a.appbundle, you sign with your Developer ID, you notarize. Full control, full friction. - Fixed Shell Player —
webharness bundleproduces a.harnesspackage (zip ofharness.json+ web assets). End users open it with the Player — no signing, no notarization. v0.2 ships the format and the file-association mechanics; the Player binary and the WebHarness.io service land in v0.3.
See DESIGN-distribution-model.md.
webharness.yml declares what the app may use. The user sees a one-time
consent sheet for tier-.sensitive capabilities (deny-defaulted for the
red-tier).
capabilities:
- fs.read
- fs.write
- vault.read # Keychain-backed credential store
- vault.write
- sql.query # Local SQLite
- sql.exec
- sql.migrate
- sql.tables
- mcp.call # Talk to declared MCP plugin servers
- mcp.discover
- mcp.route
- perception.observe # Ambient classifier feed
- notify.send
- pdf.generate
- terminal.create # PTY sessions
- browser.agent.browse # Headless Chromium (148 MCP tools)
- desktop.orchestrate # AXUIElement + CGEvent
- ane.embed # On-device Apple Neural Engine
# Capabilities that require run-time consent (sdk.requestCapability(name))
optional_capabilities:
- notify.sendSeventeen native capability domains are baked into the runtime: fs,
vault, notify, pdf, sql, perception, browser (screenshot +
agent), desktop, terminal, vector, ane, model (Apple Intelligence
on macOS 26+), mic, sys, agent, network, workflow, and the mcp
proxy. Anything beyond that — and the bulk of the agentic ecosystem — comes
through MCP plugins declared under mcp.servers.
import {
isNative,
vault,
mcp,
perception,
sql,
ane,
model,
network,
desktop,
sys,
fs,
notify,
requestCapability,
} from '@mentu/webharness-sdk'
// Keychain-backed credentials — per-bundle scope
await vault.set('openai', process.env.OPENAI_KEY!)
const { value } = await vault.get('openai')
// Local SQLite — one DB per app bundle
await sql.migrate('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)', 1)
const rows = await sql.query('SELECT * FROM notes')
// On-device LLM via Apple Intelligence (macOS 26+ / FoundationModels)
const status = await model.status()
if (status.available) {
const { text, tokens } = await model.infer('Summarize: ' + emailBody)
}
// Native Accessibility — read UI of any app without MentuDesktopMCP
const apps = await desktop.apps()
const finderButton = await desktop.find({ role: 'AXButton', app: 'Finder' })
// Active network connections via lsof
const flows = await network.flows({ limit: 20 })
// On-device ANE — embeddings, classification, search, OCR
await ane.embed('hello world')
// MCP plugins — any MCP server is callable
const tools = await mcp.discover('repo')
const out = await mcp.call('list_repos', { user: 'me' })
// Run-time consent for an optional_capability declared in webharness.yml
const ok = await requestCapability('notify.send')
if (ok) await notify.send({ title: 'WebHarness', body: 'hi' })| Electron | Tauri | WebHarness | |
|---|---|---|---|
| Bundle size | ~150MB | ~3MB | ~0MB (system WKWebView) |
| Local AI | — | — | Apple Silicon ANE |
| Plugin system | npm + bespoke | Rust crates | MCP protocol (any MCP server) |
| Distribution | DIY signing | DIY signing | .app or .harness (Player) |
| Backend language | Node.js | Rust | None (JS SDK only) |
| Platform | Any | Any | macOS |
webharness/
├── packages/
│ ├── sdk/ @mentu/webharness-sdk — TypeScript, runs in the web app
│ ├── cli/ webharness CLI — init / dev / build / pack / bundle
│ └── runtime/ Swift — WebHarnessHost (shell), WebHarnessRuntime (lib),
│ WebHarnessRuntimePackager (CFBundleDocumentTypes for
│ io.webharness.harness)
├── webharness.schema.json JSON Schema v0.2 (IDE autocomplete)
├── docs/
│ ├── design/ Eight design docs (DESIGN-*.md) — v0.3 work
│ ├── RELEASE-v0.2.md
│ └── CONTEXT-webharness-vision.md
└── CHANGELOG.md
Ten commands — init, dev, build, pack, bundle, status, call, logs, run, install-player. Full reference + flags + examples in packages/cli/README.md.
webharness init [name] --template default|menubar|spotlight
webharness dev # Connect to native shell (live reload)
webharness pack --sign "Developer ID: …" --notarize <profile>
webharness bundle --out MyApp.harness # Produce a Player package
webharness status # List running WebHarness apps
webharness call canary vault.read '{"key":"x"}'AGENT.md— usage contract for AI coding agents (Claude Code, Cursor, Codex).docs/ARCHITECTURE.md— system-level overview: runtime, bridge, capability tiers, consent flow, security.packages/cli/README.md— 10-command CLI reference.packages/sdk/README.md— 22-capability TypeScript API reference.packages/mcp/README.md— MCPHarness aggregator: 4 meta-tools, install for 8 LLM clients.docs/TOOL-SCHEMAS.md— every tool's input/output schema + examples.docs/TROUBLESHOOTING.md— port-file, tokens, app-unreachable triage.docs/MCP-USAGE.md— per-client setup (Claude Code, Cursor, Zed, ...).
docs/CAPABILITIES-surface.md— capability-tier ground truth.docs/prds/PRD-webharness-mcp.md— MCPHarness PRD.docs/intent/BUILD-webharness-v02.md— master intent doc.
The eight design docs in docs/design/ capture v0.3 work
scoped out of v0.2. Read them before proposing changes to the deferred
surfaces. Roll-up index lives in docs/ARCHITECTURE.md.
- macOS 14+ (some capabilities require macOS 26+ — e.g.
model.local.*) - Node.js 20+