ifb-mcp 0.1.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.
- ifb_mcp-0.1.0/.github/workflows/ci.yml +22 -0
- ifb_mcp-0.1.0/.github/workflows/publish.yml +51 -0
- ifb_mcp-0.1.0/.gitignore +6 -0
- ifb_mcp-0.1.0/LICENSE +21 -0
- ifb_mcp-0.1.0/PKG-INFO +144 -0
- ifb_mcp-0.1.0/README.md +116 -0
- ifb_mcp-0.1.0/pyproject.toml +56 -0
- ifb_mcp-0.1.0/server.json +32 -0
- ifb_mcp-0.1.0/src/ifb_mcp/__init__.py +5 -0
- ifb_mcp-0.1.0/src/ifb_mcp/grid.py +113 -0
- ifb_mcp-0.1.0/src/ifb_mcp/http.py +351 -0
- ifb_mcp-0.1.0/src/ifb_mcp/ifb.py +1124 -0
- ifb_mcp-0.1.0/src/ifb_mcp/registry.py +16 -0
- ifb_mcp-0.1.0/src/ifb_mcp/server.py +95 -0
- ifb_mcp-0.1.0/tests/conftest.py +80 -0
- ifb_mcp-0.1.0/tests/fixtures/announcements.html +40 -0
- ifb_mcp-0.1.0/tests/fixtures/archive.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/article.html +18 -0
- ifb_mcp-0.1.0/tests/fixtures/capital_increases.html +1110 -0
- ifb_mcp-0.1.0/tests/fixtures/flows.html +22 -0
- ifb_mcp-0.1.0/tests/fixtures/halted.html +1007 -0
- ifb_mcp-0.1.0/tests/fixtures/history.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/index.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/index_history.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/instrument.html +376 -0
- ifb_mcp-0.1.0/tests/fixtures/instrument_pb2.html +103 -0
- ifb_mcp-0.1.0/tests/fixtures/ipo.html +78 -0
- ifb_mcp-0.1.0/tests/fixtures/ipostatus.html +114 -0
- ifb_mcp-0.1.0/tests/fixtures/issuer_status.html +843 -0
- ifb_mcp-0.1.0/tests/fixtures/market_transfers.html +794 -0
- ifb_mcp-0.1.0/tests/fixtures/messages.html +18 -0
- ifb_mcp-0.1.0/tests/fixtures/moved_to_tehran.html +790 -0
- ifb_mcp-0.1.0/tests/fixtures/nelson_gov.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/nelson_non.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/nelson_tb.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/new_listings.html +798 -0
- ifb_mcp-0.1.0/tests/fixtures/news.html +33 -0
- ifb_mcp-0.1.0/tests/fixtures/quote.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/quote_bond.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/quote_bond_mfi.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/search.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/share_page.html +328 -0
- ifb_mcp-0.1.0/tests/fixtures/signalr_connect.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/signalr_poll.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/sukuk_history.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/sukuk_index.json +1 -0
- ifb_mcp-0.1.0/tests/fixtures/summary.html +3 -0
- ifb_mcp-0.1.0/tests/fixtures/underwritings.html +81 -0
- ifb_mcp-0.1.0/tests/fixtures/ytm.html +986 -0
- ifb_mcp-0.1.0/tests/fixtures/ytm_history.json +1 -0
- ifb_mcp-0.1.0/tests/test_ifb.py +430 -0
- ifb_mcp-0.1.0/tests/test_live_ifb.py +109 -0
- ifb_mcp-0.1.0/uv.lock +1362 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python }}
|
|
19
|
+
- run: uv sync --locked
|
|
20
|
+
- run: uv run ruff check .
|
|
21
|
+
- run: uv run ruff format --check .
|
|
22
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Tag a release (git tag v0.1.0 && git push --tags) after bumping the version in
|
|
4
|
+
# pyproject.toml and server.json.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
pypi:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment: pypi
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # PyPI trusted publishing
|
|
15
|
+
contents: write # GitHub Release
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
19
|
+
- name: Check versions match the tag
|
|
20
|
+
run: |
|
|
21
|
+
TAG=${GITHUB_REF_NAME#v}
|
|
22
|
+
grep -q "^version = \"$TAG\"" pyproject.toml
|
|
23
|
+
test "$(jq -r .version server.json)" = "$TAG"
|
|
24
|
+
test "$(jq -r '.packages[0].version' server.json)" = "$TAG"
|
|
25
|
+
- run: uv sync --locked && uv run pytest -q
|
|
26
|
+
- run: uv build
|
|
27
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
28
|
+
- run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes --title "$GITHUB_REF_NAME"
|
|
29
|
+
env:
|
|
30
|
+
GH_TOKEN: ${{ github.token }}
|
|
31
|
+
|
|
32
|
+
mcp-registry:
|
|
33
|
+
needs: pypi
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
permissions:
|
|
36
|
+
id-token: write # MCP Registry login via GitHub OIDC
|
|
37
|
+
contents: read
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v7
|
|
40
|
+
- name: Wait for PyPI to serve the new version
|
|
41
|
+
run: |
|
|
42
|
+
for i in $(seq 1 30); do
|
|
43
|
+
curl -sf "https://pypi.org/pypi/ifb-mcp/${GITHUB_REF_NAME#v}/json" >/dev/null && exit 0
|
|
44
|
+
sleep 10
|
|
45
|
+
done
|
|
46
|
+
exit 1
|
|
47
|
+
- name: Install mcp-publisher
|
|
48
|
+
run: |
|
|
49
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.8.1/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
50
|
+
- run: ./mcp-publisher login github-oidc
|
|
51
|
+
- run: ./mcp-publisher publish
|
ifb_mcp-0.1.0/.gitignore
ADDED
ifb_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sepehr Radmard
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ifb_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ifb-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial read-only MCP server for Iran Fara Bourse (ifb.ir): bond and sukuk yields, treasury bills, IPOs, announcements, market stats.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sepehr071/ifb-mcp
|
|
6
|
+
Project-URL: Issues, https://github.com/sepehr071/ifb-mcp/issues
|
|
7
|
+
Project-URL: Website, https://mcpfarsi.ir/
|
|
8
|
+
Author-email: Sepehr Radmard <sepehrr80@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bonds,bourse,claude,fara-bourse,farabourse,ifb,ipo,iran,iran-fara-bourse,mcp,model-context-protocol,otc,persian,sukuk,treasury-bills,ytm
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: curl-cffi>=0.16.3
|
|
24
|
+
Requires-Dist: httpx-curl-cffi>=0.1.5
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Requires-Dist: mcp<3,>=2.2
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<!-- mcp-name: io.github.sepehr071/ifb-mcp -->
|
|
30
|
+
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+
# ifb-mcp: Iran Fara Bourse (فرابورس ایران) bonds, sukuk and IPO MCP server for AI agents
|
|
34
|
+
|
|
35
|
+
**ifb-mcp is an unofficial, read-only MCP server that gives AI agents the public data of Iran Fara Bourse (ifb.ir), Iran's over-the-counter exchange.**<br>
|
|
36
|
+
Its 16 tools rank bonds, sukuk and treasury bills (اخزا) by yield to maturity, explain a paper's coupons and issuer,<br>
|
|
37
|
+
quote prices and order books, list IPOs and new offerings, search exchange notices and news, and summarise the market.<br>
|
|
38
|
+
No API key or account is needed. It works in Claude, Cursor, Copilot and any other MCP client.
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/ifb-mcp/)
|
|
41
|
+
[](https://pypi.org/project/ifb-mcp/)
|
|
42
|
+
[](https://github.com/sepehr071/ifb-mcp/blob/main/LICENSE)
|
|
43
|
+
|
|
44
|
+
[For AI agents](#for-ai-agents) · [Quick start](#quick-start) · [What it can do](#what-it-can-do) · [Tools](#tools) · [FAQ](#faq) · [فارسی](#فارسی)
|
|
45
|
+
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## For AI agents
|
|
51
|
+
|
|
52
|
+
- **What:** unofficial MCP server (model context protocol) for ifb.ir: yield to maturity of every treasury bill, sukuk and bond, instrument details with coupon schedules, live quotes and order books, price and yield history, IPOs (finished and upcoming), underwritings, exchange announcements, news and market messages, market summary, the live market watch with top movers and most traded, index history, halted symbols, capital increases and board transfers, yield curves and who trades Islamic securities.
|
|
53
|
+
- **Install:** `uvx ifb-mcp` or `claude mcp add ifb -- uvx ifb-mcp`. Transport: stdio. Needs [uv](https://docs.astral.sh/uv/getting-started/installation/) (Python 3.10+).
|
|
54
|
+
- **Auth:** none. Optional `IFB_MCP_PROXY` (HTTP proxy URL, or a comma-separated list); system proxy settings are ignored.
|
|
55
|
+
- **Access:** read-only. It cannot trade, log in or change anything, so no confirmation is needed before calling any tool.
|
|
56
|
+
- **Tools:** 16, all prefixed `ifb_`. Typical sequences: `ifb_yields` -> `ifb_instrument` / `ifb_quote`; `ifb_search` -> `ifb_history`; `ifb_ipos(stage="pipeline")`; `ifb_announcements` -> `ifb_article`.
|
|
57
|
+
- **Use when:** the user asks which bond, sukuk or treasury bill yields most or matures when, what a paper pays and when, a Fara Bourse price or order book, what IPOs are coming or happened, why a symbol is halted, or how the market did.
|
|
58
|
+
- **Do not use when:** the question is about the Tehran Stock Exchange (tse.ir), cryptocurrencies or gold; or the user wants to place an order.
|
|
59
|
+
- **Output:** compact JSON. Prices in rial, yields in percent a year, dates Jalali (`1405/08/09`). Texts are the exchange's own (Persian). The site is reachable from Iranian networks only.
|
|
60
|
+
- **Machine-readable:** [`server.json`](https://github.com/sepehr071/ifb-mcp/blob/main/server.json).
|
|
61
|
+
|
|
62
|
+
## Why
|
|
63
|
+
|
|
64
|
+
On ifb.ir, "which treasury bill maturing before next spring pays the best yield?" means opening the yield page,
|
|
65
|
+
scrolling 1,000 rows and sorting by hand. With `ifb-mcp` an agent answers it in one call:
|
|
66
|
+
|
|
67
|
+
> **You:** Best yield among treasury bills maturing before 1405/12/01?
|
|
68
|
+
>
|
|
69
|
+
> **Agent:** *calls* `ifb_yields(kind="treasury", matures_before="1405/12/01", limit=3)`
|
|
70
|
+
|
|
71
|
+
<sub>Data is the exchange's, as shown on its website; yields are computed by Fara Bourse from the last trade. This is information, not investment advice.</sub>
|
|
72
|
+
|
|
73
|
+
## What it can do
|
|
74
|
+
|
|
75
|
+
- **Rank fixed income by yield**: treasury bills, sukuk, participation papers and other bonds with last price, maturity and YTM, filtered by yield, maturity and symbol
|
|
76
|
+
- **Explain a paper**: coupon rate, maturity, issuer, guarantor, underwriter, market maker, full coupon schedule and the next payment
|
|
77
|
+
- **Quote and chart**: live price, change, volume, order book with the yield of each bid and offer, and daily history with YTM
|
|
78
|
+
- **Follow offerings**: finished IPOs with price and volume (filter by market and pricing method), companies being introduced, underwritings
|
|
79
|
+
- **Read the exchange**: search official notices, news and market messages (halts, openings) by keyword and date, and read an article
|
|
80
|
+
- **Watch the live market**: every symbol's price, change and best bid/ask ranked by change or value, per board; halted symbols with reasons; index history since 2009; capital increases and board transfers
|
|
81
|
+
- **See the market**: index, turnover, bond indices, each board's value and top movers, the fitted yield curves and who buys and sells Islamic securities
|
|
82
|
+
- **Read-only by design**: no login, no orders
|
|
83
|
+
|
|
84
|
+
## Quick start
|
|
85
|
+
|
|
86
|
+
You need [uv](https://docs.astral.sh/uv/getting-started/installation/). No API key or account. ifb.ir answers Iranian
|
|
87
|
+
networks; from abroad set `IFB_MCP_PROXY` to an Iranian proxy.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
claude mcp add ifb -- uvx ifb-mcp
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Claude Desktop, Cursor and others:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"mcpServers": {
|
|
98
|
+
"ifb": { "command": "uvx", "args": ["ifb-mcp"] }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Tools
|
|
104
|
+
|
|
105
|
+
| Tool | Use it to |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `ifb_search` | turn a symbol or name into an instrument id |
|
|
108
|
+
| `ifb_yields` | rank bonds, sukuk and treasury bills by YTM with maturity and price filters |
|
|
109
|
+
| `ifb_instrument` | read coupon rate, maturity, issuer, guarantor and the coupon schedule |
|
|
110
|
+
| `ifb_quote` | get the live price, volume, order book and yields |
|
|
111
|
+
| `ifb_history` | get daily prices and YTM |
|
|
112
|
+
| `ifb_ipos` | list finished IPOs or those upcoming and under way |
|
|
113
|
+
| `ifb_underwritings` | list securities offered by underwriting |
|
|
114
|
+
| `ifb_announcements` | search notices, news and market messages |
|
|
115
|
+
| `ifb_article` | read one news article |
|
|
116
|
+
| `ifb_market_summary` | index, turnover, bond indices, boards and top movers |
|
|
117
|
+
| `ifb_yield_curve` | fitted yield curves for government, non-government and treasury bills |
|
|
118
|
+
| `ifb_sukuk_flows` | daily value of Islamic securities bought and sold by investor type |
|
|
119
|
+
| `ifb_market_watch` | live market watch: top gainers, losers and most traded per board, halted symbols |
|
|
120
|
+
| `ifb_index_history` | daily history of the total, equal-weight, price and sukuk indices with 1d/5d/21d/250d changes |
|
|
121
|
+
| `ifb_corporate_actions` | capital increases with rights windows, board transfers, new listings, moves to Tehran Stock Exchange |
|
|
122
|
+
| `ifb_halted` | symbols halted now with date and reason, and issuers flagged for weak disclosure |
|
|
123
|
+
|
|
124
|
+
## FAQ
|
|
125
|
+
|
|
126
|
+
**Is it official?** No. It reads the public pages and web service of ifb.ir and is not affiliated with Iran Fara Bourse.
|
|
127
|
+
|
|
128
|
+
**Why do symbols end in a digit?** The exchange lists each paper on several boards (`کارون0731`, `0732`). Take the `id` from `ifb_yields` or `ifb_search`.
|
|
129
|
+
|
|
130
|
+
**Is the market watch included?** Not the live ranking grid, which the site fills through a push connection. Quotes come from the site's JSON service.
|
|
131
|
+
|
|
132
|
+
## فارسی
|
|
133
|
+
|
|
134
|
+
<div dir="rtl">
|
|
135
|
+
|
|
136
|
+
**ifb-mcp** یک سرور MCP غیررسمی و فقطخواندنی برای فرابورس ایران (ifb.ir) است. با آن میتوان اوراق بدهی، صکوک و اسناد خزانه (اخزا) را بر پایهٔ بازده تا سررسید (YTM) مرتب کرد، جزئیات و برنامهٔ پرداخت سود هر ورقه را دید، قیمت و صف خرید و فروش را گرفت، عرضههای اولیه و پذیرهنویسیها را دید، اطلاعیهها و اخبار را جستجو کرد و خلاصهٔ بازار را خواند. کلید API یا حساب لازم نیست. سایت فقط از شبکههای ایران پاسخ میدهد.
|
|
137
|
+
|
|
138
|
+
نصب: `claude mcp add ifb -- uvx ifb-mcp`
|
|
139
|
+
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
ifb_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.sepehr071/ifb-mcp -->
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
# ifb-mcp: Iran Fara Bourse (فرابورس ایران) bonds, sukuk and IPO MCP server for AI agents
|
|
6
|
+
|
|
7
|
+
**ifb-mcp is an unofficial, read-only MCP server that gives AI agents the public data of Iran Fara Bourse (ifb.ir), Iran's over-the-counter exchange.**<br>
|
|
8
|
+
Its 16 tools rank bonds, sukuk and treasury bills (اخزا) by yield to maturity, explain a paper's coupons and issuer,<br>
|
|
9
|
+
quote prices and order books, list IPOs and new offerings, search exchange notices and news, and summarise the market.<br>
|
|
10
|
+
No API key or account is needed. It works in Claude, Cursor, Copilot and any other MCP client.
|
|
11
|
+
|
|
12
|
+
[](https://pypi.org/project/ifb-mcp/)
|
|
13
|
+
[](https://pypi.org/project/ifb-mcp/)
|
|
14
|
+
[](https://github.com/sepehr071/ifb-mcp/blob/main/LICENSE)
|
|
15
|
+
|
|
16
|
+
[For AI agents](#for-ai-agents) · [Quick start](#quick-start) · [What it can do](#what-it-can-do) · [Tools](#tools) · [FAQ](#faq) · [فارسی](#فارسی)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## For AI agents
|
|
23
|
+
|
|
24
|
+
- **What:** unofficial MCP server (model context protocol) for ifb.ir: yield to maturity of every treasury bill, sukuk and bond, instrument details with coupon schedules, live quotes and order books, price and yield history, IPOs (finished and upcoming), underwritings, exchange announcements, news and market messages, market summary, the live market watch with top movers and most traded, index history, halted symbols, capital increases and board transfers, yield curves and who trades Islamic securities.
|
|
25
|
+
- **Install:** `uvx ifb-mcp` or `claude mcp add ifb -- uvx ifb-mcp`. Transport: stdio. Needs [uv](https://docs.astral.sh/uv/getting-started/installation/) (Python 3.10+).
|
|
26
|
+
- **Auth:** none. Optional `IFB_MCP_PROXY` (HTTP proxy URL, or a comma-separated list); system proxy settings are ignored.
|
|
27
|
+
- **Access:** read-only. It cannot trade, log in or change anything, so no confirmation is needed before calling any tool.
|
|
28
|
+
- **Tools:** 16, all prefixed `ifb_`. Typical sequences: `ifb_yields` -> `ifb_instrument` / `ifb_quote`; `ifb_search` -> `ifb_history`; `ifb_ipos(stage="pipeline")`; `ifb_announcements` -> `ifb_article`.
|
|
29
|
+
- **Use when:** the user asks which bond, sukuk or treasury bill yields most or matures when, what a paper pays and when, a Fara Bourse price or order book, what IPOs are coming or happened, why a symbol is halted, or how the market did.
|
|
30
|
+
- **Do not use when:** the question is about the Tehran Stock Exchange (tse.ir), cryptocurrencies or gold; or the user wants to place an order.
|
|
31
|
+
- **Output:** compact JSON. Prices in rial, yields in percent a year, dates Jalali (`1405/08/09`). Texts are the exchange's own (Persian). The site is reachable from Iranian networks only.
|
|
32
|
+
- **Machine-readable:** [`server.json`](https://github.com/sepehr071/ifb-mcp/blob/main/server.json).
|
|
33
|
+
|
|
34
|
+
## Why
|
|
35
|
+
|
|
36
|
+
On ifb.ir, "which treasury bill maturing before next spring pays the best yield?" means opening the yield page,
|
|
37
|
+
scrolling 1,000 rows and sorting by hand. With `ifb-mcp` an agent answers it in one call:
|
|
38
|
+
|
|
39
|
+
> **You:** Best yield among treasury bills maturing before 1405/12/01?
|
|
40
|
+
>
|
|
41
|
+
> **Agent:** *calls* `ifb_yields(kind="treasury", matures_before="1405/12/01", limit=3)`
|
|
42
|
+
|
|
43
|
+
<sub>Data is the exchange's, as shown on its website; yields are computed by Fara Bourse from the last trade. This is information, not investment advice.</sub>
|
|
44
|
+
|
|
45
|
+
## What it can do
|
|
46
|
+
|
|
47
|
+
- **Rank fixed income by yield**: treasury bills, sukuk, participation papers and other bonds with last price, maturity and YTM, filtered by yield, maturity and symbol
|
|
48
|
+
- **Explain a paper**: coupon rate, maturity, issuer, guarantor, underwriter, market maker, full coupon schedule and the next payment
|
|
49
|
+
- **Quote and chart**: live price, change, volume, order book with the yield of each bid and offer, and daily history with YTM
|
|
50
|
+
- **Follow offerings**: finished IPOs with price and volume (filter by market and pricing method), companies being introduced, underwritings
|
|
51
|
+
- **Read the exchange**: search official notices, news and market messages (halts, openings) by keyword and date, and read an article
|
|
52
|
+
- **Watch the live market**: every symbol's price, change and best bid/ask ranked by change or value, per board; halted symbols with reasons; index history since 2009; capital increases and board transfers
|
|
53
|
+
- **See the market**: index, turnover, bond indices, each board's value and top movers, the fitted yield curves and who buys and sells Islamic securities
|
|
54
|
+
- **Read-only by design**: no login, no orders
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
You need [uv](https://docs.astral.sh/uv/getting-started/installation/). No API key or account. ifb.ir answers Iranian
|
|
59
|
+
networks; from abroad set `IFB_MCP_PROXY` to an Iranian proxy.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
claude mcp add ifb -- uvx ifb-mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Claude Desktop, Cursor and others:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"ifb": { "command": "uvx", "args": ["ifb-mcp"] }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Tools
|
|
76
|
+
|
|
77
|
+
| Tool | Use it to |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `ifb_search` | turn a symbol or name into an instrument id |
|
|
80
|
+
| `ifb_yields` | rank bonds, sukuk and treasury bills by YTM with maturity and price filters |
|
|
81
|
+
| `ifb_instrument` | read coupon rate, maturity, issuer, guarantor and the coupon schedule |
|
|
82
|
+
| `ifb_quote` | get the live price, volume, order book and yields |
|
|
83
|
+
| `ifb_history` | get daily prices and YTM |
|
|
84
|
+
| `ifb_ipos` | list finished IPOs or those upcoming and under way |
|
|
85
|
+
| `ifb_underwritings` | list securities offered by underwriting |
|
|
86
|
+
| `ifb_announcements` | search notices, news and market messages |
|
|
87
|
+
| `ifb_article` | read one news article |
|
|
88
|
+
| `ifb_market_summary` | index, turnover, bond indices, boards and top movers |
|
|
89
|
+
| `ifb_yield_curve` | fitted yield curves for government, non-government and treasury bills |
|
|
90
|
+
| `ifb_sukuk_flows` | daily value of Islamic securities bought and sold by investor type |
|
|
91
|
+
| `ifb_market_watch` | live market watch: top gainers, losers and most traded per board, halted symbols |
|
|
92
|
+
| `ifb_index_history` | daily history of the total, equal-weight, price and sukuk indices with 1d/5d/21d/250d changes |
|
|
93
|
+
| `ifb_corporate_actions` | capital increases with rights windows, board transfers, new listings, moves to Tehran Stock Exchange |
|
|
94
|
+
| `ifb_halted` | symbols halted now with date and reason, and issuers flagged for weak disclosure |
|
|
95
|
+
|
|
96
|
+
## FAQ
|
|
97
|
+
|
|
98
|
+
**Is it official?** No. It reads the public pages and web service of ifb.ir and is not affiliated with Iran Fara Bourse.
|
|
99
|
+
|
|
100
|
+
**Why do symbols end in a digit?** The exchange lists each paper on several boards (`کارون0731`, `0732`). Take the `id` from `ifb_yields` or `ifb_search`.
|
|
101
|
+
|
|
102
|
+
**Is the market watch included?** Not the live ranking grid, which the site fills through a push connection. Quotes come from the site's JSON service.
|
|
103
|
+
|
|
104
|
+
## فارسی
|
|
105
|
+
|
|
106
|
+
<div dir="rtl">
|
|
107
|
+
|
|
108
|
+
**ifb-mcp** یک سرور MCP غیررسمی و فقطخواندنی برای فرابورس ایران (ifb.ir) است. با آن میتوان اوراق بدهی، صکوک و اسناد خزانه (اخزا) را بر پایهٔ بازده تا سررسید (YTM) مرتب کرد، جزئیات و برنامهٔ پرداخت سود هر ورقه را دید، قیمت و صف خرید و فروش را گرفت، عرضههای اولیه و پذیرهنویسیها را دید، اطلاعیهها و اخبار را جستجو کرد و خلاصهٔ بازار را خواند. کلید API یا حساب لازم نیست. سایت فقط از شبکههای ایران پاسخ میدهد.
|
|
109
|
+
|
|
110
|
+
نصب: `claude mcp add ifb -- uvx ifb-mcp`
|
|
111
|
+
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ifb-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Unofficial read-only MCP server for Iran Fara Bourse (ifb.ir): bond and sukuk yields, treasury bills, IPOs, announcements, market stats."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Sepehr Radmard", email = "sepehrr80@gmail.com" }]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
keywords = ["mcp", "model-context-protocol", "ifb", "fara-bourse", "farabourse", "iran-fara-bourse", "otc", "bonds", "sukuk", "treasury-bills", "ytm", "ipo", "iran", "bourse", "claude", "persian"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
21
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"mcp>=2.2,<3",
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"httpx-curl-cffi>=0.1.5",
|
|
27
|
+
"curl-cffi>=0.16.3",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/sepehr071/ifb-mcp"
|
|
32
|
+
Issues = "https://github.com/sepehr071/ifb-mcp/issues"
|
|
33
|
+
Website = "https://mcpfarsi.ir/"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
ifb-mcp = "ifb_mcp.server:main"
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
addopts = "-m 'not live'"
|
|
48
|
+
markers = ["live: calls the real ifb.ir site (run with -m live)"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 120
|
|
52
|
+
target-version = "py310"
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "I", "B", "UP"]
|
|
56
|
+
ignore = ["E501"] # long Field(description=...) strings read better on one line
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.sepehr071/ifb-mcp",
|
|
4
|
+
"title": "Iran Fara Bourse",
|
|
5
|
+
"description": "Read-only Iran Fara Bourse (ifb.ir): bond, sukuk and treasury yields, IPOs, notices, market stats.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/sepehr071/ifb-mcp",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"websiteUrl": "https://github.com/sepehr071/ifb-mcp",
|
|
11
|
+
"version": "0.1.0",
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "pypi",
|
|
15
|
+
"registryBaseUrl": "https://pypi.org",
|
|
16
|
+
"identifier": "ifb-mcp",
|
|
17
|
+
"version": "0.1.0",
|
|
18
|
+
"runtimeHint": "uvx",
|
|
19
|
+
"transport": {
|
|
20
|
+
"type": "stdio"
|
|
21
|
+
},
|
|
22
|
+
"environmentVariables": [
|
|
23
|
+
{
|
|
24
|
+
"name": "IFB_MCP_PROXY",
|
|
25
|
+
"description": "Optional HTTP proxy URL for the ifb.ir requests; system proxy settings are ignored. Also accepts a comma-separated list: one is used until it gets blocked or fails to connect, then the next.",
|
|
26
|
+
"isRequired": false,
|
|
27
|
+
"isSecret": false
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Reads ifb.ir's server-rendered tables and pages through its WebForms grids."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from html.parser import HTMLParser
|
|
7
|
+
|
|
8
|
+
from . import http
|
|
9
|
+
|
|
10
|
+
CP = "ctl00$ContentPlaceHolder1$" # prefix of every control name on the pages
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Row(list):
|
|
14
|
+
"""The text of each cell; `hrefs` holds the first link of each cell (or None)."""
|
|
15
|
+
|
|
16
|
+
hrefs: list[str | None]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Table:
|
|
20
|
+
def __init__(self, tid: str | None, cls: str | None = None):
|
|
21
|
+
self.id = tid
|
|
22
|
+
self.cls = cls or ""
|
|
23
|
+
self.rows: list[Row] = []
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _Parser(HTMLParser):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__(convert_charrefs=True)
|
|
29
|
+
self.tables: list[Table] = []
|
|
30
|
+
self._stack: list[dict] = []
|
|
31
|
+
|
|
32
|
+
def handle_starttag(self, tag, attrs):
|
|
33
|
+
a = dict(attrs)
|
|
34
|
+
if tag == "table":
|
|
35
|
+
t = Table(a.get("id"), a.get("class"))
|
|
36
|
+
self.tables.append(t)
|
|
37
|
+
self._stack.append({"t": t, "row": None, "cell": None})
|
|
38
|
+
elif not self._stack:
|
|
39
|
+
return
|
|
40
|
+
elif tag == "tr":
|
|
41
|
+
row = Row()
|
|
42
|
+
row.hrefs = []
|
|
43
|
+
self._stack[-1]["row"] = row
|
|
44
|
+
elif tag in ("td", "th") and self._stack[-1]["row"] is not None:
|
|
45
|
+
self._stack[-1]["cell"] = {"text": "", "href": None}
|
|
46
|
+
elif tag == "a" and (c := self._stack[-1]["cell"]) is not None and c["href"] is None and a.get("href"):
|
|
47
|
+
c["href"] = a["href"]
|
|
48
|
+
elif tag == "br" and (c := self._stack[-1]["cell"]) is not None:
|
|
49
|
+
c["text"] += " "
|
|
50
|
+
|
|
51
|
+
def handle_endtag(self, tag):
|
|
52
|
+
if not self._stack:
|
|
53
|
+
return
|
|
54
|
+
top = self._stack[-1]
|
|
55
|
+
if tag in ("td", "th") and top["cell"] is not None and top["row"] is not None:
|
|
56
|
+
top["row"].append(re.sub(r"\s+", " ", top["cell"]["text"]).strip())
|
|
57
|
+
top["row"].hrefs.append(top["cell"]["href"])
|
|
58
|
+
top["cell"] = None
|
|
59
|
+
elif tag == "tr" and top["row"] is not None:
|
|
60
|
+
if top["row"]:
|
|
61
|
+
top["t"].rows.append(top["row"])
|
|
62
|
+
top["row"] = None
|
|
63
|
+
elif tag == "table":
|
|
64
|
+
self._stack.pop()
|
|
65
|
+
|
|
66
|
+
def handle_data(self, data):
|
|
67
|
+
if self._stack and (c := self._stack[-1]["cell"]) is not None:
|
|
68
|
+
c["text"] += data
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def tables(html: str) -> list[Table]:
|
|
72
|
+
p = _Parser()
|
|
73
|
+
p.feed(html)
|
|
74
|
+
return p.tables
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def table(html: str, suffix: str) -> Table | None:
|
|
78
|
+
"""The table whose id ends with `suffix` (e.g. "grdIPOs")."""
|
|
79
|
+
return next((t for t in tables(html) if t.id and t.id.endswith(suffix)), None)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def pairs(html: str) -> dict[str, str]:
|
|
83
|
+
"""Label/value rows of the `insTable` blocks on an instrument page (first value wins)."""
|
|
84
|
+
out: dict[str, str] = {}
|
|
85
|
+
for t in tables(html):
|
|
86
|
+
if t.cls.startswith("insTable"):
|
|
87
|
+
for r in t.rows:
|
|
88
|
+
if len(r) == 2 and r[0] and r[0] not in out:
|
|
89
|
+
out[r[0]] = r[1]
|
|
90
|
+
return out
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def hrefid(href: str | None) -> int | None:
|
|
94
|
+
"""The instrument id in a link like Instruments.aspx?id=123."""
|
|
95
|
+
m = re.search(r"id=(\d+)", href or "")
|
|
96
|
+
return int(m.group(1)) if m else None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def grid(path: str, name: str, page: int = 1, form: dict[str, str] | None = None) -> tuple[Table | None, str]:
|
|
100
|
+
"""One page of the WebForms grid control `name` on `path`, after an optional search `form` (control -> value).
|
|
101
|
+
|
|
102
|
+
Searches and pager clicks are postbacks that need the page's hidden state, so this is 1 to 3 requests.
|
|
103
|
+
Returns the table and the final HTML.
|
|
104
|
+
"""
|
|
105
|
+
html = await http.get_page(path)
|
|
106
|
+
fields = {k: v for k, v in (form or {}).items()}
|
|
107
|
+
if fields:
|
|
108
|
+
html = await http.post_page(path, {**http.hidden_fields(html), **fields})
|
|
109
|
+
if page > 1:
|
|
110
|
+
target = {"__EVENTTARGET": f"{CP}{name}", "__EVENTARGUMENT": f"Page${page}"}
|
|
111
|
+
keep = {k: v for k, v in fields.items() if not k.endswith("BtnSearch")} # the button would re-run the search
|
|
112
|
+
html = await http.post_page(path, {**http.hidden_fields(html), **keep, **target})
|
|
113
|
+
return table(html, name), html
|