Skip to content

Connection and Architecture Internals

This page documents the internal architecture of tradingview-mcp: how it connects to TradingView Desktop, manages retries, validates input, waits for chart readiness, and formats MCP responses.


CDP Connection Management

File: src/connection.js

The connection layer manages a lazy singleton CDP (Chrome DevTools Protocol) client that connects to TradingView Desktop on localhost:9222.

Connection Lifecycle

getClient()
    |
    v
  client exists? --yes--> liveness check (evaluate '1')
    |                         |
    no                     passes --> return client
    |                         |
    v                      fails --> set client=null, reconnect
  connect()
    |
    v
  findChartTarget() --> scan /json/list for TradingView chart pages
    |
    v
  CDP({ host, port, target }) --> enable Runtime, Page, DOM
    |
    v
  return client

Key Behaviors

  • Lazy singleton: The client is created on first use and reused across all tool calls. No upfront connection is required.
  • Liveness check: Before reusing an existing client, getClient() evaluates '1' via Runtime.evaluate. If this fails (connection dropped, TradingView restarted), the client is discarded and a fresh connection is established.
  • Target discovery: findChartTarget() fetches http://localhost:9222/json/list and finds the first page target whose URL matches /tradingview\.com\/chart/i. Falls back to any target matching /tradingview/i.
  • Domain enablement: After connecting, the client enables Runtime, Page, and DOM domains.

Retry Logic

Connection attempts use exponential backoff:

Attempt Delay
0 500ms
1 1,000ms
2 2,000ms
3 4,000ms
4 8,000ms
  • Max retries: 5
  • Base delay: 500ms
  • Max delay cap: 30,000ms (30 seconds)
  • Formula: min(500 * 2^attempt, 30000)

After 5 failed attempts, a CDP connection failed error is thrown.


KNOWN_PATHS -- API Surface Map

The KNOWN_PATHS object documents the discovered internal API paths within TradingView's page context. These paths were found via live probing and are used throughout the codebase.

Path Key Expression Purpose
chartApi window.TradingViewApi._activeChartWidgetWV.value() Active chart widget -- primary entry point for chart operations
chartWidgetCollection window.TradingViewApi._chartWidgetCollection Collection of all chart widgets (multi-pane layouts)
bottomWidgetBar window.TradingView.bottomWidgetBar Bottom panel bar (Pine editor, strategy tester)
replayApi window.TradingViewApi._replayApi Replay mode controls
alertService window.TradingViewApi._alertService Alert management
chartApiInstance window.ChartApiInstance Alternative chart API reference
mainSeriesBars ...value()._chartWidget.model().mainSeries().bars() Direct access to OHLCV bar data
strategyStudy chart._chartWidget.model().model().dataSources() Strategy data sources (performance, orders, reports)
layoutManager window.TradingViewApi.getSavedCharts Saved chart layout management
symbolSearchApi window.TradingViewApi.searchSymbols Symbol search (returns Promise)
pineFacadeApi https://pine-facade.tradingview.com/pine-facade Pine Script REST API for saved scripts

Helper functions verify path availability before use:

getChartApi()         // verifies chartApi exists, returns the path string
getChartCollection()  // verifies chartWidgetCollection
getBottomBar()        // verifies bottomWidgetBar
getReplayApi()        // verifies replayApi
getMainSeriesBars()   // verifies mainSeriesBars

Each helper calls verifyAndReturn(path, name) which evaluates typeof (path) !== 'undefined' && (path) !== null before returning the path string.


Safety Functions

safeString(str)

Sanitizes a string for safe interpolation into JavaScript code evaluated via CDP. Uses JSON.stringify() to produce a properly escaped JS string literal (including quotes). This prevents injection via quotes, backticks, template literals, or control characters.

safeString("hello")       // '"hello"'
safeString('"; alert(1)') // '"\"; alert(1)"'  (escaped)

Every tool that interpolates user-provided strings into CDP expressions uses safeString().

requireFinite(value, name)

Validates that a value is a finite number. Throws if NaN, Infinity, or non-numeric. This prevents corrupt values from reaching TradingView APIs that persist state to the cloud.

requireFinite(42, "price")      // returns 42
requireFinite("abc", "price")   // throws: "price must be a finite number, got: abc"
requireFinite(Infinity, "price") // throws

JavaScript Execution

evaluate(expression, opts)

Executes a JavaScript expression in TradingView's page context via Runtime.evaluate. Returns the result value. If the evaluation throws, the error is extracted from exceptionDetails and re-thrown as a JavaScript Error.

Options:

  • returnByValue: true (default) -- serialize the result to JSON
  • awaitPromise: false (default) -- do not await Promises

evaluateAsync(expression)

Convenience wrapper that calls evaluate() with awaitPromise: true. Used for expressions that return a Promise (e.g., setSymbol(), searchSymbols()).


Chart Readiness Polling

File: src/wait.js

The waitForChartReady() function polls TradingView's DOM to determine when a chart has finished loading after a symbol or timeframe change.

Detection Strategy

The function polls at 200ms intervals (configurable timeout, default 10 seconds) and checks three conditions:

  1. Spinner detection: Looks for loading indicators via selectors [class*="loader"], [class*="loading"], and [data-name="loading"]. If a spinner is visible (offsetParent !== null), the chart is not ready.

  2. Symbol matching: If an expectedSymbol is provided, the function reads the current symbol from the chart legend ([data-name="legend-source-title"]) and checks for a case-insensitive match. Mismatches reset the stability counter.

  3. Bar count stability: The function counts chart bar elements and tracks whether the count remains stable across consecutive polls. The chart is considered ready when the bar count has been stable for 2 consecutive checks (approximately 400ms of stability).

Return Values

  • Returns true when all conditions pass (spinner gone, symbol matches, bars stable)
  • Returns false on timeout -- the caller should still proceed but may want to verify

Parameters

Parameter Default Description
expectedSymbol null Expected symbol to match (case-insensitive substring)
expectedTf null Reserved for future timeframe matching
timeout 10000 Maximum wait time in milliseconds

MCP Response Formatting

File: src/tools/_format.js

The jsonResult(obj, isError) function standardizes MCP tool responses into the format expected by the MCP protocol.

jsonResult({ symbol: "ES1!", price: 5200 })
// {
//   content: [{ type: "text", text: '{\n  "symbol": "ES1!",\n  "price": 5200\n}' }]
// }

jsonResult({ error: "Not connected" }, true)
// {
//   content: [{ type: "text", text: '{\n  "error": "Not connected"\n}' }],
//   isError: true
// }

All tool handler files import jsonResult from _format.js to ensure consistent response structure. The MCP protocol expects responses as content arrays with typed entries. The isError flag signals tool failure to the MCP client.


Architecture Diagram

Claude Code / MCP Client
         |
         | stdio (JSON-RPC)
         v
   MCP Server (src/server.js)
         |
         | imports tool handlers
         v
   Tool Layer (src/tools/*.js)
         |
         | calls core logic
         v
   Core Layer (src/core/*.js)
         |
         | evaluate() / evaluateAsync()
         v
   Connection Layer (src/connection.js)
         |
         | CDP over HTTP/WebSocket
         v
   TradingView Desktop (Electron)
         |
         | localhost:9222
         v
   Chrome DevTools Protocol

The CLI (src/cli/) provides an alternative entry point that bypasses the MCP server and calls the core layer directly, outputting JSON to stdout instead of MCP-formatted responses.