WCP 1.1.0

Widget Context Protocol

An open standard for widget–dashboard communication. Any server — a Docker container, a local process, or a remote service — can expose a compact UI widget and register its capabilities with any WCP-compatible host dashboard.

Current version: WCP 1.1.0. This document is the authoritative specification. WCP is free to implement — no licence required.

WCP is deliberately modelled on Anthropic's Model Context Protocol (MCP): a simple, discoverable interface that any compliant host can speak. A WCP widget exposes a small set of HTTP endpoints and, optionally, communicates with its host via the browser's postMessage API.

Protocol Overview

A WCP widget is any HTTP server that implements the mandatory endpoint set. The host dashboard discovers it by fetching its manifest, embeds it in an iframe, and routes postMessage events between widget and dashboard.

  Dashboard host (port 3737)
       |
       +-- GET /api/widget-manifest?url=http://widget:3740
       |        +-- fetches GET http://widget:3740/widget/wcp   <- WCP manifest
       |
       +-- Embeds http://widget:3740/widget/ in an <iframe>
       |
       +-- Listens for window.postMessage events
                +-- wcp:open-window, wcp:open-tab, wcp:copy-to-clipboard ...

The widget's URL is the only thing the dashboard needs. Everything else — name, version, icon, capabilities, configuration schema — is declared in the manifest.

Mandatory Endpoints

Every WCP-compliant widget server MUST expose these four endpoints under the path prefix /widget/:

EndpointMethodDescription
/widget/GETCompact widget HTML — loaded by the dashboard in an iframe
/widget/wcpGETWCP manifest (JSON) — the widget's self-description
/widget/healthGETHealth check — returns {"status":"ok","name":"<name>"}
/widget/icon.svgGETWidget icon (SVG preferred, PNG fallback)
CORS required: All endpoints must respond with Access-Control-Allow-Origin: * and handle OPTIONS preflight requests with 204 No Content.

Optional Endpoints

EndpointMethodDescription
/widget/fullGETFull-page view — opened in a new window or tab by the host
/widget/manifestGETSimplified manifest (backward compatibility)
/widget/configurePOSTAccept configuration JSON from the host WCP 1.1.0
/widget/api/searchGETAutocomplete for location-search config fields WCP 1.1.0
/widget/api/*anyWidget-specific API routes
Electron note: When /widget/full is opened in an Electron utility window with titleBarStyle: hiddenInset, 100vh is slightly larger than the visible area. Always use height: 100% cascaded from html, body { height: 100% } in full-page layouts.

WCP Manifest Schema

GET /widget/wcp returns:

{
  "wcp":        "1.1.0",
  "name":       "My Widget",
  "version":    "1.0.0",
  "description":"What it does.",
  "icon":       "/widget/icon.svg",
  "health":     "/widget/health",
  "widget": {
    "path":            "/widget/",
    "renderMode":      "iframe",
    "refreshInterval": 0,
    "authType":        "none",
    "defaultSize":     { "w": 4, "h": 3 }
  },
  "pages": [{
    "id":     "full",
    "path":   "/widget/full",
    "title":  "My Widget — Full Page",
    "window": { "width": 1100, "height": 700 }
  }],
  "actions": [
    { "id":"open-window", "type":"wcp:open-window", "label":"Open Full Screen", "page":"full" },
    { "id":"open-tab", "type":"wcp:open-tab", "label":"Open in Tab", "page":"full",
      "tab":{ "title":"My Widget", "icon":"/widget/icon.svg" }, "persist":false }
  ],
  "config": [ /* see Widget Configuration */ ]
}

Field Reference

FieldRequiredDescription
wcprequiredProtocol version — currently "1.1.0"
namerequiredHuman-readable widget name
versionrequiredWidget version (semver)
descriptionrequiredOne or two sentences
iconrequiredPath to icon (relative to widget origin)
healthrequiredPath to health endpoint
widget.pathrequiredPath to compact iframe view
widget.renderModerequired"iframe"
widget.refreshIntervaloptionalSeconds between host refreshes; 0 = widget manages itself
widget.authTypeoptional"none" | "bearer" | "session"
widget.defaultSizeoptionalDefault grid size {w, h}
pagesoptionalFull-page views
actionsoptionalAction buttons shown on the dashboard card
configoptionalConfiguration schema (WCP 1.1.0)

postMessage Protocol

Widgets inside iframes communicate with the host via window.parent.postMessage(payload, '*').

typeRequired fieldsHost action
wcp:open-windowpage, width?, height?Opens named page in a new native window
wcp:open-tabpage, tab:{title,icon}Opens page as a persistent dashboard tab
wcp:copy-to-clipboardtextCopies text — bypasses iframe clipboard sandbox
wcp:download-filefilename, content, mimeTypeTriggers file save dialog
wcp:import-themeurl (to a .pbtheme.json)Fetches and imports a dashboard theme

Example

// Open full-page view
window.parent.postMessage({ type: 'wcp:open-window', page: 'full', width: 1100, height: 700 }, '*');

// Copy text (clipboard blocked in sandboxed iframes)
window.parent.postMessage({ type: 'wcp:copy-to-clipboard', text: 'Hello!' }, '*');
Security: Hosts validate that events originate from a known widget origin. wcp:copy-to-clipboard and wcp:import-theme are exempt from this check.

Widget Configuration WCP 1.1.0

Widgets declare a config schema in their manifest. The host renders a settings form and POSTs values to /widget/configure.

"config": [
  { "id":"location", "type":"location-search", "label":"Location",
    "placeholder":"Search for a city…", "searchUrl":"http://localhost:3739" },
  { "id":"units", "type":"select", "label":"Units",
    "options":[{"value":"celsius","label":"°C"},{"value":"fahrenheit","label":"°F"}] },
  { "id":"refresh", "type":"number", "label":"Refresh (minutes)", "min":5, "max":60 }
]

Config Field Types

typeDescriptionExtra fields
location-searchSearchable autocomplete via /widget/api/searchsearchUrl, placeholder
selectDropdown with fixed optionsoptions:[{value,label}]
numberNumeric inputmin, max, step
textFree-text inputplaceholder, maxLength

Hosts proxy location searches via GET /api/widget-search?url=<widgetUrl>&q=<query> to avoid CORS restrictions.

CORS & Security

Required CORS Headers

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type

Return 204 No Content for OPTIONS preflight requests. Failing to handle OPTIONS causes cross-origin POST requests to /widget/configure to fail silently.

Iframe Sandbox

Widgets run in a sandboxed <iframe>. navigator.clipboard, file downloads, and window.open are blocked. Use the postMessage protocol — the host acts on the widget's behalf.

Origin Validation

Hosts should reject postMessage events from unknown origins, storing the widget origin at registration time. wcp:copy-to-clipboard and wcp:import-theme may be exempted.

Versioning

Protocol Versions

VersionStatusKey additions
1.0.0SupersededMandatory endpoints, manifest, postMessage types
1.1.0CurrentWidget configuration schema, location-search, config proxy

Docker Image Tags

Use a compound tag encoding widget version and WCP protocol version:

# Format: {widget-version}-wcp{wcp-version}
docker tag myimage penrithbeacon/wcp-widget-example:1.0.0-wcp1.1.0
docker tag myimage penrithbeacon/wcp-widget-example:latest

Docker Convention

Image Naming

penrithbeacon/wcp-widget-{name}:{widget-ver}-wcp{wcp-ver}
penrithbeacon/wcp-widget-{name}:latest

Port Assignment

PortWidgetImage
3737reserved WCP Bonjourpenrithbeacon/wcp-bonjour (coming soon)
3738QR Generatorpenrithbeacon/wcp-widget-qr-generator
3739Weather Tickerpenrithbeacon/wcp-widget-weather-ticker
3740WCP Theme Studiopenrithbeacon/wcp-widget-theme-studio
3741+Future widgetsSequential assignment

Minimal docker-compose.yml

services:
  my-widget:
    image: penrithbeacon/wcp-widget-example:latest
    container_name: wcp-widget-example
    ports:
      - "374X:374X"
    restart: unless-stopped
    volumes:
      - widget_data:/app/data
volumes:
  widget_data:

Widget Design Guide

This guide is not part of the WCP protocol itself — it is a practical reference for developers and designers building widgets that will be hosted in WCP-compatible dashboards. Following these conventions ensures your widget looks intentional, feels native, and works correctly across all supported sizes.

