Skip to main content
Glama
guillemustafa-ux

tradingview-mcp-proof

tradingview-mcp-proof

An MCP server for the failure that costs money when TradingView data reaches a language model: the feed stops, the tool keeps answering, and Claude analyses forty-minute-old prices as if they were the live market.

This repo does not demonstrate "building an MCP server". The official SDK documentation covers that in a page. It demonstrates, with runnable evidence, that a server handing market state to a model:

  1. never lets a stale reading pass as current -- age, missing bars and a usability verdict travel with every value;

  2. never double-counts a redelivered alert, and never lets a late one rewrite the present;

  3. reports holes in history instead of closing them, including the case where the newest bar is perfectly fresh and the window behind it is broken;

  4. rejects forged alerts, because TradingView signs nothing.

You can verify all of it in about five minutes, on your machine, without a TradingView account, without an Anthropic key and without any network access. No credentials of mine are involved anywhere.

Why this is hard with TradingView specifically

A webhook alert is fire-and-forget HTTP. There is no delivery receipt, no retry and no ordering guarantee. If your endpoint is slow, restarting or briefly unreachable, that alert is gone -- and nothing downstream is told.

Four more traps compound it:

  • {{time}} and {{timenow}} are different clocks. {{time}} is the open of the bar that triggered the alert; {{timenow}} is when the alert fired. The freshness of a market view is governed by the bar, not by the firing, and using the wrong one hides staleness behind a recent timestamp.

  • {{interval}} renders intraday resolutions as a bare number of minutes. "60" is one hour. Read it as seconds and every hourly bar looks 59 minutes stale; hard-code a unit and daily bars break instead.

  • An alert that fires once per bar arrives before the bar closes. Its values can still change. Treated as settled history, it quietly corrupts the series.

  • There is no signature. No HMAC header, no shared-secret challenge -- anyone who learns the URL can post a signal the model will then reason over.

And the trap specific to putting any of this in front of an LLM: a model has no clock and no feed. Handed {"signal": "long", "price": 21850.25} it will analyse that as the present, because nothing in the payload says otherwise. It cannot caveat what it cannot see.

Related MCP server: TradingView-MCP

The design under test

  • Freshness is computed server-side and travels with the value. Not a timestamp the model is expected to interpret -- an explicit usable_for_analysis verdict plus the reason in words.

  • The ledger is append-only and keyed by alert_id. A redelivery is a no-op. Idempotency is what makes one signal one signal.

  • Current state is ordered by bar time, never by arrival. A late packet is stored as history and never becomes the present. Discarding it instead would manufacture a gap that was not there.

  • A close revises a provisional reading; nothing revises a close.

  • Gaps inside the window are counted and reported, separately from staleness at the edge, because a fresh newest bar over a holed history is the case that looks healthy and is not.

  • The server does not claim to know why data stopped. It cannot distinguish a feed outage from a session break without a market calendar, so it reports the observable gap and refuses to call either one live. Guessing here is how a server cries wolf every weekend until the operator stops believing it.

Verify it in five minutes

git clone https://github.com/guillemustafa-ux/tradingview-mcp-proof
cd tradingview-mcp-proof
python -m venv .venv && . .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest -v
test_staleness.py::test_naive_tool_presents_a_40_minute_old_reading_as_current PASSED
test_staleness.py::test_hardened_tool_refuses_to_call_the_same_reading_current PASSED
test_staleness.py::test_a_fresh_closed_bar_is_usable PASSED
test_staleness.py::test_one_missed_bar_is_already_stale PASSED
test_staleness.py::test_empty_symbol_is_not_a_silent_none PASSED

test_delivery.py::test_redelivered_alert_is_stored_once PASSED
test_delivery.py::test_a_late_bar_never_becomes_the_present PASSED
test_delivery.py::test_bar_close_revises_the_provisional_reading PASSED
test_delivery.py::test_a_close_is_never_revised_by_a_later_provisional PASSED

