tv-cdp-mcp
# tv-cdp-mcp
**Drive TradingView Desktop from an AI agent — charts, indicators, alerts, layouts and paper trades — over the Chrome DevTools Protocol.**
[](LICENSE)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
TradingView Desktop is an Electron app. Launch it with a debugging port open and
everything the UI can do becomes scriptable — because the UI itself is just
JavaScript calling `window.TradingViewApi`. `tv-cdp-mcp` is a
[Model Context Protocol](https://modelcontextprotocol.io) server that exposes that
surface as **29 typed tools**, so an MCP-capable agent (Claude Code, Claude Desktop,
or anything else that speaks MCP) can read your charts and act on them.
It talks to *your* TradingView Desktop, on *your* machine, over *localhost*. There is
no cloud service, no account of ours, and no credential of any kind in this repo.
---
## Table of contents
- [Features](#features)
- [Have Claude Code? Hand it the whole setup](SETUP-WITH-CLAUDE-CODE.md)
- [New here? Start with GETTING-STARTED.md](GETTING-STARTED.md)
- [No Claude Code? Set it up with Claude Desktop](SETUP-CLAUDE-DESKTOP.md)
- [No AI app at all? Drive it from a terminal](USE-WITHOUT-AI.md)
- [Examples](#examples)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick start](#quick-start)
- [Tools / API reference](#tools--api-reference)
- [Common arguments](#common-arguments)
- [Configuration](#configuration)
- [Security](#security)
- [Troubleshooting](#troubleshooting)
- [Limitations and known issues](#limitations-and-known-issues)
- [Contributing](#contributing)
- [Disclaimer](#disclaimer)
- [License](#license)
---
## Features
- **Chart control** — read and set the symbol, timeframe and chart type; read the full
chart state; capture PNG screenshots of the chart or the whole tab.
- **Indicators (studies)** — list what is on the chart, read every input with its human
name, type, options and ranges, and write inputs back with type coercion and validation.
- **Market data** — pull OHLCV bars straight out of the loaded series, and read the
plotted values of any indicator bar by bar (up to 500 bars).
- **Data export** — join bars and every indicator's plots into one table and write it as
CSV or JSON. TradingView Desktop refuses its own "Export chart data"; this composes the
export from the loaded series instead.
- **Alerts** — list alerts with their *machine-readable firing condition*, inspect one in
full, pause, resume, retarget the condition, create new alerts by cloning an existing
one, and delete permanently (with a required confirmation).
- **Layouts** — list saved layouts, open any chart URL (including someone else's shared
View Only chart), "Make a copy", rename, and delete with a protect list.
- **Indicator templates** — list, snapshot to a file, save, apply and delete.
- **Paper trading** — read broker status, place bracket orders (entry + stop + target),
close positions, cancel orders and move the stop/target of an open position.
**Paper accounts only**, behind a hard refusal and a configurable risk gate.
- **Multi-tab aware** — with several chart windows open, the server finds the one that is
actually *painting* rather than trusting `visibilityState`, which lies in Electron.
- **Tested without TradingView** — 330 unit tests run against a fake page, so the whole
tool surface is covered in CI without an account or a running chart.
## Examples
- **[alert-to-order-bridge](examples/alert-to-order-bridge/)** — a beginner-friendly,
exchange-agnostic reference for the rest of the pipeline: a chart alert fires, a
webhook arrives, and a bracket order (entry + stop + target) is placed on *your own*
exchange account, behind a guided `npm run setup` wizard and a refuse-by-default risk
gate. Demo/testnet by default, your keys stay on your machine, and it is a starting
point rather than a production trading system. New to this? It ships a
[paste-into-your-AI-agent setup prompt](examples/alert-to-order-bridge/AI-SETUP.md)
as well as the terminal wizard.
## Requirements
| | |
|---|---|
| **Node.js** | >= 22 (ESM, `node --test`, built-in `WebSocket`) |
| **TradingView Desktop** | installed and logged in, launched with a remote debugging port |
| **OS** | Windows, macOS or Linux. The bundled launcher scripts are Windows-only; on other platforms start TradingView with the flag yourself |
| **An MCP client** | Claude Code, Claude Desktop, or any MCP-capable agent |
A TradingView subscription tier that allows the features you drive (alerts, multiple
layouts, etc.) is between you and TradingView — this server only clicks the buttons you
already have.
## Installation
> **Have Claude Code?** **[SETUP-WITH-CLAUDE-CODE.md](SETUP-WITH-CLAUDE-CODE.md)** is the
> fastest route in this repo — two commands, one prompt, then a library of plain-English
> prompts instead of tool names.
>
> **Never set up an MCP server before?** Use
> **[GETTING-STARTED.md](GETTING-STARTED.md)** instead — the same steps, click by click,
> with the failure modes explained as you hit them. This section is the short version for
> people who already know the drill.
>
> **No Claude Code?** **[SETUP-CLAUDE-DESKTOP.md](SETUP-CLAUDE-DESKTOP.md)** walks through the
> whole thing using the Claude Desktop app instead — including the two JSON mistakes that
> silently stop it working.
>
> **No AI app at all?** You don't need one. **[USE-WITHOUT-AI.md](USE-WITHOUT-AI.md)** drives
> every tool from a terminal via `scripts/tv-cli.mjs` — same handlers the MCP server calls, no
> subscription of any kind.
```bash
git clone https://github.com/pbajkovic-hub/tv-cdp-mcp.git
cd tv-cdp-mcp
npm install
npm test # 330 tests, no TradingView and no exchange account needed
```
### 1. Start TradingView Desktop with the debugging port
The server attaches to TradingView's own Electron process. That process must be started
with `--remote-debugging-port`, which it does **not** do by default.
**Windows** — use the bundled launcher, which resolves the Store install at run time so
TradingView updates do not break it:
```powershell
.\scripts\launch-tv.ps1 # start it (or report that the port is already open)
.\scripts\launch-tv.ps1 -Restart # already running without the port? restart it with one
```
**macOS / Linux** — start the app with the flag directly, for example:
```bash
# macOS — adjust the path to your install
"/Applications/TradingView.app/Contents/MacOS/TradingView" --remote-debugging-port=9222 &
```
Verify the port is live — this should return a JSON array of open tabs:
```bash
curl http://127.0.0.1:9222/json
```
### 2. Register the server with your MCP client
**Claude Code:**
```bash
claude mcp add tv-cdp -- node <path-to-this-repo>/src/server.mjs
```
**Claude Desktop** — add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"tv-cdp": {
"command": "node",
"args": ["<path-to-this-repo>/src/server.mjs"]
}
}
}
```
Replace `<path-to-this-repo>` with the absolute path to your clone.
## Quick start
With TradingView Desktop running on the debug port and the server registered, ask your
agent something like *"what's on my chart right now?"* and it will call:
```jsonc
// tv_get_chart_state {}
{
"layout_name": "EXAMPLE BTC 1h",
"symbol": "BYBIT:BTCUSDT.P",
"resolution": "60",
"chartType": 1,
"study_count": 2,
"layout_id": "AbC12XyZ"
}
```
The responses below are trimmed to the interesting keys; see `docs/` for the full
shapes.
A typical read → act sequence:
```jsonc
// 1. what indicators are loaded?
// tv_list_studies {}
{ "studies": [ { "id": "P6SYjF", "name": "Example Oscillator [Vendor]" } ] }
// 2. read one indicator's inputs by name substring
// tv_get_study_inputs { "study": "oscillator" }
{ "id": "P6SYjF", "inputs": [ { "id": "in_0", "name": "Length", "type": "integer", "value": 14 } ] }
// 3. change a setting
// tv_set_study_inputs { "study": "P6SYjF", "inputs": { "in_0": 21 } }
{ "changed": [ { "id": "in_0", "previous": 14, "current": 21 } ] }
// 4. export bars + every plot to CSV
// tv_export_data { "count": 300 }
{ "symbol": "BYBIT:BTCUSDT.P", "bars": 300, "file": "<repo>/exports/BYBIT_BTCUSDT.P_60_20260914-2210.csv" }
```
Every mutating tool returns the **previous** value next to the new one, so an agent can
always undo what it just did.
## Tools / API reference
29 tools across 8 modules. `MUTATES` marks anything that changes your chart, account or
broker state; everything else is strictly read-only.
### Chart
| Tool | Mutates | Description |
|---|---|---|
| `tv_list_layouts` | no | List every open chart tab (index, layout id, title, url) and which one is actually painting |
| `tv_get_chart_state` | no | Symbol, extended symbol info, resolution, chart type, study count, layout name and id |
| `tv_set_symbol` | **yes** | Change the active chart symbol; returns `{ previous, current }` and a resolution check |
| `tv_set_resolution` | **yes** | Change the timeframe (`"1"`, `"5"`, `"60"`, `"1D"`, `"1W"`); returns `{ previous, current }` |
| `tv_screenshot` | no | PNG screenshot of the chart tab; `clip_to_chart` crops to the chart container |
### Indicators (studies)
| Tool | Mutates | Description |
|---|---|---|
| `tv_list_studies` | no | Indicators and strategies on the chart with their study ids |
| `tv_get_study_inputs` | no | One study's inputs with human names, types, options and ranges; accepts id or name substring |
| `tv_set_study_inputs` | **yes** | Set one or more inputs by id, coerced to the declared type and validated, then re-read |
### Data
| Tool | Mutates | Description |
|---|---|---|
| `tv_get_bars` | no | OHLCV bars from the loaded series, newest last; max 500, only what the chart holds |
| `tv_get_study_values` | no | Plotted values of one study for the most recent bars, newest last |
| `tv_export_data` | no | Bars + every study's plots joined on bar time, written as CSV/JSON or returned inline |
### Alerts
| Tool | Mutates | Description |
|---|---|---|
| `tv_list_alerts` | no | Account alerts in panel order with their machine-readable firing condition and `panel_pos` |
| `tv_get_alert` | no | One alert in full: complete description, untruncated condition with every study input, raw model |
| `tv_set_alert_condition` | **yes** | Shallow-merge a patch (condition, message, webhook, name, resolution…) into a live alert |
| `tv_pause_alerts` | **yes** | Pause alerts — they stay defined but stop firing; returns per-id previous/current state |
| `tv_resume_alerts` | **yes** | Resume alerts so they fire again, webhooks included |
| `tv_create_alert` | **yes** | Create an alert by **cloning** an existing one and retargeting the clone; paused by default |
| `tv_delete_alerts` | **yes** | Permanently delete alerts by id; requires `confirm=true`; echoes name + symbol + id of each |
### Layouts
| Tool | Mutates | Description |
|---|---|---|
| `tv_list_saved_layouts` | no | Saved layouts of the logged-in account, newest first, with an optional name filter |
| `tv_open_chart_url` | **yes** | Open a chart URL or bare layout id in the tab — your own or someone's View Only chart |
| `tv_copy_layout` | **yes** | "Make a copy" of the layout on screen (or of `source_url` first) under a new name |
| `tv_rename_layout` | **yes** | Rename the layout on screen; refuses a read-only chart |
| `tv_delete_layout` | **yes** | Delete one saved layout by id or exact name; `confirm=true`, `protect` list, refuses the layout on screen |
### Indicator templates
| Tool | Mutates | Description |
|---|---|---|
| `tv_study_template` | **yes** | One tool switched on `action`: `list`, `snapshot` (to file), `save`, `apply` (**replaces every study**), `delete` (`confirm`) |
### Paper trading
All five refuse unless the connected broker is TradingView **Paper Trading** on a `demo`
account — checked in JS before the call and again inside the page body.
| Tool | Mutates | Description |
|---|---|---|
| `tv_trading_status` | no | Broker id/title, account id/type, connection, open positions, working orders, capabilities, active gate |
| `tv_place_order` | **yes** | Place an order with a **mandatory stop loss** and optional take profit; qty sized from `risk_usd` / stop distance; `dry_run` first |
| `tv_close_position` | **yes** | Market-close an open position by symbol or position id |
| `tv_cancel_order` | **yes** | Cancel one working order by id |
| `tv_set_position_brackets` | **yes** | Move the stop loss and/or take profit of an open position; side-sanity checked; `dry_run` supported |
**Prefer a terminal to an agent?** `node scripts/tv-cli.mjs --list` runs any tool below
directly — see [USE-WITHOUT-AI.md](USE-WITHOUT-AI.md).
See [`docs/`](docs/) for full argument shapes, return values and the research notes behind
each module: [ALERTS](docs/ALERTS.md), [LAYOUTS](docs/LAYOUTS.md),
[STUDY-INPUTS](docs/STUDY-INPUTS.md), [TEMPLATES](docs/TEMPLATES.md),
[TRADING](docs/TRADING.md), [EXPORT](docs/EXPORT.md).
## Common arguments
Every session-backed tool also accepts:
- **`layout`** — which chart tab to act on: a layout id, a DevTools target id, or a
0-based index. Default is the tab that is actually painting.
- **`expect_layout`** — the layout id the caller believes that tab is showing. The call
refuses with `expect_layout mismatch` if the tab has moved on.
`expect_layout` matters more than it looks: a tab keeps its DevTools target id when you
open a different layout in it, so an `active` pick can land on a different chart than the
agent last saw. Every mutating result therefore carries the `layout_id` / `layout_name` of
the chart it actually hit.
## Configuration
All configuration is environment variables. **No config file, no credentials, no secrets.**
| Variable | Default | Meaning |
|---|---|---|
| `TV_CDP_PORT` | `9222` | Port the CDP session connects to on `127.0.0.1` |
| `TV_EXPORT_DIR` | `<repo>/exports` | Where `tv_export_data` writes files |
| `TV_MAX_RISK_USD` | `50` | Paper-trading gate: max USD at risk per entry |
| `TV_MAX_OPEN_POSITIONS` | `2` | Paper-trading gate: max simultaneous open positions |
| `TV_DENY_SYMBOLS` | *(empty)* | Comma-separated tickers the trading tools always refuse |
| `TV_DEFAULT_EXCHANGE` | `BYBIT` | Exchange prefix applied to bare tickers (`ethusdt` → `BYBIT:ETHUSDT.P`) |
Example:
```bash
TV_MAX_RISK_USD=25 TV_DENY_SYMBOLS=FOOUSDT,BARUSDT node src/server.mjs
```
The risk gate is a guard rail against a runaway agent, not a trading strategy. Set it to
something you would be comfortable losing while you are not watching.
## Security
**The debugging port is a root shell into your logged-in TradingView session.** Anything
that can reach `127.0.0.1:9222` can read your charts, your alerts and your account, and
can act as you.
- The server connects to `127.0.0.1` only.
- **Never expose that port beyond localhost and never tunnel it** — no ngrok, no SSH
port-forward, no `--remote-debugging-address=0.0.0.0`.
- Close the debug-port instance when you are done, or run it only while you are working.
- The MCP server stores no credentials and reads no secret files. The only environment
variables it touches are the ones in the table above. (The optional example under
[`examples/`](examples/alert-to-order-bridge/) is separate software you opt into: it keeps
your exchange keys in its own git-ignored `.env`, sends them only to your exchange, and is
never loaded by the MCP server.)
## Troubleshooting
**`TradingView Desktop is not reachable on 127.0.0.1:9222`**
The app is not running with the debugging port. Run `scripts\launch-tv.ps1` (add
`-Restart` if it is already running without the port), or start it manually with
`--remote-debugging-port=9222`. Confirm with `curl http://127.0.0.1:9222/json`.
**`no TradingView chart tab is open`**
The server needs at least one `.../chart` tab as a CDP target. Open a chart window in
TradingView Desktop.
**A tool acted on the wrong chart**
You have several chart tabs open and the agent used the default `active` pick. Pass an
explicit `layout` (layout id or index), and pass `expect_layout` on mutating calls.
**Screenshots hang forever**
TradingView's `Page.captureScreenshot` never returns while the window is minimised. The
server detects the minimised state and races the capture against a 12 s timeout, but the
fix is to un-minimise the window.
**A study's inputs come back but writes silently do nothing**
Check that you are addressing the input by its `id` (`in_0`, …) as returned by
`tv_get_study_inputs`, not by its display name.
**Tests fail after a TradingView update**
They should not — the suite runs entirely against a fake page. If *live* behaviour breaks
after an update, the page-side object paths may have moved; see
[Limitations](#limitations-and-known-issues).
## Limitations and known issues
Honest list. Most of these are properties of TradingView, not bugs we can fix.
- **Private API, no stability promise.** This drives `window.TradingViewApi` and several
underscore-prefixed internals (`_activeChartWidgetWV`, `_chartWidgetCollection`). A
TradingView update can move them without warning. The page-side paths are documented in
`docs/` and in each module header so they are quick to re-verify.
- **`visibilityState` lies in Electron.** Every TradingView Desktop tab reports itself as
`visible`. The server scores tabs with a `requestAnimationFrame` probe — only the
painting tab fires one — and binds that tab. This costs ~250 ms when more than one chart
tab is open.
- **Creating an alert means cloning one.** Building an alert from scratch requires driving
the create dialog; retargeting a server-side clone through the UI's own modify path is
far more reliable. So `tv_create_alert` needs an existing alert as its template, and new
alerts are created **paused** by default.
- **Clone-retarget quirk.** When you clone an alert and point it at a new symbol, the
symbol lives in the model as *both* a parsed object and an `"="`-prefixed JSON string,
and the condition's study inputs can hold a reference to *another* study's plot. Change
only one of those and the server accepts the alert but it either fires on the old symbol
or comes back with a `study_error` and never fires at all — silently, with no UI
warning. `tv_create_alert` rewrites both representations and strips stale plot
references; always check `last_error` after creating one. This is the single sharpest
edge in the whole TradingView alert API.
- **Alert resume is eventually-consistent.** `restartAlerts` returns success before the
alert actually flips to active — allow 60–90 s, and re-check rather than assuming the
call failed.
- **Copying a layout takes 45–60 s.** It is a server-side clone plus a full page reload.
Copying *another user's* chart opens the copy in a **new tab**.
- **Export is limited to what is loaded.** Max 500 bars, and only history the chart has
actually scrolled into memory. Built-in studies without a data series (Volume, etc.)
expose only the last bar's formatted strings and are reported under `skipped`.
- **Paper trading only, by design.** The trading tools hard-refuse any broker that is not
TradingView Paper Trading on a `demo` account. Connecting a real broker and removing
that check is not supported and not advised.
- **Alert `message` bodies are opaque.** The server passes them through as text and does
not parse, validate or redact them.
- **Windows-first tooling.** The launcher scripts are PowerShell. The server itself is
platform-agnostic; contributions for macOS/Linux launchers are welcome.
## Contributing
Issues and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the
module contract, code style and test expectations. In short: one owner per file, every
tool module exports the same shape, and the test suite must keep running **without**
TradingView.
## Disclaimer
**This software automates a trading application. Trading carries financial risk and you
can lose money.**
- Provided **as-is, without warranty of any kind**. See [LICENSE](LICENSE).
- This is **not financial advice** and contains no trading strategy.
- The trading tools are restricted to **paper/demo accounts**. Do not modify them to reach
a live account unless you fully understand the consequences — an automated agent with a
bug can act faster than you can stop it.
- You are responsible for complying with TradingView's Terms of Service. Automating a
desktop client you are logged into may or may not be permitted for your account type;
check before you rely on it.
- Nothing here is affiliated with or endorsed by TradingView.
## License
[MIT](LICENSE).
TDQS
Scored across 29 tools
Every tool targets a distinct resource and action—alerts, layouts, chart state, studies, and trading each have clear boundaries. Even similar-sounding tools like tv_list_alerts vs tv_get_alert or tv_list_layouts vs tv_list_saved_layouts are unambiguously separated by read scope and detail.
The dominant pattern is tv_verb_noun in snake_case (list_alerts, set_symbol, delete_layout), which is consistent and predictable. Minor deviations include tv_screenshot, tv_study_template (noun with action parameter), and tv_trading_status, but these do not undermine the overall pattern.
29 tools is on the higher end for an MCP server, but the server covers five distinct domains (alerts, layouts, chart, studies, trading), and each tool has a distinct role. The count feels justified rather than bloated, though it is close to the upper bound of what is appropriate.
The surface is nearly complete for the stated scope: full CRUD for alerts and layouts, chart inspection and mutation, study input management, template lifecycle, and paper trading operations. Minor gaps exist—such as no way to remove a single study from a chart or modify an existing order's price/size—but agents can work around them.