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.
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/:
| Endpoint | Method | Description |
|---|---|---|
| /widget/ | GET | Compact widget HTML — loaded by the dashboard in an iframe |
| /widget/wcp | GET | WCP manifest (JSON) — the widget's self-description |
| /widget/health | GET | Health check — returns {"status":"ok","name":"<name>"} |
| /widget/icon.svg | GET | Widget icon (SVG preferred, PNG fallback) |
Access-Control-Allow-Origin: * and handle OPTIONS preflight requests with 204 No Content.Optional Endpoints
| Endpoint | Method | Description |
|---|---|---|
| /widget/full | GET | Full-page view — opened in a new window or tab by the host |
| /widget/manifest | GET | Simplified manifest (backward compatibility) |
| /widget/configure | POST | Accept configuration JSON from the host WCP 1.1.0 |
| /widget/api/search | GET | Autocomplete for location-search config fields WCP 1.1.0 |
| /widget/api/* | any | Widget-specific API routes |
/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
| Field | Required | Description |
|---|---|---|
| wcp | required | Protocol version — currently "1.1.0" |
| name | required | Human-readable widget name |
| version | required | Widget version (semver) |
| description | required | One or two sentences |
| icon | required | Path to icon (relative to widget origin) |
| health | required | Path to health endpoint |
| widget.path | required | Path to compact iframe view |
| widget.renderMode | required | "iframe" |
| widget.refreshInterval | optional | Seconds between host refreshes; 0 = widget manages itself |
| widget.authType | optional | "none" | "bearer" | "session" |
| widget.defaultSize | optional | Default grid size {w, h} |
| pages | optional | Full-page views |
| actions | optional | Action buttons shown on the dashboard card |
| config | optional | Configuration schema (WCP 1.1.0) |
postMessage Protocol
Widgets inside iframes communicate with the host via window.parent.postMessage(payload, '*').
| type | Required fields | Host action |
|---|---|---|
| wcp:open-window | page, width?, height? | Opens named page in a new native window |
| wcp:open-tab | page, tab:{title,icon} | Opens page as a persistent dashboard tab |
| wcp:copy-to-clipboard | text | Copies text — bypasses iframe clipboard sandbox |
| wcp:download-file | filename, content, mimeType | Triggers file save dialog |
| wcp:import-theme | url (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!' }, '*');
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
| type | Description | Extra fields |
|---|---|---|
| location-search | Searchable autocomplete via /widget/api/search | searchUrl, placeholder |
| select | Dropdown with fixed options | options:[{value,label}] |
| number | Numeric input | min, max, step |
| text | Free-text input | placeholder, 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
| Version | Status | Key additions |
|---|---|---|
| 1.0.0 | Superseded | Mandatory endpoints, manifest, postMessage types |
| 1.1.0 | Current | Widget 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
| Port | Widget | Image |
|---|---|---|
| 3737 | reserved WCP Bonjour | penrithbeacon/wcp-bonjour (coming soon) |
| 3738 | QR Generator | penrithbeacon/wcp-widget-qr-generator |
| 3739 | Weather Ticker | penrithbeacon/wcp-widget-weather-ticker |
| 3740 | WCP Theme Studio | penrithbeacon/wcp-widget-theme-studio |
| 3741+ | Future widgets | Sequential 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.
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
| Axis | Unit | How it scales |
|---|---|---|
| Width | 1 of 12 columns = 8.3% | Fluid — scales with dashboard width |
| Height | 1 row = 100px | Fixed — always 100px per row unit |
Common Sizes & Use Cases
| Columns × Rows | % width | Height | Best for |
|---|---|---|---|
| 1 × 1 | 8% | 100px | Single metric, icon indicator, status dot |
| 4 × 2 | 33% | 200px | Compact status card, small chart, quick-action panel |
| 4 × 4 | 33% | 400px | Control panel, settings widget, vertical list |
| 6 × 2 | 50% | 200px | Half-width dashboard card, medium chart |
| 8 × 2 | 67% | 200px | Wide chart, data table, timeline |
| 8 × 4 | 67% | 400px | Rich data visualisation, map, complex UI |
| 12 × 2 | 100% | 200px | Full-width banner, scrolling ticker strip |
| 12 × 4 | 100% | 400px | Full-width application panel |
| 12 × 12 | 100% | 1200px | Full embedded application — equivalent to a complete app UI |
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;
}
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.defaultSizeto 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
| Variable | Purpose | Dark default |
|---|---|---|
| --bg | Page/window background | #0d1117 |
| --surface | Card / panel background | #161b22 |
| --surface2 | Input / nested surface | #1c2128 |
| --border | Borders and dividers | #30363d |
| --text | Primary text | #e6edf3 |
| --muted | Secondary / de-emphasised text | #8b949e |
| --accent | Brand colour, buttons, highlights | #f0883e |
| --green | Success, running, positive | #3fb950 |
| --red | Error, stopped, negative | #f85149 |
| --yellow | Warning, pending | #d29922 |
| --blue | Info, links | #58a6ff |
| --radius | Border radius for cards/buttons | 8px |
| --shadow | Box shadow for elevated surfaces | 0 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;
}
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:
| Theme | ID | Best for |
|---|---|---|
| Penrith Beacon WCP Dark | pb-wcp-dark | Default dark environment |
| Penrith Beacon WCP Light | pb-wcp-light | Light/daytime use |
| Penrith Beacon WCP High Contrast | pb-wcp-hc | Accessibility, 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 confirms —
window.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.
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
configfield to WCP manifest — declarative configuration schema - New field type:
location-searchwith 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.