Examples¶
Real-world workflows showing how an AI agent uses the MCP tools together. Each example shows the exact JSON tool calls the agent makes and explains the pattern.
Agent best practice
Before building any widget, the agent should call get_skills to fetch the
component_reference skill. That skill contains the full property tables,
event signatures, and JSON schemas for every toolbar component.
Plotly Charts¶
Scatter Plot¶
Prompt: Create a scatter plot — hours studied vs test scores.
{
"figure_json": "{\"data\":[{\"x\":[1,2,3,4,5,6,7,8],\"y\":[52,58,65,72,78,82,88,92],\"type\":\"scatter\",\"mode\":\"markers\",\"marker\":{\"size\":10,\"color\":\"#3b82f6\"}}],\"layout\":{\"title\":\"Hours Studied vs Test Scores\",\"xaxis\":{\"title\":\"Hours Studied\"},\"yaxis\":{\"title\":\"Test Score\"}}}"
}
figure_json is always a JSON string, not a nested object.
In Python you would pass fig.to_json().
Bar Chart¶
Prompt: Bar chart of Q1 sales: North $120k, South $95k, East $110k, West $85k.
{
"figure_json": "{\"data\":[{\"x\":[\"North\",\"South\",\"East\",\"West\"],\"y\":[120000,95000,110000,85000],\"type\":\"bar\",\"marker\":{\"color\":[\"#3b82f6\",\"#10b981\",\"#f59e0b\",\"#ef4444\"]}}],\"layout\":{\"title\":\"Q1 Sales by Region\",\"yaxis\":{\"title\":\"Sales ($)\",\"tickformat\":\"$,.0f\"}}}",
"title": "Q1 Sales"
}
Update an Existing Chart¶
After a show_plotly call returns {"widget_id": "w-abc123"}, you can update the figure in place:
{
"widget_id": "w-abc123",
"figure_json": "{\"data\":[{\"x\":[\"North\",\"South\",\"East\",\"West\"],\"y\":[140000,102000,118000,91000],\"type\":\"bar\"}],\"layout\":{\"title\":\"Q2 Sales by Region\"}}",
"layout_only": false
}
Set layout_only: true to update just the title, axes, or annotations without touching the data traces.
Data Tables¶
AG Grid Table¶
Prompt: Show a table of our top 5 products.
{
"data_json": "[{\"name\":\"Widget Pro\",\"category\":\"Electronics\",\"price\":299.99,\"stock\":150},{\"name\":\"Gadget Plus\",\"category\":\"Electronics\",\"price\":199.99,\"stock\":230},{\"name\":\"Tool Master\",\"category\":\"Tools\",\"price\":89.99,\"stock\":500},{\"name\":\"Smart Hub\",\"category\":\"Smart Home\",\"price\":149.99,\"stock\":85},{\"name\":\"Power Bank X\",\"category\":\"Accessories\",\"price\":49.99,\"stock\":1200}]",
"title": "Top 5 Products"
}
The grid renders with sortable columns, filtering, and pagination out of the box.
Custom HTML Widgets¶
Information Card¶
Prompt: Show company stats: 150 employees, $12M revenue, 95% satisfaction.
{
"html": "<div style='padding:20px;font-family:system-ui'><h1>Company Stats</h1><div style='display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:20px'><div style='background:#f0fdf4;padding:20px;border-radius:8px;text-align:center'><div style='font-size:36px;font-weight:bold;color:#16a34a'>150</div><div style='color:#4b5563'>Employees</div></div><div style='background:#eff6ff;padding:20px;border-radius:8px;text-align:center'><div style='font-size:36px;font-weight:bold;color:#2563eb'>$12M</div><div style='color:#4b5563'>Revenue</div></div><div style='background:#fef3c7;padding:20px;border-radius:8px;text-align:center'><div style='font-size:36px;font-weight:bold;color:#d97706'>95%</div><div style='color:#4b5563'>Satisfaction</div></div></div></div>",
"title": "Company Stats",
"height": 250
}
Widget with Live Content Updates¶
After creating a widget, use set_content to update specific elements:
{
"html": "<div id='status' style='padding:20px;font-size:18px'>Loading…</div>",
"title": "Live Status"
}
{
"widget_id": "w-abc123",
"component_id": "status",
"html": "<strong style='color:#16a34a'>All systems operational</strong>"
}
Interactive Toolbars¶
Toolbars turn static widgets into interactive applications.
Components fire events that the agent can read with get_events.
Dashboard with Controls¶
Prompt: Sales dashboard with a region filter and export button.
{
"html": "<div id='chart'></div>",
"title": "Sales Dashboard",
"include_plotly": true,
"toolbars": [
{
"position": "top",
"items": [
{
"type": "select",
"label": "Region",
"event": "app:region",
"options": [
{"label": "All Regions", "value": "all"},
{"label": "North", "value": "north"},
{"label": "South", "value": "south"},
{"label": "East", "value": "east"},
{"label": "West", "value": "west"}
],
"selected": "all"
},
{
"type": "button",
"label": "Export CSV",
"event": "app:export"
}
]
}
]
}
When the user changes the select or clicks the button, events queue up.
The agent polls them with get_events:
Response:
{
"events": [
{
"event_type": "app:region",
"data": {"value": "north", "componentId": "region-select"},
"timestamp": 1719500000
}
]
}
The agent can then update the chart to show only the North region's data.
Form with Inputs¶
Prompt: Create a contact form with name, email, and a submit button.
{
"html": "<div id='result' style='padding:16px;display:none'></div>",
"title": "Contact Form",
"toolbars": [
{
"position": "top",
"items": [
{"type": "text", "label": "Name", "event": "form:name"},
{"type": "text", "label": "Email", "event": "form:email"},
{"type": "textarea", "label": "Message", "event": "form:message"},
{"type": "button", "label": "Submit", "event": "form:submit"}
]
}
]
}
After the user fills in the form and clicks Submit, get_events returns all the field values and the submit event. The agent processes them and can update the widget:
{
"widget_id": "w-abc123",
"component_id": "result",
"html": "<p style='color:#16a34a'>Thanks, John! We'll be in touch.</p>"
}
Marquee & Ticker¶
Live News Ticker¶
Prompt: Show a scrolling news ticker.
First build the ticker items, then insert them into a widget with a Marquee toolbar component:
{
"ticker": "headline-1",
"html": "<strong>BREAKING:</strong> Markets hit all-time high",
"style": "padding: 0 24px"
}
Repeat for each headline, then create the widget with a marquee in the toolbar. Later, update a single headline:
{
"widget_id": "w-abc123",
"ticker": "headline-1",
"html": "<strong>UPDATE:</strong> Markets rally continues into afternoon trading"
}
Or adjust the scroll speed:
{
"widget_id": "w-abc123",
"component_id": "news-marquee",
"speed": 15,
"paused": false
}
Notifications¶
Toast Messages¶
Show feedback to the user without creating a new widget:
{
"widget_id": "w-abc123",
"message": "Report exported successfully",
"type": "success",
"duration": 3000
}
Toast types: info, success, warning, error.
Theming¶
Switch to Light Mode¶
Inject Custom Styles¶
{
"widget_id": "w-abc123",
"css": "body { font-family: 'Inter', sans-serif; } .ag-theme-alpine { --ag-font-size: 14px; }",
"style_id": "custom-fonts"
}
Remove them later:
Export & Download¶
Export Widget to Python¶
After building a widget interactively, export it as reproducible Python code:
Response: A complete Python script using pywry.PyWry that recreates the widget (including data, toolbars, and callbacks).
Trigger a File Download¶
{
"widget_id": "w-abc123",
"content": "name,category,price\nWidget Pro,Electronics,299.99\nGadget Plus,Electronics,199.99",
"filename": "products.csv",
"mime_type": "text/csv"
}
Multi-Step Workflows¶
The Agent Loop¶
Most real interactions follow this pattern:
flowchart LR
A[get_skills] --> B[create widget]
B --> C[user interacts]
C --> D[get_events]
D --> E{act on events}
E -->|update| F[set_content / update_plotly / …]
E -->|done| G[export_widget]
F --> C
- Learn —
get_skillsloads the component reference. - Create —
create_widget,show_plotly, orshow_dataframe. - Wait — The user interacts with toolbars or the page.
- Read —
get_eventsreturns queued user actions. - React — The agent updates the widget, shows toasts, or exports.
- Repeat — Back to step 3 until the user is satisfied.
Practical Example: Data Explorer¶
Prompt: Show my sales data and let me filter by region.
The agent:
- Calls
get_skillswithskill: "component_reference"to learn toolbar schemas. - Calls
show_plotlywith the full dataset and a top toolbar containing aselect(Region) and abutton(Export). - Waits for the user to change the Region dropdown.
- Calls
get_events— sees{"event_type": "app:region", "data": {"value": "north"}}. - Calls
update_plotlywith filtered data for the North region. - User clicks Export —
get_eventsreturnsapp:export. - Calls
export_widgetto generate a Python script, ordownloadto trigger a CSV download.
Backend Callbacks¶
The callbacks parameter wires events directly to Python-side actions that execute on the backend when the user interacts with the widget. The server doesn't just queue the event — it runs real code and pushes results to the browser.
Counter with backend state¶
{
"html": "<div id='counter' style='font-size:48px;text-align:center;padding:40px'>0</div>",
"toolbars": [{
"position": "top",
"items": [
{"type": "button", "label": "+", "event": "counter:increment"},
{"type": "button", "label": "−", "event": "counter:decrement"},
{"type": "button", "label": "Reset", "event": "counter:reset"}
]
}],
"callbacks": {
"counter:increment": {"action": "increment", "target": "counter"},
"counter:decrement": {"action": "decrement", "target": "counter"},
"counter:reset": {"action": "set", "target": "counter", "value": 0}
}
}
When the user clicks +, the backend increments state["value"] and immediately emits pywry:set-content with {"id": "counter", "text": "1"} to update the browser. No agent polling or client-side logic needed.
Emit action — fire a custom event to the browser¶
"callbacks": {
"app:refresh": {
"action": "emit",
"emit_event": "plotly:reset-zoom",
"emit_data": {}
}
}
Clicking the button runs widget.emit("plotly:reset-zoom", {}) on the backend, which sends the event to the browser and resets the chart zoom.
Events without callbacks → agent polling¶
Toolbar events that don't have a callbacks entry are still captured and queued by the MCP server. The agent reads them with get_events and decides what to do.
Tips for Effective Prompts¶
| Do | Don't |
|---|---|
| "Create a bar chart comparing monthly revenue for Jan–Jun 2024" | "Show me a chart" |
| "Line chart with months Jan–Jun and values 10k, 12k, 15k, 14k, 18k, 22k" | "Make a chart of our sales" |
| "Dark-themed table with sortable columns" | "Show a table" |
"Update the widget w-abc123 to show Q2 data" |
"Update the chart" |
- Provide data inline — the agent can use it immediately.
- Name your widgets — refer to them by
widget_idwhen updating. - Ask for interactivity — mention "filter", "button", "toggle" and the agent will add toolbars.