Skip to main content
Glama
pbajkovic-hub

tv-cdp-mcp

README.md
# tv-cdp-mcp

**Drive TradingView Desktop from an AI agent — charts, indicators, alerts, layouts and paper trades — over the Chrome DevTools Protocol.**

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%3E%3D22-3c873a.svg)](https://nodejs.org)
[![MCP](https://img.shields.io/badge/protocol-MCP-6f42c1.svg)](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

A4/5.0

Scored across 29 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count4/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues