Jupyter Notebook Skill
This skill covers building PyWry widgets for Jupyter notebooks via MCP tools.
There are two approaches for displaying widgets in Jupyter:
| Approach | Best For | Requires |
|---|---|---|
| AnyWidget (recommended) | Native Jupyter integration, bidirectional comms | pip install 'pywry[notebook]' |
| IFrame (fallback) | When anywidget unavailable | MCP server in headless mode |
Approach 1: AnyWidget (Recommended)
AnyWidget provides native Jupyter widget integration with traitlet-based bidirectional communication. No iframe, no server - just native Jupyter widgets.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Jupyter Notebook │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Cell [1]: # Code from MCP agent │ │
│ │ from pywry.widget import PyWryWidget │ │
│ │ widget = PyWryWidget( │ │
│ │ content="<div>...</div>", │ │
│ │ height="400px" │ │
│ │ ) │ │
│ │ widget # Display in cell │ │
│ ├───────────────────────────────────────────────────┤ │
│ │ Output: ┌─────────────────────────────────────┐ │ │
│ │ │ [Native Jupyter Widget] │ │ │
│ │ │ Buttons, Charts, Tables, etc. │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲
│ Traitlet sync (bidirectional)
▼
┌─────────────────────────────────────────────────────────┐
│ Python Kernel │
│ PyWryWidget instance with .on() event handlers │
└─────────────────────────────────────────────────────────┘
MCP Workflow for AnyWidget
When asked to create an interactive widget in Jupyter with anywidget installed:
- Use
build_divto build HTML content - Provide Python code for the user to run
The MCP agent cannot directly instantiate Python objects in the user's kernel. Instead, provide code the user can execute.
Example: Parameter Tuning Widget
When user asks: "Create a widget to tune learning rate and model type"
Step 1: Build the HTML content using MCP tools:
{
"tool": "build_div",
"arguments": {
"component_id": "output",
"content": "Adjust parameters below",
"style": "padding: 1rem; min-height: 100px;"
}
}
Step 2: Provide this Python code to the user:
from pywry.widget import PyWryWidget
from pywry.toolbar import Toolbar, Slider, Select, Option, Button
# Build toolbar
toolbar = Toolbar(position="inside", items=[
Slider(label="Learning Rate", event="lr", min=0.001, max=0.1, step=0.001, value=0.01),
Select(label="Model", event="model", options=[
Option(label="Linear", value="linear"),
Option(label="Random Forest", value="rf")
]),
Button(label="Train", event="train", variant="primary")
])
# Build HTML with toolbar
html = f'''
<div id="output" style="padding: 1rem; min-height: 100px;">
Adjust parameters and click Train
</div>
{toolbar.render()}
'''
# Create and display widget
widget = PyWryWidget(content=html, height="350px")
# Handle events
def on_train(data, event_type, label):
print(f"Training with: {data}")
widget.emit("pywry:set-content", {"id": "output", "html": "<p>Training...</p>"})
widget.on("train", on_train)
widget # Display in cell
AnyWidget Classes
| Class | Use Case |
|---|---|
PyWryWidget | General HTML/toolbar widgets |
PyWryPlotlyWidget | Charts with Plotly.js bundled |
PyWryAgGridWidget | Data tables with AG Grid bundled |
PyWryPlotlyWidget Example
from pywry.widget import PyWryPlotlyWidget
widget = PyWryPlotlyWidget(
figure={"data": [{"x": [1,2,3], "y": [4,5,6], "type": "scatter"}]},
height="450px"
)
# Handle plot click events
widget.on("plotly_click", lambda data, *_: print(f"Clicked: {data}"))
widget
PyWryAgGridWidget Example
from pywry.widget import PyWryAgGridWidget
import pandas as pd
df = pd.DataFrame({"A": [1, 2, 3], "B": ["x", "y", "z"]})
widget = PyWryAgGridWidget(
data=df.to_dict("records"),
columns=[{"field": "A"}, {"field": "B"}],
height="400px"
)
# Handle row selection
widget.on("row_selected", lambda data, *_: print(f"Selected: {data}"))
widget
Approach 2: IFrame (Fallback)
When anywidget is not installed, use the MCP server in headless mode to serve widgets via HTTP.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Jupyter Notebook │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Cell [1]: from IPython.display import IFrame │ │
│ │ IFrame("http://localhost:8765/widget/ │ │
│ │ abc123", width="100%", │ │
│ │ height=400) │ │
│ ├───────────────────────────────────────────────────┤ │
│ │ Output: ┌─────────────────────────────────────┐ │ │
│ │ │ [Your Widget Rendered Here] │ │ │
│ │ │ Buttons, Charts, Tables, etc. │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲
│ HTTP (iframe src)
▼
┌─────────────────────────────────────────────────────────┐
│ PyWry Widget Server (localhost:8765) │
│ Serves widget HTML at /widget/{widget_id} │
└─────────────────────────────────────────────────────────┘
MCP Tool Workflow (IFrame Mode)
Step 1: Create the Widget
Use create_widget to build your widget:
{
"tool": "create_widget",
"arguments": {
"title": "Parameter Tuner",
"html": "<div id='output'>Adjust parameters below</div>",
"height": 350,
"toolbars": [{
"position": "inside",
"items": [
{"type": "slider", "label": "Learning Rate", "event": "lr", "min": 0.001, "max": 0.1, "step": 0.001, "value": 0.01},
{"type": "select", "label": "Model", "event": "model", "options": [
{"label": "Linear", "value": "linear"},
{"label": "Random Forest", "value": "rf"}
]}
]
}]
}
}
Step 2: Provide Display Code to User
The create_widget tool returns:
{
"widget_id": "abc123",
"path": "/widget/abc123",
"created": true
}
IMPORTANT: You must give the user Python code to display the widget. Include this in your response:
from IPython.display import IFrame
IFrame("http://localhost:8765/widget/abc123", width="100%", height=350)
Replace abc123 with the actual widget_id and 350 with the height you used.
Step 3: Poll for Events
Use get_events to check for user interactions:
{
"tool": "get_events",
"arguments": {
"widget_id": "abc123",
"clear": true
}
}
Events contain the user's selections:
{
"events": [
{"event_type": "lr", "data": {"value": 0.05}},
{"event_type": "model", "data": {"value": "rf"}}
]
}
Step 4: Update the Widget
Use set_content to update displayed results:
{
"tool": "set_content",
"arguments": {
"widget_id": "abc123",
"component_id": "output",
"html": "<p>Training with LR=0.05, Model=Random Forest...</p>"
}
}
Example Response (IFrame Mode)
When asked to create a widget in Jupyter without anywidget, your response should look like:
I've created a parameter tuning widget. Run this code in a cell to display it:
from IPython.display import IFrame
IFrame("http://localho