test_gaps.py::test_hole_inside_the_window_is_reported_not_interpolated PASSED
test_gaps.py::test_a_contiguous_window_reports_no_gap PASSED
test_gaps.py::test_recovery_makes_the_newest_bar_fresh_while_history_stays_holed PASSED

test_auth.py::test_forged_alert_never_enters_the_ledger PASSED
test_auth.py::test_missing_secret_is_rejected PASSED
test_auth.py::test_absent_and_wrong_secret_are_indistinguishable PASSED
test_auth.py::test_hourly_interval_is_read_as_minutes_not_seconds PASSED

test_server.py::test_server_exposes_the_three_tools PASSED
test_server.py::test_stale_verdict_survives_the_protocol_round_trip PASSED
test_server.py::test_feed_health_answers_without_being_asked_for_a_price PASSED
test_server.py::test_tool_survives_being_dispatched_on_another_thread PASSED

20 passed

What each scenario proves

#

Test

Situation

Proven

1a

test_naive_tool_presents_a_40_minute_old_reading_as_current

feed dead 40 min

the anti-pattern, demonstrated: the tutorial-shaped tool returns a payload with no temporal field at all, byte-identical to the one it gave when the data was fresh

1b

test_hardened_tool_refuses_to_call_the_same_reading_current

same ledger, same instant

STALE, age_seconds: 2400, missing_bars: 6, usable_for_analysis: false, plus an instruction in words

1c

test_one_missed_bar_is_already_stale

exactly one dropped alert

staleness does not require an outage; a single drop breaks the claim

1d

test_a_fresh_closed_bar_is_usable

healthy feed

no false alarm -- the verdict is usable and no warning is attached

1e

test_empty_symbol_is_not_a_silent_none

never-seen symbol

EMPTY, not a null that reads as "no signal"

2a

test_redelivered_alert_is_stored_once

TradingView re-fires the same alert

stored once; one signal stays one signal

2b

test_a_late_bar_never_becomes_the_present

bar 2 arrives after bar 3

the present does not rewind, and the late bar is still kept as history

2c

test_bar_close_revises_the_provisional_reading

intrabar alert, then its close

provisional while open, revised by the close, flagged throughout

2d

test_a_close_is_never_revised_by_a_later_provisional

stray intrabar after a close

settled history is not reopened

3a

test_hole_inside_the_window_is_reported_not_interpolated

bars 2 and 3 dropped

the hole is counted and named; the missing bars are absent, not guessed

3b

test_recovery_makes_the_newest_bar_fresh_while_history_stays_holed

feed recovers

"the feed is back" and "the data is usable" are two different claims

3c

test_a_contiguous_window_reports_no_gap

clean window

no false positive

4a

test_forged_alert_never_enters_the_ledger

wrong secret

rejected before storage

4b

test_absent_and_wrong_secret_are_indistinguishable

missing vs wrong secret

identical error, so the endpoint is not an oracle

4c

test_hourly_interval_is_read_as_minutes_not_seconds

1-hour chart

{{interval}} of "60" is 3600s, so an hourly feed is not permanently "stale"

5a

test_server_exposes_the_three_tools

real MCP wiring

the tools exist on an actual MCPServer

5b

test_stale_verdict_survives_the_protocol_round_trip

real call_tool

the verdict reaches the client through the protocol, not just the unit test

5c

test_tool_survives_being_dispatched_on_another_thread

SDK threading

see below

The drill

python scripts/drill.py

Replays a 5-minute feed that stops at 14:15 and prints what each server hands to Claude at four points in time:

=== Feed healthy -- wall clock 14:21 UTC ===
  naive server  -> {"symbol": "NQ1!", "signal": "long", "price": 21850.25}
  this server   -> {"price": 21850.25, "status": "FRESH", "age_seconds": 360,
                    "missing_bars": 0, "usable_for_analysis": true}

