datasinking 0.2.7__tar.gz → 0.2.9__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.7 → datasinking-0.2.9}/PKG-INFO +42 -10
- {datasinking-0.2.7 → datasinking-0.2.9}/README.md +40 -9
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking/__init__.py +1 -1
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking/_version.py +1 -1
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking/client.py +9 -10
- datasinking-0.2.9/datasinking/mcp_server.py +170 -0
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking.egg-info/PKG-INFO +42 -10
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking.egg-info/requires.txt +1 -0
- {datasinking-0.2.7 → datasinking-0.2.9}/pyproject.toml +1 -1
- datasinking-0.2.7/datasinking/mcp_server.py +0 -136
- {datasinking-0.2.7 → datasinking-0.2.9}/LICENSE +0 -0
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking.egg-info/SOURCES.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking.egg-info/dependency_links.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking.egg-info/entry_points.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.9}/datasinking.egg-info/top_level.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasinking
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: Python client for DataSinking — full-text Asian financial reports (China, Korea, Japan, Taiwan) as Markdown.
|
|
5
5
|
Author: DataSinking
|
|
6
6
|
License: MIT
|
|
@@ -18,6 +18,7 @@ License-File: LICENSE
|
|
|
18
18
|
Provides-Extra: mcp
|
|
19
19
|
Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
20
20
|
Requires-Dist: requests>=2.28; extra == "mcp"
|
|
21
|
+
Requires-Dist: pydantic>=2.0; extra == "mcp"
|
|
21
22
|
Dynamic: license-file
|
|
22
23
|
|
|
23
24
|
# DataSinking
|
|
@@ -30,27 +31,58 @@ Dynamic: license-file
|
|
|
30
31
|
**Full-text financial reports across Asia, as clean Markdown.**
|
|
31
32
|
|
|
32
33
|
[DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
|
|
33
|
-
and quarterly — from **China, Korea and
|
|
34
|
-
and RAG. Query by FMP-style symbol (`600519.SS`, `005930.KS`, `7203.T`) or filter by
|
|
35
|
-
report period, or **section** — pull just the MD&A / risk section instead of the whole
|
|
36
|
-
Reports are sourced from official disclosure platforms and parsed into structured Markdown
|
|
37
|
-
YAML frontmatter, preserved headings, paragraphs and tables.
|
|
34
|
+
and quarterly — from **China, Korea, Japan and Taiwan** as clean **Markdown**, ready for LLM reading
|
|
35
|
+
and RAG. Query by FMP-style symbol (`600519.SS`, `005930.KS`, `7203.T`, `2330.TW`) or filter by
|
|
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
|
|
38
|
+
with YAML frontmatter, preserved headings, paragraphs and tables.
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
41
42
|
## MCP server
|
|
42
43
|
|
|
43
|
-
Ship DataSinking to any AI agent (Claude
|
|
44
|
+
Ship DataSinking to any AI agent (Claude / Cursor / Codex / Windsurf) as an
|
|
44
45
|
[MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
|
|
45
46
|
list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
|
|
46
47
|
|
|
48
|
+
### Hosted — nothing to install
|
|
49
|
+
|
|
50
|
+
Point any MCP client at our endpoint and you're done. No package, no Python, no local server:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"datasinking": {
|
|
56
|
+
"type": "http",
|
|
57
|
+
"url": "https://api.datasink.ing/mcp?apikey=YOUR_KEY"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Claude Code, in one line:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
|
|
67
|
+
--header "Authorization: Bearer YOUR_KEY"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Your key rides inside the URL, so treat that config as a secret. Clients that support custom
|
|
71
|
+
headers can send `Authorization: Bearer YOUR_KEY` instead — Claude Code redacts headers in its
|
|
72
|
+
output but can't redact a URL.
|
|
73
|
+
|
|
74
|
+
### Local — run it yourself
|
|
75
|
+
|
|
76
|
+
If you'd rather keep everything on your own machine:
|
|
77
|
+
|
|
47
78
|
```bash
|
|
48
79
|
pip install "datasinking[mcp]"
|
|
49
80
|
datasinking-mcp # requires DATASINK_API_KEY (free at https://datasink.ing)
|
|
50
81
|
```
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
|
|
83
|
+
Then use `command: datasinking-mcp` in your client.
|
|
84
|
+
|
|
85
|
+
Full per-client setup: [`mcp-server.md`](mcp-server.md).
|
|
54
86
|
|
|
55
87
|

|
|
56
88
|
|
|
@@ -111,7 +143,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
111
143
|
|
|
112
144
|
Every example pulls from the live API and runs as-is.
|
|
113
145
|
|
|
114
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
146
|
+
> `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.
|
|
115
147
|
|
|
116
148
|
## Research (`research/`)
|
|
117
149
|
|
|
@@ -8,27 +8,58 @@
|
|
|
8
8
|
**Full-text financial reports across Asia, as clean Markdown.**
|
|
9
9
|
|
|
10
10
|
[DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
|
|
11
|
-
and quarterly — from **China, Korea and
|
|
12
|
-
and RAG. Query by FMP-style symbol (`600519.SS`, `005930.KS`, `7203.T`) or filter by
|
|
13
|
-
report period, or **section** — pull just the MD&A / risk section instead of the whole
|
|
14
|
-
Reports are sourced from official disclosure platforms and parsed into structured Markdown
|
|
15
|
-
YAML frontmatter, preserved headings, paragraphs and tables.
|
|
11
|
+
and quarterly — from **China, Korea, Japan and Taiwan** as clean **Markdown**, ready for LLM reading
|
|
12
|
+
and RAG. Query by FMP-style symbol (`600519.SS`, `005930.KS`, `7203.T`, `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
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
## MCP server
|
|
20
20
|
|
|
21
|
-
Ship DataSinking to any AI agent (Claude
|
|
21
|
+
Ship DataSinking to any AI agent (Claude / Cursor / Codex / Windsurf) as an
|
|
22
22
|
[MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
|
|
23
23
|
list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
|
|
24
24
|
|
|
25
|
+
### Hosted — nothing to install
|
|
26
|
+
|
|
27
|
+
Point any MCP client at our endpoint and you're done. No package, no Python, no local server:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"datasinking": {
|
|
33
|
+
"type": "http",
|
|
34
|
+
"url": "https://api.datasink.ing/mcp?apikey=YOUR_KEY"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Claude Code, in one line:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
|
|
44
|
+
--header "Authorization: Bearer YOUR_KEY"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Your key rides inside the URL, so treat that config as a secret. Clients that support custom
|
|
48
|
+
headers can send `Authorization: Bearer YOUR_KEY` instead — Claude Code redacts headers in its
|
|
49
|
+
output but can't redact a URL.
|
|
50
|
+
|
|
51
|
+
### Local — run it yourself
|
|
52
|
+
|
|
53
|
+
If you'd rather keep everything on your own machine:
|
|
54
|
+
|
|
25
55
|
```bash
|
|
26
56
|
pip install "datasinking[mcp]"
|
|
27
57
|
datasinking-mcp # requires DATASINK_API_KEY (free at https://datasink.ing)
|
|
28
58
|
```
|
|
29
59
|
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
Then use `command: datasinking-mcp` in your client.
|
|
61
|
+
|
|
62
|
+
Full per-client setup: [`mcp-server.md`](mcp-server.md).
|
|
32
63
|
|
|
33
64
|

|
|
34
65
|
|
|
@@ -89,7 +120,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
89
120
|
|
|
90
121
|
Every example pulls from the live API and runs as-is.
|
|
91
122
|
|
|
92
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
123
|
+
> `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.
|
|
93
124
|
|
|
94
125
|
## Research (`research/`)
|
|
95
126
|
|
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
try:
|
|
12
12
|
ds.get_stock_reports("600519.SS", limit=-1)
|
|
13
13
|
except QuotaExceeded as e:
|
|
14
|
-
print(e.code, e) # e.g.
|
|
14
|
+
print(e.code, e) # e.g. quota_7d / free_quota_key / free_quota_global
|
|
15
15
|
"""
|
|
16
16
|
from ._version import __version__
|
|
17
17
|
from .client import DataSinking, QuotaExceeded
|
|
@@ -24,14 +24,13 @@ class QuotaExceeded(RuntimeError):
|
|
|
24
24
|
"""额度用尽(HTTP 429 + 服务端返回的 `code`)。
|
|
25
25
|
|
|
26
26
|
和「限流」不是一回事:限流等几秒就好(客户端自己会重试),
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
常见 code
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
free_quota_month 免费共享池 最近 31 天额度用尽(所有免费用户合计)
|
|
27
|
+
额度类要等**7 天窗口滚动**才有用 —— 重试没有意义,所以直接抛出来,不再重试。
|
|
28
|
+
|
|
29
|
+
常见 code(2026-09-21 起配额统一成「每 7 天 rolling」,日/月两层已取消):
|
|
30
|
+
quota_7d 年费 key 的 7 天额度用尽(每 7 天 524,287 篇)
|
|
31
|
+
free_quota_key 免费 key 的 7 天额度用尽(每 7 天 8,191 篇)
|
|
32
|
+
free_quota_global 免费共享池的 7 天额度用尽
|
|
33
|
+
(所有免费 key + 网页访客合计每 7 天 524,287 篇)
|
|
35
34
|
"""
|
|
36
35
|
|
|
37
36
|
def __init__(self, code=None, detail=None):
|
|
@@ -81,8 +80,8 @@ class DataSinking:
|
|
|
81
80
|
raise # 401/403/404/400… 直接抛给上层
|
|
82
81
|
code, detail = _error_info(e)
|
|
83
82
|
if code:
|
|
84
|
-
# 额度类 429
|
|
85
|
-
# (以前这里不分青红皂白 sleep(2)
|
|
83
|
+
# 额度类 429:要等 7 天窗口滚动,重试没有意义 —— 立刻抛,并带上服务端的原因。
|
|
84
|
+
# (以前这里不分青红皂白 sleep(2) 重试,额度打满时会空转,
|
|
86
85
|
# 最后还抛出一句 "Request failed after retries: None" —— last 变量
|
|
87
86
|
# 只在网络异常分支被赋值,429 分支根本不赋值。)
|
|
88
87
|
raise QuotaExceeded(code, detail)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""DataSinking MCP server (Model Context Protocol).
|
|
3
|
+
|
|
4
|
+
Expose the DataSinking API — full-text financial reports across Asia
|
|
5
|
+
(China, Korea, Japan, Taiwan) as clean Markdown — to AI agents (Claude, Cursor,
|
|
6
|
+
Codex, DeepSeek, Windsurf, …).
|
|
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 os
|
|
22
|
+
from typing import Annotated, Optional
|
|
23
|
+
|
|
24
|
+
import requests
|
|
25
|
+
from pydantic import Field
|
|
26
|
+
|
|
27
|
+
from ._version import __version__ # 版本号唯一来源(原来是硬编码,漂到了 0.2.3)
|
|
28
|
+
|
|
29
|
+
# mcp v1 uses FastMCP; v2 renamed it to MCPServer. Support both.
|
|
30
|
+
try:
|
|
31
|
+
from mcp.server.fastmcp import FastMCP # mcp v1
|
|
32
|
+
except ImportError: # pragma: no cover
|
|
33
|
+
from mcp.server.mcpserver import MCPServer as FastMCP # mcp v2
|
|
34
|
+
|
|
35
|
+
# ⚠️ 参数描述必须写成 `Annotated[T, Field(description=...)]`,**不能只靠 docstring 的 Args 段**。
|
|
36
|
+
# mcp v2(MCPServer)不再解析 docstring 的 Args —— 实测把整段 Args 当散文塞进工具描述,
|
|
37
|
+
# 参数级 description 全是空的(2026-09-22 用真实 stdio 握手验证)。v1 两种都认,所以这样写两边通用。
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
BASE_URL = "https://api.datasink.ing"
|
|
41
|
+
API_KEY = os.environ.get("DATASINK_API_KEY", "")
|
|
42
|
+
|
|
43
|
+
mcp = FastMCP(
|
|
44
|
+
"DataSinking",
|
|
45
|
+
title="DataSinking — Full-text Asian Financial Reports",
|
|
46
|
+
description="Full-text Asian financial reports (China, Korea, Japan, Taiwan) as clean Markdown via API, with chapter-level access for RAG and AI agents.",
|
|
47
|
+
version=__version__,
|
|
48
|
+
instructions=(
|
|
49
|
+
"DataSinking serves full-text financial reports (annual / semi-annual / quarterly) "
|
|
50
|
+
"from China, Korea, Japan and Taiwan as clean Markdown, ready for LLM reading and RAG. "
|
|
51
|
+
"Use FMP-style symbols: 600519.SS (Kweichow Moutai), 005930.KS (Samsung Electronics), "
|
|
52
|
+
"7203.T (Toyota), 2330.TW (TSMC). To save tokens, prefer get_section to pull one chapter "
|
|
53
|
+
"(e.g. MD&A) instead of get_report for the whole document."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _get(path: str, params: Optional[dict] = None) -> dict:
|
|
59
|
+
"""Call the DataSinking API, carrying the API key automatically."""
|
|
60
|
+
if not API_KEY:
|
|
61
|
+
raise RuntimeError(
|
|
62
|
+
"Missing DATASINK_API_KEY environment variable (get a free key at https://datasink.ing)"
|
|
63
|
+
)
|
|
64
|
+
p = dict(params or {})
|
|
65
|
+
p["apikey"] = API_KEY
|
|
66
|
+
r = requests.get(f"{BASE_URL}{path}", params=p, timeout=90)
|
|
67
|
+
r.raise_for_status()
|
|
68
|
+
return r.json()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@mcp.tool()
|
|
72
|
+
def list_exchanges() -> list:
|
|
73
|
+
"""List the exchanges DataSinking covers and their report counts.
|
|
74
|
+
|
|
75
|
+
Returns exchange codes (sse / szse / bj / ksc / koe / knx / jpx / twse / tpex) with
|
|
76
|
+
the number of reports available per exchange. Call this first to discover coverage.
|
|
77
|
+
Sources: A-shares = cninfo.com.cn, Korea = DART, Japan = EDINET, Taiwan = MOPS.
|
|
78
|
+
"""
|
|
79
|
+
return _get("/exchanges").get("exchanges", [])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@mcp.tool()
|
|
83
|
+
def list_stocks(
|
|
84
|
+
exchange: Annotated[
|
|
85
|
+
str, Field(description="Exchange code: sse / szse / bj / ksc / koe / knx / jpx / twse / tpex")
|
|
86
|
+
],
|
|
87
|
+
limit: Annotated[
|
|
88
|
+
int, Field(description="Return only the first N companies (default 20) to keep the response short.")
|
|
89
|
+
] = 20,
|
|
90
|
+
) -> dict:
|
|
91
|
+
"""List stocks on an exchange, including the report count per company."""
|
|
92
|
+
data = _get("/stocks", {"exchange": exchange})
|
|
93
|
+
return {"exchange": exchange, "total": data.get("total", 0), "items": data.get("items", [])[:limit]}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@mcp.tool()
|
|
97
|
+
def list_reports(
|
|
98
|
+
symbol: Annotated[
|
|
99
|
+
str, Field(description="FMP-style symbol, e.g. 600519.SS / 005930.KS / 7203.T / 2330.TW")
|
|
100
|
+
],
|
|
101
|
+
doc_type: Annotated[
|
|
102
|
+
str, Field(description="annual / semiannual / q1 / q3")
|
|
103
|
+
] = "annual",
|
|
104
|
+
size: Annotated[int, Field(description="Number of reports to return (default 10).")] = 10,
|
|
105
|
+
) -> dict:
|
|
106
|
+
"""List a company's reports — metadata only (id, title, period), no body text.
|
|
107
|
+
|
|
108
|
+
Each item carries a ``source`` field naming the official disclosure platform;
|
|
109
|
+
keep that attribution when you cite it.
|
|
110
|
+
"""
|
|
111
|
+
return _get("/documents", {"symbol": symbol, "doc_type": doc_type, "size": size})
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool()
|
|
115
|
+
def get_report(
|
|
116
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
117
|
+
) -> dict:
|
|
118
|
+
"""Fetch a single report's full text (metadata + Markdown body).
|
|
119
|
+
|
|
120
|
+
The ``source`` field names the official disclosure platform; keep that attribution
|
|
121
|
+
when you cite it. Expensive in tokens — prefer get_section when you only need one chapter.
|
|
122
|
+
"""
|
|
123
|
+
return _get(f"/documents/{document_id}")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@mcp.tool()
|
|
127
|
+
def list_sections(
|
|
128
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
129
|
+
) -> dict:
|
|
130
|
+
"""List every section of a report with its size — call this before pulling anything.
|
|
131
|
+
|
|
132
|
+
Returns ``sections`` (titles, in order) plus ``section_details``: the same list as
|
|
133
|
+
objects with ``title``, ``has_tables``, ``chars`` and ``estimated_tokens``.
|
|
134
|
+
|
|
135
|
+
Use ``estimated_tokens`` to avoid pulling a chapter that would blow your context,
|
|
136
|
+
and ``has_tables`` to know whether a chapter needs special handling (tables are the
|
|
137
|
+
part RAG pipelines usually get wrong). Then call get_section with a heading keyword —
|
|
138
|
+
the headings are in the report's own language.
|
|
139
|
+
"""
|
|
140
|
+
return _get(f"/documents/{document_id}/sections")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@mcp.tool()
|
|
144
|
+
def get_section(
|
|
145
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
146
|
+
section: Annotated[
|
|
147
|
+
str,
|
|
148
|
+
Field(
|
|
149
|
+
description=(
|
|
150
|
+
"Heading keyword, matched as a substring against the report's OWN headings, "
|
|
151
|
+
"so pass it in the report's language. A-share reports have Chinese headings "
|
|
152
|
+
"(e.g. 第三节管理层讨论与分析) — use 管理层讨论与分析 / 财务报告 there. "
|
|
153
|
+
"For English-language filings, \"MD&A\" / \"financial statements\" / \"notes\" work. "
|
|
154
|
+
"If nothing matches, the API returns 404 with the real headings — retry with one "
|
|
155
|
+
"of those, or call list_sections first."
|
|
156
|
+
)
|
|
157
|
+
),
|
|
158
|
+
],
|
|
159
|
+
) -> dict:
|
|
160
|
+
"""Fetch only one section of a report by keyword — cheaper than get_report for RAG."""
|
|
161
|
+
return _get(f"/documents/{document_id}", {"section": section})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main() -> None:
|
|
165
|
+
"""Entry point for the ``datasinking-mcp`` console script."""
|
|
166
|
+
mcp.run()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasinking
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: Python client for DataSinking — full-text Asian financial reports (China, Korea, Japan, Taiwan) as Markdown.
|
|
5
5
|
Author: DataSinking
|
|
6
6
|
License: MIT
|
|
@@ -18,6 +18,7 @@ License-File: LICENSE
|
|
|
18
18
|
Provides-Extra: mcp
|
|
19
19
|
Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
20
20
|
Requires-Dist: requests>=2.28; extra == "mcp"
|
|
21
|
+
Requires-Dist: pydantic>=2.0; extra == "mcp"
|
|
21
22
|
Dynamic: license-file
|
|
22
23
|
|
|
23
24
|
# DataSinking
|
|
@@ -30,27 +31,58 @@ Dynamic: license-file
|
|
|
30
31
|
**Full-text financial reports across Asia, as clean Markdown.**
|
|
31
32
|
|
|
32
33
|
[DataSinking](https://datasink.ing) serves **full-text financial reports** — annual, semi-annual
|
|
33
|
-
and quarterly — from **China, Korea and
|
|
34
|
-
and RAG. Query by FMP-style symbol (`600519.SS`, `005930.KS`, `7203.T`) or filter by
|
|
35
|
-
report period, or **section** — pull just the MD&A / risk section instead of the whole
|
|
36
|
-
Reports are sourced from official disclosure platforms and parsed into structured Markdown
|
|
37
|
-
YAML frontmatter, preserved headings, paragraphs and tables.
|
|
34
|
+
and quarterly — from **China, Korea, Japan and Taiwan** as clean **Markdown**, ready for LLM reading
|
|
35
|
+
and RAG. Query by FMP-style symbol (`600519.SS`, `005930.KS`, `7203.T`, `2330.TW`) or filter by
|
|
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
|
|
38
|
+
with YAML frontmatter, preserved headings, paragraphs and tables.
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
41
42
|
## MCP server
|
|
42
43
|
|
|
43
|
-
Ship DataSinking to any AI agent (Claude
|
|
44
|
+
Ship DataSinking to any AI agent (Claude / Cursor / Codex / Windsurf) as an
|
|
44
45
|
[MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
|
|
45
46
|
list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
|
|
46
47
|
|
|
48
|
+
### Hosted — nothing to install
|
|
49
|
+
|
|
50
|
+
Point any MCP client at our endpoint and you're done. No package, no Python, no local server:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"datasinking": {
|
|
56
|
+
"type": "http",
|
|
57
|
+
"url": "https://api.datasink.ing/mcp?apikey=YOUR_KEY"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Claude Code, in one line:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
|
|
67
|
+
--header "Authorization: Bearer YOUR_KEY"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Your key rides inside the URL, so treat that config as a secret. Clients that support custom
|
|
71
|
+
headers can send `Authorization: Bearer YOUR_KEY` instead — Claude Code redacts headers in its
|
|
72
|
+
output but can't redact a URL.
|
|
73
|
+
|
|
74
|
+
### Local — run it yourself
|
|
75
|
+
|
|
76
|
+
If you'd rather keep everything on your own machine:
|
|
77
|
+
|
|
47
78
|
```bash
|
|
48
79
|
pip install "datasinking[mcp]"
|
|
49
80
|
datasinking-mcp # requires DATASINK_API_KEY (free at https://datasink.ing)
|
|
50
81
|
```
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
|
|
83
|
+
Then use `command: datasinking-mcp` in your client.
|
|
84
|
+
|
|
85
|
+
Full per-client setup: [`mcp-server.md`](mcp-server.md).
|
|
54
86
|
|
|
55
87
|

|
|
56
88
|
|
|
@@ -111,7 +143,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
111
143
|
|
|
112
144
|
Every example pulls from the live API and runs as-is.
|
|
113
145
|
|
|
114
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
146
|
+
> `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.
|
|
115
147
|
|
|
116
148
|
## Research (`research/`)
|
|
117
149
|
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""DataSinking MCP server (Model Context Protocol).
|
|
3
|
-
|
|
4
|
-
Expose the DataSinking API — full-text financial reports across Asia
|
|
5
|
-
(China, Korea, Japan) as clean Markdown — to AI agents (Claude, Cursor,
|
|
6
|
-
Codex, DeepSeek, Windsurf, …).
|
|
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 os
|
|
22
|
-
from typing import Optional
|
|
23
|
-
|
|
24
|
-
import requests
|
|
25
|
-
|
|
26
|
-
from ._version import __version__ # 版本号唯一来源(原来是硬编码,漂到了 0.2.3)
|
|
27
|
-
|
|
28
|
-
# mcp v1 uses FastMCP; v2 renamed it to MCPServer. Support both.
|
|
29
|
-
try:
|
|
30
|
-
from mcp.server.fastmcp import FastMCP # mcp v1
|
|
31
|
-
except ImportError: # pragma: no cover
|
|
32
|
-
from mcp.server.mcpserver import MCPServer as FastMCP # mcp v2
|
|
33
|
-
|
|
34
|
-
BASE_URL = "https://api.datasink.ing"
|
|
35
|
-
API_KEY = os.environ.get("DATASINK_API_KEY", "")
|
|
36
|
-
|
|
37
|
-
mcp = FastMCP(
|
|
38
|
-
"DataSinking",
|
|
39
|
-
title="DataSinking — Full-text Asian Financial Reports",
|
|
40
|
-
description="Full-text Asian financial reports (China, Korea, Japan, Taiwan) as clean Markdown via API, with chapter-level access for RAG and AI agents.",
|
|
41
|
-
version=__version__,
|
|
42
|
-
instructions=(
|
|
43
|
-
"DataSinking serves full-text financial reports (annual / semi-annual / quarterly) "
|
|
44
|
-
"from China, Korea, Japan and Taiwan as clean Markdown, ready for LLM reading and RAG. "
|
|
45
|
-
"Use FMP-style symbols: 600519.SS (Kweichow Moutai), 005930.KS (Samsung Electronics), "
|
|
46
|
-
"7203.T (Toyota), 2330.TW (TSMC). To save tokens, prefer get_section to pull one chapter "
|
|
47
|
-
"(e.g. MD&A) instead of get_report for the whole document."
|
|
48
|
-
),
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _get(path: str, params: Optional[dict] = None) -> dict:
|
|
53
|
-
"""Call the DataSinking API, carrying the API key automatically."""
|
|
54
|
-
if not API_KEY:
|
|
55
|
-
raise RuntimeError(
|
|
56
|
-
"Missing DATASINK_API_KEY environment variable (get a free key at https://datasink.ing)"
|
|
57
|
-
)
|
|
58
|
-
p = dict(params or {})
|
|
59
|
-
p["apikey"] = API_KEY
|
|
60
|
-
r = requests.get(f"{BASE_URL}{path}", params=p, timeout=90)
|
|
61
|
-
r.raise_for_status()
|
|
62
|
-
return r.json()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@mcp.tool()
|
|
66
|
-
def list_exchanges() -> list:
|
|
67
|
-
"""List the exchanges DataSinking covers and their report counts.
|
|
68
|
-
|
|
69
|
-
Returns exchange codes (sse / szse / bj / ksc / koe / knx / jpx) with the number
|
|
70
|
-
of reports available per exchange. Call this first to discover coverage.
|
|
71
|
-
"""
|
|
72
|
-
return _get("/exchanges").get("exchanges", [])
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@mcp.tool()
|
|
76
|
-
def list_stocks(exchange: str, limit: int = 20) -> dict:
|
|
77
|
-
"""List stocks on an exchange, including the report count per company.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
exchange: Exchange code, e.g. sse / szse / bj / ksc / koe / knx / jpx
|
|
81
|
-
limit: Return the first N companies (default 20) to keep responses short.
|
|
82
|
-
"""
|
|
83
|
-
data = _get("/stocks", {"exchange": exchange})
|
|
84
|
-
return {"exchange": exchange, "total": data.get("total", 0), "items": data.get("items", [])[:limit]}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@mcp.tool()
|
|
88
|
-
def list_reports(symbol: str, doc_type: str = "annual", size: int = 10) -> dict:
|
|
89
|
-
"""List a company's reports — metadata only (id, title, period), no body text.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
symbol: FMP-style symbol, e.g. 600519.SS / 005930.KS / 7203.T
|
|
93
|
-
doc_type: annual / semiannual / q1 / q3
|
|
94
|
-
size: Number of reports to return (default 10).
|
|
95
|
-
"""
|
|
96
|
-
return _get("/documents", {"symbol": symbol, "doc_type": doc_type, "size": size})
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@mcp.tool()
|
|
100
|
-
def get_report(document_id: int) -> dict:
|
|
101
|
-
"""Fetch a single report's full text (metadata + Markdown body).
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
document_id: Report id, from list_reports items[].id
|
|
105
|
-
"""
|
|
106
|
-
return _get(f"/documents/{document_id}")
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
@mcp.tool()
|
|
110
|
-
def list_sections(document_id: int) -> dict:
|
|
111
|
-
"""List every section heading in a report (feed the headings to get_section).
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
document_id: Report id
|
|
115
|
-
"""
|
|
116
|
-
return _get(f"/documents/{document_id}/sections")
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
@mcp.tool()
|
|
120
|
-
def get_section(document_id: int, section: str) -> dict:
|
|
121
|
-
"""Fetch only one section of a report by keyword — cheaper than get_report for RAG.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
document_id: Report id
|
|
125
|
-
section: Heading keyword, e.g. "management discussion" / "MD&A" / "financial statements" / "notes"
|
|
126
|
-
"""
|
|
127
|
-
return _get(f"/documents/{document_id}", {"section": section})
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def main() -> None:
|
|
131
|
-
"""Entry point for the ``datasinking-mcp`` console script."""
|
|
132
|
-
mcp.run()
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if __name__ == "__main__":
|
|
136
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|