tradingview
by KajcsaErno
README.md
# tradingview-mcp
An MCP server and CLI that read and control a **live TradingView Desktop chart** over the Chrome
DevTools Protocol. 82 tools — chart state, OHLCV and indicator values, Pine Script edit/compile,
drawings, alerts, bar replay, screenshots — reachable from an LLM client or from a shell.
[](https://github.com/KajcsaErno/tradingview-mcp/actions/workflows/ci.yml)
```
MCP client <-> MCP server (stdio or HTTP) <-> CDP :9222 <-> TradingView Desktop (Electron)
```
There is no REST API behind this. TradingView Desktop is an Electron app, so the whole surface is
JavaScript evaluated inside the running page against `window.TradingViewApi` and its internals —
which is what lets it read things no public API exposes (Pine `line.new()` / `label.new()` /
`table.new()` output, strategy equity curves, the data window, replay state), and also what makes it
fragile: see [Caveats](#caveats).
## Quick start
Requires Node.js >= 22.15 and TradingView Desktop installed.
```bash
npm install
npm run tv -- launch # starts TradingView Desktop with CDP on :9222
npm run tv -- status # confirm the link is live
npm run tv -- symbol BTCUSD # drive the chart
npm run tv -- ohlcv --count 20 --summary
```
Then register the MCP server with your client:
```json
{
"mcpServers": {
"tradingview": {
"command": "node",
"args": ["/absolute/path/to/tradingview-mcp/src/tradingview/server.js"]
}
}
}
```
## Two surfaces, kept in sync by a test
Every MCP tool is also a shell command, and that is **enforced** rather than hoped for.
`tests/cli-parity.test.js` enumerates both surfaces from the real registration code — MCP tools via
a stub server that records `.tool()` calls, CLI commands via the router's own map — and diffs them
against `tests/_cli_parity_map.js`. Adding a tool without a CLI command (or the reverse) fails CI
unless the mismatch is declared with a written reason.
That map is also where the deliberate asymmetries live: the seven `tv stream *` commands are
CLI-only, because they are long-running JSONL poll loops that never return and so were never
sensible as MCP tools.
`tv --help` is grouped into labelled sections, and `tests/cli-groups.test.js` asserts those sections
are a *total, non-overlapping partition* of the registered commands — so a command added without a
section becomes a test failure instead of silently landing under "Other".
```bash
tv --help # grouped command list
tv <command> --help # that command's subcommands / options
tv help <term> # search command names + descriptions
```
31 top-level commands, 90 leaf commands, 82 MCP tools.
## Tool groups
| Group | Tools | What |
|---|---:|---|
| `data` | 12 | OHLCV, indicator values, strategy results, trades, equity, quote, depth, Pine graphics |
| `pine` | 12 | Pine source get/set, compile, errors, console, save, static analysis, server-side check |
| `ui` | 12 | Raw UI automation — click, keyboard, type, hover, scroll, find element, evaluate |
| `chart` | 10 | Symbol, timeframe, chart type, indicators, visible range, scroll-to-date, symbol search |
| `replay` | 6 | Bar-replay practice: start, step, autoplay, trade, status, stop |
| `drawing` | 5 | Draw shapes, list, clear, remove one, read properties |
| `alerts` | 4 | Create, list, delete, activate |
| `health` | 4 | CDP health check, launch, API-path discovery, UI state |
| `pane` | 4 | Pane/layout management |
| `tab` | 4 | Chart-tab management |
| `morning` | 3 | Rules-driven session brief + save/get (see `rules.example.json`) |
| `indicators` | 2 | Set inputs, toggle visibility |
| `watchlist` | 2 | Read, add |
| `batch` | 1 | Run one action across many symbols and/or timeframes |
| `capture` | 1 | Screenshot the chart |
## Architecture
```
core/*.js pure logic — builds a JS expression, evaluates it, returns { success, ... }
|
+-> tools/*.js MCP wrappers (Zod schemas) cli/*.js shell commands
| |
+-> mcp-app.js assembles the server +-> cli/index.js
|
+-> server.js (stdio) http.js (:3333, bearer auth + optional CF Access)
```
**The `_deps` seam.** Every core function takes a trailing `_deps` parameter defaulting to the real
`evaluate` / `evaluateAsync` / `waitForChartReady`:
```js
export async function setSymbol(symbol, _deps = {evaluate, waitForChartReady}) {
const expr = `window.TradingViewApi.setSymbol(${safeString(symbol)})`;
await _deps.evaluate(expr);
return _deps.waitForChartReady();
}
```
Tests inject a fake evaluator and assert on **the exact expression string** that would have been
sent. That is why the whole suite — 198 tests — runs offline with no TradingView, no chart and no
network, and why an injection regression is a red test rather than a live incident.
Two files carry most of the weight:
- **`connection.js`** — singleton CDP client with exponential-backoff reconnect, picking the Electron
target whose URL matches `tradingview.com/chart`. Exports `KNOWN_PATHS`, the verified deep paths
into the app internals.
- **`wait.js`** — `waitForChartReady()` polls the DOM (loader spinner, bar-count stability, symbol
match) after any chart mutation. There is no `setTimeout` anywhere in the codebase.
## Injection safety
Every user value interpolated into an evaluated JS string goes through `safeString` (a
`JSON.stringify` round-trip producing a properly escaped literal); every numeric goes through
`requireFinite`. This is the entire CDP-injection defense, so it is tested two ways:
1. **Behavioural** — `tests/tradingview/sanitization.test.js` feeds injection payloads through the
`_deps` seam and asserts on the generated expression.
2. **Source audit** — the same suite scans every `core/*.js` for raw interpolation of user input
into an `evaluate()` string, so a new function that forgets `safeString` fails the test even if
nobody wrote a case for it.
Filenames are additionally stripped of path separators (screenshot capture, batch output).
## HTTP transport
```bash
cp .env.example .env.http # set MCP_AUTH_TOKEN_TV
npm run start:http # 127.0.0.1:3333
```
Bearer auth is mandatory — the server refuses to start without a token of at least 16 characters.
`CF_ACCESS_TEAM_DOMAIN` + `CF_ACCESS_AUD` additionally require a valid Cloudflare Access JWT,
verified at the origin against the team JWKS with a zero-dependency RS256 check.
Beyond the full surface at `POST /mcp`, three group mounts expose a narrower tool set so a session
loads only what it needs: `POST /mcp/tv-chart` (read + light control), `/mcp/tv-pine` (Pine
development loop), `/mcp/tv-advanced` (drawing, alerts, replay, batch, raw UI). The grouping is a
clean partition of all 82 tools, enforced by a test.
## Testing
```bash
npm test # 198 tests, fully offline
npm run lint
npm run test:sanitization # the injection suite alone
npm run test:e2e # REQUIRES a live TradingView Desktop on :9222
```
`node:test` only — no Jest, Mocha or Vitest. The package is ESM (`"type": "module"`).
## Caveats
- **Unofficial.** Not affiliated with, endorsed by, or supported by TradingView Inc. or Anthropic.
- **It drives your own installed desktop app** through its debugging port. Nothing is scraped from
TradingView's servers and no account credentials pass through this code.
- **`KNOWN_PATHS` are undocumented internals** and can break on any TradingView update. Pin the
desktop version if you need stability.
- `chart_manage_indicator` needs full indicator names — "Relative Strength Index", not "RSI".
- Entity IDs from `chart_get_state` are session-scoped; never cache them across sessions.
- `ui_evaluate` runs arbitrary JS in the page and **bypasses input sanitization** by design. It is a
last-resort escape hatch, not a normal tool.
- Caps: OHLCV 500 bars, trades 20/request, Pine labels 50/study.
- Screenshots are written to `screenshots/` and the tool returns a **path**, not image bytes.
## License
MIT — see [LICENSE](LICENSE). Affiliation and trademark notices are in [NOTICE.md](NOTICE.md).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing