@youhaozhao/cninfo-mcp 1.2.0 → 1.4.0
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.
- package/README.md +67 -54
- package/bin/cninfo-mcp.js +5 -2
- package/package.json +18 -4
- package/python/check_deps.py +89 -0
- package/python/mcp_server.py +194 -268
- package/python/requirements.txt +1 -1
- package/python/spider.py +372 -260
- package/scripts/install-python-deps.js +5 -2
package/README.md
CHANGED
|
@@ -1,54 +1,67 @@
|
|
|
1
|
-
# cninfo-mcp
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@youhaozhao/cninfo-mcp)
|
|
4
|
-
|
|
5
|
-
通过 MCP
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
1
|
+
# cninfo-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@youhaozhao/cninfo-mcp)
|
|
4
|
+
|
|
5
|
+
通过 MCP 协议查询和下载巨潮资讯网上市公司定期报告及招股书 PDF 的工具,适用于 Claude Desktop / Claude Code。
|
|
6
|
+
|
|
7
|
+
## 并发限制
|
|
8
|
+
巨潮资讯网后端禁止大量并发,推荐将并发数设置为 4 以防止后端返回大量 403 导致 IP 短暂被封
|
|
9
|
+
|
|
10
|
+
## 使用方法
|
|
11
|
+
|
|
12
|
+
在 Claude Desktop / Claude Code 配置文件中添加:
|
|
13
|
+
|
|
14
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
15
|
+
|
|
16
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"cninfo": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "@youhaozhao/cninfo-mcp"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
重启 Claude Desktop 后即可使用。
|
|
30
|
+
|
|
31
|
+
## 可用工具
|
|
32
|
+
|
|
33
|
+
- **`query_annual_reports_tool`** — 查询报告列表,参数:股票代码(必填)、年份(可选)、报告类型(可选,默认 `annual`)
|
|
34
|
+
- **`download_annual_reports_tool`** — 下载报告 PDF,参数:股票代码(必填)、年份(可选)、保存路径(可选)、报告类型(可选,默认 `annual`)
|
|
35
|
+
|
|
36
|
+
支持的 `report_type`:
|
|
37
|
+
|
|
38
|
+
- `annual` — 年度报告 / 年报
|
|
39
|
+
- `semiannual` — 半年度报告 / 半年报 / 中报
|
|
40
|
+
- `q1` — 第一季度报告 / 一季报
|
|
41
|
+
- `q3` — 第三季度报告 / 三季报
|
|
42
|
+
- `prospectus` — 招股书 / 招股说明书 / 招股意向书(招股书无固定年份,省略年份参数即可)
|
|
43
|
+
|
|
44
|
+
示例对话:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
查询 000888 的 2024 年报
|
|
48
|
+
查询 000001 的 2024 半年报
|
|
49
|
+
查询 600519 的 2024 一季报
|
|
50
|
+
下载 300750 的 2023 三季报
|
|
51
|
+
下载 688777 的年报
|
|
52
|
+
查询 920185 的年报 # 北交所,新旧代码(如 835185)均可
|
|
53
|
+
查询 688777 的招股书
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 系统要求
|
|
57
|
+
|
|
58
|
+
- Node.js 18+
|
|
59
|
+
- Python 3.10+(Python 依赖会自动安装;需要 MCP Python SDK v2,旧环境会在下次启动时自动升级)
|
|
60
|
+
|
|
61
|
+
## 数据来源
|
|
62
|
+
|
|
63
|
+
[巨潮资讯网](https://www.cninfo.com.cn) — 支持沪深两市(主板、创业板、科创板)及北京证券交易所(北交所)
|
|
64
|
+
|
|
65
|
+
## Credits
|
|
66
|
+
|
|
67
|
+
爬虫逻辑基于 [gaodechen/cninfo_process](https://github.com/gaodechen/cninfo_process)。
|
package/bin/cninfo-mcp.js
CHANGED
|
@@ -73,6 +73,9 @@ async function ensureVenv(systemPythonCmd) {
|
|
|
73
73
|
return venvPython;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// 依赖探针:校验 venv 是否满足 requirements.txt 的全部约束
|
|
77
|
+
const DEPS_CHECK = path.join(__dirname, "..", "python", "check_deps.py");
|
|
78
|
+
|
|
76
79
|
// 检查并安装 Python 依赖(使用 venv 中的 python)
|
|
77
80
|
async function ensureDependencies(venvPython) {
|
|
78
81
|
const requirementsPath = PYTHON_REQUIREMENTS;
|
|
@@ -83,8 +86,8 @@ async function ensureDependencies(venvPython) {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
try {
|
|
86
|
-
//
|
|
87
|
-
await spawnAsync(venvPython, [
|
|
89
|
+
// 校验依赖是否满足约束(不满足时抛错,转入下方安装流程)
|
|
90
|
+
await spawnAsync(venvPython, [DEPS_CHECK]);
|
|
88
91
|
} catch (error) {
|
|
89
92
|
// 未安装,执行安装
|
|
90
93
|
console.error("Installing Python dependencies...");
|
package/package.json
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youhaozhao/cninfo-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP Server for querying and downloading Chinese listed companies'
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "MCP Server for querying and downloading Chinese listed companies' periodic reports from CNINFO (巨潮资讯网)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
7
7
|
"mcp-server",
|
|
8
8
|
"cninfo",
|
|
9
9
|
"chinese-stock",
|
|
10
10
|
"annual-reports",
|
|
11
|
+
"quarterly-reports",
|
|
12
|
+
"semiannual-reports",
|
|
13
|
+
"periodic-reports",
|
|
11
14
|
"finance",
|
|
12
|
-
"python"
|
|
15
|
+
"python",
|
|
16
|
+
"巨潮",
|
|
17
|
+
"巨潮资讯",
|
|
18
|
+
"定期报告",
|
|
19
|
+
"年报",
|
|
20
|
+
"半年报",
|
|
21
|
+
"季报",
|
|
22
|
+
"招股书",
|
|
23
|
+
"A股",
|
|
24
|
+
"上市公司",
|
|
25
|
+
"财报"
|
|
13
26
|
],
|
|
14
27
|
"homepage": "https://github.com/youhaozhao/cninfo-mcp#readme",
|
|
15
28
|
"repository": {
|
|
@@ -27,7 +40,7 @@
|
|
|
27
40
|
"postinstall": "node scripts/install-python-deps.js",
|
|
28
41
|
"start": "node bin/cninfo-mcp.js",
|
|
29
42
|
"dev": "node bin/cninfo-mcp.js",
|
|
30
|
-
"test": "
|
|
43
|
+
"test": "python3 -m pytest python/test_spider.py -q"
|
|
31
44
|
},
|
|
32
45
|
"dependencies": {
|
|
33
46
|
"spawn-please": "^2.0.2"
|
|
@@ -46,6 +59,7 @@
|
|
|
46
59
|
"files": [
|
|
47
60
|
"bin/",
|
|
48
61
|
"python/mcp_server.py",
|
|
62
|
+
"python/check_deps.py",
|
|
49
63
|
"python/spider.py",
|
|
50
64
|
"python/requirements.txt",
|
|
51
65
|
"scripts/install-python-deps.js",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""校验当前解释器是否满足 requirements.txt 的全部约束。
|
|
3
|
+
|
|
4
|
+
退出码 0 表示全部满足,调用方可以跳过 pip 安装;非 0 表示需要执行
|
|
5
|
+
`pip install -r requirements.txt`。
|
|
6
|
+
|
|
7
|
+
这个脚本只是一个优化——用来省掉一次很慢的 pip 调用,pip 自身才是约束的
|
|
8
|
+
最终权威。因此所有无法确定的情况(依赖缺失、版本不符、约束解析失败、
|
|
9
|
+
连比较版本号的库都找不到)一律返回非 0,让 pip 去做真正的判断。宁可多跑
|
|
10
|
+
一次 pip,也不能放行一个不满足约束的环境。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
16
|
+
|
|
17
|
+
REQUIREMENTS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "requirements.txt")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _load_requirement_parser():
|
|
21
|
+
"""返回 packaging 的 Requirement 类,找不到时返回 None。
|
|
22
|
+
|
|
23
|
+
packaging 不是本项目的直接依赖,全新 venv 里通常不存在,因此回退到 pip
|
|
24
|
+
自带的 vendored 副本。部分发行版(如 Debian)会去除 vendoring,那时两者
|
|
25
|
+
都不可用,由调用方按“未满足”处理。
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
from packaging.requirements import Requirement
|
|
29
|
+
|
|
30
|
+
return Requirement
|
|
31
|
+
except ImportError:
|
|
32
|
+
pass
|
|
33
|
+
try:
|
|
34
|
+
from pip._vendor.packaging.requirements import Requirement
|
|
35
|
+
|
|
36
|
+
return Requirement
|
|
37
|
+
except ImportError:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def unsatisfied(requirements_path=None):
|
|
42
|
+
"""返回未满足的约束列表;无法判断时抛出异常由调用方兜底。"""
|
|
43
|
+
# 默认值在调用时解析,而不是在 def 时绑定,便于测试替换 REQUIREMENTS
|
|
44
|
+
if requirements_path is None:
|
|
45
|
+
requirements_path = REQUIREMENTS
|
|
46
|
+
|
|
47
|
+
Requirement = _load_requirement_parser()
|
|
48
|
+
if Requirement is None:
|
|
49
|
+
raise RuntimeError("packaging is unavailable; cannot verify requirements")
|
|
50
|
+
|
|
51
|
+
missing = []
|
|
52
|
+
with open(requirements_path, encoding="utf-8") as fh:
|
|
53
|
+
for raw in fh:
|
|
54
|
+
line = raw.split("#", 1)[0].strip()
|
|
55
|
+
# 跳过空行,以及 -r/-e/--index-url 这类非约束指令
|
|
56
|
+
if not line or line.startswith("-"):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
req = Requirement(line)
|
|
60
|
+
try:
|
|
61
|
+
installed = version(req.name)
|
|
62
|
+
except PackageNotFoundError:
|
|
63
|
+
missing.append(f"{req.name} (not installed)")
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
# prereleases=True:已装的预发布版本若落在区间内也算满足,
|
|
67
|
+
# 避免把 pip 已经接受的环境反复重装。
|
|
68
|
+
if not req.specifier.contains(installed, prereleases=True):
|
|
69
|
+
missing.append(f"{req.name} {installed} does not satisfy {req.specifier}")
|
|
70
|
+
|
|
71
|
+
return missing
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main():
|
|
75
|
+
try:
|
|
76
|
+
missing = unsatisfied()
|
|
77
|
+
except Exception as exc: # 解析失败、文件缺失等一律按“需要安装”处理
|
|
78
|
+
print(f"dependency check inconclusive: {exc}", file=sys.stderr)
|
|
79
|
+
return 1
|
|
80
|
+
|
|
81
|
+
if missing:
|
|
82
|
+
for item in missing:
|
|
83
|
+
print(f"dependency not satisfied: {item}", file=sys.stderr)
|
|
84
|
+
return 1
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
raise SystemExit(main())
|