Skip to main content
Glama
README.md
# tsetmc-mcp

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](pyproject.toml)
[![Tests](https://img.shields.io/badge/tests-107%20passing-brightgreen.svg)](tests/)
[![MCP](https://img.shields.io/badge/MCP-server-8A2BE2.svg)](https://modelcontextprotocol.io)

A local **MCP server** that gives Claude Code (and any MCP client) live **Tehran Stock
Exchange** (TSETMC) market data — quotes, order-book queues (صف), حقیقی/حقوقی money-flow,
market screens, price history, and **custom filters you describe in plain language**. Ask
Claude about the بازار and it pulls the data for you.

It reads public data straight from `tsetmc.com`. It is **not affiliated** with TSETMC,
needs **no API key**, and is **read-only**.

---

## ⚠️ One hard requirement: run it on an Iranian IP

`tsetmc.com` serves its data API to **Iranian IPs** and blocks most foreign / VPN
addresses. Run this on a machine in Iran, and make sure the process is **not** routed
through a foreign VPN. From inside Iran it just works — no proxy needed. Outside Iran it
is **not supported** (you would need your own in-Iran relay).

The Tehran session is **Saturday–Wednesday, 09:00–12:30 (Asia/Tehran)**. Outside those
hours the market is closed: live prices read as empty and money-flow/صف filters return
nothing until the session opens. That's expected, not a bug.

---

## Features

| Tool | What it does |
|------|--------------|
| `search_symbol` | نماد (Persian) → instrument code |
| `get_quote` | live price, %change, volume/value, EPS/PE |
| `get_order_book` | 5-level صف خرید/فروش (bid/ask) |
| `get_money_flow` | حقیقی/حقوقی buy/sell + net institutional |
| `get_market_watch` | filtered/sorted whole-market view (never dumps ~700 rows) |
| `screen` | top gainers/losers, most active, market breadth |
| `get_index_overview` | TEDPIX + equal-weight index at a glance |
| `get_price_history` | daily OHLCV; large pulls → CSV file |
| `market_status` | is the Tehran market open now |
| `describe_fields` | field / units reference |
| `run_filter` | run a custom filter (describe it, or paste a TSETMC filter) |
| `run_saved_filter` | run a saved filter or a built-in preset by name |
| `filter_help` | filter variables, operators, presets |

Prices are in **Rial**. Every response is stamped with freshness (`market_open`,
`staleness_seconds`, `upstream_reachable`) so the model never treats stale data as live.

---

## Prerequisites
- A machine **in Iran** (see above), not on a foreign VPN.
- **Python ≥ 3.11** (the `uv` installer can provision it for you).
- [`uv`](https://docs.astral.sh/uv/) — install with `curl -LsSf https://astral.sh/uv/install.sh | sh`
- The **Claude Code** CLI (`claude`), or any MCP client.

## Get the code & install
```bash
git clone https://github.com/solitraderbusiness/tsetmc-mcp
cd tsetmc-mcp
uv sync            # creates .venv and installs everything
```

## Step 1 — verify it can reach TSETMC (run this in Iran)
```bash
uv run tsetmc-mcp-selfcheck
```
Read the result:
- **mostly ✅** → you're good, continue.
- **all 🚫 BLOCKED** → you're on a non-Iranian IP. Turn off any foreign VPN and re-run.
- **✅ but `WARN calendar trading_day=False` / `0 traded`** → fine, the **market is just
  closed**. Reachability is proven; continue.

## Step 2 — register it with Claude Code
Run this **from inside the clone** so the absolute path is filled in for you:
```bash
claude mcp add tsetmc -- uv run --directory "$(pwd)" tsetmc-mcp
```
> The `--directory` **must be an absolute path**. A relative or `~` path is the #1 cause
> of a server that registers "successfully" but then shows disconnected.

**Alternative — one line, no clone** (once the repo is public; you can't run the Iran-IP
selfcheck first this way):
```bash
claude mcp add tsetmc -- uvx --from 'git+https://github.com/solitraderbusiness/tsetmc-mcp@v0.1.0' tsetmc-mcp
```

## Verify it worked
Restart Claude Code and run `/mcp` — you should see **tsetmc** connected. Ask Claude
"is the Tehran market open?" — a stamped `market_status` reply confirms it's live.

---

## Usage — just talk to Claude
Ask in plain Persian or English; Claude picks the right tool:

- «قیمت فولاد چنده؟» / "what's فولاد trading at?"
- «صف خرید خودرو چطوره؟» / "show خودرو's order book"
- «امروز کدوم نمادها +۳٪ با حجم بالان؟» / "stocks up >3% on high volume"
- «بیشترین رشد امروز» / "top gainers now"
- «حقیقی/حقوقی شستا؟» / "smart-money on شستا"
- «شاخص کل چنده؟» / "where's TEDPIX?"
- «تاریخچه ۶ ماه فولاد» / "pull فولاد's price history" (large pulls saved to CSV)

### Custom filters — describe it, Claude writes it, runs it
TSETMC's "دیده‌بان فیلتر" lets you write filter scripts. This does the same, but you just
**describe** what you want:

> «سهم‌هایی که پول حقیقی واردشون میشه و مثبت‌ان» → Claude writes
> `net_individual > 0 and change_pct > 0`, runs it, lists the matches.

- **Paste a real TSETMC filter** and it runs as-is: `(ct).Buy_I_Volume-(ct).Sell_I_Volume>0 && (plp)>0`
- Say *"save this as my-flow"* → later *"run my-flow"*.
- Built-in presets: `buyer_power_2x` (پول داغ), `code_to_code` (کد به کد),
  `institutional_accumulation`, `volume_spike`, `buy_queue`/`sell_queue` (صف), …
- **Safety:** filters are **parsed, never executed as code** — no filter string can run
  code or touch your system.
- **Not in v1:** 30-day-average / RSI history terms and market-cap/sector (Claude will say
  so and suggest an alternative). Filters are most useful **during market hours**.

---

## How it works
A background poller pulls the whole market from TSETMC every few seconds **during the
Tehran session** and keeps it in memory. Every tool answers from that snapshot, so asking
Claude a question never hammers TSETMC (which would get you rate-limited / IP-blocked).
Order books and history are fetched per-symbol on demand. Full architecture:
[`docs/design.md`](docs/design.md); endpoint reference: [`docs/endpoints.md`](docs/endpoints.md).

## Configuration
Everything has a safe default; override via env vars (see [`.env.example`](.env.example)) —
poll interval, request rate, market hours, output limits, data dir. Extra market holidays:
put one `YYYY-MM-DD` per line in `~/.tsetmc-mcp/holidays.txt` (weekends Thu/Fri are already
handled). Pass env to the server via `claude mcp add --env KEY=value …`.

## Optional: the 5j9/tsetmc accelerator
The default `raw` backend needs no extra packages. On **Python ≥ 3.13** you can opt into
the actively-maintained [`5j9/tsetmc`](https://github.com/5j9/tsetmc) library as an
alternate backend behind the same interface:
```bash
uv pip install "tsetmc>=3.0"
TSETMC_SOURCE=library uv run tsetmc-mcp
```

## Troubleshooting
| Symptom | Cause / fix |
|---|---|
| self-check is **all 🚫** | non-Iranian IP — turn off any foreign VPN |
| `/mcp` shows **tsetmc disconnected** | `--directory` was relative/`~` — re-add with an absolute path (`"$(pwd)"` from the clone) |
| **empty** market_watch / screen | outside the Sat–Wed 09:00–12:30 session (market closed) |
| self-check `WARN trading_day=False` | it's Thursday/Friday (Tehran weekend) or a holiday — not an error |
| `TSETMC_SOURCE=library` errors | needs Python ≥ 3.13 + `pip install "tsetmc>=3.0"` |

## Update / uninstall
```bash
git -C tsetmc-mcp pull && uv sync   # update
claude mcp remove tsetmc            # unregister
# delete the clone and ~/.tsetmc-mcp/ to remove all local state
```

## Develop
```bash
uv run pytest         # 107 offline tests (no network needed)
uv run ruff check . && uv run ruff format --check .
uv run mcp dev src/tsetmc_mcp/server.py   # click-test tools in the MCP Inspector
```
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Security reports:
[SECURITY.md](SECURITY.md). Your requests go directly from your machine to `tsetmc.com`;
this tool sends nothing anywhere else.

## License
[MIT](LICENSE). The optional `library` extra pulls `5j9/tsetmc` (GPL-3.0); it is **not
vendored or bundled** here — it's only an optional dependency specifier that pip fetches
from PyPI if you opt in, so it places no copyleft obligation on this MIT distribution.
(Don't bake an installed `tsetmc` into a distributed Docker image / single-file binary, or
that bundle becomes a GPL combined work.)

---

## Disclaimer

This project is an independent, open-source client. It is NOT affiliated with, endorsed by,
or connected to the Tehran Securities Exchange Technology Management Co. (TSETMC), the
Tehran Stock Exchange, or any regulatory body.

It reads PUBLICLY available market data from tsetmc.com through the same undocumented
endpoints the website itself uses. All requests are READ-ONLY. It accesses no
authenticated, paywalled, or non-public resource and circumvents no access control.

It is provided for personal, educational, and research use, and is rate-limited by design;
please use it responsibly and do not send abusive request volumes. You are responsible for
how you use it and for complying with any applicable terms of use and local laws.

The data is provided "AS IS", without any warranty of accuracy, completeness, or timeliness,
and may be delayed or incorrect. Nothing here is financial or investment advice. The authors
accept no liability for any loss arising from use of this software or the data it retrieves.

### سلب مسئولیت

این ابزار یک کلاینت متن‌باز و مستقل است و هیچ وابستگی، تأییدیه یا ارتباطی با شرکت مدیریت فناوری
بورس تهران (TSETMC)، بورس اوراق بهادار تهران یا هیچ نهاد ناظری ندارد.

این کتابخانه صرفاً داده‌های عمومی و در دسترسِ همگان را از tsetmc.com و از همان مسیرهایی که خودِ
وب‌سایت استفاده می‌کند، به‌صورت فقط-خواندنی دریافت می‌کند؛ به هیچ منبع احرازهویت‌شده، پولی یا
غیرعمومی دسترسی ندارد و هیچ سازوکار امنیتی را دور نمی‌زند.

این ابزار برای استفادهٔ شخصی، آموزشی و پژوهشی ارائه شده و به‌صورت پیش‌فرض دارای محدودیت نرخ
درخواست است؛ لطفاً مسئولانه استفاده کنید. داده‌ها «همان‌گونه که هست» و بدون هیچ تضمینی نسبت به
صحت یا به‌روزبودن ارائه می‌شوند و ممکن است نادرست باشند. هیچ‌یک از موارد ارائه‌شده توصیهٔ مالی
نیست و مسئولیت هرگونه استفاده بر عهدهٔ کاربر است.

TDQS

A3.8/5.0

Scored across 13 tools

Disambiguation5/5

Each tool serves a clearly distinct purpose: reference, market overview, live snapshot, filtering, historical data, order book, etc. Even similar tools like get_market_watch, screen, and run_filter are differentiated by their descriptions (filtered view vs prebuilt screens vs custom filters). No ambiguity in tool selection.

Naming Consistency4/5

Most tools follow a clear verb_noun pattern (e.g., get_quote, run_filter, search_symbol). However, 'market_status_tool' deviates by placing 'tool' at the end instead of using a verb prefix. This is a minor inconsistency in an otherwise coherent naming scheme.

Tool Count5/5

With 13 tools, the server is well-scoped for a stock market data provider. Each tool adds necessary functionality without redundancy. The count is appropriate for covering quoting, history, order book, screening, filtering, symbols, and market status.

Completeness5/5

The tool set covers all essential operations for the domain: live quotes, historical data, order book, money flow, market overview, screening, filtering, symbol resolution, and market status. No obvious gaps; reference tools for fields and filters further enhance completeness.

Maintenance

ActivityInactive
ResponsivenessNo issues