Try themes before you build. The WCP Theme Studio widget lets you explore all built-in themes and create your own. Pull it from Docker Hub and add it to any WCP dashboard to design with the real colour tokens before writing a line of widget code.

Sizing & The Grid

WCP-compatible dashboards use a 12-column fluid grid with 100px fixed row height. Columns are percentage-based — their pixel width scales with the available dashboard area.

Grid Dimensions

AxisUnitHow it scales
Width1 of 12 columns = 8.3%Fluid — scales with dashboard width
Height1 row = 100pxFixed — always 100px per row unit

Common Sizes & Use Cases

Columns × Rows% widthHeightBest for
1 × 18%100pxSingle metric, icon indicator, status dot
4 × 233%200pxCompact status card, small chart, quick-action panel
4 × 433%400pxControl panel, settings widget, vertical list
6 × 250%200pxHalf-width dashboard card, medium chart
8 × 267%200pxWide chart, data table, timeline
8 × 467%400pxRich data visualisation, map, complex UI
12 × 2100%200pxFull-width banner, scrolling ticker strip
12 × 4100%400pxFull-width application panel
12 × 12100%1200pxFull embedded application — equivalent to a complete app UI
12 × 12 = full application. There is no upper limit on rows. A 12-column, many-row widget is a fully embedded application inside the dashboard. This is intentional — WCP is designed to host both compact widgets and complete applications in the same grid.

Setting Your Default Size

Declare your widget's preferred grid size in the WCP manifest under widget.defaultSize. Choose the minimum size at which your widget is still fully functional and readable:

"widget": {
  "defaultSize": { "w": 4, "h": 4 }
}

Users can always resize your widget after adding it. The default is just the starting point.

Aspect Ratios

  • Square (1:1 columns:rows at equal scale) — suits clocks, gauges, donut charts
  • Wide and shallow (e.g. 12×1, 8×2) — suits tickers, progress bars, status strips
  • Tall and narrow (e.g. 4×4, 4×6) — suits vertical lists, control panels, menus
  • Wide and tall (e.g. 8×4, 12×6) — suits data tables, charts, full app panels

Responsive Behaviour

Your widget's compact view (/widget/) runs inside a sandboxed iframe. The iframe is resized by the user — your widget must fill its allocated space exactly and respond correctly to any valid grid size.

Essential CSS

/* Always set these on your widget's root */
html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;   /* prevent scrollbars inside compact view */
  box-sizing: border-box;
}

.widget-root {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}
Never hardcode pixel dimensions inside a compact widget. A widget hardcoded to 400px wide will break at 3-column or 12-column sizes. Use width: 100%, height: 100%, and relative units throughout.

Minimum Practical Sizes

Design with a minimum in mind — the smallest size at which your widget is still useful:

  • Anything below 2 columns × 1 row (≈200px × 100px) is too small for interactive content — use it only for indicators or icons
  • 4 × 2 (≈33% × 200px) is the practical minimum for most useful widgets
  • Set widget.defaultSize to your minimum functional size, not your ideal size

Scrollbars in Compact View

Avoid scrollbars in the compact widget view — they signal that content overflows the allocated space, which feels broken. Design the compact view to fit within its grid cell. If you have more content, expose it through the full-page view (/widget/full) opened via wcp:open-window or wcp:open-tab.

Full-Page View (/widget/full)

The full-page view opens in a dedicated native window at the dimensions you specify in the manifest. This is where you can show the complete application experience without space constraints. Use height: 100% cascaded from html, body — never 100vh inside an Electron-hosted window (see Optional Endpoints).

Theme Compatibility

WCP dashboards use a CSS custom property theming system. If your widget reads these variables, it will automatically match whatever theme the user has selected — including all 15 themes in the WCP Theme Studio and any custom theme they create.

CSS Custom Properties

VariablePurposeDark default
--bgPage/window background#0d1117
--surfaceCard / panel background#161b22
--surface2Input / nested surface#1c2128
--borderBorders and dividers#30363d
--textPrimary text#e6edf3
--mutedSecondary / de-emphasised text#8b949e
--accentBrand colour, buttons, highlights#f0883e
--greenSuccess, running, positive#3fb950
--redError, stopped, negative#f85149
--yellowWarning, pending#d29922
--blueInfo, links#58a6ff
--radiusBorder radius for cards/buttons8px
--shadowBox shadow for elevated surfaces0 4px 16px rgba(0,0,0,.45)

