@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/python/mcp_server.py
CHANGED
|
@@ -1,268 +1,194 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
巨潮资讯 MCP 服务器
|
|
4
|
-
用于查询和下载 A
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
saving_path,
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
"reports": [],
|
|
196
|
-
"error": str(e),
|
|
197
|
-
"message": f"Error querying prospectus: {str(e)}",
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@mcp.tool()
|
|
202
|
-
def download_prospectus_tool(stock_code: str, save_path: Optional[str] = None) -> dict:
|
|
203
|
-
"""
|
|
204
|
-
Download prospectus documents for a Chinese listed company
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
stock_code: Stock code (e.g., '000888' for 峨眉山, '688777' for 中科德芯)
|
|
208
|
-
save_path: Optional directory to save files (e.g., '/Users/me/reports'). Defaults to pdf/ in package directory
|
|
209
|
-
|
|
210
|
-
Returns:
|
|
211
|
-
Dictionary containing:
|
|
212
|
-
- success: Boolean indicating if download was successful
|
|
213
|
-
- stock_code: The stock code
|
|
214
|
-
- downloaded: Number of files downloaded
|
|
215
|
-
- path: Directory where files were saved
|
|
216
|
-
- message: Status message
|
|
217
|
-
"""
|
|
218
|
-
try:
|
|
219
|
-
output_dir = save_path or saving_path
|
|
220
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
221
|
-
|
|
222
|
-
result = download_prospectus(stock_code, save_path=output_dir)
|
|
223
|
-
result["stock_code"] = stock_code
|
|
224
|
-
|
|
225
|
-
return result
|
|
226
|
-
|
|
227
|
-
except Exception as e:
|
|
228
|
-
return {
|
|
229
|
-
"success": False,
|
|
230
|
-
"stock_code": stock_code,
|
|
231
|
-
"downloaded": 0,
|
|
232
|
-
"path": save_path or saving_path,
|
|
233
|
-
"error": str(e),
|
|
234
|
-
"message": f"Error downloading prospectus: {str(e)}",
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
@mcp.resource("annual-reports-list://{stock_code}")
|
|
239
|
-
def get_annual_reports_list(stock_code: str) -> str:
|
|
240
|
-
"""返回指定股票代码的年度报告格式化列表"""
|
|
241
|
-
try:
|
|
242
|
-
reports = query_annual_reports(stock_code)
|
|
243
|
-
|
|
244
|
-
if not reports:
|
|
245
|
-
return f"No annual reports found for stock {stock_code}"
|
|
246
|
-
|
|
247
|
-
output = [f"Annual Reports for {stock_code}:", "=" * 60]
|
|
248
|
-
|
|
249
|
-
for report in reports:
|
|
250
|
-
title = report.get("announcementTitle", "N/A")
|
|
251
|
-
time = report.get("announcementTime", "N/A")
|
|
252
|
-
name = report.get("secName", "N/A")
|
|
253
|
-
output.append(f"\n📄 {title}")
|
|
254
|
-
output.append(f" Company: {name}")
|
|
255
|
-
output.append(f" Date: {time}")
|
|
256
|
-
|
|
257
|
-
output.append("\n" + "=" * 60)
|
|
258
|
-
output.append(f"Total: {len(reports)} report(s)")
|
|
259
|
-
|
|
260
|
-
return "\n".join(output)
|
|
261
|
-
|
|
262
|
-
except Exception as e:
|
|
263
|
-
return f"Error retrieving annual reports: {str(e)}"
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if __name__ == "__main__":
|
|
267
|
-
# 以 stdio 方式运行服务器
|
|
268
|
-
mcp.run()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
巨潮资讯 MCP 服务器
|
|
4
|
+
用于查询和下载 A 股定期报告、招股书的 MCP 工具服务
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
# 将当前目录加入模块搜索路径
|
|
13
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
14
|
+
|
|
15
|
+
from mcp.server.mcpserver import MCPServer
|
|
16
|
+
from spider import (
|
|
17
|
+
query_reports,
|
|
18
|
+
download_reports,
|
|
19
|
+
format_reports,
|
|
20
|
+
saving_path,
|
|
21
|
+
supported_report_types,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def _package_version(default: str = "0.0.0") -> str:
|
|
25
|
+
"""从 package.json 读取版本,避免与 npm 包版本各写一份而漂移。
|
|
26
|
+
|
|
27
|
+
package.json 始终随 npm 包发布(不受 files 白名单影响),因此在已安装的
|
|
28
|
+
包里也能读到。读取失败时退回 default,不让版本号问题挡住服务器启动。
|
|
29
|
+
"""
|
|
30
|
+
manifest = os.path.join(
|
|
31
|
+
os.path.dirname(os.path.abspath(__file__)), "..", "package.json"
|
|
32
|
+
)
|
|
33
|
+
try:
|
|
34
|
+
with open(manifest, encoding="utf-8") as fh:
|
|
35
|
+
return json.load(fh).get("version") or default
|
|
36
|
+
except (OSError, ValueError):
|
|
37
|
+
return default
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# 创建 MCP 服务器实例
|
|
41
|
+
mcp = MCPServer(
|
|
42
|
+
name="cninfo-server",
|
|
43
|
+
instructions="CNINFO reports server - Query and download Chinese listed companies' periodic reports from cninfo.com.cn",
|
|
44
|
+
version=_package_version(),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _supported_report_types_text() -> str:
|
|
49
|
+
return ", ".join(supported_report_types().keys())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@mcp.tool()
|
|
53
|
+
def query_annual_reports_tool(
|
|
54
|
+
stock_code: str, year: Optional[int] = None, report_type: str = "annual"
|
|
55
|
+
) -> dict:
|
|
56
|
+
"""
|
|
57
|
+
Query periodic reports for a Chinese listed company.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
stock_code: Stock code (e.g., '000888' for 峨眉山, '688777' for 中科德芯)
|
|
61
|
+
year: Optional year to filter (e.g., 2024). If not provided, returns all available years
|
|
62
|
+
report_type: Optional report type. Supported values: annual, semiannual, q1, q3, prospectus. Defaults to annual for backward compatibility.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dictionary containing:
|
|
66
|
+
- success: Boolean indicating if the query was successful
|
|
67
|
+
- stock_code: The queried stock code
|
|
68
|
+
- report_type: The requested report type
|
|
69
|
+
- year: The filtered year (if any)
|
|
70
|
+
- count: Number of reports found
|
|
71
|
+
- reports: List of report details (announcementTitle, announcementTime, secCode, secName, adjunctUrl)
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
reports = query_reports(stock_code, report_type, year)
|
|
75
|
+
|
|
76
|
+
if not reports:
|
|
77
|
+
return {
|
|
78
|
+
"success": False,
|
|
79
|
+
"stock_code": stock_code,
|
|
80
|
+
"report_type": report_type,
|
|
81
|
+
"year": year,
|
|
82
|
+
"count": 0,
|
|
83
|
+
"reports": [],
|
|
84
|
+
"message": f"No {report_type} reports found for stock {stock_code}"
|
|
85
|
+
+ (f" in year {year}" if year else ""),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"success": True,
|
|
90
|
+
"stock_code": stock_code,
|
|
91
|
+
"report_type": report_type,
|
|
92
|
+
"year": year,
|
|
93
|
+
"count": len(reports),
|
|
94
|
+
"reports": format_reports(reports),
|
|
95
|
+
"message": f"Found {len(reports)} {report_type} report(s)"
|
|
96
|
+
+ (f" for year {year}" if year else ""),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
return {
|
|
101
|
+
"success": False,
|
|
102
|
+
"stock_code": stock_code,
|
|
103
|
+
"report_type": report_type,
|
|
104
|
+
"year": year,
|
|
105
|
+
"count": 0,
|
|
106
|
+
"reports": [],
|
|
107
|
+
"error": str(e),
|
|
108
|
+
"message": f"Error querying reports: {str(e)}. Supported report_type values: {_supported_report_types_text()}",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@mcp.tool()
|
|
113
|
+
def download_annual_reports_tool(
|
|
114
|
+
stock_code: str,
|
|
115
|
+
year: Optional[int] = None,
|
|
116
|
+
save_path: Optional[str] = None,
|
|
117
|
+
report_type: str = "annual",
|
|
118
|
+
) -> dict:
|
|
119
|
+
"""
|
|
120
|
+
Download periodic reports for a Chinese listed company.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
stock_code: Stock code (e.g., '000888' for 峨眉山, '688777' for 中科德芯)
|
|
124
|
+
year: Optional year to filter (e.g., 2024). If not provided, downloads all available years
|
|
125
|
+
save_path: Optional directory to save files (e.g., '/Users/me/reports'). Defaults to pdf/ in package directory
|
|
126
|
+
report_type: Optional report type. Supported values: annual, semiannual, q1, q3, prospectus. Defaults to annual for backward compatibility.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Dictionary containing:
|
|
130
|
+
- success: Boolean indicating if download was successful
|
|
131
|
+
- stock_code: The stock code
|
|
132
|
+
- report_type: The requested report type
|
|
133
|
+
- year: The filtered year (if any)
|
|
134
|
+
- downloaded: Number of files downloaded
|
|
135
|
+
- path: Directory where files were saved
|
|
136
|
+
- message: Status message
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
output_dir = save_path or saving_path
|
|
140
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
141
|
+
|
|
142
|
+
result = download_reports(
|
|
143
|
+
stock_code, report_type, year=year, save_path=output_dir
|
|
144
|
+
)
|
|
145
|
+
result["stock_code"] = stock_code
|
|
146
|
+
result["report_type"] = report_type
|
|
147
|
+
result["year"] = year
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
return {
|
|
153
|
+
"success": False,
|
|
154
|
+
"stock_code": stock_code,
|
|
155
|
+
"report_type": report_type,
|
|
156
|
+
"year": year,
|
|
157
|
+
"downloaded": 0,
|
|
158
|
+
"path": save_path or saving_path,
|
|
159
|
+
"error": str(e),
|
|
160
|
+
"message": f"Error downloading reports: {str(e)}. Supported report_type values: {_supported_report_types_text()}",
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@mcp.resource("annual-reports-list://{stock_code}")
|
|
165
|
+
def get_annual_reports_list(stock_code: str) -> str:
|
|
166
|
+
"""返回指定股票代码的年度报告格式化列表"""
|
|
167
|
+
try:
|
|
168
|
+
reports = query_reports(stock_code, "annual")
|
|
169
|
+
|
|
170
|
+
if not reports:
|
|
171
|
+
return f"No annual reports found for stock {stock_code}"
|
|
172
|
+
|
|
173
|
+
output = [f"Annual Reports for {stock_code}:", "=" * 60]
|
|
174
|
+
|
|
175
|
+
for report in reports:
|
|
176
|
+
title = report.get("announcementTitle", "N/A")
|
|
177
|
+
time = report.get("announcementTime", "N/A")
|
|
178
|
+
name = report.get("secName", "N/A")
|
|
179
|
+
output.append(f"\n📄 {title}")
|
|
180
|
+
output.append(f" Company: {name}")
|
|
181
|
+
output.append(f" Date: {time}")
|
|
182
|
+
|
|
183
|
+
output.append("\n" + "=" * 60)
|
|
184
|
+
output.append(f"Total: {len(reports)} report(s)")
|
|
185
|
+
|
|
186
|
+
return "\n".join(output)
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
return f"Error retrieving annual reports: {str(e)}"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
# 以 stdio 方式运行服务器
|
|
194
|
+
mcp.run()
|
package/python/requirements.txt
CHANGED