datasinking 0.2.13__tar.gz → 0.2.15__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.
- {datasinking-0.2.13 → datasinking-0.2.15}/PKG-INFO +21 -11
- {datasinking-0.2.13 → datasinking-0.2.15}/README.md +216 -206
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking/_version.py +11 -11
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking/mcp_server.py +220 -213
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking.egg-info/PKG-INFO +21 -11
- {datasinking-0.2.13 → datasinking-0.2.15}/pyproject.toml +39 -39
- {datasinking-0.2.13 → datasinking-0.2.15}/LICENSE +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking/__init__.py +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking/client.py +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking.egg-info/SOURCES.txt +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking.egg-info/dependency_links.txt +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking.egg-info/entry_points.txt +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking.egg-info/requires.txt +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/datasinking.egg-info/top_level.txt +0 -0
- {datasinking-0.2.13 → datasinking-0.2.15}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasinking
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Python client for DataSinking — full-text
|
|
3
|
+
Version: 0.2.15
|
|
4
|
+
Summary: Python client for DataSinking — full-text financial reports (US, UK, China, Japan, Korea, Taiwan) as Markdown. Earn $7 per yearly referral: https://datasink.ing/affiliate
|
|
5
5
|
Author: DataSinking
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://datasink.ing
|
|
8
8
|
Project-URL: Documentation, https://datasink.ing/docs
|
|
9
9
|
Project-URL: Repository, https://github.com/heubme2020/datasinking
|
|
10
|
-
Keywords: a-share,china,korea,japan,financial reports,markdown,api,mcp,model-context-protocol,rag,llm
|
|
10
|
+
Keywords: a-share,china,korea,japan,us,sec,financial reports,markdown,api,mcp,model-context-protocol,rag,llm
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
@@ -28,20 +28,21 @@ Dynamic: license-file
|
|
|
28
28
|
[](https://pypi.org/project/datasinking/)
|
|
29
29
|
[](https://github.com/heubme2020/datasinking#mcp-server)
|
|
30
30
|
|
|
31
|
-
**Full-text financial reports across Asia, as clean Markdown.**
|
|
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 **
|
|
35
|
-
and RAG. Query by FMP-style symbol (`600519.SS`, `
|
|
34
|
+
and quarterly — from **the US, the UK, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
|
|
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
|
-
|
|
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
|
|
|
@@ -144,6 +145,9 @@ errors that client actually produces:
|
|
|
144
145
|
| Cursor | [`docs/mcp/cursor.md`](docs/mcp/cursor.md) |
|
|
145
146
|
| DeepSeek Harness (`dsh`) | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
|
|
146
147
|
| Devin Desktop | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
|
|
148
|
+
| OpenCode | [`docs/mcp/opencode.md`](docs/mcp/opencode.md) |
|
|
149
|
+
| Qoder | [`docs/mcp/qoder.md`](docs/mcp/qoder.md) |
|
|
150
|
+
| Reasonix | [`docs/mcp/reasonix.md`](docs/mcp/reasonix.md) |
|
|
147
151
|
|
|
148
152
|
## Quick start
|
|
149
153
|
|
|
@@ -184,6 +188,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
184
188
|
| `01_quickstart.py` | The 5 core functions: list exchanges / stocks / reports / fetch a report / fetch a stock's reports |
|
|
185
189
|
| `02_download_company.py` | Download a company's full reports to local Markdown files |
|
|
186
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 |
|
|
187
192
|
|
|
188
193
|
Every example pulls from the live API and runs as-is.
|
|
189
194
|
|
|
@@ -203,12 +208,12 @@ Start from [`research/TEMPLATE.md`](research/TEMPLATE.md).
|
|
|
203
208
|
|
|
204
209
|
| | |
|
|
205
210
|
|---|---|
|
|
206
|
-
| Coverage | China (SSE / SZSE / BSE) · Korea (KOSPI / KOSDAQ / KONEX) ·
|
|
211
|
+
| Coverage | US (NYSE / NASDAQ / CBOE) · China (SSE / SZSE / BSE) · Japan (TSE) · Korea (KOSPI / KOSDAQ / KONEX) · Taiwan (TWSE / TPEx) |
|
|
207
212
|
| Document types | annual / semiannual / q1 / q3 / amendment |
|
|
208
|
-
| Update frequency | Daily — Korea/Japan via official DART/EDINET APIs (new filings within ~24h of publication) |
|
|
213
|
+
| Update frequency | Daily — US/Korea/Japan via official SEC EDGAR / DART / EDINET APIs (new filings within ~24h of publication) |
|
|
209
214
|
| Format | Full-text Markdown (with YAML frontmatter) |
|
|
210
215
|
| API | REST — `GET /documents`, batch download, `with_content=1` for full text, `?section=` + `/sections` for chapter-level access |
|
|
211
|
-
| Symbols | FMP style: `600519.SS` / `
|
|
216
|
+
| Symbols | FMP style: `AAPL` / `600519.SS` / `7203.T` / `005930.KS` |
|
|
212
217
|
| Auth | `?apikey=` query parameter (FMP style) |
|
|
213
218
|
|
|
214
219
|
## Data source
|
|
@@ -217,6 +222,7 @@ Reports are sourced from the official regulatory disclosure platform of each mar
|
|
|
217
222
|
|
|
218
223
|
| Market | Source | Platform |
|
|
219
224
|
|---|---|---|
|
|
225
|
+
| US (bare ticker, e.g. `AAPL`) | SEC EDGAR | US Securities and Exchange Commission — sec.gov/edgar |
|
|
220
226
|
| China A-shares (`.SS` `.SZ` `.BJ`) | 巨潮资讯网 cninfo | CSRC-designated disclosure platform |
|
|
221
227
|
| Korea (`.KS` `.KQ` `.KN`) | DART | Financial Supervisory Service — opendart.fss.or.kr |
|
|
222
228
|
| Japan (`.T`) | EDINET | Financial Services Agency — disclosure2.edinet-fsa.go.jp |
|
|
@@ -224,6 +230,10 @@ Reports are sourced from the official regulatory disclosure platform of each mar
|
|
|
224
230
|
|
|
225
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.**
|
|
226
232
|
|
|
233
|
+
## Affiliate
|
|
234
|
+
|
|
235
|
+
Earn **$7** for every customer who buys the yearly plan — one-time, PayPal, $31 minimum withdrawal: [datasink.ing/affiliate](https://datasink.ing/affiliate)
|
|
236
|
+
|
|
227
237
|
## License
|
|
228
238
|
|
|
229
239
|
[MIT](LICENSE)
|
|
@@ -1,206 +1,216 @@
|
|
|
1
|
-
# DataSinking
|
|
2
|
-
|
|
3
|
-
<!-- mcp-name: io.github.heubme2020/datasinking -->
|
|
4
|
-
|
|
5
|
-
[](https://pypi.org/project/datasinking/)
|
|
6
|
-
[](https://github.com/heubme2020/datasinking#mcp-server)
|
|
7
|
-
|
|
8
|
-
**Full-text financial reports across Asia, as clean Markdown.**
|
|
9
|
-
|
|
10
|
-
[DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
|
|
11
|
-
and quarterly — from **
|
|
12
|
-
and RAG. Query by FMP-style symbol (`600519.SS`, `
|
|
13
|
-
exchange, report period, or **section** — pull just the MD&A / risk section instead of the whole
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
datasinking
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
[`
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
├──
|
|
102
|
-
├──
|
|
103
|
-
├──
|
|
104
|
-
├──
|
|
105
|
-
├── mcp-
|
|
106
|
-
├──
|
|
107
|
-
├──
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
| Claude
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
|
187
|
-
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
1
|
+
# DataSinking
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.heubme2020/datasinking -->
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/datasinking/)
|
|
6
|
+
[](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
|
+

|
|
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** for every customer who buys the yearly plan — one-time, 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.
|
|
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.15"
|
|
@@ -1,213 +1,220 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""DataSinking MCP server (Model Context Protocol).
|
|
3
|
-
|
|
4
|
-
Expose the DataSinking API — full-text financial reports across Asia
|
|
5
|
-
(
|
|
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
|
|
66
|
-
description="Full-text
|
|
67
|
-
version=__version__,
|
|
68
|
-
instructions=(
|
|
69
|
-
"DataSinking serves full-text financial reports (annual / semi-annual / quarterly) "
|
|
70
|
-
"from
|
|
71
|
-
"Use FMP-style symbols: 600519.SS (Kweichow Moutai),
|
|
72
|
-
"
|
|
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 (sse / szse / bj / ksc / koe / knx /
|
|
119
|
-
the number of reports available per exchange. Call this first to discover coverage.
|
|
120
|
-
Sources: A-shares = cninfo.com.cn, Korea = DART,
|
|
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. sse / szse / bj / ksc / koe / knx /
|
|
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. 600519.SS /
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
"
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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 (nyse / nasdaq / cboe / sse / szse / bj / jpx / ksc / koe / knx / twse / tpex / lse) 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.
|
|
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 / lse")
|
|
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
|
+
Content is annotated with block markers (`<!-- ds:block:N -->`) so you can cite a
|
|
166
|
+
specific paragraph or table.
|
|
167
|
+
"""
|
|
168
|
+
return _get(f"/documents/{document_id}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@mcp.tool()
|
|
172
|
+
def list_sections(
|
|
173
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
174
|
+
) -> dict:
|
|
175
|
+
"""List every section of a report with its size, before you decide what to pull.
|
|
176
|
+
|
|
177
|
+
Returns `sections` (titles, in order) plus `section_details` — same order, one entry
|
|
178
|
+
per section with `title`, `has_tables`, `chars` and `estimated_tokens`.
|
|
179
|
+
|
|
180
|
+
Use `estimated_tokens` to avoid pulling a chapter that would blow your context,
|
|
181
|
+
and `has_tables` to know whether the chapter needs special handling (tables are the
|
|
182
|
+
part RAG pipelines usually get wrong). Then call get_section with a heading keyword —
|
|
183
|
+
the headings are in the report's own language.
|
|
184
|
+
"""
|
|
185
|
+
return _get(f"/documents/{document_id}/sections")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@mcp.tool()
|
|
189
|
+
def get_section(
|
|
190
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
191
|
+
section: Annotated[
|
|
192
|
+
str,
|
|
193
|
+
Field(
|
|
194
|
+
description=(
|
|
195
|
+
"Heading keyword. Matches the report's own headings, and also understands "
|
|
196
|
+
"cross-market concepts — MD&A / financial statements / notes / audit report / "
|
|
197
|
+
"risk / governance (or their Chinese/Japanese/Korean equivalents) resolve to the "
|
|
198
|
+
"right section in any market. Otherwise pass a heading in the report's language "
|
|
199
|
+
"(A-share: 管理层讨论与分析 / 财务报告; English filings: \"MD&A\" / "
|
|
200
|
+
"\"financial statements\" / \"notes\"). If nothing matches, the API returns 404 "
|
|
201
|
+
"with the real headings — retry with one of those, or call list_sections first."
|
|
202
|
+
)
|
|
203
|
+
),
|
|
204
|
+
],
|
|
205
|
+
) -> dict:
|
|
206
|
+
"""Fetch only one section of a report by keyword — much cheaper than get_report, best for RAG.
|
|
207
|
+
|
|
208
|
+
Content is annotated with block markers (`<!-- ds:block:N -->`) so you can cite a
|
|
209
|
+
specific paragraph or table.
|
|
210
|
+
"""
|
|
211
|
+
return _get(f"/documents/{document_id}", {"section": section})
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def main() -> None:
|
|
215
|
+
"""Entry point for the ``datasinking-mcp`` console script."""
|
|
216
|
+
mcp.run()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
main()
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasinking
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Python client for DataSinking — full-text
|
|
3
|
+
Version: 0.2.15
|
|
4
|
+
Summary: Python client for DataSinking — full-text financial reports (US, UK, China, Japan, Korea, Taiwan) as Markdown. Earn $7 per yearly referral: https://datasink.ing/affiliate
|
|
5
5
|
Author: DataSinking
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://datasink.ing
|
|
8
8
|
Project-URL: Documentation, https://datasink.ing/docs
|
|
9
9
|
Project-URL: Repository, https://github.com/heubme2020/datasinking
|
|
10
|
-
Keywords: a-share,china,korea,japan,financial reports,markdown,api,mcp,model-context-protocol,rag,llm
|
|
10
|
+
Keywords: a-share,china,korea,japan,us,sec,financial reports,markdown,api,mcp,model-context-protocol,rag,llm
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
@@ -28,20 +28,21 @@ Dynamic: license-file
|
|
|
28
28
|
[](https://pypi.org/project/datasinking/)
|
|
29
29
|
[](https://github.com/heubme2020/datasinking#mcp-server)
|
|
30
30
|
|
|
31
|
-
**Full-text financial reports across Asia, as clean Markdown.**
|
|
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 **
|
|
35
|
-
and RAG. Query by FMP-style symbol (`600519.SS`, `
|
|
34
|
+
and quarterly — from **the US, the UK, China, Japan, Korea and Taiwan** as clean **Markdown**, ready for LLM reading
|
|
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
|
-
|
|
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
|
|
|
@@ -144,6 +145,9 @@ errors that client actually produces:
|
|
|
144
145
|
| Cursor | [`docs/mcp/cursor.md`](docs/mcp/cursor.md) |
|
|
145
146
|
| DeepSeek Harness (`dsh`) | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
|
|
146
147
|
| Devin Desktop | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
|
|
148
|
+
| OpenCode | [`docs/mcp/opencode.md`](docs/mcp/opencode.md) |
|
|
149
|
+
| Qoder | [`docs/mcp/qoder.md`](docs/mcp/qoder.md) |
|
|
150
|
+
| Reasonix | [`docs/mcp/reasonix.md`](docs/mcp/reasonix.md) |
|
|
147
151
|
|
|
148
152
|
## Quick start
|
|
149
153
|
|
|
@@ -184,6 +188,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
184
188
|
| `01_quickstart.py` | The 5 core functions: list exchanges / stocks / reports / fetch a report / fetch a stock's reports |
|
|
185
189
|
| `02_download_company.py` | Download a company's full reports to local Markdown files |
|
|
186
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 |
|
|
187
192
|
|
|
188
193
|
Every example pulls from the live API and runs as-is.
|
|
189
194
|
|
|
@@ -203,12 +208,12 @@ Start from [`research/TEMPLATE.md`](research/TEMPLATE.md).
|
|
|
203
208
|
|
|
204
209
|
| | |
|
|
205
210
|
|---|---|
|
|
206
|
-
| Coverage | China (SSE / SZSE / BSE) · Korea (KOSPI / KOSDAQ / KONEX) ·
|
|
211
|
+
| Coverage | US (NYSE / NASDAQ / CBOE) · China (SSE / SZSE / BSE) · Japan (TSE) · Korea (KOSPI / KOSDAQ / KONEX) · Taiwan (TWSE / TPEx) |
|
|
207
212
|
| Document types | annual / semiannual / q1 / q3 / amendment |
|
|
208
|
-
| Update frequency | Daily — Korea/Japan via official DART/EDINET APIs (new filings within ~24h of publication) |
|
|
213
|
+
| Update frequency | Daily — US/Korea/Japan via official SEC EDGAR / DART / EDINET APIs (new filings within ~24h of publication) |
|
|
209
214
|
| Format | Full-text Markdown (with YAML frontmatter) |
|
|
210
215
|
| API | REST — `GET /documents`, batch download, `with_content=1` for full text, `?section=` + `/sections` for chapter-level access |
|
|
211
|
-
| Symbols | FMP style: `600519.SS` / `
|
|
216
|
+
| Symbols | FMP style: `AAPL` / `600519.SS` / `7203.T` / `005930.KS` |
|
|
212
217
|
| Auth | `?apikey=` query parameter (FMP style) |
|
|
213
218
|
|
|
214
219
|
## Data source
|
|
@@ -217,6 +222,7 @@ Reports are sourced from the official regulatory disclosure platform of each mar
|
|
|
217
222
|
|
|
218
223
|
| Market | Source | Platform |
|
|
219
224
|
|---|---|---|
|
|
225
|
+
| US (bare ticker, e.g. `AAPL`) | SEC EDGAR | US Securities and Exchange Commission — sec.gov/edgar |
|
|
220
226
|
| China A-shares (`.SS` `.SZ` `.BJ`) | 巨潮资讯网 cninfo | CSRC-designated disclosure platform |
|
|
221
227
|
| Korea (`.KS` `.KQ` `.KN`) | DART | Financial Supervisory Service — opendart.fss.or.kr |
|
|
222
228
|
| Japan (`.T`) | EDINET | Financial Services Agency — disclosure2.edinet-fsa.go.jp |
|
|
@@ -224,6 +230,10 @@ Reports are sourced from the official regulatory disclosure platform of each mar
|
|
|
224
230
|
|
|
225
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.**
|
|
226
232
|
|
|
233
|
+
## Affiliate
|
|
234
|
+
|
|
235
|
+
Earn **$7** for every customer who buys the yearly plan — one-time, PayPal, $31 minimum withdrawal: [datasink.ing/affiliate](https://datasink.ing/affiliate)
|
|
236
|
+
|
|
227
237
|
## License
|
|
228
238
|
|
|
229
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
|
|
11
|
-
readme = "README.md"
|
|
12
|
-
requires-python = ">=3.8"
|
|
13
|
-
license = { text = "MIT" }
|
|
14
|
-
authors = [{ name = "DataSinking" }]
|
|
15
|
-
keywords = ["a-share", "china", "korea", "japan", "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 yearly referral: 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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|