Smart Panels
БесплатноНе проверенSmart Panels for DataGrout: declarative UI stored as logic-cell facts, rendered natively with egui or transpiled to MCP Apps
Описание
Smart Panels for DataGrout: declarative UI stored as logic-cell facts, rendered natively with egui or transpiled to MCP Apps
README
Render DataGrout Smart Panels — declarative UI stored as logic-cell facts — on any surface.
cargo add datagrout-panels # the model
cargo add datagrout-panels-egui # native GUI renderer
cargo add datagrout-panels-mcp # MCP Apps transpiler
A panel is facts, not a blob
You create a panel by calling the gateway's smart_panel.publish tool:
{
"id": "stage_mix",
"kind": "bar_chart",
"namespace": "pipeline_pulse",
"props": { "title": "Open Opportunities by Stage" },
"source": { "namespace": "pipeline_pulse", "query": "stage_count(Stage, N)" }
}
DataGrout compiles that into Prolog facts in the _panels namespace of a logic
cell — the same knowledge base the rest of your rules and data live in:
panel(stage_mix, bar_chart, pipeline_pulse).
panel_prop(stage_mix, title, 'Open Opportunities by Stage').
panel_source(stage_mix, pipeline_pulse, 'stage_count(Stage, N)').
Read positionally:
| fact | arguments |
|---|---|
panel/3 |
panel id, kind, and the namespace that owns it |
panel_prop/3 |
panel id, a config key, its value — one fact per prop |
panel_source/3 |
panel id, the namespace to query, and a Prolog goal |
panel_data/2 |
panel id and a static row snapshot, for a panel with no query |
stage_count(Stage, N) is an ordinary goal against that cell, so whatever it
proves is what the chart draws: one row per solution, Stage and N as the
columns. Nothing is copied into the panel, and the goal re-runs on every read.
That is what makes a panel derived rather than stored — and queryable,
composable and versioned like any other knowledge in the cell.
You would not normally write these facts by hand; smart_panel.publish does
it. But they are facts like any others, so logic.query can read them and
rules can reason over them.
Composition is a fact too
A container does not list its parts. Each part names its container in a
parent prop, so a dashboard is one panel plus children pointing at it:
panel(pipeline_dashboard, dashboard, pipeline_pulse).
panel_prop(pipeline_dashboard, title, 'Pipeline Pulse').
panel_prop(stage_mix, parent, pipeline_dashboard).
Published as kind: "dashboard" for the container and
"props": { "parent": "pipeline_dashboard" } on each child. A form is
kind: "form" with a fields array, where each field is itself a mini-panel
that may declare inputs (sibling fields it depends on), a trigger and an
emit.
The parser inverts those edges: a resolved Panel carries its children or
fields, and the parts do not appear at the top level.
Kinds and props are a fixed vocabulary
Display kinds are bar_chart, line_chart, pie_chart, scatter, table,
metric, gauge, markdown, list, area_chart, heatmap, timeline,
funnel, game, doc and dashboard. Form kinds are form, text_input,
textarea, dropdown, select, checkbox, radio, button,
number_input, date_input, file_upload and rich_text. Props seen most
often are title, description, parent, slot, columns (a list of
table headers) and published.
SPEC.md is the full contract: every kind, every observed prop with its type, how rows normalize, and what a renderer must do with a kind it does not recognise — draw a labelled placeholder, never fail.
What makes them smart
Storing UI as facts would be a curiosity on its own. Four properties are what the name is actually pointing at.
The rows are inferred, not fetched. panel_source holds a goal, so it can
call rules, not just match stored facts:
at_risk(Deal) :-
stage(Deal, Stage), late_stage(Stage),
days_since_contact(Deal, Days), Days > 14.
A panel over at_risk(Deal) shows whatever satisfies that rule right now. The
definition of "at risk" lives in the cell, so changing it changes every panel
built on it, and no panel had to be edited.
It is current by construction. The goal re-runs on every read, so a fact an agent asserted a second ago is already in the panel. There is no cache to invalidate, no build step, and no refresh path to get wrong.
An agent can build the UI. Because a panel is just facts, anything holding
smart_panel.publish can create or amend one as part of doing its work — which
is why props carry created_by_agent provenance. A dashboard can be an outcome
of reasoning rather than something a person laid out in advance.
A form is a small dataflow graph. Fields declare what they depend on
(field_input), when they fire (field_trigger), and what happens to their
output (field_emit) — and a field's panel_source can invoke a tool, skill
or workflow rather than query data. So a field can take another field's value,
call something with it, and replace its own contents with the result:
{
"id": "enriched_desc",
"kind": "textarea",
"props": { "label": "Description" },
"inputs": ["company_name"],
"trigger": "on_event",
"emit": "replacement",
"source": { "query": "enrich_company(CompanyName, Out)" }
}
Driving that graph is not client-side work. On DataGrout, submitting a form runs
the panel's goal in the cell: if the goal's functor is a rule published with
reactor.expose, the fields bind to that rule's declared + inputs and its -
outputs come back shaped by the contract; otherwise the fields bind into the
panel_source goal itself, matching field ids to the goal's Prolog variables
and collecting the unbound ones as outputs. Either way it runs under the cell's
sandbox with a bounded timeout, and a rule body may call tools — so a field can
invoke a skill or workflow with no application code in between.
What a renderer does is collect values and hand the interaction back. These
crates return PanelActions rather than dispatching them because where to
submit is the host's choice, even though what runs is already defined in the
cell.
The same rule can also be an HTTP endpoint. reactor.expose publishes an
LC rule with a mode contract — +invoice:atom, -result:list, where + binds
from the request and - is returned — and DataGrout then serves it as JSON
and as the backing for a panel form, from one definition inside one sandbox.
Facts supplied with a request are asserted ephemerally (assert under a unique
tag, query, retract), so submitted data is not retained between calls.
And since the definitions are facts, they are queryable and auditable like
anything else in the cell — logic.query can ask which panels exist, which are
published, or which an agent created.
Where panels live, and how you read them
A logic cell is scoped to one account and one hub server, so panels
published through one server are not visible through another: which panels you
see depends on which server you connected to. Within _panels, each panel's
own namespace — pipeline_pulse above — groups it with its siblings.
Anything that speaks MCP can publish: an agent handed the tool, your own code
through an MCP client such as
conduit-sdk, or the Smart Panels
page in the DataGrout web app. Reading them back is a single
smart_panel.list call, and that response is exactly what
Panel::all_from_list parses.
These crates render panels; they neither create nor fetch them, and have no transport. You bring the MCP client; they turn what it returned into something on screen.
Trying it without an account
The parser takes plain JSON, so a hand-written list response renders like a real one — useful for tests, examples, and seeing the renderers work before you have panels of your own:
use datagrout_panels::Panel;
use serde_json::json;
let panels = Panel::all_from_list(&json!({
"panels": [{
"id": "revenue", "kind": "bar_chart", "namespace": "demo",
"props": { "title": "Revenue by Month", "columns": ["Month", "Amount"] },
"data_preview": [["Jan", 12500], ["Feb", 18300], ["Mar", 21100]]
}]
}));
assert_eq!(panels[0].title(), "Revenue by Month");
One model, many surfaces
The model crate parses facts into a renderer-agnostic tree and stops. Renderers are separate, so a consumer that only transpiles never links a GUI toolkit, and adding a backend never touches the model.
| crate | surface | version | docs |
|---|---|---|---|
| datagrout-panels | the model — facts → Panel tree |
crates.io | docs.rs |
| datagrout-panels-egui | native immediate-mode GUI | crates.io | docs.rs |
| datagrout-panels-mcp | MCP Apps (SEP-1865) ui:// resources |
crates.io | docs.rs |
datagrout-panels-tui |
terminal, via ratatui | planned |
Why immediate mode is the natural fit
egui redraws from state every frame with no retained widget tree. A Smart Panel re-derives its rows from the rulebase on every read with no retained DOM. They are the same architecture — so an immediate-mode renderer pays no reconciliation cost that a retained-tree renderer must.
Why MCP Apps transpiles cleanly
MCP Apps — the first official MCP extension, released 2026-01-26 — lets a
server hand a host an interactive UI as a ui:// resource with mime type
text/html;profile=mcp-app, rendered in a sandboxed iframe that speaks
JSON-RPC over postMessage. Tools link to their view via _meta.ui.resourceUri.
Crucially, an MCP App receives its data by notification
(ui/notifications/tool-result) rather than fetching it up front — which is
exactly how a Smart Panel already behaves. The panel's kind and props become the
document; the rows arrive at render time. So one definition, stored once as
facts, drives a native GUI and an MCP host with no second authoring step and
no drift between them.
Usage
The intended input is the gateway's own smart_panel.list response — one call,
props included, dashboards resolved with their children:
use datagrout_panels::Panel;
use datagrout_panels_egui::render_panel;
// `list_response` is what `smart_panel.list` returned, however you called it.
let panels = Panel::all_from_list(&list_response);
egui::CentralPanel::default().show(ctx, |ui| {
for panel in &panels {
render_panel(ui, panel);
}
});
Raw logic.query rows are accepted too when you need full row sets or field
metadata — see Panel::all_from_facts and datagrout_panels::goals.
Transpiling the same panel for an MCP host:
use datagrout_panels_mcp::{to_ui_resource, tool_meta, TranspileOptions};
let resource = to_ui_resource(&panel, &TranspileOptions {
server: "my-app".into(),
..Default::default()
});
// Publish `resource.to_resource_json()` as a resource, and tag the tool that
// produces its rows:
let meta = tool_meta(&resource.uri, true);
Checked against a live cell
The model was corrected against panels published to a real DataGrout account,
not written from the schema alone. What that changed is recorded in
SPEC.md and the CHANGELOG — in short: parts are
linked by the child's parent prop, dashboard is a kind, props are
typed (columns is a list), and identity is (namespace, id).
examples/parse_fixture.rs runs a captured fact dump through the parser and
reports what came out; it is how the crate stays honest as the schema moves.
Language parity
Rust is the reference implementation. The model and fact parsing are the portable core and should exist in every supported language; renderers are whatever suits that language. SPEC.md is the contract a port implements.
| language | model | renderers |
|---|---|---|
| Rust | ✅ | egui, MCP Apps |
| TypeScript | planned | MCP Apps, DOM |
Security
Panel facts may have been asserted by an agent. Treat every prop as untrusted: the MCP transpiler escapes all panel-authored text, declares no CSP domains by default, and emits no callback path for display-only panels.
Layout
datagrout-panels/
├── SPEC.md the fact schema + renderer contract (portable)
├── CHANGELOG.md
├── rust/ reference implementation (three crates)
└── typescript/ (planned)
License
MIT OR Apache-2.0, at your option.
Установка Smart Panels
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/DataGrout/smart-panelsFAQ
Smart Panels MCP бесплатный?
Да, Smart Panels MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Smart Panels?
Нет, Smart Panels работает без API-ключей и переменных окружения.
Smart Panels — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Smart Panels в Claude Desktop, Claude Code или Cursor?
Открой Smart Panels на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
LibreOffice Tools
Enables AI agents to read, write, and edit Office documents via LibreOffice with token-efficient design. Supports multiple formats including DOCX, XLSX, PPTX, a
автор: passerbyflutterdannote/figma-use
Full Figma control: create shapes, text, components, set styles, auto-layout, variables, export. 80+ tools.
автор: dannoteLogo.dev
Search and retrieve company logos by brand or domain. Customize size, format, and theme to match your design needs. Accelerate design, prototyping, and content
автор: NOVA-3951Design Inspiration Server
Searches top design platforms like Dribbble and Behance to provide UI inspiration, color palettes, and layout patterns via the Serper API. It allows users to re
автор: YonasValentinPIX4Dmatic
Enables GUI automation for controlling PIX4Dmatic on Windows through MCP. Supports launching, focusing, capturing screenshots, sending hotkeys, clicking UI elem
автор: jangjo123Figma
Extract design specs and assets
автор: Figmamcp-dockmaster
An Open-Sourced UI to install and manage MCP servers for Windows, Linux and macOS.
ariekogan/ateam-mcp
Build, validate, and deploy multi-agent AI solutions on the ADAS platform. Design skills with tools, manage solution lifecycle, and connect from any AI environm
автор: ariekoganthinkchainai/mcpbundles
MCP Bundles: Create custom bundles of tools and connect providers with OAuth or API keys. Use one MCP server across thousands of integrations, with programmatic
автор: thinkchainaiarikusi/nakkas
MCP server that turns AI into an SVG artist. One rendering engine with JSON config, AI controls all design parameters. CSS @keyframes + SMIL animations, 16+ ele
автор: arikusiCompare Smart Panels with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории design
