tradingview-mcp 26.2.0__tar.gz → 26.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/PKG-INFO +10 -3
  2. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/README.md +9 -2
  3. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/pyproject.toml +1 -1
  4. tradingview_mcp-26.3.0/src/tradingview_mcp/constants.py +196 -0
  5. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/docs_data.py +41 -0
  6. tradingview_mcp-26.3.0/src/tradingview_mcp/resources.py +63 -0
  7. tradingview_mcp-26.3.0/src/tradingview_mcp/server.py +249 -0
  8. tradingview_mcp-26.3.0/src/tradingview_mcp/tools/__init__.py +0 -0
  9. tradingview_mcp-26.3.0/src/tradingview_mcp/tools/reference.py +70 -0
  10. tradingview_mcp-26.3.0/src/tradingview_mcp/tools/screener.py +87 -0
  11. tradingview_mcp-26.3.0/src/tradingview_mcp/tools/search.py +326 -0
  12. tradingview_mcp-26.3.0/src/tradingview_mcp/tools/technical.py +113 -0
  13. tradingview_mcp-26.2.0/src/tradingview_mcp/constants.py +0 -427
  14. tradingview_mcp-26.2.0/src/tradingview_mcp/server.py +0 -1615
  15. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/.gitignore +0 -0
  16. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/LICENSE +0 -0
  17. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/__init__.py +0 -0
  18. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/column.py +0 -0
  19. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/__init__.py +0 -0
  20. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/column_display_names.json +0 -0
  21. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/__init__.py +0 -0
  22. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/ai_quick_reference.json +0 -0
  23. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/common_fields.json +0 -0
  24. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/fields_by_market.json +0 -0
  25. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/screener_code_examples.json +0 -0
  26. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/stock_screener_presets.json +0 -0
  27. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/markets.json +0 -0
  28. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/bond.json +0 -0
  29. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/bonds.json +0 -0
  30. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/cfd.json +0 -0
  31. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/coin.json +0 -0
  32. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/crypto.json +0 -0
  33. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/economics2.json +0 -0
  34. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/forex.json +0 -0
  35. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/futures.json +0 -0
  36. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/ireland.json +0 -0
  37. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/options.json +0 -0
  38. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/stocks.json +0 -0
  39. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/main_screeners.json +0 -0
  40. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/markets.json +0 -0
  41. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/stocks.json +0 -0
  42. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/stocks_failed.json +0 -0
  43. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/models.py +0 -0
  44. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/query.py +0 -0
  45. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/scanner.py +0 -0
  46. {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tradingview-mcp
3
- Version: 26.2.0
3
+ Version: 26.3.0
4
4
  Summary: A comprehensive MCP server for TradingView market screening with integrated screener functionality
5
5
  Project-URL: Homepage, https://github.com/k73a/tradingview-mcp
6
6
  Project-URL: Documentation, https://github.com/k73a/tradingview-mcp#readme
@@ -43,7 +43,6 @@ Description-Content-Type: text/markdown
43
43
  MCP server for TradingView market screening. Supports stocks, crypto, forex, futures, bonds across 76+ markets.
44
44
 
45
45
  ## Quick Start
46
-
47
46
  ```json
48
47
  {
49
48
  "mcpServers": {
@@ -55,6 +54,12 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
55
54
  }
56
55
  ```
57
56
 
57
+ ## Smart Features
58
+
59
+ - **Auto-Market Detection**: If you search for `EURUSD` or `BTCUSDT`, the server automatically checks Forex and Crypto markets if not found in stocks.
60
+ - **Strict Exchange Support**: Use `EXCHANGE:SYMBOL` format (e.g., `BINANCE:BTCUSDT`, `FX:EURUSD`) to target specific markets.
61
+ - **Smart Filters**: You can use human-readable names in filters (e.g., `"Relative Strength Index (14)"` instead of `"RSI"`).
62
+
58
63
  ## Main Tools
59
64
 
60
65
  | Tool | Description |
@@ -87,7 +92,9 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
87
92
  | `list_fields_for_market` | List fields for a market |
88
93
  | `get_screener_preset` | Get predefined screener presets |
89
94
 
90
- ## Resources
95
+ ## Reference Resources
96
+
97
+ These resources provide raw lists of available markets and fields for reference.
91
98
 
92
99
  | Resource | Description |
93
100
  |----------|-------------|
@@ -7,7 +7,6 @@
7
7
  MCP server for TradingView market screening. Supports stocks, crypto, forex, futures, bonds across 76+ markets.
8
8
 
9
9
  ## Quick Start
10
-
11
10
  ```json
12
11
  {
13
12
  "mcpServers": {
@@ -19,6 +18,12 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
19
18
  }
20
19
  ```
21
20
 
21
+ ## Smart Features
22
+
23
+ - **Auto-Market Detection**: If you search for `EURUSD` or `BTCUSDT`, the server automatically checks Forex and Crypto markets if not found in stocks.
24
+ - **Strict Exchange Support**: Use `EXCHANGE:SYMBOL` format (e.g., `BINANCE:BTCUSDT`, `FX:EURUSD`) to target specific markets.
25
+ - **Smart Filters**: You can use human-readable names in filters (e.g., `"Relative Strength Index (14)"` instead of `"RSI"`).
26
+
22
27
  ## Main Tools
23
28
 
24
29
  | Tool | Description |
@@ -51,7 +56,9 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
51
56
  | `list_fields_for_market` | List fields for a market |
52
57
  | `get_screener_preset` | Get predefined screener presets |
53
58
 
54
- ## Resources
59
+ ## Reference Resources
60
+
61
+ These resources provide raw lists of available markets and fields for reference.
55
62
 
56
63
  | Resource | Description |
57
64
  |----------|-------------|
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tradingview-mcp"
3
- version = "26.2.0"
3
+ version = "26.3.0"
4
4
  description = "A comprehensive MCP server for TradingView market screening with integrated screener functionality"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -0,0 +1,196 @@
1
+ """
2
+ Constants for TradingView Screener integration.
3
+
4
+ Contains API URLs, headers, supported markets, and column definitions.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from pathlib import Path
10
+
11
+ # API Configuration
12
+ URL = "https://scanner.tradingview.com/{market}/scan"
13
+
14
+ HEADERS = {
15
+ "authority": "scanner.tradingview.com",
16
+ "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"',
17
+ "accept": "text/plain, */*; q=0.01",
18
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
19
+ "sec-ch-ua-mobile": "?0",
20
+ "user-agent": (
21
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
22
+ "(KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
23
+ ),
24
+ "sec-ch-ua-platform": '"Windows"',
25
+ "origin": "https://www.tradingview.com",
26
+ "sec-fetch-site": "same-site",
27
+ "sec-fetch-mode": "cors",
28
+ "sec-fetch-dest": "empty",
29
+ "referer": "https://www.tradingview.com/",
30
+ "accept-language": "en-US,en;q=0.9",
31
+ }
32
+
33
+ DEFAULT_RANGE = [0, 50]
34
+
35
+ # =============================================================================
36
+ # Load Data from JSON (Dynamic)
37
+ # =============================================================================
38
+
39
+ _DATA_DIR = Path(__file__).parent / "data"
40
+
41
+ def _load_markets() -> set[str]:
42
+ """Load supported markets from JSON."""
43
+ try:
44
+ with open(_DATA_DIR / "markets.json", "r", encoding="utf-8") as f:
45
+ data = json.load(f)
46
+ return set(data.get("countries", []) + data.get("other", []))
47
+ except Exception:
48
+ # Fallback if file not found (dev/test env)
49
+ return {"america", "crypto", "forex"}
50
+
51
+ def _load_columns() -> dict[str, str]:
52
+ """
53
+ Load column definitions from JSON.
54
+ Returns: Dict[Display Name, Field Name] (Reverse of JSON)
55
+ """
56
+ try:
57
+ with open(_DATA_DIR / "column_display_names.json", "r", encoding="utf-8") as f:
58
+ data = json.load(f)
59
+ fields = data.get("fields", {})
60
+ # Reverse mapping: Display Name -> Field Name
61
+ # e.g. "Relative Strength Index (14)" -> "RSI"
62
+ return {v: k for k, v in fields.items()}
63
+ except Exception:
64
+ return {}
65
+
66
+ # Public Constants
67
+ MARKETS = _load_markets()
68
+ COLUMNS = _load_columns()
69
+
70
+ # Exchange to Screener Mapping (Expanded)
71
+ EXCHANGE_SCREENER = {
72
+ # Crypto
73
+ "all": "crypto",
74
+ "binance": "crypto",
75
+ "coinbase": "crypto",
76
+ "kraken": "crypto",
77
+ "kucoin": "crypto",
78
+ "bitfinex": "crypto",
79
+ "bitstamp": "crypto",
80
+ "okx": "crypto",
81
+ "bybit": "crypto",
82
+ "gateio": "crypto",
83
+ "huobi": "crypto",
84
+ "gemini": "crypto",
85
+ "poloniex": "crypto",
86
+ "bittrex": "crypto",
87
+ "bitmex": "crypto",
88
+ "deribit": "crypto",
89
+ "mexc": "crypto",
90
+ "bingx": "crypto",
91
+ "phemex": "crypto",
92
+ "bitget": "crypto",
93
+ # Forex
94
+ "fx": "forex",
95
+ "fx_idc": "forex",
96
+ "oanda": "forex",
97
+ "forex": "forex",
98
+ "ice": "forex", # Often forex/futures
99
+ "saxo": "forex",
100
+ "cityindex": "forex",
101
+ # Futures
102
+ "cme": "futures",
103
+ "cbot": "futures",
104
+ "comex": "futures",
105
+ "nymex": "futures",
106
+ "eurex": "futures",
107
+ # US Stocks
108
+ "nasdaq": "america",
109
+ "nyse": "america",
110
+ "amex": "america",
111
+ "arca": "america",
112
+ "otc": "america",
113
+ # Global Stocks
114
+ "lse": "uk", # London
115
+ "tsx": "canada", # Toronto
116
+ "asx": "australia",
117
+ "sse": "china", # Shanghai
118
+ "szse": "china", # Shenzhen
119
+ "hkex": "hongkong",
120
+ "hk": "hongkong",
121
+ "tse": "japan", # Tokyo
122
+ "tyo": "japan",
123
+ "ebs": "switzerland", # Swiss SIX
124
+ "six": "switzerland",
125
+ "xetra": "germany",
126
+ "fwb": "germany",
127
+ "euronext": "france", # Generic Euronext
128
+ "nse": "india",
129
+ "bse": "india",
130
+ "krx": "korea",
131
+ "kosdaq": "korea",
132
+ "b3": "brazil",
133
+ "moex": "russia",
134
+ "sgx": "singapore",
135
+ "twse": "taiwan",
136
+ }
137
+
138
+ # Allowed Timeframes
139
+ ALLOWED_TIMEFRAMES = {"1m", "5m", "15m", "30m", "1h", "2h", "4h", "1D", "1W", "1M"}
140
+
141
+ # Timeframe to TradingView Resolution
142
+ TIMEFRAME_MAP = {
143
+ "1m": "1",
144
+ "5m": "5",
145
+ "15m": "15",
146
+ "30m": "30",
147
+ "1h": "60",
148
+ "2h": "120",
149
+ "4h": "240",
150
+ "1D": "1D",
151
+ "1W": "1W",
152
+ "1M": "1M",
153
+ }
154
+
155
+ # Default columns for different query types
156
+ # Always include 'description' for full name (e.g., "Apple Inc." instead of just "AAPL")
157
+ DEFAULT_COLUMNS = ["name", "description", "close", "change", "volume", "market_cap_basic"]
158
+
159
+ PREMARKET_COLUMNS = ["name", "description", "close", "volume", "premarket_change", "premarket_change_abs", "premarket_volume"]
160
+
161
+ POSTMARKET_COLUMNS = ["name", "description", "close", "volume", "postmarket_change", "postmarket_change_abs", "postmarket_volume"]
162
+
163
+ CRYPTO_COLUMNS = ["name", "description", "close", "change", "volume", "market_cap_basic", "24h_vol|5"]
164
+
165
+ TECHNICAL_COLUMNS = [
166
+ "name",
167
+ "description",
168
+ "close",
169
+ "volume",
170
+ "change",
171
+ "RSI",
172
+ "MACD.macd",
173
+ "MACD.signal",
174
+ "BB.upper",
175
+ "BB.lower",
176
+ "SMA20",
177
+ "EMA50",
178
+ "EMA200",
179
+ "ADX",
180
+ "Stoch.K",
181
+ "Stoch.D",
182
+ ]
183
+
184
+ def get_column_name(name: str) -> str:
185
+ """Get the API column name from a human-readable name or return as-is."""
186
+ # Try exact match first
187
+ if name in COLUMNS:
188
+ return COLUMNS[name]
189
+
190
+ # Try case-insensitive keys
191
+ name_lower = name.lower()
192
+ for k, v in COLUMNS.items():
193
+ if k.lower() == name_lower:
194
+ return v
195
+
196
+ return name
@@ -165,6 +165,47 @@ def get_fields_by_market(market: str | None = None) -> dict[str, Any]:
165
165
  return data
166
166
 
167
167
 
168
+ def get_default_columns_for_market(market: str) -> list[str]:
169
+ """Get smart default columns for a market to avoid 400 errors."""
170
+ # Base columns safe for all
171
+ cols = ["name", "description", "close", "change", "change_abs", "exchange", "type"]
172
+
173
+ # Check if market has specific fields in data
174
+ fields = get_fields_by_market(market)
175
+ if not fields:
176
+ # Fallback defaults if market data missing
177
+ if market == "crypto":
178
+ return cols + ["volume", "market_cap_basic", "24h_vol|5"]
179
+ if market == "forex":
180
+ return cols + ["bid", "ask"]
181
+ return cols + ["volume", "market_cap_basic", "price_earnings_ttm", "sector", "industry"]
182
+
183
+ # Extract available field names
184
+ valid_names = {f.get("name") for f in fields if f.get("name")}
185
+
186
+ # Add common extra columns if they exist in this market
187
+ extras = [
188
+ "open", "high", "low", "volume", "market_cap_basic",
189
+ "24h_vol|5", "bid", "ask", "price_earnings_ttm",
190
+ "earnings_per_share_basic_ttm", "dividend_yield_recent",
191
+ "sector", "industry"
192
+ ]
193
+
194
+ for extra in extras:
195
+ if extra in valid_names:
196
+ cols.append(extra)
197
+
198
+ # Deduplicate while preserving order
199
+ seen = set()
200
+ unique_cols = []
201
+ for c in cols:
202
+ if c not in seen:
203
+ seen.add(c)
204
+ unique_cols.append(c)
205
+
206
+ return unique_cols
207
+
208
+
168
209
  def paginate_data(
169
210
  data: list | dict,
170
211
  offset: int = 0,
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+ from typing import Any
3
+
4
+ from tradingview_mcp.docs_data import (
5
+ get_markets_data,
6
+ get_column_display_names,
7
+ get_screener_presets,
8
+ get_screener_markets,
9
+ get_stock_screeners,
10
+ get_stock_screeners_failed,
11
+ get_fields_by_market,
12
+ get_ai_quick_reference,
13
+ get_screener_code_examples,
14
+ )
15
+ from tradingview_mcp.constants import EXCHANGE_SCREENER
16
+
17
+ def list_markets() -> dict[str, Any]:
18
+ """List all available markets."""
19
+ return get_markets_data()
20
+
21
+ def list_columns() -> dict[str, str]:
22
+ """List all available columns."""
23
+ return get_column_display_names()
24
+
25
+ def list_crypto_exchanges() -> list[str]:
26
+ """List all crypto exchanges."""
27
+ return [k for k, v in EXCHANGE_SCREENER.items() if v == "crypto"]
28
+
29
+ def list_scanner_presets() -> list[dict[str, Any]]:
30
+ """List scanner presets."""
31
+ return get_screener_presets()
32
+
33
+ def docs_params() -> dict[str, Any]:
34
+ """Documentation for parameters."""
35
+ return {"limit": "Max 500", "range": "[0, 100]"}
36
+
37
+ def docs_screeners() -> list[str]:
38
+ """Documentation for screeners."""
39
+ return get_screener_markets()
40
+
41
+ def docs_stock_screeners() -> list[dict[str, Any]]:
42
+ """Documentation for stock screeners."""
43
+ return get_stock_screeners()
44
+
45
+ def docs_stock_screeners_failed() -> list[dict[str, Any]]:
46
+ """Documentation for failed stock screeners."""
47
+ return get_stock_screeners_failed()
48
+
49
+ def docs_fields() -> dict[str, Any]:
50
+ """Documentation for fields."""
51
+ return get_fields_by_market("stocks") # Default to stocks
52
+
53
+ def docs_markets() -> dict[str, Any]:
54
+ """Documentation for markets."""
55
+ return get_markets_data()
56
+
57
+ def docs_ai_reference() -> dict[str, Any]:
58
+ """Documentation for AI reference."""
59
+ return get_ai_quick_reference()
60
+
61
+ def docs_code_examples() -> dict[str, Any]:
62
+ """Documentation for code examples."""
63
+ return get_screener_code_examples()
@@ -0,0 +1,249 @@
1
+ """
2
+ TradingView MCP Server - Main server implementation.
3
+
4
+ Provides comprehensive MCP tools and resources for TradingView market screening.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+ import json
11
+ import os
12
+ import traceback
13
+ from datetime import datetime
14
+ from functools import wraps
15
+ from typing import Any
16
+
17
+ from mcp.server.fastmcp import FastMCP
18
+
19
+ from tradingview_mcp.docs_data import suggest_fields_for_error
20
+ from tradingview_mcp.tools.search import search_symbols, get_symbol_info
21
+ from tradingview_mcp.tools.screener import (
22
+ screen_market,
23
+ get_top_gainers,
24
+ get_top_losers,
25
+ get_most_active
26
+ )
27
+ from tradingview_mcp.tools.technical import (
28
+ get_technical_analysis,
29
+ scan_rsi_extremes,
30
+ scan_bollinger_bands,
31
+ scan_macd_crossover
32
+ )
33
+ from tradingview_mcp.tools.reference import (
34
+ ai_get_reference,
35
+ search_available_fields,
36
+ get_code_example,
37
+ list_fields_for_market,
38
+ get_common_fields_summary,
39
+ lookup_single_field,
40
+ get_filter_example,
41
+ check_market,
42
+ get_help,
43
+ get_field_info,
44
+ get_screener_preset,
45
+ get_market_metainfo,
46
+ )
47
+ from tradingview_mcp.resources import (
48
+ list_markets,
49
+ list_columns,
50
+ list_crypto_exchanges,
51
+ list_scanner_presets,
52
+ docs_params,
53
+ docs_screeners,
54
+ docs_stock_screeners,
55
+ docs_stock_screeners_failed,
56
+ docs_fields,
57
+ docs_markets,
58
+ docs_ai_reference,
59
+ docs_code_examples,
60
+ )
61
+
62
+ # Initialize MCP Server
63
+ mcp = FastMCP(
64
+ name="TradingView Screener MCP",
65
+ instructions="""TradingView Market Screener MCP
66
+
67
+ Quick start:
68
+ 1. Call `get_help()` for usage
69
+ 2. Call `ai_get_reference()` for markets and fields
70
+
71
+ Common tools:
72
+ - `screen_market(market, limit)`
73
+ - `get_top_gainers(market, limit)`
74
+ - `get_top_losers(market, limit)`
75
+ - `search_symbols(query, market)`
76
+ - `get_symbol_info(symbol)`
77
+
78
+ Notes:
79
+ - `limit` default 25, max 500
80
+ - Results include `description`
81
+
82
+ Resources:
83
+ - docs://ai-reference
84
+ - markets://list
85
+ - columns://list
86
+ """,
87
+ )
88
+
89
+ DEBUG_MODE = os.getenv("TRADINGVIEW_MCP_DEBUG", "0").lower() in {"1", "true", "yes"}
90
+
91
+
92
+ def _safe_json(value: Any) -> Any:
93
+ try:
94
+ return json.loads(json.dumps(value, default=str))
95
+ except Exception:
96
+ return str(value)
97
+
98
+
99
+ def _debug_hint(exc: Exception, context: dict[str, Any] | None = None) -> dict[str, Any] | str | None:
100
+ """Generate helpful hints based on error type."""
101
+ message = str(exc).lower()
102
+
103
+ # Extract market from context if available
104
+ market = None
105
+ if context:
106
+ market = context.get("kwargs", {}).get("market") or context.get("market")
107
+
108
+ if "market" in message and "invalid" in message:
109
+ return "Check the market name using the markets://list or docs://screeners resources."
110
+
111
+ if "column" in message or "unknown" in message or "field" in message:
112
+ # Return structured field suggestions
113
+ market_type = market or "stocks"
114
+ if market_type in ["crypto", "coin"]:
115
+ market_type = market_type
116
+ elif market_type in ["forex"]:
117
+ market_type = "forex"
118
+ else:
119
+ # Default to stocks for field suggestions
120
+ market_type = "stocks"
121
+
122
+ return suggest_fields_for_error(str(exc), market_type)
123
+
124
+ if "http" in message or "status" in message:
125
+ return "TradingView API may be rate-limiting or blocked. Retry with fewer requests."
126
+
127
+ return None
128
+
129
+
130
+ def _build_error_response(tool_name: str, exc: Exception, context: dict[str, Any]) -> dict[str, Any]:
131
+ payload: dict[str, Any] = {
132
+ "ok": False,
133
+ "tool": tool_name,
134
+ "error": {"type": exc.__class__.__name__, "message": str(exc)},
135
+ "context": _safe_json(context),
136
+ "timestamp": datetime.utcnow().isoformat() + "Z",
137
+ }
138
+
139
+ hint = _debug_hint(exc, context)
140
+ if hint:
141
+ if isinstance(hint, dict):
142
+ payload["field_suggestions"] = hint
143
+ else:
144
+ payload["hint"] = hint
145
+
146
+ if DEBUG_MODE:
147
+ payload["trace"] = traceback.format_exc()
148
+ return payload
149
+
150
+
151
+ def debug_tool(fn):
152
+ """Decorator to return structured debug responses on failure."""
153
+
154
+ @wraps(fn)
155
+ def wrapper(*args, **kwargs):
156
+ try:
157
+ result = fn(*args, **kwargs)
158
+ if isinstance(result, dict) and "error" in result:
159
+ return _build_error_response(
160
+ fn.__name__,
161
+ Exception(str(result.get("error"))),
162
+ {"args": args, "kwargs": kwargs, "note": "error returned by tool"},
163
+ )
164
+ if (
165
+ isinstance(result, list)
166
+ and len(result) == 1
167
+ and isinstance(result[0], dict)
168
+ and "error" in result[0]
169
+ ):
170
+ return _build_error_response(
171
+ fn.__name__,
172
+ Exception(str(result[0].get("error"))),
173
+ {"args": args, "kwargs": kwargs, "note": "error returned by tool"},
174
+ )
175
+ return result
176
+ except Exception as exc:
177
+ context = {"args": args, "kwargs": kwargs}
178
+ return _build_error_response(fn.__name__, exc, context)
179
+
180
+ return wrapper
181
+
182
+
183
+ _original_mcp_tool = mcp.tool
184
+
185
+
186
+ def _debug_mcp_tool(*args, **kwargs):
187
+ """Wrap FastMCP tool registration with debug handling."""
188
+
189
+ def decorator(fn):
190
+ return _original_mcp_tool(*args, **kwargs)(debug_tool(fn))
191
+
192
+ return decorator
193
+
194
+
195
+ mcp.tool = _debug_mcp_tool
196
+
197
+
198
+ # =============================================================================
199
+ # Register Tools
200
+ # =============================================================================
201
+
202
+ mcp.tool()(search_symbols)
203
+ mcp.tool()(get_symbol_info)
204
+ mcp.tool()(screen_market)
205
+ mcp.tool()(get_top_gainers)
206
+ mcp.tool()(get_top_losers)
207
+ mcp.tool()(get_most_active)
208
+
209
+ mcp.tool()(get_technical_analysis)
210
+ mcp.tool()(scan_rsi_extremes)
211
+ mcp.tool()(scan_bollinger_bands)
212
+ mcp.tool()(scan_macd_crossover)
213
+
214
+ mcp.tool()(ai_get_reference)
215
+ mcp.tool()(search_available_fields)
216
+ mcp.tool()(get_code_example)
217
+ mcp.tool()(list_fields_for_market)
218
+ mcp.tool()(get_common_fields_summary)
219
+ mcp.tool()(lookup_single_field)
220
+ mcp.tool()(get_field_info)
221
+ mcp.tool()(get_filter_example)
222
+ mcp.tool()(check_market)
223
+ mcp.tool()(get_help)
224
+ mcp.tool()(get_screener_preset)
225
+ mcp.tool()(get_market_metainfo)
226
+
227
+ # =============================================================================
228
+ # Register Resources
229
+ # =============================================================================
230
+
231
+ mcp.resource("markets://list")(list_markets)
232
+ mcp.resource("columns://list")(list_columns)
233
+ mcp.resource("exchanges://crypto")(list_crypto_exchanges)
234
+ mcp.resource("scanners://presets")(list_scanner_presets)
235
+ mcp.resource("docs://params")(docs_params)
236
+ mcp.resource("docs://screeners")(docs_screeners)
237
+ mcp.resource("docs://stock-screeners")(docs_stock_screeners)
238
+ mcp.resource("docs://stock-screeners-failed")(docs_stock_screeners_failed)
239
+ mcp.resource("docs://fields")(docs_fields)
240
+ mcp.resource("docs://markets")(docs_markets)
241
+ mcp.resource("docs://ai-reference")(docs_ai_reference)
242
+ mcp.resource("docs://code-examples")(docs_code_examples)
243
+
244
+
245
+ def main():
246
+ mcp.run()
247
+
248
+ if __name__ == "__main__":
249
+ main()