jupyter — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited jupyter (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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 |
AnyWidget provides native Jupyter widget integration with traitlet-based bidirectional communication. No iframe, no server - just native Jupyter widgets.
┌─────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────┘When asked to create an interactive widget in Jupyter with anywidget installed:
The MCP agent cannot directly instantiate Python objects in the user's kernel. Instead, provide code the user can execute.
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| Class | Use Case |
|---|---|
PyWryWidget | General HTML/toolbar widgets |
PyWryPlotlyWidget | Charts with Plotly.js bundled |
PyWryAgGridWidget | Data tables with AG Grid bundled |
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}"))
widgetfrom 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}"))
widgetWhen anywidget is not installed, use the MCP server in headless mode to serve widgets via HTTP.
┌─────────────────────────────────────────────────────────┐
│ 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} │
└─────────────────────────────────────────────────────────┘#### 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>"
}
}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://localhost:8765/widget/abc123", width="100%", height=350)Use the sliders and dropdowns to adjust parameters. Let me know when you've made your selections and I'll process them.
position: "inside" for ToolbarsPlace toolbars inside the widget to maximize vertical space in notebook cells:
{
"toolbars": [{
"position": "inside",
"items": [...]
}]
}Notebook cells have limited vertical space. Use heights between 300-500px:
{
"height": 400
}Set include_plotly: true when building data visualizations:
{
"tool": "create_widget",
"arguments": {
"html": "<div id='chart'></div>",
"include_plotly": true,
"height": 450
}
}Then use show_plotly or inject chart data via events.
Set include_aggrid: true for data tables:
{
"tool": "create_widget",
"arguments": {
"html": "<div id='table'></div>",
"include_aggrid": true,
"height": 400
}
}Create widgets that let users tune model parameters:
{
"tool": "create_widget",
"arguments": {
"title": "Hyperparameter Tuning",
"html": "<div id='results'>Select parameters and click Train</div>",
"height": 400,
"toolbars": [{
"position": "inside",
"items": [
{"type": "number", "label": "Epochs", "event": "epochs", "value": 100, "min": 1, "max": 1000},
{"type": "slider", "label": "Dropout", "event": "dropout", "min": 0, "max": 0.5, "step": 0.05, "value": 0.2},
{"type": "button", "label": "Train Model", "event": "train", "variant": "primary"}
]
}]
}
}Create filtering interfaces for dataframes:
{
"tool": "create_widget",
"arguments": {
"title": "Data Explorer",
"html": "<div id='preview'>Apply filters to see results</div>",
"height": 450,
"include_aggrid": true,
"toolbars": [{
"position": "inside",
"items": [
{"type": "multiselect", "label": "Columns", "event": "columns", "options": [
{"label": "Date", "value": "date"},
{"label": "Price", "value": "price"},
{"label": "Volume", "value": "volume"}
]},
{"type": "date", "label": "Start Date", "event": "start_date"},
{"type": "date", "label": "End Date", "event": "end_date"},
{"type": "button", "label": "Apply", "event": "apply", "variant": "primary"}
]
}]
}
}For real-time updates, create a widget and periodically update it:
{
"tool": "create_widget",
"arguments": {
"title": "Live Metrics",
"html": "<div style='display:grid;grid-template-columns:1fr 1fr;gap:1rem;padding:1rem'><div id='metric1' style='padding:1rem;background:#1a1a2e;border-radius:8px'><h3>Accuracy</h3><p style='font-size:2rem'>--</p></div><div id='metric2' style='padding:1rem;background:#1a1a2e;border-radius:8px'><h3>Loss</h3><p style='font-size:2rem'>--</p></div></div>",
"height": 200
}
}Update metrics with set_content:
{
"tool": "set_content",
"arguments": {
"widget_id": "abc123",
"component_id": "metric1",
"html": "<h3>Accuracy</h3><p style='font-size:2rem;color:#4ade80'>94.5%</p>"
}
}pip install 'pywry[notebook]' was runwidget (not print(widget))PYWRY_HEADLESS=1http://localhost:8765widget_id in the IFrame URL matchesevent properties setget_events with clear: false first to inspect without clearingbuild_div, etc.).on() handlerscreate_widget toolget_events when user interactsset_content or set_style~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.