Open-source interactive UI widget registry for Docsbook documentation chats.
Widgets render inline in the chat as interactive UI panels — powered by the MCP Apps standard (io.modelcontextprotocol/ui, spec 2026-01-26). Built-in widgets are active by default; users can connect their own widget repositories and enable/disable any widget from the Docsbook admin panel.
Works in Docsbook chat, ChatGPT (confirmed), and Claude.ai (officially supported).
- Each widget is a MCP tool with a
_meta.ui.resourceUripointing to a self-contained HTML file. - The LLM reads the widget's
triggersanddescription, then decides when to call the tool. - The host (Docsbook / ChatGPT / Claude.ai) renders the HTML in a sandboxed iframe inline in the conversation.
- The widget communicates with the host via the
@modelcontextprotocol/ext-appsAppclass overpostMessage.
docs-widgets/
widgets/
<widget-name>/
WIDGET.md # Frontmatter (metadata) + prose description the LLM reads
widget.html # Self-contained HTML widget
schema/
widget.schema.json # JSON Schema for WIDGET.md frontmatter
index.json # Generated widget catalog (consumed by Docsbook)
---
name: my-widget # Unique kebab-case identifier (required)
description: "..." # Human-readable description + when to use (required, used as MCP tool description)
version: 1.0.0 # Semver (required)
category: developer # visualization|forms|navigation|ai|developer|analytics|media (required)
modes:
- user # admin|user — which chat context shows this widget (required, at least one)
triggers:
- show my widget # Keyword phrases the LLM matches to activate (required, at least one)
mcp_resource_uri: "ui://my-widget/render" # The ui:// URI stem (required)
keywords: # Discovery keywords for scoring (optional)
- keyword1
discriminators: # Exact high-precision phrases like proper nouns (optional)
- ExactName
requires_plan: pro # pro|pro_plus — gate on Docsbook plan (optional)
built_in: false # true = official Docsbook widget (default: false)
author: Your Name # Optional
repository: https://github.com/you/your-widgets # Optional
homepage: https://yoursite.com # Optional
---| Value | When shown |
|---|---|
user |
End-user documentation chat (public visitors reading your docs) |
admin |
Docsbook admin chat (only you / your team) |
triggers become part of the MCP tool description that the LLM reads. The LLM matches the user's message against these phrases (and description, keywords, discriminators) to decide when to render the widget. Write them as short action phrases:
triggers:
- show API reference
- view Swagger documentation
- explore REST endpointsmy-widgets/
widgets/
openapi-renderer/
WIDGET.md
widget.html
index.json
package.json
Start with the YAML frontmatter (see reference above), followed by a prose description the LLM will read to understand what the widget does and when to use it. Be specific about input format and output.
The HTML file must be fully self-contained (all JS/CSS inlined or from CDN). Use the @modelcontextprotocol/ext-apps App class to receive data from the host:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>My Widget</title>
</head>
<body>
<div id="output"></div>
<script type="module">
import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps@latest/dist/app.js';
const app = new App();
// Receive data pushed from the LLM tool result
app.on('toolResult', (result) => {
document.getElementById('output').textContent = JSON.stringify(result, null, 2);
});
// Receive initial params on first render
app.on('initialize', (params) => {
if (params?.data) {
document.getElementById('output').textContent = JSON.stringify(params.data, null, 2);
}
});
// Call a tool back on the MCP server from user interaction
document.getElementById('refresh-btn')?.addEventListener('click', async () => {
const data = await app.callTool('my_tool', { param: 'value' });
document.getElementById('output').textContent = JSON.stringify(data, null, 2);
});
</script>
</body>
</html>MCP Apps postMessage flow:
- Host renders your HTML in a sandboxed
<iframe srcDoc="..."> - Host sends
ui/initializeviapostMessage→Appclass receives it - LLM tool result is pushed to iframe as
toolResultevent - Widget can call
app.callTool(name, args)to invoke MCP tools - Widget can call
app.sendMessage(text)to add context to the LLM conversation
Generate (or hand-write) a catalog file listing all widgets. Each entry mirrors the WIDGET.md frontmatter plus raw_url and widget_url:
[
{
"name": "my-widget",
"description": "...",
"version": "1.0.0",
"category": "developer",
"modes": ["user"],
"triggers": ["show my widget"],
"keywords": ["my", "widget"],
"discriminators": [],
"mcp_resource_uri": "ui://my-widget/render",
"requires_plan": null,
"built_in": false,
"author": "Your Name",
"repository": "https://github.com/you/my-widgets",
"raw_url": "https://raw.githubusercontent.com/you/my-widgets/main/widgets/my-widget/WIDGET.md",
"widget_url": "https://raw.githubusercontent.com/you/my-widgets/main/widgets/my-widget/widget.html"
}
]Host your index.json at a public URL (GitHub raw, npm, or any CDN), then connect it in Docsbook Admin → Widgets → Add widget repository. Docsbook will fetch your index and surface your widgets in the chat.
To publish to npm for easy versioning:
// package.json
{
"name": "@yourscope/widgets",
"version": "1.0.0",
"main": "index.json",
"files": ["widgets/", "index.json"]
}| Host | Status |
|---|---|
| Docsbook chat | Native — full integration |
| ChatGPT | Confirmed — renders via MCP Apps standard |
| Claude.ai | Officially supported (minor rendering bugs in some environments as of 2026) |
| VS Code (Continue, Copilot) | Partial — depends on client MCP Apps support |
The MCP Apps spec (io.modelcontextprotocol/ui) is ratified as of 2026-01-26 and is the single recommended authoring format for widgets that need to work across multiple LLM hosts.
| Widget | Category | Modes | Description |
|---|---|---|---|
openapi-renderer |
developer | user | Interactive Swagger UI for OpenAPI specs |
Everything above is about chat widgets: interactive panels rendered in an iframe inside the AI chat.
Docsbook also has content widgets, which render inside a documentation page. An author wraps a region of ordinary markdown in invisible HTML comments, and it renders as a card grid, an accordion, and so on:
<!-- widget:cards -->
## Start Here
- [Overview](./overview.md) — What this product is {compass}
- [Getting Started](./quick-start.md) — Begin in 3 minutes {rocket}
<!-- /widget -->The comments are invisible in every markdown reader, so the page stays correct on GitHub. Rendering happens server-side, so the output is static, indexable HTML with no client bundle.
| Content widget | Chat widget | |
|---|---|---|
| Renders in | A documentation page | The AI chat |
| Authored as | A rehype renderer in the Docsbook app | WIDGET.md + widget.html here |
| Input | The page's own markdown | A tool result from the LLM |
| Interactive | HTML/CSS only (<details>, :hover) |
Full JS, can call tools back |
To discover the live set and each one's markdown contract, call the list_content_widgets MCP tool. To add one, see skills/content-widget-authoring.
PRs welcome. Each chat widget lives in widgets/<name>/ with a WIDGET.md and widget.html. Open an issue first for new categories or schema changes.
Adding a content widget touches the main Docsbook repository rather than this one — skills/content-widget-authoring walks through the renderer, styling, catalog entry, tests, and what the PR needs to show.
MIT