Using Theme Variables in Your Widget

/* Your widget will automatically match the host theme */
body {
  background: var(--bg, #0d1117);      /* fallback for standalone viewing */
  color: var(--text, #e6edf3);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.card {
  background: var(--surface, #161b22);
  border: 1px solid var(--border, #30363d);
  border-radius: var(--radius, 8px);
}

.btn-primary {
  background: var(--accent, #f0883e);
  color: #0d1117;
}
Always provide fallback values. When your widget is opened standalone (e.g. directly in a browser for testing), the host's CSS custom properties won't be injected. Fallbacks ensure the widget looks correct in both contexts.

Penrith Beacon WCP Native Themes

Three themes match the Penrith Beacon WCP Dashboard exactly. Encourage users who want consistent look-and-feel across multiple dashboards and widgets to select one of these as their base:

ThemeIDBest for
Penrith Beacon WCP Darkpb-wcp-darkDefault dark environment
Penrith Beacon WCP Lightpb-wcp-lightLight/daytime use
Penrith Beacon WCP High Contrastpb-wcp-hcAccessibility, high ambient light

All three are available in the WCP Theme Studio (v1.1.0+) and shareable as .pbtheme.json URLs.

Dashboard UX Conventions

  • Base font size: 13px — keep text legible at this scale; avoid fonts smaller than 11px
  • No modal dialogs in compact view — modals in iframes are clipped by the grid cell; use the full-page view instead
  • No native alerts or confirmswindow.alert() is blocked in sandboxed iframes; use inline status messages
  • Prefer dark backgrounds — the majority of dashboard users use dark themes; test dark first, light second
  • Touch-friendly tap targets — minimum 32px for interactive elements, as dashboards may run on tablets

WCP Bonjour Coming Soon — Port 3737

WCP Bonjour is the zero-configuration service discovery component of WCP — equivalent to Apple's Bonjour networking protocol. Running on port 3737, it maintains a registry of available WCP widgets, enabling dashboards to discover and load authorised widgets automatically.

Port 3737 is reserved across all WCP installations. Do not assign other widgets to this port.

Planned Capabilities

  • Widget registry — searchable catalogue of known WCP widgets with cached manifests
  • Authorisation control — administrators select which widgets are available locally
  • Auto-population — dashboards fetch the Bonjour manifest to create a default widget folder and add all authorised widgets in one step, without manual configuration
  • Health monitoring — tracks reachability of all registered widgets
  • Admin UI — browser-based management interface

Planned Manifest Extension

// GET http://localhost:3737/widget/wcp
{
  "wcp":     "1.1.0",
  "name":    "WCP Bonjour",
  "version": "1.0.0",
  "role":    "registry",
  "widgets": [
    { "url":"http://localhost:3739", "authorised":true, "manifest":{ /* cached */ } }
  ]
}

Full specification will be published at widgetcontextprotocol.com/#bonjour when the container is released.

Changelog

WCP 1.1.0 — 2026-05

  • Added config field to WCP manifest — declarative configuration schema
  • New field type: location-search with server-side autocomplete proxy
  • New field types: select, number, text
  • Host proxies config submissions via POST /api/widget-configure
  • Host proxies location searches via GET /api/widget-search
  • Compound version tag convention: {widget-ver}-wcp{wcp-ver}
  • Port 3737 reserved for WCP Bonjour discovery service

WCP 1.0.0 — 2026-05

  • Initial specification
  • Mandatory endpoints: /widget/, /widget/wcp, /widget/health, /widget/icon.svg
  • postMessage types: open-window, open-tab, copy-to-clipboard, download-file, import-theme
  • WCP manifest schema with pages and actions

About

The Widget Context Protocol was designed and developed by Penrith Beacon® as an open standard for local-first dashboard widget communication.

WCP is free to implement. Reference widgets and the reference host dashboard are published on Docker Hub and GitHub under the penrithbeacon namespace.

Penrith Beacon® is a registered trademark. The WCP specification itself carries no licence restrictions.