tvchart — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited tvchart (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.
Use this when an agent needs to read or mutate a live `tvchart` widget. Every action is an MCP tool call on the PyWry FastMCP server — there are no local helpers, no side channels, no custom tools. Pick the typed tool that matches the user's intent, pass the required arguments, and quote the tool's return values in your reply.
widget_idwidget_id identifies which chart to operate on. On a single-chart server the framework auto-resolves it from the registry; on a multi-chart server you must pass it explicitly. Read the value from the user's @<name> attachment (the chat prepends --- Attached: <name> ---\nwidget_id: <id>) or call list_widgets() to enumerate.
tvchart_request_stateNever report symbol / interval / indicators / bars / last close from memory. Call the tool, quote the return.
tvchart_request_state(widget_id)
→ {
"widget_id": "chart",
"state": {
"symbol": "AAPL",
"interval": "1D",
"series": [{ "seriesId": "main", "bars": [...], ... }],
"indicators": [...],
"visibleRange": { "from": ..., "to": ... },
"chartType": "Candles",
...
}
}When the user asks "what's on the chart", "what's the current price", "what indicators are applied", call this and quote from state.
Every mutation returns the real post-change state. The model never has to guess whether the change took effect. If the mutation didn't land within the settle window, the tool includes a note field — relay it to the user.
tvchart_symbol_search(widget_id, query, auto_select=True,
symbol_type=None, exchange=None)
→ { "widget_id": "chart", "symbol": "MSFT", "state": {...} }Use this to switch the ticker. auto_select=True commits the selection; auto_select=False just opens the search dialog for the user. The tool polls chart state until the symbol actually changes to the target (up to ~6s) so the return reflects reality.
Pass symbol_type to narrow the datafeed search to a specific security class — values come from the datafeed, typically equity, etf, index, mutualfund, future, cryptocurrency, currency. Use it whenever the user's query is ambiguous: SPY with symbol_type="etf" resolves to the SPDR S&P 500 ETF instead of picking a near-prefix equity like SPYM. exchange narrows to a specific venue the same way. Both are case-insensitive; unknown values are silently dropped rather than erroring.
tvchart_change_interval(widget_id, value)
→ { "widget_id": "chart", "interval": "1W", "state": {...} }Valid values: 1m 3m 5m 15m 30m 45m 1h 2h 3h 4h 1d 1w 1M 3M 6M 12M. Tool confirms the change via state polling.
tvchart_add_indicator(widget_id, name, period=..., color=..., ...)
tvchart_remove_indicator(widget_id, series_id)
tvchart_list_indicators(widget_id)Supported names: SMA, EMA, WMA, RSI, ATR, VWAP, Bollinger Bands, plus the rest of the built-in library. period defaults to a sensible value per indicator; override when the user asks.
#### Compare-derivative indicators
Spread, Ratio, Sum, Product, Correlation require a secondary series — a second ticker to spread/ratio/etc. against the main series. The flow is two steps:
tvchart_compare(widget_id, query="<ticker>") to add thesecondary ticker as a compare series and confirm it landed in state.compareSymbols.
tvchart_add_indicator(widget_id, name="Spread", ...). Thechart picks up the most recent compare series as the secondary automatically; pass source / method / multiplier to tune.
State reporting for these indicators:
state.indicators[i].type is "spread" / "ratio" / etc.state.indicators[i].secondarySeriesId — the compare seriesId.state.indicators[i].secondarySymbol — the ticker it resolves to(this is what the user actually cares about when you describe the indicator).
state.indicatorSourceSymbols — the compare-series map restrictedto indicator inputs; these are NOT user-facing compares (they're hidden from the Compare panel). Don't conflate with state.compareSymbols when listing "what's compared on the chart".
If the user asks "what's on the chart" for a chart with a Spread against MSFT, quote it as Spread(AAPL, MSFT) using the indicator's secondarySymbol, not the raw seriesId.
tvchart_chart_type(widget_id, value)
# value ∈ { "Candles", "Line", "Heikin Ashi", "Bars", "Area" }
tvchart_log_scale(widget_id, value) # true / false
tvchart_auto_scale(widget_id, value)tvchart_set_visible_range(widget_id, from_time, to_time)
# times are Unix seconds
tvchart_fit_content(widget_id)
tvchart_time_range(widget_id, value) # "1D", "5D", "1M", "6M", "YTD", "1Y", "5Y", "All"
tvchart_time_range_picker(widget_id) # opens custom picker UItvchart_add_markers(widget_id, markers)
# markers = [{ time, position, color, shape, text }, ...]
# position: "aboveBar" | "belowBar" | "inBar"
# shape: "arrowUp" | "arrowDown" | "circle" | "square"
tvchart_add_price_line(widget_id, price, title="", color="#2196F3", line_width=1)Use markers for signals / events on specific bars. Use price lines for support / resistance / targets (horizontal lines across the whole chart).
tvchart_drawing_tool(widget_id, tool)
# tool ∈ { "trendline", "horizontal", "rectangle", "brush", "eraser", "cursor", ... }tvchart_undo(widget_id)
tvchart_redo(widget_id)
tvchart_save_layout(widget_id, name)
tvchart_open_layout(widget_id, name)
tvchart_save_state(widget_id)tvchart_show_indicators(widget_id) # open indicator panel
tvchart_show_settings(widget_id)
tvchart_screenshot(widget_id)
tvchart_fullscreen(widget_id)
tvchart_toggle_dark_mode(widget_id)tvchart_compare(widget_id, query, auto_add=True,
symbol_type=None, exchange=None)
→ { "widget_id": "chart", "compareSymbols": { "compare-spy": "SPY" },
"state": {...} }query is the ticker to add. Pass symbol_type to disambiguate: SPY without it may resolve to SPYM (a near-prefix equity); symbol_type="etf" routes it to the SPDR ETF. exchange narrows to a specific venue. Both are case-insensitive and silently dropped when the datafeed doesn't know the value.
The tool polls state.compareSymbols for up to ~10s (compares require a full datafeed round-trip) before reporting a note. Calling tvchart_compare(widget_id) with no query just opens the dialog for the user — no state confirmation.
Use these only when the chart is NOT in datafeed mode (the datafeed manages its own streams). In datafeed mode, new symbol / interval data is fetched automatically via tvchart:data-request — don't try to push bars yourself.
tvchart_update_series(widget_id, series_id, bars, volume=None)
tvchart_update_bar(widget_id, series_id, bar) # live tick
tvchart_add_series(widget_id, series_id, bars, series_type="Line", series_options={...})
tvchart_remove_series(widget_id, series_id)
tvchart_apply_options(widget_id, chart_options=..., series_id=..., series_options=...)If no typed tool exists for a specific event, use:
send_event(widget_id, event_type, data)Event types are namespaced tvchart:<event-name>. This is a raw passthrough — prefer the typed tools above in every other case.
pseudo-JSON blocks in replies — only quote real tool returns.
widget_id — read it from the attachment or calllist_widgets().
send_event when a typed tool already exists for theevent.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.