pywry-orientation
When to reach for PyWry MCP tools (native webview rendering, Plotly, TradingView, AgGrid, chat) instead of writing Flask/Streamlit/Dash/Electron code. Use at…
The PyWry event system — namespaced events, request/response round-trips, widget IDs, component IDs, and how tool results flow back to the agent.
$ npx -y skills add deeleeramone/PyWry --skill events --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/eventsContext preview
The summary Claude sees to decide when to auto-load this skill.
The PyWry event system — namespaced events, request/response round-trips, widget IDs, component IDs, and how tool results flow back to the agent.
description: The PyWry event system — namespaced events, request/response round-trips, widget IDs, component IDs, and how tool results flow back to the agent.
> **The event bus is the plumbing underneath every MCP tool.** You > rarely need to think about it — the typed tools wrap emit + wait + > state-poll for you — but when you reach for `send_event` or > interpret tool results, this is how it works.
Every event has the form `namespace:event-name`, e.g.:
Never emit an event with a name that doesn't match `namespace:event-name` — the framework rejects it.
**widget_id** — identifies the top-level PyWry widget (a chart, a grid, a chat panel, a dashboard). Every MCP tool takes `widget_id` as an argument because all events route to the widget first.
**componentId** — identifies a child *inside* a widget (a specific toolbar button, a marquee ticker slot, a chart pane). Component IDs are scoped to their containing widget.
When you call `send_event(widget_id, event_type, data)`, the `widget_id` picks the target widget; anything identifying a specific component goes in the `data` payload (typically as `data.componentId` or `data.chartId`).
Some events are fire-and-forget (e.g. `tvchart:symbol-search` — "please do this"). Others are request/response round-trips where the caller wants a reply (e.g. `tvchart:request-state` → `tvchart:state-response`).
The framework correlates request/response with a `context` token:
1. Emitter generates a random `context` token. 2. Emitter injects it into the request payload. 3. Listener sees the request, attaches the same `context` to its response, and emits the response event. 4. Emitter sees the matching `context` on the response and wakes up.
All of this is handled inside `request_response()` in `pywry.mcp.state` — you never construct tokens yourself. Typed MCP tools that need a reply (`tvchart_request_state`, `tvchart_list_indicators`) use this under the hood and return the stripped response (no `context` token) in their tool result.
Agent MCP Server PyWry Widget (JS) │ │ │ │ tool call ──────► │ │ │ │ widget.emit() ──────► │ │ │ │ (updates chart) │ │ │ │ │ ◄───── bridge.emit() │ │ │ (state-response) │ │ │ │ │ ◄──── tool result │ │ │ (includes state) │ │
Mutating tools (`tvchart_symbol_search`, `tvchart_change_interval`) poll `tvchart:request-state` after emitting the mutation, wait for the chart to actually reflect the change, and return the real post-change state in the tool result. The `state` field in the tool result contains the SAME structure as a direct call to `tvchart_request_state`.
If the mutation didn't settle in time, the result contains a `note` field explaining the discrepancy — relay the note to the user, do not invent state.
Only reach for `send_event` when no typed MCP tool exists for the target event. It's a raw passthrough with no state polling:
send_event(widget_id, event_type, data)
→ { "widget_id": ..., "event_sent": true, "event_type": ... }Example — apply a rare tvchart option with no typed wrapper:
send_event(
widget_id="chart",
event_type="tvchart:apply-options",
data={"chartOptions": {"timeScale": {"secondsVisible": False}}},
)The returned `event_sent: true` means the event was successfully handed to the widget — it does NOT mean the JS handler ran successfully. For confirmation, follow up with `tvchart_request_state` to read the new state.
Some events fire from the widget *to* Python (e.g. the user clicked a bar, moved the crosshair). These are automatically captured into a per-widget event buffer and can be retrieved with:
get_events(widget_id, event_types=[...], clear=True)
→ { "events": [{ "event_type": ..., "data": ..., "label": ... }, ...] }Default captured events for charts:
Use `get_events` if the user asks "what did I just click" or "what was the last drawing I added".
framework actually handed you.
rejected.
return an error listing the registered widgets; correct and retry.
When it matters, follow up with `tvchart_request_state` (or the relevant state query) to confirm.
PyWry is a cross-platform app factory, rendering engine and UI toolkit for Python that produces native desktop, web, and notebook experiences from a single API.
Repo: deeleeramone/PyWry
When to reach for PyWry MCP tools (native webview rendering, Plotly, TradingView, AgGrid, chat) instead of writing Flask/Streamlit/Dash/Electron code. Use at…
Add OAuth2 authentication to PyWry apps — Google, GitHub, Microsoft, or any OIDC provider.
Autonomous PyWry application building using LLM sampling, elicitation, and progress reporting.
How an agent operates inside a PyWry chat widget — reading user messages, attachments, @-context, tool-call result cards, edit/resend, settings changes.
**STOP. THIS IS THE AUTHORITATIVE REFERENCE FOR ALL COMPONENTS.** **YOU MUST USE THE EXACT EVENT SIGNATURES DOCUMENTED HERE.** **THERE ARE NO EXCEPTIONS. NO…