=== Feed down for 40 minutes -- wall clock 14:55 UTC ===
  naive server  -> {"symbol": "NQ1!", "signal": "long", "price": 21850.25}
  this server   -> {"price": 21850.25, "status": "STALE", "age_seconds": 2400,
                    "missing_bars": 6, "usable_for_analysis": false}
  warning       -> DO NOT present this as the current market. newest bar is
                   2400s old on a 300s chart; 6 bar(s) that should have arrived
                   did not. [...]

The naive line never changes. Same four fields at 14:21 and at 14:55 -- so a model reading it answers "NQ is long at 21850.25" with equal confidence on data that is four seconds old and on data that is forty minutes dead.

Two things that bite in production and not in tests

The SDK renamed FastMCP. In the Python SDK 2.x it is from mcp.server.mcpserver import MCPServer. Every from mcp.server.fastmcp import FastMCP example online is v1 code and will not import. The package raises a pointed migration error rather than a bare ModuleNotFoundError, which is a kindness worth knowing about.

The SDK runs synchronous tools on a worker thread. Tool functions are dispatched through anyio.to_thread.run_sync, so a connection opened on one thread gets used from another. A default sqlite3 connection raises ProgrammingError: SQLite objects created in a thread can only be used in that same thread on the first real client call -- while every direct-call unit test keeps passing. This repo hit exactly that, fixed it with check_same_thread=False plus a lock, and pinned it with test_tool_survives_being_dispatched_on_another_thread.

Wiring it to Claude Desktop

{
  "mcpServers": {
    "tradingview": {
      "command": "/absolute/path/to/.venv/bin/python",
      "args": ["-m", "tradingview_mcp_proof.server"],
      "env": {
        "TVMCP_LEDGER": "/absolute/path/to/tradingview-alerts.db"
      }
    }
  }
}

Ingest and serving are deliberately separate: your webhook receiver writes to the ledger, the MCP server only reads from it. Coupling them means a slow model call can drop an alert, and TradingView will not send it again.

Deliberately out of scope

Honesty about the edges is part of the argument:

  • No market-session calendar. The server reports the observable gap and refuses to guess whether it is an outage or a closed market. Supplying session hours would let it distinguish the two; that is a configuration decision an operator should make, not a default this repo invents.

  • No HTTP receiver. The transport in front of the ledger is a thin concern and varies per deployment (FastAPI, a Cloudflare Worker, a tunnel). Ledger.record() is the seam.

  • No price-history backfill. This models the alert stream, which is what a Pine strategy actually emits. Reconstructing full OHLC needs a data vendor and is a different problem.

  • One symbol per query. No fan-out, no subscriptions, no streaming.

Licence

MIT.

Available Tools

3 tools
get_feed_healthB

Whether the alert feed itself is alive, without asking for a price. Use this before answering any question that assumes live data.

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral disclosure burden, and it does clarify that this is a feed-liveness check rather than a price/data fetch. It does not disclose the output shape, whether the check is per-symbol, or runtime implications, but the core non-price health-check behavior is reasonably conveyed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two short sentences with no filler, and the primary purpose is front-loaded. The usage instruction is separate and actionable, so every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description should clarify what the healthy/unhealthy result looks like, but it only vaguely suggests a boolean alive/not-alive idea. The mandatory symbol parameter is also completely unexplained, leaving an agent uncertain about how to call the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema provides only a title 'Symbol' for the sole required parameter, and the description never mentions symbol at all. With 0% schema description coverage, the description was expected to explain why a symbol is needed for a feed-health check and how it affects the result; it fails to do so.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The predicate 'Whether the alert feed itself is alive' clearly identifies the resource and the health-check operation, and it explicitly distances the tool from price retrieval. It is distinguishable from siblings such as get_recent_bars and get_market_state, though it lacks a direct imperative verb like 'check' or 'get'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The sentence 'Use this before answering any question that assumes live data' is an explicit, actionable condition for invoking the tool. 'Without asking for a price' provides a boundary, but the description does not name alternative tools or broader when-not-to-use scenarios.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_market_stateB

