datasinking 0.2.6__tar.gz → 0.2.8__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.6 → datasinking-0.2.8}/PKG-INFO +3 -2
- {datasinking-0.2.6 → datasinking-0.2.8}/README.md +1 -1
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking/__init__.py +1 -1
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking/_version.py +11 -11
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking/client.py +9 -10
- datasinking-0.2.8/datasinking/mcp_server.py +165 -0
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking.egg-info/PKG-INFO +3 -2
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking.egg-info/requires.txt +1 -0
- {datasinking-0.2.6 → datasinking-0.2.8}/pyproject.toml +1 -1
- datasinking-0.2.6/datasinking/mcp_server.py +0 -134
- {datasinking-0.2.6 → datasinking-0.2.8}/LICENSE +0 -0
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking.egg-info/SOURCES.txt +0 -0
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking.egg-info/dependency_links.txt +0 -0
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking.egg-info/entry_points.txt +0 -0
- {datasinking-0.2.6 → datasinking-0.2.8}/datasinking.egg-info/top_level.txt +0 -0
- {datasinking-0.2.6 → datasinking-0.2.8}/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.8
|
|
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
|
|
@@ -111,7 +112,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
111
112
|
|
|
112
113
|
Every example pulls from the live API and runs as-is.
|
|
113
114
|
|
|
114
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
115
|
+
> `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
116
|
|
|
116
117
|
## Research (`research/`)
|
|
117
118
|
|
|
@@ -89,7 +89,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
89
89
|
|
|
90
90
|
Every example pulls from the live API and runs as-is.
|
|
91
91
|
|
|
92
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
92
|
+
> `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
93
|
|
|
94
94
|
## Research (`research/`)
|
|
95
95
|
|
|
@@ -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
|
|
@@ -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.8"
|
|
@@ -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,165 @@
|
|
|
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 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 heading in a report (feed the headings to get_section).
|
|
131
|
+
|
|
132
|
+
Call this before get_section to see the exact headings — the headings are in the
|
|
133
|
+
report's own language.
|
|
134
|
+
"""
|
|
135
|
+
return _get(f"/documents/{document_id}/sections")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@mcp.tool()
|
|
139
|
+
def get_section(
|
|
140
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
141
|
+
section: Annotated[
|
|
142
|
+
str,
|
|
143
|
+
Field(
|
|
144
|
+
description=(
|
|
145
|
+
"Heading keyword, matched as a substring against the report's OWN headings, "
|
|
146
|
+
"so pass it in the report's language. A-share reports have Chinese headings "
|
|
147
|
+
"(e.g. 第三节管理层讨论与分析) — use 管理层讨论与分析 / 财务报告 there. "
|
|
148
|
+
"For English-language filings, \"MD&A\" / \"financial statements\" / \"notes\" work. "
|
|
149
|
+
"If nothing matches, the API returns 404 with the real headings — retry with one "
|
|
150
|
+
"of those, or call list_sections first."
|
|
151
|
+
)
|
|
152
|
+
),
|
|
153
|
+
],
|
|
154
|
+
) -> dict:
|
|
155
|
+
"""Fetch only one section of a report by keyword — cheaper than get_report for RAG."""
|
|
156
|
+
return _get(f"/documents/{document_id}", {"section": section})
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def main() -> None:
|
|
160
|
+
"""Entry point for the ``datasinking-mcp`` console script."""
|
|
161
|
+
mcp.run()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasinking
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
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
|
|
@@ -111,7 +112,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
111
112
|
|
|
112
113
|
Every example pulls from the live API and runs as-is.
|
|
113
114
|
|
|
114
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
115
|
+
> `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
116
|
|
|
116
117
|
## Research (`research/`)
|
|
117
118
|
|
|
@@ -1,134 +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
|
-
# mcp v1 uses FastMCP; v2 renamed it to MCPServer. Support both.
|
|
27
|
-
try:
|
|
28
|
-
from mcp.server.fastmcp import FastMCP # mcp v1
|
|
29
|
-
except ImportError: # pragma: no cover
|
|
30
|
-
from mcp.server.mcpserver import MCPServer as FastMCP # mcp v2
|
|
31
|
-
|
|
32
|
-
BASE_URL = "https://api.datasink.ing"
|
|
33
|
-
API_KEY = os.environ.get("DATASINK_API_KEY", "")
|
|
34
|
-
|
|
35
|
-
mcp = FastMCP(
|
|
36
|
-
"DataSinking",
|
|
37
|
-
title="DataSinking — Full-text Asian Financial Reports",
|
|
38
|
-
description="Full-text Asian financial reports (China, Korea, Japan) as clean Markdown via API, with chapter-level access for RAG and AI agents.",
|
|
39
|
-
version="0.2.3",
|
|
40
|
-
instructions=(
|
|
41
|
-
"DataSinking serves full-text financial reports (annual / semi-annual / quarterly) "
|
|
42
|
-
"from China, Korea and Japan as clean Markdown, ready for LLM reading and RAG. "
|
|
43
|
-
"Use FMP-style symbols: 600519.SS (Kweichow Moutai), 005930.KS (Samsung Electronics), "
|
|
44
|
-
"7203.T (Toyota). To save tokens, prefer get_section to pull one chapter (e.g. MD&A) "
|
|
45
|
-
"instead of get_report for the whole document."
|
|
46
|
-
),
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _get(path: str, params: Optional[dict] = None) -> dict:
|
|
51
|
-
"""Call the DataSinking API, carrying the API key automatically."""
|
|
52
|
-
if not API_KEY:
|
|
53
|
-
raise RuntimeError(
|
|
54
|
-
"Missing DATASINK_API_KEY environment variable (get a free key at https://datasink.ing)"
|
|
55
|
-
)
|
|
56
|
-
p = dict(params or {})
|
|
57
|
-
p["apikey"] = API_KEY
|
|
58
|
-
r = requests.get(f"{BASE_URL}{path}", params=p, timeout=90)
|
|
59
|
-
r.raise_for_status()
|
|
60
|
-
return r.json()
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@mcp.tool()
|
|
64
|
-
def list_exchanges() -> list:
|
|
65
|
-
"""List the exchanges DataSinking covers and their report counts.
|
|
66
|
-
|
|
67
|
-
Returns exchange codes (sse / szse / bj / ksc / koe / knx / jpx) with the number
|
|
68
|
-
of reports available per exchange. Call this first to discover coverage.
|
|
69
|
-
"""
|
|
70
|
-
return _get("/exchanges").get("exchanges", [])
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@mcp.tool()
|
|
74
|
-
def list_stocks(exchange: str, limit: int = 20) -> dict:
|
|
75
|
-
"""List stocks on an exchange, including the report count per company.
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
exchange: Exchange code, e.g. sse / szse / bj / ksc / koe / knx / jpx
|
|
79
|
-
limit: Return the first N companies (default 20) to keep responses short.
|
|
80
|
-
"""
|
|
81
|
-
data = _get("/stocks", {"exchange": exchange})
|
|
82
|
-
return {"exchange": exchange, "total": data.get("total", 0), "items": data.get("items", [])[:limit]}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@mcp.tool()
|
|
86
|
-
def list_reports(symbol: str, doc_type: str = "annual", size: int = 10) -> dict:
|
|
87
|
-
"""List a company's reports — metadata only (id, title, period), no body text.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
symbol: FMP-style symbol, e.g. 600519.SS / 005930.KS / 7203.T
|
|
91
|
-
doc_type: annual / semiannual / q1 / q3
|
|
92
|
-
size: Number of reports to return (default 10).
|
|
93
|
-
"""
|
|
94
|
-
return _get("/documents", {"symbol": symbol, "doc_type": doc_type, "size": size})
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@mcp.tool()
|
|
98
|
-
def get_report(document_id: int) -> dict:
|
|
99
|
-
"""Fetch a single report's full text (metadata + Markdown body).
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
document_id: Report id, from list_reports items[].id
|
|
103
|
-
"""
|
|
104
|
-
return _get(f"/documents/{document_id}")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@mcp.tool()
|
|
108
|
-
def list_sections(document_id: int) -> dict:
|
|
109
|
-
"""List every section heading in a report (feed the headings to get_section).
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
document_id: Report id
|
|
113
|
-
"""
|
|
114
|
-
return _get(f"/documents/{document_id}/sections")
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
@mcp.tool()
|
|
118
|
-
def get_section(document_id: int, section: str) -> dict:
|
|
119
|
-
"""Fetch only one section of a report by keyword — cheaper than get_report for RAG.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
document_id: Report id
|
|
123
|
-
section: Heading keyword, e.g. "management discussion" / "MD&A" / "financial statements" / "notes"
|
|
124
|
-
"""
|
|
125
|
-
return _get(f"/documents/{document_id}", {"section": section})
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def main() -> None:
|
|
129
|
-
"""Entry point for the ``datasinking-mcp`` console script."""
|
|
130
|
-
mcp.run()
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if __name__ == "__main__":
|
|
134
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|