datasinking 0.2.14__tar.gz → 0.2.17__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasinking
3
- Version: 0.2.14
4
- Summary: Python client for DataSinking — full-text financial reports (US, China, Japan, Korea, Taiwan) as Markdown.
3
+ Version: 0.2.17
4
+ Summary: Python client for DataSinking — full-text financial reports (US, UK, China, Japan, Korea, Taiwan) as Markdown. Earn $7 per purchase per referral (recurring): https://datasink.ing/affiliate
5
5
  Author: DataSinking
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://datasink.ing
@@ -31,17 +31,18 @@ Dynamic: license-file
31
31
  **Full-text financial reports across the US and Asia, as clean Markdown.**
32
32
 
33
33
  [DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
34
- and quarterly — from **the US, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
34
+ and quarterly — from **the US, the UK, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
35
35
  and RAG. Query by FMP-style symbol (`AAPL`, `600519.SS`, `7203.T`, `005930.KS`, `2330.TW`) or filter by
36
36
  exchange, report period, or **section** — pull just the MD&A / risk section instead of the whole
37
- report. Reports are sourced from official disclosure platforms and parsed into structured Markdown
37
+ filing, saving tokens. Every figure is traceable to its source disclosure and
38
+ block. Reports are sourced from official disclosure platforms and parsed into structured Markdown
38
39
  with YAML frontmatter, preserved headings, paragraphs and tables.
39
40
 
40
41
  ---
41
42
 
42
43
  ## MCP server
43
44
 
44
- Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop) as an
45
+ Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop / n8n) as an
45
46
  [MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
46
47
  list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
47
48
 
@@ -187,6 +188,7 @@ coverage, list a company's reports, and extract a figure with correct units.
187
188
  | `01_quickstart.py` | The 5 core functions: list exchanges / stocks / reports / fetch a report / fetch a stock's reports |
188
189
  | `02_download_company.py` | Download a company's full reports to local Markdown files |
189
190
  | `03_download_exchange.py` | Download an entire exchange's reports (all stocks) to local Markdown files |
191
+ | `rag_ingest.py` | Slice reports into RAG chunks (on the `<!-- ds:block:N -->` anchors, with `source` traceability) and ingest into a vector store |
190
192
 
191
193
  Every example pulls from the live API and runs as-is.
192
194
 
@@ -228,6 +230,10 @@ Reports are sourced from the official regulatory disclosure platform of each mar
228
230
 
229
231
  Every document also carries a `source` field in the API response, so the attribution travels with the data. **Please keep it when you redistribute.**
230
232
 
233
+ ## Affiliate
234
+
235
+ Earn **$7 every purchase** for every customer you refer — recurring, PayPal, $31 minimum withdrawal: [datasink.ing/affiliate](https://datasink.ing/affiliate)
236
+
231
237
  ## License
232
238
 
233
239
  [MIT](LICENSE)
@@ -1,210 +1,216 @@
1
- # DataSinking
2
-
3
- <!-- mcp-name: io.github.heubme2020/datasinking -->
4
-
5
- [![PyPI version](https://img.shields.io/pypi/v/datasinking.svg)](https://pypi.org/project/datasinking/)
6
- [![MCP server](https://img.shields.io/badge/MCP-server-blue)](https://github.com/heubme2020/datasinking#mcp-server)
7
-
8
- **Full-text financial reports across the US and Asia, as clean Markdown.**
9
-
10
- [DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
11
- and quarterly — from **the US, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
12
- and RAG. Query by FMP-style symbol (`AAPL`, `600519.SS`, `7203.T`, `005930.KS`, `2330.TW`) or filter by
13
- exchange, report period, or **section** — pull just the MD&A / risk section instead of the whole
14
- report. Reports are sourced from official disclosure platforms and parsed into structured Markdown
15
- with YAML frontmatter, preserved headings, paragraphs and tables.
16
-
17
- ---
18
-
19
- ## MCP server
20
-
21
- Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop) as an
22
- [MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
23
- list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
24
-
25
- ### Hosted — nothing to install
26
-
27
- One URL, no package, no Python, no local server. Most clients take this shape:
28
-
29
- ```json
30
- {
31
- "mcpServers": {
32
- "datasinking": {
33
- "type": "http",
34
- "url": "https://api.datasink.ing/mcp",
35
- "headers": { "Authorization": "Bearer YOUR_KEY" }
36
- }
37
- }
38
- }
39
- ```
40
-
41
- Claude Code, in one line:
42
-
43
- ```bash
44
- claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
45
- --header "Authorization: Bearer YOUR_KEY"
46
- ```
47
-
48
- A client that can't set a header can put the key in the URL instead —
49
- `https://api.datasink.ing/mcp?apikey=YOUR_KEY` — but only do that when you must: a key in a URL
50
- ends up in logs and screen shares, and no client can redact it.
51
-
52
- Two clients don't fit the shape above: Codex's config is TOML, and Devin Desktop's remote field is
53
- `serverUrl`, not `url`. Per-client files: [`docs/mcp/`](docs/mcp/).
54
-
55
- ### Local — run it yourself
56
-
57
- If you'd rather keep everything on your own machine, there are two identical builds — pick
58
- whichever runtime you already have:
59
-
60
- **Node 18+ (no Python needed):**
61
-
62
- ```json
63
- {
64
- "mcpServers": {
65
- "datasinking": {
66
- "command": "npx",
67
- "args": ["-y", "datasinking-mcp"],
68
- "env": { "DATASINK_API_KEY": "YOUR_KEY" }
69
- }
70
- }
71
- }
72
- ```
73
-
74
- **Python 3.8+:**
75
-
76
- ```bash
77
- pip install "datasinking[mcp]"
78
- datasinking-mcp # requires DATASINK_API_KEY (free at https://datasink.ing)
79
- ```
80
-
81
- Then use `command: datasinking-mcp` in your client.
82
-
83
- Both run the same six tools with the same schemas — `npm/` and `datasinking/mcp_server.py` are
84
- kept in lockstep by [`check_mcp_parity.py`](check_mcp_parity.py).
85
-
86
- Full per-client setup: [`mcp-server.md`](mcp-server.md) (overview) ·
87
- [`docs/mcp/`](docs/mcp/) (one guide per client).
88
-
89
- The hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, and no session id is
90
- issued. Most clients read MCP config at launch, so restart one after changing its config.
91
-
92
- ![DataSinking MCP in Claude](docs/images/mcp-demo.png)
93
-
94
- ## What this repo is
95
-
96
- Examples, research and tutorials showing how to work with financial report data, including reproducing the presentation styles found in financial-report research papers.
97
-
98
- ```
99
- datasinking/
100
- ├── examples/ # Example scripts: pull data from the API and analyze it
101
- ├── research/ # Research notes / blog posts (reproducing paper-style presentation)
102
- ├── datasinking/ # Python client + MCP server — pip install "datasinking[mcp]"
103
- ├── npm/ # The same MCP server on npm — npx -y datasinking-mcp (Node 18+)
104
- ├── docs/mcp/ # Per-client MCP setup — Claude Code, Desktop, Cursor, Codex, WorkBuddy, Devin…
105
- ├── mcp-server.md # MCP server overview — endpoint, per-client table, tools, troubleshooting
106
- ├── llm-examples.md # Ask an LLM — no code needed (8 end-to-end examples)
107
- ├── api-examples.md # 7 examples × 3 interfaces (curl / Python / LLM)
108
- └── README.md
109
- ```
110
-
111
- Per-client MCP guides — one file each, with the exact config path, both scopes, verification and the
112
- errors that client actually produces:
113
-
114
- | Client | Guide |
115
- |---|---|
116
- | Claude Code | [`docs/mcp/claude-code.md`](docs/mcp/claude-code.md) |
117
- | Claude Desktop | [`docs/mcp/claude-desktop.md`](docs/mcp/claude-desktop.md) |
118
- | OpenAI Codex CLI | [`docs/mcp/codex.md`](docs/mcp/codex.md) |
119
- | WorkBuddy / CodeBuddy | [`docs/mcp/workbuddy.md`](docs/mcp/workbuddy.md) |
120
- | Doubao Work / 豆包工作 | [`docs/mcp/doubao.md`](docs/mcp/doubao.md) |
121
- | Cursor | [`docs/mcp/cursor.md`](docs/mcp/cursor.md) |
122
- | DeepSeek Harness (`dsh`) | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
123
- | Devin Desktop | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
124
- | OpenCode | [`docs/mcp/opencode.md`](docs/mcp/opencode.md) |
125
- | Qoder | [`docs/mcp/qoder.md`](docs/mcp/qoder.md) |
126
- | Reasonix | [`docs/mcp/reasonix.md`](docs/mcp/reasonix.md) |
127
-
128
- ## Quick start
129
-
130
- 1. Get an API key at [datasink.ing](https://datasink.ing)
131
- 2. One line (FMP-style `?apikey=`):
132
-
133
- ```bash
134
- curl "https://api.datasink.ing/documents?symbol=600519.SS&with_content=1&apikey=YOUR_KEY"
135
- ```
136
-
137
- Or in Python:
138
-
139
- ```bash
140
- pip install datasinking
141
- ```
142
-
143
- ```python
144
- from datasinking import DataSinking
145
-
146
- ds = DataSinking("YOUR_KEY")
147
- for r in ds.get_stock_reports("600519.SS", limit=3):
148
- print(r["report_period"], r["title"], len(r["content"]), "chars")
149
- ```
150
-
151
- All five functions (curl / Python / LLM): [`api-examples.md`](api-examples.md).
152
-
153
- ## Ask an LLM (no code)
154
-
155
- Don't want to write code? Point any LLM at [datasink.ing](https://datasink.ing),
156
- give it your API key, and ask in plain language. See
157
- [`llm-examples.md`](llm-examples.md) for eight end-to-end examples — explore
158
- coverage, list a company's reports, and extract a figure with correct units.
159
-
160
- ## Examples (`examples/`)
161
-
162
- | File | What it does |
163
- |---|---|
164
- | `01_quickstart.py` | The 5 core functions: list exchanges / stocks / reports / fetch a report / fetch a stock's reports |
165
- | `02_download_company.py` | Download a company's full reports to local Markdown files |
166
- | `03_download_exchange.py` | Download an entire exchange's reports (all stocks) to local Markdown files |
167
-
168
- Every example pulls from the live API and runs as-is.
169
-
170
- > `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**, over a rolling 7-day window: a free key gets 3 req/s and 8,191 documents per 7 days, inside a pool of 524,287 per 7 days shared by all free users and website visitors. A whole exchange will therefore take well over a week on a free key — a **paid (yearly)** key (31 req/s, 524,287 documents per 7 days) is strongly recommended.
171
-
172
- ## Research (`research/`)
173
-
174
- `research/` hosts research notes and blog posts, each based on DataSinking data with the source cited. You can reproduce charts and presentations found in financial-report research papers, e.g.:
175
-
176
- - Long-term revenue / profit trends
177
- - Industry comparison and distribution
178
- - Time series of financial metrics
179
-
180
- Start from [`research/TEMPLATE.md`](research/TEMPLATE.md).
181
-
182
- ## Data overview
183
-
184
- | | |
185
- |---|---|
186
- | Coverage | US (NYSE / NASDAQ / CBOE) · China (SSE / SZSE / BSE) · Japan (TSE) · Korea (KOSPI / KOSDAQ / KONEX) · Taiwan (TWSE / TPEx) |
187
- | Document types | annual / semiannual / q1 / q3 / amendment |
188
- | Update frequency | Daily — US/Korea/Japan via official SEC EDGAR / DART / EDINET APIs (new filings within ~24h of publication) |
189
- | Format | Full-text Markdown (with YAML frontmatter) |
190
- | API | REST — `GET /documents`, batch download, `with_content=1` for full text, `?section=` + `/sections` for chapter-level access |
191
- | Symbols | FMP style: `AAPL` / `600519.SS` / `7203.T` / `005930.KS` |
192
- | Auth | `?apikey=` query parameter (FMP style) |
193
-
194
- ## Data source
195
-
196
- Reports are sourced from the official regulatory disclosure platform of each market and converted in-house to clean Markdown:
197
-
198
- | Market | Source | Platform |
199
- |---|---|---|
200
- | US (bare ticker, e.g. `AAPL`) | SEC EDGAR | US Securities and Exchange Commission — sec.gov/edgar |
201
- | China A-shares (`.SS` `.SZ` `.BJ`) | 巨潮资讯网 cninfo | CSRC-designated disclosure platform |
202
- | Korea (`.KS` `.KQ` `.KN`) | DART | Financial Supervisory Service — opendart.fss.or.kr |
203
- | Japan (`.T`) | EDINET | Financial Services Agency — disclosure2.edinet-fsa.go.jp |
204
- | Taiwan (`.TW` `.TWO`) | 公開資訊觀測站 MOPS | Taiwan Stock Exchange — mops.twse.com.tw |
205
-
206
- Every document also carries a `source` field in the API response, so the attribution travels with the data. **Please keep it when you redistribute.**
207
-
208
- ## License
209
-
210
- [MIT](LICENSE)
1
+ # DataSinking
2
+
3
+ <!-- mcp-name: io.github.heubme2020/datasinking -->
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/datasinking.svg)](https://pypi.org/project/datasinking/)
6
+ [![MCP server](https://img.shields.io/badge/MCP-server-blue)](https://github.com/heubme2020/datasinking#mcp-server)
7
+
8
+ **Full-text financial reports across the US and Asia, as clean Markdown.**
9
+
10
+ [DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
11
+ and quarterly — from **the US, the UK, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
12
+ and RAG. Query by FMP-style symbol (`AAPL`, `600519.SS`, `7203.T`, `005930.KS`, `2330.TW`) or filter by
13
+ exchange, report period, or **section** — pull just the MD&A / risk section instead of the whole
14
+ filing, saving tokens. Every figure is traceable to its source disclosure and
15
+ block. Reports are sourced from official disclosure platforms and parsed into structured Markdown
16
+ with YAML frontmatter, preserved headings, paragraphs and tables.
17
+
18
+ ---
19
+
20
+ ## MCP server
21
+
22
+ Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop / n8n) as an
23
+ [MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
24
+ list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
25
+
26
+ ### Hosted — nothing to install
27
+
28
+ One URL, no package, no Python, no local server. Most clients take this shape:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "datasinking": {
34
+ "type": "http",
35
+ "url": "https://api.datasink.ing/mcp",
36
+ "headers": { "Authorization": "Bearer YOUR_KEY" }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ Claude Code, in one line:
43
+
44
+ ```bash
45
+ claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
46
+ --header "Authorization: Bearer YOUR_KEY"
47
+ ```
48
+
49
+ A client that can't set a header can put the key in the URL instead —
50
+ `https://api.datasink.ing/mcp?apikey=YOUR_KEY` — but only do that when you must: a key in a URL
51
+ ends up in logs and screen shares, and no client can redact it.
52
+
53
+ Two clients don't fit the shape above: Codex's config is TOML, and Devin Desktop's remote field is
54
+ `serverUrl`, not `url`. Per-client files: [`docs/mcp/`](docs/mcp/).
55
+
56
+ ### Local — run it yourself
57
+
58
+ If you'd rather keep everything on your own machine, there are two identical builds — pick
59
+ whichever runtime you already have:
60
+
61
+ **Node 18+ (no Python needed):**
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "datasinking": {
67
+ "command": "npx",
68
+ "args": ["-y", "datasinking-mcp"],
69
+ "env": { "DATASINK_API_KEY": "YOUR_KEY" }
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ **Python 3.8+:**
76
+
77
+ ```bash
78
+ pip install "datasinking[mcp]"
79
+ datasinking-mcp # requires DATASINK_API_KEY (free at https://datasink.ing)
80
+ ```
81
+
82
+ Then use `command: datasinking-mcp` in your client.
83
+
84
+ Both run the same six tools with the same schemas — `npm/` and `datasinking/mcp_server.py` are
85
+ kept in lockstep by [`check_mcp_parity.py`](check_mcp_parity.py).
86
+
87
+ Full per-client setup: [`mcp-server.md`](mcp-server.md) (overview) ·
88
+ [`docs/mcp/`](docs/mcp/) (one guide per client).
89
+
90
+ The hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, and no session id is
91
+ issued. Most clients read MCP config at launch, so restart one after changing its config.
92
+
93
+ ![DataSinking MCP in Claude](docs/images/mcp-demo.png)
94
+
95
+ ## What this repo is
96
+
97
+ Examples, research and tutorials showing how to work with financial report data, including reproducing the presentation styles found in financial-report research papers.
98
+
99
+ ```
100
+ datasinking/
101
+ ├── examples/ # Example scripts: pull data from the API and analyze it
102
+ ├── research/ # Research notes / blog posts (reproducing paper-style presentation)
103
+ ├── datasinking/ # Python client + MCP server — pip install "datasinking[mcp]"
104
+ ├── npm/ # The same MCP server on npm — npx -y datasinking-mcp (Node 18+)
105
+ ├── docs/mcp/ # Per-client MCP setup — Claude Code, Desktop, Cursor, Codex, WorkBuddy, Devin…
106
+ ├── mcp-server.md # MCP server overview — endpoint, per-client table, tools, troubleshooting
107
+ ├── llm-examples.md # Ask an LLM — no code needed (8 end-to-end examples)
108
+ ├── api-examples.md # 7 examples × 3 interfaces (curl / Python / LLM)
109
+ └── README.md
110
+ ```
111
+
112
+ Per-client MCP guides — one file each, with the exact config path, both scopes, verification and the
113
+ errors that client actually produces:
114
+
115
+ | Client | Guide |
116
+ |---|---|
117
+ | Claude Code | [`docs/mcp/claude-code.md`](docs/mcp/claude-code.md) |
118
+ | Claude Desktop | [`docs/mcp/claude-desktop.md`](docs/mcp/claude-desktop.md) |
119
+ | OpenAI Codex CLI | [`docs/mcp/codex.md`](docs/mcp/codex.md) |
120
+ | WorkBuddy / CodeBuddy | [`docs/mcp/workbuddy.md`](docs/mcp/workbuddy.md) |
121
+ | Doubao Work / 豆包工作 | [`docs/mcp/doubao.md`](docs/mcp/doubao.md) |
122
+ | Cursor | [`docs/mcp/cursor.md`](docs/mcp/cursor.md) |
123
+ | DeepSeek Harness (`dsh`) | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
124
+ | Devin Desktop | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
125
+ | OpenCode | [`docs/mcp/opencode.md`](docs/mcp/opencode.md) |
126
+ | Qoder | [`docs/mcp/qoder.md`](docs/mcp/qoder.md) |
127
+ | Reasonix | [`docs/mcp/reasonix.md`](docs/mcp/reasonix.md) |
128
+
129
+ ## Quick start
130
+
131
+ 1. Get an API key at [datasink.ing](https://datasink.ing)
132
+ 2. One line (FMP-style `?apikey=`):
133
+
134
+ ```bash
135
+ curl "https://api.datasink.ing/documents?symbol=600519.SS&with_content=1&apikey=YOUR_KEY"
136
+ ```
137
+
138
+ Or in Python:
139
+
140
+ ```bash
141
+ pip install datasinking
142
+ ```
143
+
144
+ ```python
145
+ from datasinking import DataSinking
146
+
147
+ ds = DataSinking("YOUR_KEY")
148
+ for r in ds.get_stock_reports("600519.SS", limit=3):
149
+ print(r["report_period"], r["title"], len(r["content"]), "chars")
150
+ ```
151
+
152
+ All five functions (curl / Python / LLM): [`api-examples.md`](api-examples.md).
153
+
154
+ ## Ask an LLM (no code)
155
+
156
+ Don't want to write code? Point any LLM at [datasink.ing](https://datasink.ing),
157
+ give it your API key, and ask in plain language. See
158
+ [`llm-examples.md`](llm-examples.md) for eight end-to-end examples — explore
159
+ coverage, list a company's reports, and extract a figure with correct units.
160
+
161
+ ## Examples (`examples/`)
162
+
163
+ | File | What it does |
164
+ |---|---|
165
+ | `01_quickstart.py` | The 5 core functions: list exchanges / stocks / reports / fetch a report / fetch a stock's reports |
166
+ | `02_download_company.py` | Download a company's full reports to local Markdown files |
167
+ | `03_download_exchange.py` | Download an entire exchange's reports (all stocks) to local Markdown files |
168
+ | `rag_ingest.py` | Slice reports into RAG chunks (on the `<!-- ds:block:N -->` anchors, with `source` traceability) and ingest into a vector store |
169
+
170
+ Every example pulls from the live API and runs as-is.
171
+
172
+ > `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**, over a rolling 7-day window: a free key gets 3 req/s and 8,191 documents per 7 days, inside a pool of 524,287 per 7 days shared by all free users and website visitors. A whole exchange will therefore take well over a week on a free key — a **paid (yearly)** key (31 req/s, 524,287 documents per 7 days) is strongly recommended.
173
+
174
+ ## Research (`research/`)
175
+
176
+ `research/` hosts research notes and blog posts, each based on DataSinking data with the source cited. You can reproduce charts and presentations found in financial-report research papers, e.g.:
177
+
178
+ - Long-term revenue / profit trends
179
+ - Industry comparison and distribution
180
+ - Time series of financial metrics
181
+
182
+ Start from [`research/TEMPLATE.md`](research/TEMPLATE.md).
183
+
184
+ ## Data overview
185
+
186
+ | | |
187
+ |---|---|
188
+ | Coverage | US (NYSE / NASDAQ / CBOE) · China (SSE / SZSE / BSE) · Japan (TSE) · Korea (KOSPI / KOSDAQ / KONEX) · Taiwan (TWSE / TPEx) |
189
+ | Document types | annual / semiannual / q1 / q3 / amendment |
190
+ | Update frequency | Daily — US/Korea/Japan via official SEC EDGAR / DART / EDINET APIs (new filings within ~24h of publication) |
191
+ | Format | Full-text Markdown (with YAML frontmatter) |
192
+ | API | REST — `GET /documents`, batch download, `with_content=1` for full text, `?section=` + `/sections` for chapter-level access |
193
+ | Symbols | FMP style: `AAPL` / `600519.SS` / `7203.T` / `005930.KS` |
194
+ | Auth | `?apikey=` query parameter (FMP style) |
195
+
196
+ ## Data source
197
+
198
+ Reports are sourced from the official regulatory disclosure platform of each market and converted in-house to clean Markdown:
199
+
200
+ | Market | Source | Platform |
201
+ |---|---|---|
202
+ | US (bare ticker, e.g. `AAPL`) | SEC EDGAR | US Securities and Exchange Commission — sec.gov/edgar |
203
+ | China A-shares (`.SS` `.SZ` `.BJ`) | 巨潮资讯网 cninfo | CSRC-designated disclosure platform |
204
+ | Korea (`.KS` `.KQ` `.KN`) | DART | Financial Supervisory Service — opendart.fss.or.kr |
205
+ | Japan (`.T`) | EDINET | Financial Services Agency — disclosure2.edinet-fsa.go.jp |
206
+ | Taiwan (`.TW` `.TWO`) | 公開資訊觀測站 MOPS | Taiwan Stock Exchange — mops.twse.com.tw |
207
+
208
+ Every document also carries a `source` field in the API response, so the attribution travels with the data. **Please keep it when you redistribute.**
209
+
210
+ ## Affiliate
211
+
212
+ Earn **$7 every purchase** for every customer you refer — recurring, PayPal, $31 minimum withdrawal: [datasink.ing/affiliate](https://datasink.ing/affiliate)
213
+
214
+ ## License
215
+
216
+ [MIT](LICENSE)
@@ -1,11 +1,11 @@
1
- # -*- coding: utf-8 -*-
2
- """版本号的**唯一来源**。
3
-
4
- pyproject.toml 用 `dynamic = ["version"]` 从这里读,`client.py` 的 User-Agent 也读它。
5
-
6
- 为什么单独抽一个文件:之前 pyproject.toml 和 `__init__.py` 各写各的版本号,
7
- 结果 `__version__` 长期停在 0.1.0 没跟上(发 0.2.4 时才发现,User-Agent 里也一直
8
- 写着旧版本)。现在只有这一处需要改。
9
- """
10
-
11
- __version__ = "0.2.14"
1
+ # -*- coding: utf-8 -*-
2
+ """版本号的**唯一来源**。
3
+
4
+ pyproject.toml 用 `dynamic = ["version"]` 从这里读,`client.py` 的 User-Agent 也读它。
5
+
6
+ 为什么单独抽一个文件:之前 pyproject.toml 和 `__init__.py` 各写各的版本号,
7
+ 结果 `__version__` 长期停在 0.1.0 没跟上(发 0.2.4 时才发现,User-Agent 里也一直
8
+ 写着旧版本)。现在只有这一处需要改。
9
+ """
10
+
11
+ __version__ = "0.2.17"
@@ -1,214 +1,235 @@
1
- # -*- coding: utf-8 -*-
2
- """DataSinking MCP server (Model Context Protocol).
3
-
4
- Expose the DataSinking API — full-text financial reports across the US and Asia
5
- (the US, China, Japan, Korea, Taiwan) as clean Markdown — to AI agents (Claude, Cursor,
6
- Codex, DeepSeek, Devin Desktop, …).
7
-
8
- Install the MCP extra::
9
-
10
- pip install "datasinking[mcp]"
11
-
12
- Then run::
13
-
14
- datasinking-mcp
15
-
16
- or add to any MCP client with ``command: datasinking-mcp`` (stdio). Requires
17
- the environment variable ``DATASINK_API_KEY`` (get a free key at
18
- https://datasink.ing).
19
- """
20
-
21
- import json
22
- import os
23
- from typing import Annotated, Optional
24
-
25
- import requests
26
- from pydantic import Field
27
-
28
- from ._version import __version__ # 版本号唯一来源(原来是硬编码,漂到了 0.2.3)
29
-
30
- # mcp v1 uses FastMCP; v2 renamed it to MCPServer. Support both.
31
- try:
32
- from mcp.server.fastmcp import FastMCP # mcp v1
33
- except ImportError: # pragma: no cover
34
- from mcp.server.mcpserver import MCPServer as FastMCP # mcp v2
35
-
36
- # ⚠️ **工具里必须抛 ToolError,不能抛 RuntimeError。**
37
- #
38
- # mcp v2(实测 2.2.0)对两者的处理完全不同:
39
- # 抛 RuntimeError → 客户端收到 isError:true,但文本是**通用句**
40
- # 「Error executing tool <名字>」,**消息内容被剥掉**,只进 stderr
41
- # 抛 ToolError → 客户端收到 isError:true,文本带上你的原话
42
- # 「Error executing tool <名字>: <你的消息>」
43
- #
44
- # 这不是细节:get_section 的描述让模型「用真实标题重试」,标题列表就在消息里;
45
- # 缺 API key 的提示也只在这里。用 RuntimeError 的话,模型看到的是一个**没有任何线索**的
46
- # 失败 —— 2026-09-22 用真 key 走 uvx 发布版实测确认(`Error executing tool get_section`,32 字符)。
47
- try:
48
- from mcp.server.fastmcp.exceptions import ToolError # mcp v1
49
- except ImportError: # pragma: no cover
50
- try:
51
- from mcp.server.mcpserver.exceptions import ToolError # mcp v2
52
- except ImportError: # 极老的 mcp 没有这个类:退回普通异常(消息会丢,但不至于起不来)
53
- ToolError = RuntimeError # type: ignore[assignment,misc]
54
-
55
- # ⚠️ 参数描述必须写成 `Annotated[T, Field(description=...)]`,**不能只靠 docstring 的 Args 段**。
56
- # mcp v2(MCPServer)不再解析 docstring 的 Args —— 实测把整段 Args 当散文塞进工具描述,
57
- # 参数级 description 全是空的(2026-09-22 用真实 stdio 握手验证)。v1 两种都认,所以这样写两边通用。
58
-
59
-
60
- BASE_URL = "https://api.datasink.ing"
61
- API_KEY = os.environ.get("DATASINK_API_KEY", "")
62
-
63
- mcp = FastMCP(
64
- "DataSinking",
65
- title="DataSinking — Full-text Financial Reports",
66
- description="Full-text financial reports (US, China, Japan, Korea, Taiwan) as clean Markdown via API, with chapter-level access for RAG and AI agents.",
67
- version=__version__,
68
- instructions=(
69
- "DataSinking serves full-text financial reports (annual / semi-annual / quarterly) "
70
- "from the US, China, Japan, Korea and Taiwan as clean Markdown, ready for LLM reading and RAG. "
71
- "Use FMP-style symbols: AAPL (Apple), 600519.SS (Kweichow Moutai), 7203.T (Toyota), "
72
- "005930.KS (Samsung Electronics), 2330.TW (TSMC). To save tokens, prefer get_section to pull one chapter "
73
- "(e.g. MD&A) instead of get_report for the whole document."
74
- ),
75
- )
76
-
77
-
78
- def _get(path: str, params: Optional[dict] = None) -> dict:
79
- """Call the DataSinking API, carrying the API key automatically.
80
-
81
- 出错时**把服务端 body 原文带出去**。它写的不是「失败了」,而是「该怎么办」:
82
-
83
- ``detail`` 一句人话(「未找到章节「X」」)
84
- ``available`` 那份报告的**全部真实标题**
85
-
86
- ``get_section`` 的工具描述明确让模型「retry with one of those」,所以这个列表
87
- 必须跟着异常走。原来这里是一句 ``r.raise_for_status()``,body 整个丢掉 ——
88
- 模型只看到裸的 ``404 Client Error: Not Found for url: ...``,既不知道错在哪,
89
- 也没有可重试的标题(2026-09-22 用真 key 实测,三份实现里这份丢得最干净)。
90
- """
91
- if not API_KEY:
92
- raise ToolError(
93
- "Missing DATASINK_API_KEY environment variable (get a free key at https://datasink.ing)"
94
- )
95
- p = dict(params or {})
96
- p["apikey"] = API_KEY
97
- r = requests.get(f"{BASE_URL}{path}", params=p, timeout=90)
98
- if not r.ok:
99
- try:
100
- body = r.json()
101
- except ValueError:
102
- body = None
103
- if not isinstance(body, dict):
104
- body = {}
105
- msg = f"HTTP {r.status_code}: {body.get('detail') or r.text[:2000]}"
106
- available = body.get("available")
107
- if isinstance(available, list):
108
- msg += "\nAvailable sections: " + json.dumps(available, ensure_ascii=False)
109
- # ToolError 而非 RuntimeError —— 否则 mcp v2 把 msg 剥成通用句,见文件顶部注释
110
- raise ToolError(msg)
111
- return r.json()
112
-
113
-
114
- @mcp.tool()
115
- def list_exchanges() -> list:
116
- """List the exchanges DataSinking covers.
117
-
118
- Returns exchange codes (nyse / nasdaq / cboe / sse / szse / bj / jpx / ksc / koe / knx / twse / tpex) with
119
- the number of reports available per exchange. Call this first to discover coverage.
120
- Sources: US = SEC EDGAR, A-shares = cninfo.com.cn, Japan = EDINET, Korea = DART, Taiwan = MOPS.
121
- """
122
- return _get("/exchanges").get("exchanges", [])
123
-
124
-
125
- @mcp.tool()
126
- def list_stocks(
127
- exchange: Annotated[
128
- str, Field(description="Exchange code, e.g. nyse / nasdaq / cboe / sse / szse / bj / jpx / ksc / koe / knx / twse / tpex")
129
- ],
130
- limit: Annotated[
131
- int, Field(description="Return only the first N companies (default 20) to keep the response short.")
132
- ] = 20,
133
- ) -> dict:
134
- """List the stocks on one exchange, including the report count per company."""
135
- data = _get("/stocks", {"exchange": exchange})
136
- return {"exchange": exchange, "total": data.get("total", 0), "items": data.get("items", [])[:limit]}
137
-
138
-
139
- @mcp.tool()
140
- def list_reports(
141
- symbol: Annotated[
142
- str, Field(description="FMP-style symbol, e.g. AAPL / 600519.SS / 7203.T / 005930.KS / 2330.TW")
143
- ],
144
- doc_type: Annotated[
145
- str, Field(description="Report type to filter on. Defaults to annual.")
146
- ] = "annual",
147
- size: Annotated[int, Field(description="Number of reports to return (default 10).")] = 10,
148
- ) -> dict:
149
- """List a company's reports — metadata only (id, title, period), no body text.
150
-
151
- Each item carries a `source` field naming the official disclosure platform;
152
- keep that attribution when you cite it.
153
- """
154
- return _get("/documents", {"symbol": symbol, "doc_type": doc_type, "size": size})
155
-
156
-
157
- @mcp.tool()
158
- def get_report(
159
- document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
160
- ) -> dict:
161
- """Fetch one report's full text (metadata + Markdown body).
162
-
163
- The `source` field names the official disclosure platform; keep that attribution
164
- when you cite it. Expensive in tokens — prefer get_section when you only need one chapter.
165
- """
166
- return _get(f"/documents/{document_id}")
167
-
168
-
169
- @mcp.tool()
170
- def list_sections(
171
- document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
172
- ) -> dict:
173
- """List every section of a report with its size, before you decide what to pull.
174
-
175
- Returns `sections` (titles, in order) plus `section_details` — same order, one entry
176
- per section with `title`, `has_tables`, `chars` and `estimated_tokens`.
177
-
178
- Use `estimated_tokens` to avoid pulling a chapter that would blow your context,
179
- and `has_tables` to know whether the chapter needs special handling (tables are the
180
- part RAG pipelines usually get wrong). Then call get_section with a heading keyword —
181
- the headings are in the report's own language.
182
- """
183
- return _get(f"/documents/{document_id}/sections")
184
-
185
-
186
- @mcp.tool()
187
- def get_section(
188
- document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
189
- section: Annotated[
190
- str,
191
- Field(
192
- description=(
193
- "Heading keyword. Matches the report's own headings, and also understands "
194
- "cross-market concepts — MD&A / financial statements / notes / audit report / "
195
- "risk / governance (or their Chinese/Japanese/Korean equivalents) resolve to the "
196
- "right section in any market. Otherwise pass a heading in the report's language "
197
- "(A-share: 管理层讨论与分析 / 财务报告; English filings: \"MD&A\" / "
198
- "\"financial statements\" / \"notes\"). If nothing matches, the API returns 404 "
199
- "with the real headings — retry with one of those, or call list_sections first."
200
- )
201
- ),
202
- ],
203
- ) -> dict:
204
- """Fetch only one section of a report by keyword — much cheaper than get_report, best for RAG."""
205
- return _get(f"/documents/{document_id}", {"section": section})
206
-
207
-
208
- def main() -> None:
209
- """Entry point for the ``datasinking-mcp`` console script."""
210
- mcp.run()
211
-
212
-
213
- if __name__ == "__main__":
214
- main()
1
+ # -*- coding: utf-8 -*-
2
+ """DataSinking MCP server (Model Context Protocol).
3
+
4
+ Expose the DataSinking API — full-text financial reports across the US and Asia
5
+ (the US, the UK, China, Japan, Korea, Taiwan) as clean Markdown — to AI agents (Claude, Cursor,
6
+ Codex, DeepSeek, Devin Desktop, …).
7
+
8
+ Install the MCP extra::
9
+
10
+ pip install "datasinking[mcp]"
11
+
12
+ Then run::
13
+
14
+ datasinking-mcp
15
+
16
+ or add to any MCP client with ``command: datasinking-mcp`` (stdio). Requires
17
+ the environment variable ``DATASINK_API_KEY`` (get a free key at
18
+ https://datasink.ing).
19
+ """
20
+
21
+ import json
22
+ import os
23
+ from typing import Annotated, Optional
24
+
25
+ import requests
26
+ from pydantic import Field
27
+
28
+ from ._version import __version__ # 版本号唯一来源(原来是硬编码,漂到了 0.2.3)
29
+
30
+ # mcp v1 uses FastMCP; v2 renamed it to MCPServer. Support both.
31
+ try:
32
+ from mcp.server.fastmcp import FastMCP # mcp v1
33
+ except ImportError: # pragma: no cover
34
+ from mcp.server.mcpserver import MCPServer as FastMCP # mcp v2
35
+
36
+ # ⚠️ **工具里必须抛 ToolError,不能抛 RuntimeError。**
37
+ #
38
+ # mcp v2(实测 2.2.0)对两者的处理完全不同:
39
+ # 抛 RuntimeError → 客户端收到 isError:true,但文本是**通用句**
40
+ # 「Error executing tool <名字>」,**消息内容被剥掉**,只进 stderr
41
+ # 抛 ToolError → 客户端收到 isError:true,文本带上你的原话
42
+ # 「Error executing tool <名字>: <你的消息>」
43
+ #
44
+ # 这不是细节:get_section 的描述让模型「用真实标题重试」,标题列表就在消息里;
45
+ # 缺 API key 的提示也只在这里。用 RuntimeError 的话,模型看到的是一个**没有任何线索**的
46
+ # 失败 —— 2026-09-22 用真 key 走 uvx 发布版实测确认(`Error executing tool get_section`,32 字符)。
47
+ try:
48
+ from mcp.server.fastmcp.exceptions import ToolError # mcp v1
49
+ except ImportError: # pragma: no cover
50
+ try:
51
+ from mcp.server.mcpserver.exceptions import ToolError # mcp v2
52
+ except ImportError: # 极老的 mcp 没有这个类:退回普通异常(消息会丢,但不至于起不来)
53
+ ToolError = RuntimeError # type: ignore[assignment,misc]
54
+
55
+ # ⚠️ 参数描述必须写成 `Annotated[T, Field(description=...)]`,**不能只靠 docstring 的 Args 段**。
56
+ # mcp v2(MCPServer)不再解析 docstring 的 Args —— 实测把整段 Args 当散文塞进工具描述,
57
+ # 参数级 description 全是空的(2026-09-22 用真实 stdio 握手验证)。v1 两种都认,所以这样写两边通用。
58
+
59
+
60
+ BASE_URL = "https://api.datasink.ing"
61
+ API_KEY = os.environ.get("DATASINK_API_KEY", "")
62
+
63
+ mcp = FastMCP(
64
+ "DataSinking",
65
+ title="DataSinking — Full-text Financial Reports",
66
+ description="Full-text financial reports (US, UK, China, Japan, Korea, Taiwan) as clean Markdown via API, with chapter-level access for RAG and AI agents.",
67
+ version=__version__,
68
+ instructions=(
69
+ "DataSinking serves full-text financial reports (annual / semi-annual / quarterly) "
70
+ "from the US, the UK, China, Japan, Korea and Taiwan as clean Markdown, ready for LLM reading and RAG. "
71
+ "Use FMP-style symbols: AAPL (Apple), 600519.SS (Kweichow Moutai), 7203.T (Toyota), "
72
+ "005930.KS (Samsung Electronics), 2330.TW (TSMC). To save tokens, prefer get_section to pull one chapter "
73
+ "(e.g. MD&A) instead of get_report for the whole document."
74
+ ),
75
+ )
76
+
77
+
78
+ def _get(path: str, params: Optional[dict] = None) -> dict:
79
+ """Call the DataSinking API, carrying the API key automatically.
80
+
81
+ 出错时**把服务端 body 原文带出去**。它写的不是「失败了」,而是「该怎么办」:
82
+
83
+ ``detail`` 一句人话(「未找到章节「X」」)
84
+ ``available`` 那份报告的**全部真实标题**
85
+
86
+ ``get_section`` 的工具描述明确让模型「retry with one of those」,所以这个列表
87
+ 必须跟着异常走。原来这里是一句 ``r.raise_for_status()``,body 整个丢掉 ——
88
+ 模型只看到裸的 ``404 Client Error: Not Found for url: ...``,既不知道错在哪,
89
+ 也没有可重试的标题(2026-09-22 用真 key 实测,三份实现里这份丢得最干净)。
90
+ """
91
+ if not API_KEY:
92
+ raise ToolError(
93
+ "Missing DATASINK_API_KEY environment variable (get a free key at https://datasink.ing)"
94
+ )
95
+ p = dict(params or {})
96
+ p["apikey"] = API_KEY
97
+ r = requests.get(f"{BASE_URL}{path}", params=p, timeout=90)
98
+ if not r.ok:
99
+ try:
100
+ body = r.json()
101
+ except ValueError:
102
+ body = None
103
+ if not isinstance(body, dict):
104
+ body = {}
105
+ msg = f"HTTP {r.status_code}: {body.get('detail') or r.text[:2000]}"
106
+ available = body.get("available")
107
+ if isinstance(available, list):
108
+ msg += "\nAvailable sections: " + json.dumps(available, ensure_ascii=False)
109
+ # ToolError 而非 RuntimeError —— 否则 mcp v2 把 msg 剥成通用句,见文件顶部注释
110
+ raise ToolError(msg)
111
+ return r.json()
112
+
113
+
114
+ @mcp.tool()
115
+ def list_exchanges() -> list:
116
+ """List the exchanges DataSinking covers.
117
+
118
+ Returns exchange codes (bj / br / by / jpx / koe / ksc / lk / lse / nasdaq / np / nyse / otc / sse / szse / tpex / twse) with
119
+ the number of reports available per exchange. Call this first to discover coverage.
120
+ Sources: US = SEC EDGAR, A-shares = cninfo.com.cn, Japan = EDINET, Korea = DART, Taiwan = MOPS, UK = FCA NSM, Nepal = NEPSE, Brazil = CVM, Belarus = ЕПФР, Sri Lanka = CSE.
121
+ """
122
+ return _get("/exchanges").get("exchanges", [])
123
+
124
+
125
+ @mcp.tool()
126
+ def list_stocks(
127
+ exchange: Annotated[
128
+ str, Field(description="Exchange code, e.g. bj / br / by / jpx / koe / ksc / lk / lse / nasdaq / np / nyse / otc / sse / szse / tpex / twse")
129
+ ],
130
+ limit: Annotated[
131
+ int, Field(description="Return only the first N companies (default 20) to keep the response short.")
132
+ ] = 20,
133
+ ) -> dict:
134
+ """List the stocks on one exchange, including the report count per company."""
135
+ data = _get("/stocks", {"exchange": exchange})
136
+ return {"exchange": exchange, "total": data.get("total", 0), "items": data.get("items", [])[:limit]}
137
+
138
+
139
+ @mcp.tool()
140
+ def list_reports(
141
+ symbol: Annotated[
142
+ str, Field(description="FMP-style symbol, e.g. AAPL / 600519.SS / 7203.T / 005930.KS / 2330.TW / NABIL.NP")
143
+ ] = "",
144
+ exchange: Annotated[
145
+ str, Field(description="Exchange code (from list_exchanges). Use with stock_code when the code isn't ticker-shaped.")
146
+ ] = "",
147
+ stock_code: Annotated[
148
+ str, Field(description="Exact stock_code from list_stocks. Use with exchange.")
149
+ ] = "",
150
+ doc_type: Annotated[
151
+ str, Field(description="Report type to filter on. Defaults to annual.")
152
+ ] = "annual",
153
+ size: Annotated[int, Field(description="Number of reports to return (default 10).")] = 10,
154
+ ) -> dict:
155
+ """List a company's reports — metadata only (id, title, period), no body text.
156
+
157
+ Each item carries a `source` field naming the official disclosure platform;
158
+ keep that attribution when you cite it.
159
+ Look up by `symbol` (FMP-style, e.g. AAPL / 600519.SS / NABIL.NP) or by
160
+ `exchange` + `stock_code` (from list_stocks) for markets whose codes aren't
161
+ ticker-shaped (e.g. Brazil CNPJ, Sri Lanka 'AAA.N0000').
162
+ """
163
+ params = {"doc_type": doc_type, "size": size}
164
+ if symbol:
165
+ params["symbol"] = symbol
166
+ elif exchange and stock_code:
167
+ params["exchange"] = exchange
168
+ params["stock_code"] = stock_code
169
+ return _get("/documents", params)
170
+
171
+
172
+ @mcp.tool()
173
+ def get_report(
174
+ document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
175
+ ) -> dict:
176
+ """Fetch one report's full text (metadata + Markdown body).
177
+
178
+ The `source` field names the official disclosure platform; keep that attribution
179
+ when you cite it. Expensive in tokens — prefer get_section when you only need one chapter.
180
+ Content is annotated with block markers (`<!-- ds:block:N -->`) so you can cite a
181
+ specific paragraph or table.
182
+ """
183
+ return _get(f"/documents/{document_id}")
184
+
185
+
186
+ @mcp.tool()
187
+ def list_sections(
188
+ document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
189
+ ) -> dict:
190
+ """List every section of a report with its size, before you decide what to pull.
191
+
192
+ Returns `sections` (titles, in order) plus `section_details` — same order, one entry
193
+ per section with `title`, `has_tables`, `chars` and `estimated_tokens`.
194
+
195
+ Use `estimated_tokens` to avoid pulling a chapter that would blow your context,
196
+ and `has_tables` to know whether the chapter needs special handling (tables are the
197
+ part RAG pipelines usually get wrong). Then call get_section with a heading keyword —
198
+ the headings are in the report's own language.
199
+ """
200
+ return _get(f"/documents/{document_id}/sections")
201
+
202
+
203
+ @mcp.tool()
204
+ def get_section(
205
+ document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
206
+ section: Annotated[
207
+ str,
208
+ Field(
209
+ description=(
210
+ "Heading keyword. Matches the report's own headings, and also understands "
211
+ "cross-market concepts — MD&A / financial statements / notes / audit report / "
212
+ "risk / governance (or their Chinese/Japanese/Korean equivalents) resolve to the "
213
+ "right section in any market. Otherwise pass a heading in the report's language "
214
+ "(A-share: 管理层讨论与分析 / 财务报告; English filings: \"MD&A\" / "
215
+ "\"financial statements\" / \"notes\"). If nothing matches, the API returns 404 "
216
+ "with the real headings — retry with one of those, or call list_sections first."
217
+ )
218
+ ),
219
+ ],
220
+ ) -> dict:
221
+ """Fetch only one section of a report by keyword — much cheaper than get_report, best for RAG.
222
+
223
+ Content is annotated with block markers (`<!-- ds:block:N -->`) so you can cite a
224
+ specific paragraph or table.
225
+ """
226
+ return _get(f"/documents/{document_id}", {"section": section})
227
+
228
+
229
+ def main() -> None:
230
+ """Entry point for the ``datasinking-mcp`` console script."""
231
+ mcp.run()
232
+
233
+
234
+ if __name__ == "__main__":
235
+ main()
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasinking
3
- Version: 0.2.14
4
- Summary: Python client for DataSinking — full-text financial reports (US, China, Japan, Korea, Taiwan) as Markdown.
3
+ Version: 0.2.17
4
+ Summary: Python client for DataSinking — full-text financial reports (US, UK, China, Japan, Korea, Taiwan) as Markdown. Earn $7 per purchase per referral (recurring): https://datasink.ing/affiliate
5
5
  Author: DataSinking
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://datasink.ing
@@ -31,17 +31,18 @@ Dynamic: license-file
31
31
  **Full-text financial reports across the US and Asia, as clean Markdown.**
32
32
 
33
33
  [DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
34
- and quarterly — from **the US, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
34
+ and quarterly — from **the US, the UK, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
35
35
  and RAG. Query by FMP-style symbol (`AAPL`, `600519.SS`, `7203.T`, `005930.KS`, `2330.TW`) or filter by
36
36
  exchange, report period, or **section** — pull just the MD&A / risk section instead of the whole
37
- report. Reports are sourced from official disclosure platforms and parsed into structured Markdown
37
+ filing, saving tokens. Every figure is traceable to its source disclosure and
38
+ block. Reports are sourced from official disclosure platforms and parsed into structured Markdown
38
39
  with YAML frontmatter, preserved headings, paragraphs and tables.
39
40
 
40
41
  ---
41
42
 
42
43
  ## MCP server
43
44
 
44
- Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop) as an
45
+ Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop / n8n) as an
45
46
  [MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
46
47
  list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
47
48
 
@@ -187,6 +188,7 @@ coverage, list a company's reports, and extract a figure with correct units.
187
188
  | `01_quickstart.py` | The 5 core functions: list exchanges / stocks / reports / fetch a report / fetch a stock's reports |
188
189
  | `02_download_company.py` | Download a company's full reports to local Markdown files |
189
190
  | `03_download_exchange.py` | Download an entire exchange's reports (all stocks) to local Markdown files |
191
+ | `rag_ingest.py` | Slice reports into RAG chunks (on the `<!-- ds:block:N -->` anchors, with `source` traceability) and ingest into a vector store |
190
192
 
191
193
  Every example pulls from the live API and runs as-is.
192
194
 
@@ -228,6 +230,10 @@ Reports are sourced from the official regulatory disclosure platform of each mar
228
230
 
229
231
  Every document also carries a `source` field in the API response, so the attribution travels with the data. **Please keep it when you redistribute.**
230
232
 
233
+ ## Affiliate
234
+
235
+ Earn **$7 every purchase** for every customer you refer — recurring, PayPal, $31 minimum withdrawal: [datasink.ing/affiliate](https://datasink.ing/affiliate)
236
+
231
237
  ## License
232
238
 
233
239
  [MIT](LICENSE)
@@ -1,39 +1,39 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "datasinking"
7
- # 版本号唯一来源 = datasinking/_version.py(client.py 的 User-Agent 也读它)。
8
- # 以前这里和包内 __version__ 各写各的,结果漂了半年(发 0.2.4 时才发现 __init__ 还停在 0.1.0)。
9
- dynamic = ["version"]
10
- description = "Python client for DataSinking — full-text financial reports (US, China, Japan, Korea, Taiwan) as Markdown."
11
- readme = "README.md"
12
- requires-python = ">=3.8"
13
- license = { text = "MIT" }
14
- authors = [{ name = "DataSinking" }]
15
- keywords = ["a-share", "china", "korea", "japan", "us", "sec", "financial reports", "markdown", "api", "mcp", "model-context-protocol", "rag", "llm"]
16
- classifiers = [
17
- "Programming Language :: Python :: 3",
18
- "License :: OSI Approved :: MIT License",
19
- "Operating System :: OS Independent",
20
- "Topic :: Office/Business :: Financial",
21
- ]
22
- dependencies = []
23
-
24
- [project.optional-dependencies]
25
- mcp = ["mcp>=1.0.0", "requests>=2.28", "pydantic>=2.0"]
26
-
27
- [project.scripts]
28
- datasinking-mcp = "datasinking.mcp_server:main"
29
-
30
- [project.urls]
31
- Homepage = "https://datasink.ing"
32
- Documentation = "https://datasink.ing/docs"
33
- Repository = "https://github.com/heubme2020/datasinking"
34
-
35
- [tool.setuptools]
36
- packages = ["datasinking"]
37
-
38
- [tool.setuptools.dynamic]
39
- version = { attr = "datasinking._version.__version__" }
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "datasinking"
7
+ # 版本号唯一来源 = datasinking/_version.py(client.py 的 User-Agent 也读它)。
8
+ # 以前这里和包内 __version__ 各写各的,结果漂了半年(发 0.2.4 时才发现 __init__ 还停在 0.1.0)。
9
+ dynamic = ["version"]
10
+ description = "Python client for DataSinking — full-text financial reports (US, UK, China, Japan, Korea, Taiwan) as Markdown. Earn $7 per purchase per referral (recurring): https://datasink.ing/affiliate"
11
+ readme = "README.md"
12
+ requires-python = ">=3.8"
13
+ license = { text = "MIT" }
14
+ authors = [{ name = "DataSinking" }]
15
+ keywords = ["a-share", "china", "korea", "japan", "us", "sec", "financial reports", "markdown", "api", "mcp", "model-context-protocol", "rag", "llm"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Topic :: Office/Business :: Financial",
21
+ ]
22
+ dependencies = []
23
+
24
+ [project.optional-dependencies]
25
+ mcp = ["mcp>=1.0.0", "requests>=2.28", "pydantic>=2.0"]
26
+
27
+ [project.scripts]
28
+ datasinking-mcp = "datasinking.mcp_server:main"
29
+
30
+ [project.urls]
31
+ Homepage = "https://datasink.ing"
32
+ Documentation = "https://datasink.ing/docs"
33
+ Repository = "https://github.com/heubme2020/datasinking"
34
+
35
+ [tool.setuptools]
36
+ packages = ["datasinking"]
37
+
38
+ [tool.setuptools.dynamic]
39
+ version = { attr = "datasinking._version.__version__" }
File without changes
File without changes