Current market state for a symbol, with an explicit freshness verdict. Check freshness.usable_for_analysis before using the value.

ParametersJSON Schema
NameRequiredDescriptionDefault
symbolYes

TDQS

B3.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the behavioral disclosure burden. It adds meaningful behavioral context: the response includes a freshness verdict and a usable_for_analysis field that must be checked before relying on the value, implying staleness is possible. This is genuinely informative, though it could also state read-only guarantees or error behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two tight sentences with the core purpose front-loaded and a critical usage caveat included. There is no filler or redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with no output schema, the description gives essential freshness-checking guidance but does not explain what the market state payload actually contains or what other fields to expect. An agent can call it correctly but may not fully understand the response beyond the freshness verdict.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the description only restates that the tool works 'for a symbol' without adding format, examples, or accepted value conventions. It adds no real semantic value beyond the schema's property name and type.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool returns the current market state for a symbol and flags an explicit freshness verdict. It identifies the resource and the nature of the result, though it does not explicitly distinguish itself from siblings like get_recent_bars or get_feed_health.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no guidance on when to use this tool instead of its siblings, nor any exclusions or alternative routing. The advice to check freshness.usable_for_analysis is useful after the call but does not help an agent decide when to invoke this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_recent_barsB

The most recent bars, newest first, with a count of any bars missing inside the window. A gapped window cannot carry a trend.

ParametersJSON Schema
NameRequiredDescriptionDefault
countNo
symbolYes

TDQS

B3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description partially carries the behavioral burden: it discloses ordering, recency, and the missing-bar count. However, it does not disclose output shape, the meaning of 'window', paging/limits, side effects, or other operational behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the main result and ordering, with no filler. The second sentence adds a meaningful caveat rather than redundant restatement.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There are no annotations and no output schema, so the description must make the tool self-contained. It does not explain parameter semantics, the structure of returned bars, or the exact meaning of the window, leaving important invocation details unspecified.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description does not define the count or symbol parameters. The word 'window' might relate to count, but the description never explicitly states that count controls how many bars are returned or that symbol selects the bar series.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states the resource (recent bars), the ordering (newest first), and a notable output trait (count of missing bars). It is clearly distinct from get_market_state and get_feed_health, though it lacks a direct verb such as 'retrieves'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit when-to-use or when-not-to-use guidance is given. The second sentence implies a trend-analysis caveat, but it does not describe alternatives or conditions that would select one sibling tool over another.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A3.6/5.0
Disambiguation5/5

Each tool targets a clearly distinct concern: feed liveness, current market state, and recent bar history. There is no overlap because the metadata exposed by each tool is unique to its purpose.

Naming Consistency5/5

All three tools follow the same get_<noun> pattern in lower_snake_case. The naming is perfectly uniform and predictable.

Tool Count5/5

Three tools is a minimal but well-scoped set for a market-data proof server. Each tool earns its place and adds a non-redundant capability.

Completeness4/5

The set covers the core lifecycle of checking whether data is live, retrieving a current state, and inspecting recent bars. It lacks broader historical queries or symbol metadata, but those are reasonable gaps for a proof-focused server.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI co-pilots to interact with TradingView charts, manage alerts via REST API, automate morning briefs with custom trading rules, and perform real-time market analysis.
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI to analyze and manipulate TradingView charts via Chrome DevTools Protocol, and access market data through public scanner API.
    542
    1
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Integrates with TradingView Desktop to enable morning briefs, chart analysis, Pine Script development, and trading workflow automation through natural language.
    552
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to read and control TradingView Desktop charts in real time, supporting chart analysis, Pine Script development, alerts, replay practice, and multi-pane automation.
    552

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/guillemustafa-ux/tradingview-mcp-proof'

If you have feedback or need assistance with the MCP directory API, please join our Discord server