kronos-mcp 0.1.0__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.
- kronos_mcp-0.1.0/Dockerfile +16 -0
- kronos_mcp-0.1.0/PKG-INFO +156 -0
- kronos_mcp-0.1.0/README.md +131 -0
- kronos_mcp-0.1.0/kronos_mcp/__init__.py +5 -0
- kronos_mcp-0.1.0/kronos_mcp/server.py +306 -0
- kronos_mcp-0.1.0/pyproject.toml +43 -0
- kronos_mcp-0.1.0/requirements.txt +3 -0
- kronos_mcp-0.1.0/server.json +42 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# kronos-mcp · 远程传输形态(常驻服务 · 多客户端连)
|
|
2
|
+
#
|
|
3
|
+
# 只想在 Claude Desktop / Cursor 里用的话**不需要这个镜像** ——
|
|
4
|
+
# 那边走 stdio,`uvx kronos-mcp` 就够了。这个镜像是给「跑成一个服务」的场景。
|
|
5
|
+
FROM python:3.11-slim
|
|
6
|
+
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
COPY requirements.txt .
|
|
9
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
10
|
+
COPY kronos_mcp/ ./kronos_mcp/
|
|
11
|
+
|
|
12
|
+
ENV KRONOS_MCP_TRANSPORT=streamable-http KRONOS_MCP_HOST=0.0.0.0 KRONOS_MCP_PORT=8932
|
|
13
|
+
|
|
14
|
+
# KRONOS_API_KEY 必须在 run 的时候传进来 —— 不要 build 进镜像
|
|
15
|
+
EXPOSE 8932
|
|
16
|
+
CMD ["python", "-m", "kronos_mcp.server"]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kronos-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Kronos stock price forecasting — A-shares, US and HK equities
|
|
5
|
+
Project-URL: Homepage, https://github.com/agentpit-io/hunter-community
|
|
6
|
+
Project-URL: Repository, https://github.com/agentpit-io/hunter-community
|
|
7
|
+
Project-URL: Documentation, https://github.com/agentpit-io/hunter-community/blob/main/tools/kronos-mcp/README.md
|
|
8
|
+
Project-URL: Issues, https://github.com/agentpit-io/hunter-community/issues
|
|
9
|
+
Author-email: AgentPit <hangeaiagent@gmail.com>
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: a-share,finance,forecast,kronos,mcp,model-context-protocol,quant,stock,timeseries
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: mcp>=2.0.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# kronos-mcp
|
|
27
|
+
|
|
28
|
+
<!-- mcp-name: io.github.agentpit-io/kronos-mcp -->
|
|
29
|
+
|
|
30
|
+
**Stock price forecasting for your AI agent — A-shares, US and HK equities.**
|
|
31
|
+
|
|
32
|
+
An [MCP](https://modelcontextprotocol.io) server wrapping **Kronos**, a K-line
|
|
33
|
+
time-series model. Ask it for a symbol, get the next N daily candles predicted:
|
|
34
|
+
open / high / low / close / volume.
|
|
35
|
+
|
|
36
|
+
[](https://github.com/agentpit-io/hunter-community/blob/main/LICENSE)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Tools
|
|
41
|
+
|
|
42
|
+
| Tool | What it does |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `kronos_health()` | Check the service is reachable and your key works. **Call this first when something fails.** |
|
|
45
|
+
| `kronos_predict(symbol, pred_len)` | Predict the next `pred_len` daily candles (1–30, default 10) |
|
|
46
|
+
|
|
47
|
+
`kronos_predict` also returns `expected_return` — last predicted close over last
|
|
48
|
+
real close, minus one. That ratio is the number most people actually want.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
You need an API key first — see [below](#api-key).
|
|
55
|
+
|
|
56
|
+
### Claude Desktop / Cursor
|
|
57
|
+
|
|
58
|
+
Add to `claude_desktop_config.json`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"kronos": {
|
|
64
|
+
"command": "uvx",
|
|
65
|
+
"args": ["kronos-mcp"],
|
|
66
|
+
"env": { "KRONOS_API_KEY": "oapk_xxx" }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Command line
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# no install needed
|
|
76
|
+
KRONOS_API_KEY=oapk_xxx uvx kronos-mcp
|
|
77
|
+
|
|
78
|
+
# or install it
|
|
79
|
+
pip install kronos-mcp
|
|
80
|
+
KRONOS_API_KEY=oapk_xxx kronos-mcp
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## API key
|
|
86
|
+
|
|
87
|
+
**This server has no keyless mode, and cannot have one** — the upstream returns
|
|
88
|
+
401 without a key.
|
|
89
|
+
|
|
90
|
+
The reason is cost, not gatekeeping: Kronos runs on our own GPUs and a single
|
|
91
|
+
inference takes **30–70 seconds**. Open anonymous access would let a handful of
|
|
92
|
+
loops saturate the service, with no way to tell who is doing it.
|
|
93
|
+
|
|
94
|
+
Get one:
|
|
95
|
+
|
|
96
|
+
1. Open <https://hunter.agentpit.io/dev/api-keys> and sign in
|
|
97
|
+
2. Click **申请 API Key** and choose API type **KRONOS**
|
|
98
|
+
3. Copy the `oapk_…` key — **it is shown once**
|
|
99
|
+
4. Set it as `KRONOS_API_KEY`
|
|
100
|
+
|
|
101
|
+
Approval usually lands within hours on a business day. Free, with a default
|
|
102
|
+
quota of 1000 calls per key.
|
|
103
|
+
|
|
104
|
+
If the key is missing, this server does **not** return sample data or a fake
|
|
105
|
+
success. It returns an error that tells you exactly where to get a key.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Environment variables
|
|
110
|
+
|
|
111
|
+
| Variable | Default | Notes |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `KRONOS_API_KEY` | — | **Required.** `oapk_` key of type KRONOS |
|
|
114
|
+
| `KRONOS_URL` | `https://kronos.agentpit.io` | Point this at your own deployment if you self-host |
|
|
115
|
+
| `KRONOS_TIMEOUT` | `180` | Seconds. GPU inference is 30–70s — don't set this tight |
|
|
116
|
+
| `KRONOS_MCP_TRANSPORT` | `stdio` | `stdio` \| `streamable-http` \| `sse` |
|
|
117
|
+
| `KRONOS_MCP_HOST` / `KRONOS_MCP_PORT` | `0.0.0.0` / `8932` | Remote transports only |
|
|
118
|
+
|
|
119
|
+
`stdio` is what Claude Desktop, Cursor and `uvx` use. Run a remote transport
|
|
120
|
+
only if you want one long-running server serving several clients — and note the
|
|
121
|
+
MCP spec marks SSE as deprecated, so prefer `streamable-http` for new setups.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## What to expect
|
|
126
|
+
|
|
127
|
+
**Every call takes 30–70 seconds.** That is the GPU, not a hang. Don't retry
|
|
128
|
+
because it feels slow, and don't loop it over dozens of symbols — upstream takes
|
|
129
|
+
one symbol per request.
|
|
130
|
+
|
|
131
|
+
**This is a statistical extrapolation, not investment advice.** The model reads
|
|
132
|
+
price history and nothing else: no news, no halts, no earnings dates. It will
|
|
133
|
+
drift badly in unusual conditions. Treat the output as one input among many.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Errors you might hit
|
|
138
|
+
|
|
139
|
+
| Error | Meaning |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `missing_api_key` | `KRONOS_API_KEY` not set — see [API key](#api-key) |
|
|
142
|
+
| `invalid_api_key` (401) | Key wrong, incomplete or revoked. Revocation takes up to 5 min to propagate |
|
|
143
|
+
| `wrong_key_type` (403) | Key is valid but isn't a **KRONOS** key — you may have applied for KPRED or FIN_R1 |
|
|
144
|
+
| `symbol_not_found` (404) | A-shares `600519` / `600519.SH`, US tickers `AAPL`, HK `00700` |
|
|
145
|
+
| `rate_limited` (429) | Per-IP cap on invalid keys, or your quota is exhausted |
|
|
146
|
+
| `upstream_down` (502/504) | GPU service restarting. If it lasts more than a few minutes, please open an issue |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Related
|
|
151
|
+
|
|
152
|
+
- **[truesource-mcp](https://github.com/agentpit-io/hunter-community/tree/main/tools/truesource-mcp)** — verifiable first-hand market signals: filings, procurement wins, macro data
|
|
153
|
+
- **[akshare-mcp](https://github.com/agentpit-io/hunter-community/tree/main/tools/akshare-mcp)** — lets a model explore AKShare's 1000+ China market data functions on its own
|
|
154
|
+
- **[hunter-community](https://github.com/agentpit-io/hunter-community)** — the open-source stack these come from
|
|
155
|
+
|
|
156
|
+
Apache-2.0.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# kronos-mcp
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.agentpit-io/kronos-mcp -->
|
|
4
|
+
|
|
5
|
+
**Stock price forecasting for your AI agent — A-shares, US and HK equities.**
|
|
6
|
+
|
|
7
|
+
An [MCP](https://modelcontextprotocol.io) server wrapping **Kronos**, a K-line
|
|
8
|
+
time-series model. Ask it for a symbol, get the next N daily candles predicted:
|
|
9
|
+
open / high / low / close / volume.
|
|
10
|
+
|
|
11
|
+
[](https://github.com/agentpit-io/hunter-community/blob/main/LICENSE)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Tools
|
|
16
|
+
|
|
17
|
+
| Tool | What it does |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `kronos_health()` | Check the service is reachable and your key works. **Call this first when something fails.** |
|
|
20
|
+
| `kronos_predict(symbol, pred_len)` | Predict the next `pred_len` daily candles (1–30, default 10) |
|
|
21
|
+
|
|
22
|
+
`kronos_predict` also returns `expected_return` — last predicted close over last
|
|
23
|
+
real close, minus one. That ratio is the number most people actually want.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
You need an API key first — see [below](#api-key).
|
|
30
|
+
|
|
31
|
+
### Claude Desktop / Cursor
|
|
32
|
+
|
|
33
|
+
Add to `claude_desktop_config.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"kronos": {
|
|
39
|
+
"command": "uvx",
|
|
40
|
+
"args": ["kronos-mcp"],
|
|
41
|
+
"env": { "KRONOS_API_KEY": "oapk_xxx" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Command line
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# no install needed
|
|
51
|
+
KRONOS_API_KEY=oapk_xxx uvx kronos-mcp
|
|
52
|
+
|
|
53
|
+
# or install it
|
|
54
|
+
pip install kronos-mcp
|
|
55
|
+
KRONOS_API_KEY=oapk_xxx kronos-mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## API key
|
|
61
|
+
|
|
62
|
+
**This server has no keyless mode, and cannot have one** — the upstream returns
|
|
63
|
+
401 without a key.
|
|
64
|
+
|
|
65
|
+
The reason is cost, not gatekeeping: Kronos runs on our own GPUs and a single
|
|
66
|
+
inference takes **30–70 seconds**. Open anonymous access would let a handful of
|
|
67
|
+
loops saturate the service, with no way to tell who is doing it.
|
|
68
|
+
|
|
69
|
+
Get one:
|
|
70
|
+
|
|
71
|
+
1. Open <https://hunter.agentpit.io/dev/api-keys> and sign in
|
|
72
|
+
2. Click **申请 API Key** and choose API type **KRONOS**
|
|
73
|
+
3. Copy the `oapk_…` key — **it is shown once**
|
|
74
|
+
4. Set it as `KRONOS_API_KEY`
|
|
75
|
+
|
|
76
|
+
Approval usually lands within hours on a business day. Free, with a default
|
|
77
|
+
quota of 1000 calls per key.
|
|
78
|
+
|
|
79
|
+
If the key is missing, this server does **not** return sample data or a fake
|
|
80
|
+
success. It returns an error that tells you exactly where to get a key.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Environment variables
|
|
85
|
+
|
|
86
|
+
| Variable | Default | Notes |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `KRONOS_API_KEY` | — | **Required.** `oapk_` key of type KRONOS |
|
|
89
|
+
| `KRONOS_URL` | `https://kronos.agentpit.io` | Point this at your own deployment if you self-host |
|
|
90
|
+
| `KRONOS_TIMEOUT` | `180` | Seconds. GPU inference is 30–70s — don't set this tight |
|
|
91
|
+
| `KRONOS_MCP_TRANSPORT` | `stdio` | `stdio` \| `streamable-http` \| `sse` |
|
|
92
|
+
| `KRONOS_MCP_HOST` / `KRONOS_MCP_PORT` | `0.0.0.0` / `8932` | Remote transports only |
|
|
93
|
+
|
|
94
|
+
`stdio` is what Claude Desktop, Cursor and `uvx` use. Run a remote transport
|
|
95
|
+
only if you want one long-running server serving several clients — and note the
|
|
96
|
+
MCP spec marks SSE as deprecated, so prefer `streamable-http` for new setups.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## What to expect
|
|
101
|
+
|
|
102
|
+
**Every call takes 30–70 seconds.** That is the GPU, not a hang. Don't retry
|
|
103
|
+
because it feels slow, and don't loop it over dozens of symbols — upstream takes
|
|
104
|
+
one symbol per request.
|
|
105
|
+
|
|
106
|
+
**This is a statistical extrapolation, not investment advice.** The model reads
|
|
107
|
+
price history and nothing else: no news, no halts, no earnings dates. It will
|
|
108
|
+
drift badly in unusual conditions. Treat the output as one input among many.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Errors you might hit
|
|
113
|
+
|
|
114
|
+
| Error | Meaning |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `missing_api_key` | `KRONOS_API_KEY` not set — see [API key](#api-key) |
|
|
117
|
+
| `invalid_api_key` (401) | Key wrong, incomplete or revoked. Revocation takes up to 5 min to propagate |
|
|
118
|
+
| `wrong_key_type` (403) | Key is valid but isn't a **KRONOS** key — you may have applied for KPRED or FIN_R1 |
|
|
119
|
+
| `symbol_not_found` (404) | A-shares `600519` / `600519.SH`, US tickers `AAPL`, HK `00700` |
|
|
120
|
+
| `rate_limited` (429) | Per-IP cap on invalid keys, or your quota is exhausted |
|
|
121
|
+
| `upstream_down` (502/504) | GPU service restarting. If it lasts more than a few minutes, please open an issue |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Related
|
|
126
|
+
|
|
127
|
+
- **[truesource-mcp](https://github.com/agentpit-io/hunter-community/tree/main/tools/truesource-mcp)** — verifiable first-hand market signals: filings, procurement wins, macro data
|
|
128
|
+
- **[akshare-mcp](https://github.com/agentpit-io/hunter-community/tree/main/tools/akshare-mcp)** — lets a model explore AKShare's 1000+ China market data functions on its own
|
|
129
|
+
- **[hunter-community](https://github.com/agentpit-io/hunter-community)** — the open-source stack these come from
|
|
130
|
+
|
|
131
|
+
Apache-2.0.
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Kronos MCP —— 把清华 Kronos 走势预测变成 AI 能直接调的工具。
|
|
3
|
+
|
|
4
|
+
## 它做什么
|
|
5
|
+
|
|
6
|
+
Kronos 是一个 K 线时序模型:给它一个股票代码,它预测未来 N 根 K 线的
|
|
7
|
+
开高低收和成交量。A 股 / 美股 / 港股都能问。
|
|
8
|
+
|
|
9
|
+
这个 MCP 把它包成两个工具,让 Claude / Cursor 这类客户端能直接调:
|
|
10
|
+
|
|
11
|
+
kronos_health() 先确认 key 是通的
|
|
12
|
+
kronos_predict("600519", 10) 预测未来 10 根 K 线
|
|
13
|
+
|
|
14
|
+
## 为什么必须要 API key
|
|
15
|
+
|
|
16
|
+
上游是我们自己跑的 GPU 服务,一次推理 30-70 秒。没有 key 的话:
|
|
17
|
+
|
|
18
|
+
· 任何人都能白嫖 GPU,几个爬虫就能把服务打满
|
|
19
|
+
· 没有任何计量,出问题查不到是谁
|
|
20
|
+
· 我们没法给正常用户保证可用性
|
|
21
|
+
|
|
22
|
+
所以**这个 MCP 不做无 key 模式**,也做不了 —— 上游 nginx 直接返 401。
|
|
23
|
+
没配 key 时它不会假装成功、不会返回样例数据,而是明确告诉你去哪申请。
|
|
24
|
+
|
|
25
|
+
申请:https://hunter.agentpit.io/dev/api-keys · API 类型选 **KRONOS**
|
|
26
|
+
拿到的 key 形如 `oapk_` + 32 位。工作日内通常几小时审批。
|
|
27
|
+
|
|
28
|
+
## 跑起来
|
|
29
|
+
|
|
30
|
+
# 方式一 · uvx(推荐 · 不用装)
|
|
31
|
+
KRONOS_API_KEY=oapk_xxx uvx kronos-mcp
|
|
32
|
+
|
|
33
|
+
# 方式二 · pip
|
|
34
|
+
pip install kronos-mcp
|
|
35
|
+
KRONOS_API_KEY=oapk_xxx kronos-mcp
|
|
36
|
+
|
|
37
|
+
Claude Desktop / Cursor 的配置(`claude_desktop_config.json`):
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"kronos": {
|
|
42
|
+
"command": "uvx",
|
|
43
|
+
"args": ["kronos-mcp"],
|
|
44
|
+
"env": { "KRONOS_API_KEY": "oapk_xxx" }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
## 环境变量
|
|
50
|
+
|
|
51
|
+
KRONOS_API_KEY 必填 · oapk_ 开头的 KRONOS 类型 key
|
|
52
|
+
KRONOS_URL 可选 · 默认 https://kronos.agentpit.io
|
|
53
|
+
自建部署或走别的网关时改这个
|
|
54
|
+
KRONOS_TIMEOUT 可选 · 默认 180 秒。GPU 推理 30-70s,别设太紧
|
|
55
|
+
KRONOS_MCP_TRANSPORT 可选 · stdio(默认)| streamable-http | sse
|
|
56
|
+
KRONOS_MCP_HOST/PORT 仅远程传输时用 · 默认 0.0.0.0:8932
|
|
57
|
+
"""
|
|
58
|
+
from __future__ import annotations
|
|
59
|
+
|
|
60
|
+
import json
|
|
61
|
+
import os
|
|
62
|
+
|
|
63
|
+
import httpx
|
|
64
|
+
from mcp.server.mcpserver import MCPServer
|
|
65
|
+
|
|
66
|
+
mcp = MCPServer("kronos-mcp")
|
|
67
|
+
|
|
68
|
+
DEFAULT_URL = "https://kronos.agentpit.io"
|
|
69
|
+
APPLY_URL = "https://hunter.agentpit.io/dev/api-keys"
|
|
70
|
+
|
|
71
|
+
# GPU 推理 30-70s 是常态,不是异常。connect 短、read 长 ——
|
|
72
|
+
# 连不上要快速失败,连上了就得耐心等。
|
|
73
|
+
_TIMEOUT = httpx.Timeout(
|
|
74
|
+
connect=5.0,
|
|
75
|
+
read=float(os.getenv("KRONOS_TIMEOUT", "180")),
|
|
76
|
+
write=5.0,
|
|
77
|
+
pool=5.0,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _base_url() -> str:
|
|
82
|
+
return (os.getenv("KRONOS_URL") or DEFAULT_URL).rstrip("/")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _key() -> str:
|
|
86
|
+
return (os.getenv("KRONOS_API_KEY") or "").strip()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _no_key_error() -> str:
|
|
90
|
+
"""没配 key 时的回话。
|
|
91
|
+
|
|
92
|
+
**不返回假数据。**告诉模型确切的下一步 —— 它没法替用户去申请 key,
|
|
93
|
+
但它可以把这段话原样转达给用户,用户照着做就行。
|
|
94
|
+
"""
|
|
95
|
+
return json.dumps({
|
|
96
|
+
"error": "missing_api_key",
|
|
97
|
+
"message": "没有配置 KRONOS_API_KEY,无法调用 Kronos。",
|
|
98
|
+
"how_to_fix": [
|
|
99
|
+
f"1. 打开 {APPLY_URL} 登录",
|
|
100
|
+
"2. 点「申请 API Key」· API 类型选 KRONOS",
|
|
101
|
+
"3. 拿到 oapk_ 开头的 key(只显示一次,立即复制)",
|
|
102
|
+
"4. 把它设成环境变量 KRONOS_API_KEY 后重启这个 MCP",
|
|
103
|
+
],
|
|
104
|
+
"note": "审批通常在工作日内几小时。这个服务没有免 key 模式 —— "
|
|
105
|
+
"上游是自建 GPU,一次推理 30-70 秒,开放匿名调用会被打满。",
|
|
106
|
+
}, ensure_ascii=False)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _http_error(status: int, body: str) -> str:
|
|
110
|
+
"""把上游的 HTTP 状态码翻译成模型能据此行动的话。
|
|
111
|
+
|
|
112
|
+
照搬 docs/kronos-direct-access.md 里的错误表 —— 那张表是实际踩出来的,
|
|
113
|
+
尤其 403(key 有效但类型不对)这条,光看 "403 Forbidden" 根本猜不到。
|
|
114
|
+
"""
|
|
115
|
+
table = {
|
|
116
|
+
401: ("invalid_api_key",
|
|
117
|
+
"key 无效、缺失或已被撤销。检查 KRONOS_API_KEY 是否复制完整"
|
|
118
|
+
"(oapk_ 后面还有 32 位)。刚撤销的 key 服务端有最多 5 分钟缓存。"),
|
|
119
|
+
403: ("wrong_key_type",
|
|
120
|
+
f"key 本身有效,但它的类型不是 KRONOS(可能申请成了 KPRED / FIN_R1)。"
|
|
121
|
+
f"到 {APPLY_URL} 重新申请一把 KRONOS 类型的。"),
|
|
122
|
+
404: ("symbol_not_found",
|
|
123
|
+
"找不到这个代码。A 股用 600519 或 600519.SH,美股用 ticker(AAPL),"
|
|
124
|
+
"港股用 5 位数字(00700)。"),
|
|
125
|
+
429: ("rate_limited",
|
|
126
|
+
"触发速率限制。无效 key 每 IP 每分钟有硬顶(防洪水);"
|
|
127
|
+
"如果你的 key 是有效的,大概率是 quota 打完了(默认 1000 次)。"),
|
|
128
|
+
502: ("upstream_down", "上游 GPU 服务异常,通常是重启中,稍后重试。"),
|
|
129
|
+
504: ("upstream_timeout", "上游超时。GPU 排队时会这样,稍后重试。"),
|
|
130
|
+
}
|
|
131
|
+
code, msg = table.get(status, ("http_error", f"上游返回 HTTP {status}。"))
|
|
132
|
+
return json.dumps({
|
|
133
|
+
"error": code, "http_status": status, "message": msg,
|
|
134
|
+
"upstream_body": body[:300],
|
|
135
|
+
}, ensure_ascii=False)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _request(method: str, path: str, **kw) -> str | dict:
|
|
139
|
+
"""统一的请求出口。失败一律返回 JSON 字符串,成功返回 dict。
|
|
140
|
+
|
|
141
|
+
调用方靠 isinstance 区分 —— 和 akshare-mcp 里 _resolve 同一套路,
|
|
142
|
+
省掉一层异常包装。
|
|
143
|
+
"""
|
|
144
|
+
key = _key()
|
|
145
|
+
if not key:
|
|
146
|
+
return _no_key_error()
|
|
147
|
+
|
|
148
|
+
url = f"{_base_url()}{path}"
|
|
149
|
+
try:
|
|
150
|
+
with httpx.Client(timeout=_TIMEOUT) as client:
|
|
151
|
+
r = client.request(
|
|
152
|
+
method, url,
|
|
153
|
+
headers={"Authorization": f"Bearer {key}",
|
|
154
|
+
"Content-Type": "application/json"},
|
|
155
|
+
**kw,
|
|
156
|
+
)
|
|
157
|
+
except httpx.TimeoutException:
|
|
158
|
+
return json.dumps({
|
|
159
|
+
"error": "timeout",
|
|
160
|
+
"message": f"请求超时({_TIMEOUT.read:.0f}s)。GPU 推理本来就要 30-70 秒,"
|
|
161
|
+
f"排队时更久 —— 调大 KRONOS_TIMEOUT 再试。",
|
|
162
|
+
}, ensure_ascii=False)
|
|
163
|
+
except httpx.HTTPError as e:
|
|
164
|
+
return json.dumps({
|
|
165
|
+
"error": "network_error",
|
|
166
|
+
"message": f"{type(e).__name__}: {str(e)[:200]}",
|
|
167
|
+
"url": url,
|
|
168
|
+
"hint": "连不上服务。检查网络,或用 KRONOS_URL 指到你自己的部署。",
|
|
169
|
+
}, ensure_ascii=False)
|
|
170
|
+
|
|
171
|
+
if r.status_code != 200:
|
|
172
|
+
return _http_error(r.status_code, r.text)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
return r.json()
|
|
176
|
+
except ValueError:
|
|
177
|
+
return json.dumps({
|
|
178
|
+
"error": "bad_response",
|
|
179
|
+
"message": "上游返回的不是 JSON。",
|
|
180
|
+
"body": r.text[:300],
|
|
181
|
+
}, ensure_ascii=False)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@mcp.tool()
|
|
185
|
+
def kronos_health() -> str:
|
|
186
|
+
"""确认 Kronos 服务能连上、你的 key 是有效的。
|
|
187
|
+
|
|
188
|
+
**遇到任何问题先调这个。**它能把「key 不对」和「代码不对」区分开 ——
|
|
189
|
+
直接调 predict 失败时,这两种原因看起来是一样的。
|
|
190
|
+
"""
|
|
191
|
+
res = _request("GET", "/health")
|
|
192
|
+
if isinstance(res, str):
|
|
193
|
+
return res
|
|
194
|
+
return json.dumps({"ok": True, "upstream": res,
|
|
195
|
+
"base_url": _base_url()}, ensure_ascii=False)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@mcp.tool()
|
|
199
|
+
def kronos_predict(symbol: str, pred_len: int = 10) -> str:
|
|
200
|
+
"""预测一只股票未来 N 根日 K 线。
|
|
201
|
+
|
|
202
|
+
symbol A 股 `600519` / `600519.SH`,美股 `AAPL`,港股 `00700`
|
|
203
|
+
pred_len 预测多少根,1~30。默认 10
|
|
204
|
+
|
|
205
|
+
返回每根预测 K 线的 date / open / high / low / close / volume,
|
|
206
|
+
外加 `last_close`(最后一根真实收盘价)和 `expected_return`
|
|
207
|
+
(末根预测收盘 / 最后真实收盘 − 1)—— 这个比例是最常用的那个数。
|
|
208
|
+
|
|
209
|
+
⚠️ **一次调用要 30-70 秒**(GPU 推理),这是正常的,不要因为慢就重试。
|
|
210
|
+
要问多只票就依次调用,不要指望批量 —— 上游一次只接受一个代码。
|
|
211
|
+
|
|
212
|
+
⚠️ 这是模型的**统计外推,不是投资建议**。它没有读新闻、不知道停牌和财报,
|
|
213
|
+
极端行情下会显著偏离。把它当成一个参考信号,不要当结论。
|
|
214
|
+
"""
|
|
215
|
+
sym = (symbol or "").strip()
|
|
216
|
+
if not sym:
|
|
217
|
+
return json.dumps({"error": "bad_symbol",
|
|
218
|
+
"message": "symbol 不能为空"}, ensure_ascii=False)
|
|
219
|
+
try:
|
|
220
|
+
n = int(pred_len)
|
|
221
|
+
except (TypeError, ValueError):
|
|
222
|
+
return json.dumps({"error": "bad_pred_len",
|
|
223
|
+
"message": f"pred_len 要是整数,收到 {pred_len!r}"},
|
|
224
|
+
ensure_ascii=False)
|
|
225
|
+
if not 1 <= n <= 30:
|
|
226
|
+
return json.dumps({
|
|
227
|
+
"error": "bad_pred_len",
|
|
228
|
+
"message": f"pred_len 要在 1~30 之间,收到 {n}。"
|
|
229
|
+
f"预测越远越不可靠,超过 30 根没有参考价值。",
|
|
230
|
+
}, ensure_ascii=False)
|
|
231
|
+
|
|
232
|
+
res = _request("POST", "/predict", json={"symbol": sym, "pred_len": n})
|
|
233
|
+
if isinstance(res, str):
|
|
234
|
+
return res
|
|
235
|
+
|
|
236
|
+
preds = res.get("predictions") or []
|
|
237
|
+
last_close = res.get("last_close")
|
|
238
|
+
|
|
239
|
+
out: dict = {
|
|
240
|
+
"symbol": sym,
|
|
241
|
+
"pred_len": n,
|
|
242
|
+
"last_close": last_close,
|
|
243
|
+
"predictions": preds,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# expected_return 只在两个数都真实存在且为正时才给。
|
|
247
|
+
# 算不出就**不放这个字段**,不填 0 —— 0 会被读成「预测持平」,
|
|
248
|
+
# 那是个结论,而真相是「没算出来」。
|
|
249
|
+
try:
|
|
250
|
+
lc = float(last_close)
|
|
251
|
+
end = float(preds[-1].get("close"))
|
|
252
|
+
if lc > 0 and end > 0:
|
|
253
|
+
out["expected_return"] = round(end / lc - 1.0, 6)
|
|
254
|
+
out["expected_return_pct"] = round((end / lc - 1.0) * 100, 2)
|
|
255
|
+
except (TypeError, ValueError, IndexError, AttributeError):
|
|
256
|
+
out["expected_return_note"] = (
|
|
257
|
+
"算不出预期收益率 —— 上游没给 last_close 或末根预测收盘价")
|
|
258
|
+
|
|
259
|
+
out["disclaimer"] = ("Kronos 是纯时序统计模型,不读新闻、不知停牌与财报。"
|
|
260
|
+
"输出是参考信号,不是投资建议。")
|
|
261
|
+
return json.dumps(out, ensure_ascii=False)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def main() -> None:
|
|
265
|
+
"""入口 —— PyPI 的 console_scripts 指向这里。
|
|
266
|
+
|
|
267
|
+
默认 stdio:Claude Desktop / Cursor / uvx 都走这个,是 MCP 的默认形态。
|
|
268
|
+
streamable-http / sse 留给「跑成常驻服务、多个客户端连」的场景
|
|
269
|
+
(比如接进 Hunter 的「能力 → 接入一个工具」)。MCP 规范已把 SSE 标记为
|
|
270
|
+
过时,新部署优先用 streamable-http。
|
|
271
|
+
"""
|
|
272
|
+
transport = (os.getenv("KRONOS_MCP_TRANSPORT") or "stdio").strip().lower()
|
|
273
|
+
if transport == "stdio":
|
|
274
|
+
mcp.run(transport="stdio")
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
# 远程传输 · host/port 在 mcp 2.x 里是 run() 的关键字参数,
|
|
278
|
+
# 不再是 1.x 那个 mcp.settings.host/port
|
|
279
|
+
host = os.getenv("KRONOS_MCP_HOST", "0.0.0.0")
|
|
280
|
+
port = int(os.getenv("KRONOS_MCP_PORT", "8932"))
|
|
281
|
+
if transport in ("http", "streamable-http"):
|
|
282
|
+
mcp.run(transport="streamable-http", host=host, port=port)
|
|
283
|
+
elif transport == "sse":
|
|
284
|
+
mcp.run(transport="sse", host=host, port=port)
|
|
285
|
+
else:
|
|
286
|
+
raise SystemExit(
|
|
287
|
+
f"未知的 KRONOS_MCP_TRANSPORT={transport!r} —— "
|
|
288
|
+
f"只支持 stdio(默认)/ streamable-http / sse")
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
# 远程传输 · host/port 在 mcp 2.x 里是 run() 的关键字参数,
|
|
292
|
+
# 不再是 1.x 那个 mcp.settings.host/port
|
|
293
|
+
host = os.getenv("KRONOS_MCP_HOST", "0.0.0.0")
|
|
294
|
+
port = int(os.getenv("KRONOS_MCP_PORT", "8932"))
|
|
295
|
+
if transport in ("http", "streamable-http"):
|
|
296
|
+
mcp.run(transport="streamable-http", host=host, port=port)
|
|
297
|
+
elif transport == "sse":
|
|
298
|
+
mcp.run(transport="sse", host=host, port=port)
|
|
299
|
+
else:
|
|
300
|
+
raise SystemExit(
|
|
301
|
+
f"未知的 KRONOS_MCP_TRANSPORT={transport!r} —— "
|
|
302
|
+
f"只支持 stdio(默认)/ streamable-http / sse")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
main()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kronos-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for Kronos stock price forecasting — A-shares, US and HK equities"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [{ name = "AgentPit", email = "hangeaiagent@gmail.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"mcp", "model-context-protocol", "stock", "forecast", "quant",
|
|
15
|
+
"finance", "kronos", "a-share", "timeseries",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: Apache Software License",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"mcp>=2.0.0",
|
|
30
|
+
"httpx>=0.27.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/agentpit-io/hunter-community"
|
|
35
|
+
Repository = "https://github.com/agentpit-io/hunter-community"
|
|
36
|
+
Documentation = "https://github.com/agentpit-io/hunter-community/blob/main/tools/kronos-mcp/README.md"
|
|
37
|
+
Issues = "https://github.com/agentpit-io/hunter-community/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
kronos-mcp = "kronos_mcp.server:main"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["kronos_mcp"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.agentpit-io/kronos-mcp",
|
|
4
|
+
"title": "Kronos",
|
|
5
|
+
"description": "Stock price forecasting for A-shares, US and HK equities — K-line time-series model",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/agentpit-io/hunter-community",
|
|
8
|
+
"source": "github",
|
|
9
|
+
"subfolder": "tools/kronos-mcp"
|
|
10
|
+
},
|
|
11
|
+
"version": "0.1.0",
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "pypi",
|
|
15
|
+
"registryBaseUrl": "https://pypi.org",
|
|
16
|
+
"identifier": "kronos-mcp",
|
|
17
|
+
"version": "0.1.0",
|
|
18
|
+
"runtimeHint": "uvx",
|
|
19
|
+
"transport": {
|
|
20
|
+
"type": "stdio"
|
|
21
|
+
},
|
|
22
|
+
"environmentVariables": [
|
|
23
|
+
{
|
|
24
|
+
"name": "KRONOS_API_KEY",
|
|
25
|
+
"description": "KRONOS-type API key from https://hunter.agentpit.io/dev/api-keys",
|
|
26
|
+
"isRequired": true,
|
|
27
|
+
"isSecret": true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "KRONOS_URL",
|
|
31
|
+
"description": "Override the upstream endpoint if you self-host",
|
|
32
|
+
"default": "https://kronos.agentpit.io"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "KRONOS_TIMEOUT",
|
|
36
|
+
"description": "Read timeout in seconds. GPU inference takes 30-70s",
|
|
37
|
+
"default": "180"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|