cyberquant-mcp 0.1.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 +106 -0
- package/dist/index.js +508 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# CyberQuant MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://nodejs.org/)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://modelcontextprotocol.io/)
|
|
6
|
+
|
|
7
|
+
CyberQuant 数据共享平台的 MCP(Model Context Protocol)服务器,让 AI 助手(Claude Desktop、ChatGPT 等)能够直接查询和分析金融数据。
|
|
8
|
+
|
|
9
|
+
> 工作方式:AI 客户端通过 stdio 与 MCP Server 通信,MCP Server 再向 API Gateway 拉取数据,以 AI 友好的 **CSV 格式**返回。
|
|
10
|
+
|
|
11
|
+
## 功能特性
|
|
12
|
+
|
|
13
|
+
### MCP Tools(3 个)
|
|
14
|
+
|
|
15
|
+
| 工具 | 说明 |
|
|
16
|
+
|------|------|
|
|
17
|
+
| `configure` | 配置 API Key,首次使用时调用 |
|
|
18
|
+
| `list_routes` | 列出当前用户可用的数据路由及参数 Schema |
|
|
19
|
+
| `query_data` | 查询指定路由数据,返回 **CSV 格式**(节省 token) |
|
|
20
|
+
|
|
21
|
+
### MCP Resources(2 个)
|
|
22
|
+
|
|
23
|
+
| 资源 URI | 说明 |
|
|
24
|
+
|----------|------|
|
|
25
|
+
| `cyberquant://user/profile` | 当前用户信息(等级、市场权限、速率限制) |
|
|
26
|
+
| `cyberquant://routes` | 当前用户可用的数据路由列表 |
|
|
27
|
+
|
|
28
|
+
### 设计亮点
|
|
29
|
+
|
|
30
|
+
- **CSV 输出**:相比 JSON 节省 40–60% token,表格结构天然适合 AI 分析
|
|
31
|
+
- **自然语言引导**:数据返回附带提示,引导 AI 缩小查询范围而非暴力翻页
|
|
32
|
+
- **pageSize 上限保护**:超过 1000 条自动拦截,避免 AI 处理超大数据集
|
|
33
|
+
- **运行时配置**:通过 `configure` 工具动态更新 API Key,无需重启
|
|
34
|
+
|
|
35
|
+
## 快速开始
|
|
36
|
+
|
|
37
|
+
### 环境要求
|
|
38
|
+
|
|
39
|
+
- **Node.js** >= 20.0.0
|
|
40
|
+
|
|
41
|
+
### 1. 接入 MCP 客户端
|
|
42
|
+
|
|
43
|
+
在 Claude Desktop(或任意 MCP 客户端)的配置文件中添加:
|
|
44
|
+
|
|
45
|
+
- **macOS**:`~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
46
|
+
- **Windows**:`%APPDATA%/Claude/claude_desktop_config.json`
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"cyberquant": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "cyberquant-mcp"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
保存后重启客户端即可。> 从源码本地构建运行的方式见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
|
|
60
|
+
|
|
61
|
+
### 2. 配置 API Key
|
|
62
|
+
|
|
63
|
+
MCP Server 与 `cyberquant-cli` 共用配置文件 `~/.cyberquant/config.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"endpoint": "https://api.cyberspace2077.com",
|
|
68
|
+
"apiKey": "sk_live_your_api_key_here",
|
|
69
|
+
"mcp": {
|
|
70
|
+
"pageSize": 200,
|
|
71
|
+
"timeout": 30000
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> 💡 也可不手动编辑文件——启动后在对话中直接说"请配置我的 API Key",AI 会调用 `configure` 工具完成配置。
|
|
77
|
+
|
|
78
|
+
完整字段说明见 [配置文档](./docs/configuration.md)。
|
|
79
|
+
|
|
80
|
+
### 3. 开始使用
|
|
81
|
+
|
|
82
|
+
在对话中直接提问,例如:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
帮我查一下平安银行最近一周的日K线数据
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
AI 会依次调用 `list_routes` → `query_data`,并以表格形式返回分析结果。更多场景见 [使用示例](./examples/usage-examples.md)。
|
|
89
|
+
|
|
90
|
+
## 文档
|
|
91
|
+
|
|
92
|
+
| 文档 | 说明 |
|
|
93
|
+
|------|------|
|
|
94
|
+
| [配置说明](./docs/configuration.md) | 配置文件格式与字段说明 |
|
|
95
|
+
| [工具说明](./docs/tools.md) | 三个 MCP Tool 的详细文档 |
|
|
96
|
+
| [Resources 说明](./docs/resources.md) | MCP Resources 介绍 |
|
|
97
|
+
| [故障排查](./docs/troubleshooting.md) | 常见问题与解决方案 |
|
|
98
|
+
| [使用示例](./examples/usage-examples.md) | 典型对话场景 |
|
|
99
|
+
|
|
100
|
+
## 贡献
|
|
101
|
+
|
|
102
|
+
参与开发、构建或发布请参考 [CONTRIBUTING.md](./CONTRIBUTING.md)。
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
[MIT](./LICENSE)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/config.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import os from "os";
|
|
10
|
+
|
|
11
|
+
// src/types/index.ts
|
|
12
|
+
var MCP_DEFAULTS = {
|
|
13
|
+
pageSize: 200,
|
|
14
|
+
timeout: 3e4
|
|
15
|
+
};
|
|
16
|
+
var PAGE_SIZE_MAX = 1e3;
|
|
17
|
+
|
|
18
|
+
// src/lib/config.ts
|
|
19
|
+
var CONFIG_DIR = path.join(os.homedir(), ".cyberquant");
|
|
20
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
21
|
+
var DEFAULT_ENDPOINT = "https://api.cyberspace2077.com";
|
|
22
|
+
function readRawConfig() {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(CONFIG_FILE)) return null;
|
|
25
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
26
|
+
return JSON.parse(raw);
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function ensureMcpField(raw) {
|
|
32
|
+
if (raw.mcp && raw.mcp.pageSize !== void 0 && raw.mcp.timeout !== void 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
raw.mcp = { ...MCP_DEFAULTS, ...raw.mcp };
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
38
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(raw, null, 2), "utf-8");
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function buildConfig(raw) {
|
|
45
|
+
if (!raw.endpoint || !raw.apiKey) return null;
|
|
46
|
+
ensureMcpField(raw);
|
|
47
|
+
const mcp = {
|
|
48
|
+
pageSize: raw.mcp?.pageSize ?? MCP_DEFAULTS.pageSize,
|
|
49
|
+
timeout: raw.mcp?.timeout ?? MCP_DEFAULTS.timeout
|
|
50
|
+
};
|
|
51
|
+
if (mcp.pageSize > PAGE_SIZE_MAX) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`[cyberquant-mcp] \u8B66\u544A\uFF1Amcp.pageSize=${mcp.pageSize} \u8D85\u8FC7\u4E0A\u9650 ${PAGE_SIZE_MAX}\uFF0C\u67E5\u8BE2\u65F6\u5C06\u8FD4\u56DE\u8B66\u544A\u63D0\u793A
|
|
54
|
+
`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
endpoint: raw.endpoint.replace(/\/+$/, ""),
|
|
59
|
+
apiKey: raw.apiKey,
|
|
60
|
+
mcp
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function loadConfig() {
|
|
64
|
+
const raw = readRawConfig();
|
|
65
|
+
if (!raw) return null;
|
|
66
|
+
return buildConfig(raw);
|
|
67
|
+
}
|
|
68
|
+
function saveConfig(apiKey, endpoint) {
|
|
69
|
+
const config = {
|
|
70
|
+
endpoint: (endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, ""),
|
|
71
|
+
apiKey,
|
|
72
|
+
mcp: { ...MCP_DEFAULTS }
|
|
73
|
+
};
|
|
74
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
75
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
78
|
+
return {
|
|
79
|
+
endpoint: config.endpoint,
|
|
80
|
+
apiKey: config.apiKey,
|
|
81
|
+
mcp: { ...MCP_DEFAULTS }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/lib/errors.ts
|
|
86
|
+
var McpError = class extends Error {
|
|
87
|
+
constructor(message) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.name = "McpError";
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var ApiError = class extends McpError {
|
|
93
|
+
constructor(statusCode, message) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.statusCode = statusCode;
|
|
96
|
+
this.name = "ApiError";
|
|
97
|
+
}
|
|
98
|
+
statusCode;
|
|
99
|
+
};
|
|
100
|
+
var ERROR_MESSAGES = {
|
|
101
|
+
400: "\u53C2\u6570\u6821\u9A8C\u5931\u8D25",
|
|
102
|
+
401: "API Key \u65E0\u6548\u6216\u5DF2\u8FC7\u671F",
|
|
103
|
+
403: "\u6743\u9650\u4E0D\u8DB3",
|
|
104
|
+
404: "\u672A\u627E\u5230 API \u8DEF\u7531",
|
|
105
|
+
429: "\u8BF7\u6C42\u9891\u7387\u8D85\u9650",
|
|
106
|
+
500: "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF",
|
|
107
|
+
503: "\u670D\u52A1\u6682\u4E0D\u53EF\u7528"
|
|
108
|
+
};
|
|
109
|
+
function mapApiError(status, detail) {
|
|
110
|
+
const base = ERROR_MESSAGES[status] ?? `\u8BF7\u6C42\u5931\u8D25 (${status})`;
|
|
111
|
+
return new ApiError(status, detail ? `${base}: ${detail}` : base);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/lib/api-client.ts
|
|
115
|
+
var ApiClient = class {
|
|
116
|
+
endpoint;
|
|
117
|
+
apiKey;
|
|
118
|
+
timeout;
|
|
119
|
+
constructor(config) {
|
|
120
|
+
this.endpoint = config.endpoint;
|
|
121
|
+
this.apiKey = config.apiKey;
|
|
122
|
+
this.timeout = config.mcp.timeout;
|
|
123
|
+
}
|
|
124
|
+
/** 通用请求(含 429/503 重试) */
|
|
125
|
+
async request(path2) {
|
|
126
|
+
const url = `${this.endpoint}${path2}`;
|
|
127
|
+
const maxRetries = 3;
|
|
128
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
129
|
+
const res = await fetch(url, {
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
"X-Client-Type": "mcp"
|
|
134
|
+
},
|
|
135
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
136
|
+
});
|
|
137
|
+
if (res.ok) {
|
|
138
|
+
return await res.json();
|
|
139
|
+
}
|
|
140
|
+
if ((res.status === 429 || res.status === 503) && attempt < maxRetries) {
|
|
141
|
+
await res.body?.cancel().catch(() => {
|
|
142
|
+
});
|
|
143
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
144
|
+
const delay = retryAfter ? Number(retryAfter) * 1e3 : Math.pow(2, attempt) * 1e3;
|
|
145
|
+
await new Promise((r) => setTimeout(r, Math.min(delay, 1e4)));
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
let detail = "";
|
|
149
|
+
try {
|
|
150
|
+
const body = await res.json();
|
|
151
|
+
detail = body.error ?? "";
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
throw mapApiError(res.status, detail);
|
|
155
|
+
}
|
|
156
|
+
throw mapApiError(503, "\u91CD\u8BD5\u6B21\u6570\u5DF2\u7528\u5B8C\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5");
|
|
157
|
+
}
|
|
158
|
+
/** 获取用户信息 */
|
|
159
|
+
async getProfile() {
|
|
160
|
+
return this.request("/api/v1/me");
|
|
161
|
+
}
|
|
162
|
+
/** 获取可用路由列表 */
|
|
163
|
+
async listRoutes() {
|
|
164
|
+
return this.request("/api/v1/api-list");
|
|
165
|
+
}
|
|
166
|
+
/** 查询数据 */
|
|
167
|
+
async queryData(routeSlug, params, pageSize) {
|
|
168
|
+
const qs = new URLSearchParams();
|
|
169
|
+
qs.set("pageSize", String(pageSize));
|
|
170
|
+
for (const [k, v] of Object.entries(params)) {
|
|
171
|
+
if (v === void 0 || v === "") continue;
|
|
172
|
+
if (Array.isArray(v)) {
|
|
173
|
+
for (const item of v) {
|
|
174
|
+
if (item !== void 0 && item !== "") qs.append(k, String(item));
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
qs.set(k, String(v));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const query = qs.toString();
|
|
181
|
+
return this.request(`/api/v1/data/${routeSlug}${query ? "?" + query : ""}`);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/server.ts
|
|
186
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
187
|
+
|
|
188
|
+
// src/tools/configure.ts
|
|
189
|
+
import { z } from "zod";
|
|
190
|
+
function registerConfigureTool(server, state) {
|
|
191
|
+
server.tool(
|
|
192
|
+
"configure",
|
|
193
|
+
"\u914D\u7F6E API Key \u4EE5\u8BBF\u95EE\u6570\u636E\u670D\u52A1\u3002\u9996\u6B21\u4F7F\u7528\u65F6\u5FC5\u987B\u8C03\u7528\u6B64\u5DE5\u5177\u5B8C\u6210\u914D\u7F6E\u3002endpoint \u9ED8\u8BA4\u4E3A https://api.cyberspace2077.com\u3002",
|
|
194
|
+
{
|
|
195
|
+
apiKey: z.string().describe("API Key\uFF0C\u683C\u5F0F\u4E3A sk_live_xxx \u6216 sk_test_xxx"),
|
|
196
|
+
endpoint: z.string().optional().describe(`API Gateway \u5730\u5740\uFF0C\u9ED8\u8BA4 ${DEFAULT_ENDPOINT}`)
|
|
197
|
+
},
|
|
198
|
+
async ({ apiKey, endpoint }) => {
|
|
199
|
+
try {
|
|
200
|
+
const config = saveConfig(apiKey, endpoint);
|
|
201
|
+
state.config = config;
|
|
202
|
+
state.client = new ApiClient(config);
|
|
203
|
+
process.stderr.write(
|
|
204
|
+
`[cyberquant-mcp] \u914D\u7F6E\u5DF2\u66F4\u65B0\uFF1Aendpoint=${config.endpoint}, pageSize=${config.mcp.pageSize}
|
|
205
|
+
`
|
|
206
|
+
);
|
|
207
|
+
return {
|
|
208
|
+
content: [
|
|
209
|
+
{
|
|
210
|
+
type: "text",
|
|
211
|
+
text: `\u914D\u7F6E\u6210\u529F\uFF01endpoint: ${config.endpoint}\uFF0C\u73B0\u5728\u53EF\u4EE5\u4F7F\u7528 list_routes \u67E5\u770B\u53EF\u7528\u6570\u636E\u8DEF\u7531\uFF0C\u6216\u4F7F\u7528 query_data \u67E5\u8BE2\u6570\u636E\u3002`
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
};
|
|
215
|
+
} catch (err) {
|
|
216
|
+
const msg = err instanceof Error ? err.message : "\u672A\u77E5\u9519\u8BEF";
|
|
217
|
+
return {
|
|
218
|
+
content: [
|
|
219
|
+
{
|
|
220
|
+
type: "text",
|
|
221
|
+
text: `\u914D\u7F6E\u4FDD\u5B58\u5931\u8D25\uFF1A${msg}\u3002\u8BF7\u68C0\u67E5\u662F\u5426\u6709\u5199\u5165 ~/.cyberquant/ \u76EE\u5F55\u7684\u6743\u9650\u3002`
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/lib/state.ts
|
|
231
|
+
var NO_CONFIG_HINT = "\u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E\u3002\u8BF7\u5148\u8C03\u7528 configure \u5DE5\u5177\uFF0C\u4F20\u5165 apiKey \u53C2\u6570\u5B8C\u6210\u914D\u7F6E\u3002endpoint \u9ED8\u8BA4\u4E3A https://api.cyberspace2077.com\uFF0C\u4E5F\u53EF\u81EA\u5B9A\u4E49\u3002";
|
|
232
|
+
|
|
233
|
+
// src/tools/list.ts
|
|
234
|
+
function formatRoute(route) {
|
|
235
|
+
const lines = [
|
|
236
|
+
`\u3010${route.displayName}\u3011routeSlug: ${route.routeSlug}`,
|
|
237
|
+
`\u8BF4\u660E\uFF1A${route.description}`,
|
|
238
|
+
`\u5206\u7C7B\uFF1A${route.category}`
|
|
239
|
+
];
|
|
240
|
+
if (route.queryParams.length > 0) {
|
|
241
|
+
lines.push("\u67E5\u8BE2\u53C2\u6570\uFF1A");
|
|
242
|
+
for (const p of route.queryParams) {
|
|
243
|
+
const required = p.required ? "\u5FC5\u586B" : "\u53EF\u9009";
|
|
244
|
+
lines.push(` - ${p.name} (${p.type}, ${required}): ${p.desc}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (route.responseParams.length > 0) {
|
|
248
|
+
lines.push("\u8FD4\u56DE\u5B57\u6BB5\uFF1A");
|
|
249
|
+
for (const f of route.responseParams) {
|
|
250
|
+
lines.push(` - ${f.name} (${f.type}): ${f.desc}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return lines.join("\n");
|
|
254
|
+
}
|
|
255
|
+
function registerListRoutesTool(server, state) {
|
|
256
|
+
server.tool(
|
|
257
|
+
"list_routes",
|
|
258
|
+
"\u5217\u51FA\u5F53\u524D\u7528\u6237\u53EF\u7528\u7684\u6570\u636E\u8DEF\u7531\u53CA\u5176\u53C2\u6570 Schema\uFF0C\u5305\u542B\u6BCF\u4E2A\u8DEF\u7531\u7684\u67E5\u8BE2\u53C2\u6570\u548C\u8FD4\u56DE\u5B57\u6BB5\u8BF4\u660E",
|
|
259
|
+
{},
|
|
260
|
+
async () => {
|
|
261
|
+
if (!state.client) {
|
|
262
|
+
return { content: [{ type: "text", text: NO_CONFIG_HINT }] };
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const res = await state.client.listRoutes();
|
|
266
|
+
if (!res.success || !res.data) {
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "text",
|
|
271
|
+
text: `\u83B7\u53D6\u8DEF\u7531\u5217\u8868\u5931\u8D25\uFF1A${res.error ?? "\u672A\u77E5\u9519\u8BEF"}\u3002\u8BF7\u68C0\u67E5 API Key \u914D\u7F6E\u662F\u5426\u6B63\u786E\u3002`
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const routes = res.data;
|
|
277
|
+
const total = res.meta?.total ?? routes.length;
|
|
278
|
+
const header = `\u53EF\u7528\u6570\u636E\u8DEF\u7531\uFF08\u5171 ${total} \u4E2A\uFF09\uFF1A
|
|
279
|
+
`;
|
|
280
|
+
const body = routes.map(formatRoute).join("\n\n");
|
|
281
|
+
const footer = total > 0 ? "\n\n\u4F7F\u7528 query_data \u5DE5\u5177\u67E5\u8BE2\u6307\u5B9A\u8DEF\u7531\u7684\u6570\u636E\uFF0C\u4F20\u5165 routeSlug \u548C\u67E5\u8BE2\u53C2\u6570\u3002" : "";
|
|
282
|
+
return {
|
|
283
|
+
content: [{ type: "text", text: header + body + footer }]
|
|
284
|
+
};
|
|
285
|
+
} catch (err) {
|
|
286
|
+
const msg = err instanceof Error ? err.message : "\u672A\u77E5\u9519\u8BEF";
|
|
287
|
+
return {
|
|
288
|
+
content: [
|
|
289
|
+
{ type: "text", text: `\u83B7\u53D6\u8DEF\u7531\u5217\u8868\u5931\u8D25\uFF1A${msg}` }
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/tools/query.ts
|
|
298
|
+
import { z as z2 } from "zod";
|
|
299
|
+
function toCsv(rows) {
|
|
300
|
+
if (rows.length === 0) return "";
|
|
301
|
+
const headers = Object.keys(rows[0]);
|
|
302
|
+
const headerLine = headers.join(",");
|
|
303
|
+
const dataLines = rows.map(
|
|
304
|
+
(row) => headers.map((h) => {
|
|
305
|
+
const val = row[h];
|
|
306
|
+
if (val === null || val === void 0) return "";
|
|
307
|
+
const str = String(val);
|
|
308
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
|
|
309
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
310
|
+
}
|
|
311
|
+
return str;
|
|
312
|
+
}).join(",")
|
|
313
|
+
);
|
|
314
|
+
return [headerLine, ...dataLines].join("\n");
|
|
315
|
+
}
|
|
316
|
+
var PAGE_SIZE_WARNING = (n) => `\u26A0\uFE0F \u5F53\u524D\u914D\u7F6E\u7684\u5355\u6B21\u67E5\u8BE2\u6570\u91CF\u4E3A ${n} \u6761\uFF0C\u8D85\u8FC7\u4E0A\u9650 ${PAGE_SIZE_MAX} \u6761\u3002\u5927\u6A21\u578B\u4E0D\u64C5\u957F\u76F4\u63A5\u5728\u5E9E\u5927\u7684\u6570\u503C\u77E9\u9635\u4E2D\u505A\u590D\u6742\u7684\u6570\u5B66\u8FD0\u7B97\uFF08\u5982\u7CBE\u786E\u8BA1\u7B97\u957F\u671F\u5747\u7EBF\u3001RSI\u3001\u5E03\u6797\u5E26\u7B49\uFF09\uFF0C\u5EFA\u8BAE\uFF1A
|
|
317
|
+
1. \u5C06 mcp.pageSize \u8C03\u6574\u4E3A ${PAGE_SIZE_MAX} \u4EE5\u5185\uFF08\u63A8\u8350 200\uFF09
|
|
318
|
+
2. \u4F7F\u7528\u7F16\u7A0B\u811A\u672C\uFF08Python/Node.js\uFF09\u914D\u5408 cyberquant-cli \u5904\u7406\u5927\u6570\u636E\u91CF\u4EFB\u52A1
|
|
319
|
+
3. \u7F29\u5C0F\u67E5\u8BE2\u53C2\u6570\u8303\u56F4\uFF0C\u83B7\u53D6\u66F4\u7CBE\u51C6\u7684\u6570\u636E\u5B50\u96C6`;
|
|
320
|
+
var HAS_MORE_HINT = "\u26A0\uFE0F \u5F53\u524D\u67E5\u8BE2\u8303\u56F4\u4E0B\u8FD8\u6709\u66F4\u591A\u6570\u636E\u672A\u8FD4\u56DE\u3002\u5EFA\u8BAE\u7F29\u5C0F\u67E5\u8BE2\u53C2\u6570\u8303\u56F4\uFF08\u5982\u7F29\u5C0F\u65E5\u671F\u533A\u95F4\u3001\u6307\u5B9A\u5177\u4F53\u4EE3\u7801\u7B49\uFF09\u4EE5\u83B7\u53D6\u7CBE\u786E\u7684\u6570\u636E\u5B50\u96C6\uFF0C\u5927\u6A21\u578B\u66F4\u9002\u5408\u5206\u6790\u7CBE\u51C6\u7684\u5C0F\u6570\u636E\u96C6\u3002";
|
|
321
|
+
var NO_DATA_HINT = "\u672A\u67E5\u8BE2\u5230\u7B26\u5408\u6761\u4EF6\u7684\u6570\u636E\u3002\u8BF7\u68C0\u67E5\u67E5\u8BE2\u53C2\u6570\u662F\u5426\u6B63\u786E\uFF0C\u53EF\u901A\u8FC7 list_routes \u5DE5\u5177\u67E5\u770B\u8BE5\u8DEF\u7531\u652F\u6301\u7684\u53C2\u6570\u8BF4\u660E\u3002";
|
|
322
|
+
function registerQueryDataTool(server, state) {
|
|
323
|
+
server.tool(
|
|
324
|
+
"query_data",
|
|
325
|
+
"\u67E5\u8BE2\u6307\u5B9A\u8DEF\u7531\u7684\u6570\u636E\uFF0C\u8FD4\u56DE CSV \u683C\u5F0F\u3002\u4F7F\u7528 list_routes \u67E5\u770B\u53EF\u7528\u8DEF\u7531\u548C\u53C2\u6570\u8BF4\u660E\u3002",
|
|
326
|
+
{
|
|
327
|
+
routeSlug: z2.string().describe('\u8DEF\u7531\u6807\u8BC6\uFF0C\u5982 "daily-stock"\u3002\u901A\u8FC7 list_routes \u83B7\u53D6\u53EF\u7528\u8DEF\u7531\u3002'),
|
|
328
|
+
params: z2.record(z2.union([z2.string(), z2.number(), z2.boolean(), z2.array(z2.union([z2.string(), z2.number()]))])).optional().describe("\u67E5\u8BE2\u53C2\u6570\uFF0C\u952E\u503C\u5BF9\u900F\u4F20\u7ED9 API\u3002\u5177\u4F53\u53C2\u6570\u53C2\u8003 list_routes \u8FD4\u56DE\u7684\u8DEF\u7531\u8BF4\u660E\u3002")
|
|
329
|
+
},
|
|
330
|
+
async ({ routeSlug, params }) => {
|
|
331
|
+
if (!state.client || !state.config) {
|
|
332
|
+
return { content: [{ type: "text", text: NO_CONFIG_HINT }] };
|
|
333
|
+
}
|
|
334
|
+
const pageSize = state.config.mcp.pageSize;
|
|
335
|
+
if (pageSize > PAGE_SIZE_MAX) {
|
|
336
|
+
return {
|
|
337
|
+
content: [{ type: "text", text: PAGE_SIZE_WARNING(pageSize) }]
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const res = await state.client.queryData(routeSlug, params ?? {}, pageSize);
|
|
342
|
+
if (!res.success) {
|
|
343
|
+
return {
|
|
344
|
+
content: [
|
|
345
|
+
{
|
|
346
|
+
type: "text",
|
|
347
|
+
text: `\u67E5\u8BE2\u5931\u8D25\uFF1A${res.error ?? "\u672A\u77E5\u9519\u8BEF"}\u3002\u8BF7\u68C0\u67E5\u8DEF\u7531\u6807\u8BC6\u548C\u53C2\u6570\u662F\u5426\u6B63\u786E\u3002`
|
|
348
|
+
}
|
|
349
|
+
]
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const rows = res.data;
|
|
353
|
+
const count = res.meta?.count ?? (Array.isArray(rows) ? rows.length : 0);
|
|
354
|
+
const hasMore = res.meta?.pagination?.hasMore ?? false;
|
|
355
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
356
|
+
return {
|
|
357
|
+
content: [{ type: "text", text: NO_DATA_HINT }]
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const parts = [toCsv(rows), "", `\u672C\u6B21\u67E5\u8BE2\u8FD4\u56DE ${count} \u6761\u6570\u636E\u3002`];
|
|
361
|
+
if (hasMore) {
|
|
362
|
+
parts.push(HAS_MORE_HINT);
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
366
|
+
};
|
|
367
|
+
} catch (err) {
|
|
368
|
+
const msg = err instanceof Error ? err.message : "\u672A\u77E5\u9519\u8BEF";
|
|
369
|
+
return {
|
|
370
|
+
content: [{ type: "text", text: `\u67E5\u8BE2\u5931\u8D25\uFF1A${msg}` }]
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/resources/user-profile.ts
|
|
378
|
+
function createUserProfileResource(server, state) {
|
|
379
|
+
server.resource(
|
|
380
|
+
"user-profile",
|
|
381
|
+
"cyberquant://user/profile",
|
|
382
|
+
{
|
|
383
|
+
description: "\u5F53\u524D\u7528\u6237\u4FE1\u606F\uFF08\u7B49\u7EA7\u3001\u5E02\u573A\u6743\u9650\u3001\u5230\u671F\u65F6\u95F4\u3001\u901F\u7387\u9650\u5236\uFF09",
|
|
384
|
+
mimeType: "application/json"
|
|
385
|
+
},
|
|
386
|
+
async (uri) => {
|
|
387
|
+
if (!state.client) {
|
|
388
|
+
return {
|
|
389
|
+
contents: [
|
|
390
|
+
{
|
|
391
|
+
uri: uri.href,
|
|
392
|
+
text: JSON.stringify({
|
|
393
|
+
error: "\u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E\uFF0C\u8BF7\u5148\u8C03\u7528 configure \u5DE5\u5177\u5B8C\u6210\u914D\u7F6E\u3002"
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const res = await state.client.getProfile();
|
|
400
|
+
if (!res.success || !res.data) {
|
|
401
|
+
return {
|
|
402
|
+
contents: [
|
|
403
|
+
{
|
|
404
|
+
uri: uri.href,
|
|
405
|
+
text: JSON.stringify({ error: res.error ?? "\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25" })
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
contents: [
|
|
412
|
+
{
|
|
413
|
+
uri: uri.href,
|
|
414
|
+
text: JSON.stringify(res.data, null, 2)
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/resources/route-list.ts
|
|
423
|
+
function createRouteListResource(server, state) {
|
|
424
|
+
server.resource(
|
|
425
|
+
"routes",
|
|
426
|
+
"cyberquant://routes",
|
|
427
|
+
{
|
|
428
|
+
description: "\u5F53\u524D\u7528\u6237\u53EF\u7528\u7684\u6570\u636E\u8DEF\u7531\u5217\u8868",
|
|
429
|
+
mimeType: "application/json"
|
|
430
|
+
},
|
|
431
|
+
async (uri) => {
|
|
432
|
+
if (!state.client) {
|
|
433
|
+
return {
|
|
434
|
+
contents: [
|
|
435
|
+
{
|
|
436
|
+
uri: uri.href,
|
|
437
|
+
text: JSON.stringify({
|
|
438
|
+
error: "\u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E\uFF0C\u8BF7\u5148\u8C03\u7528 configure \u5DE5\u5177\u5B8C\u6210\u914D\u7F6E\u3002"
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
const res = await state.client.listRoutes();
|
|
445
|
+
if (!res.success || !res.data) {
|
|
446
|
+
return {
|
|
447
|
+
contents: [
|
|
448
|
+
{
|
|
449
|
+
uri: uri.href,
|
|
450
|
+
text: JSON.stringify({ error: res.error ?? "\u83B7\u53D6\u8DEF\u7531\u5217\u8868\u5931\u8D25" })
|
|
451
|
+
}
|
|
452
|
+
]
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
contents: [
|
|
457
|
+
{
|
|
458
|
+
uri: uri.href,
|
|
459
|
+
text: JSON.stringify(res.data, null, 2)
|
|
460
|
+
}
|
|
461
|
+
]
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/server.ts
|
|
468
|
+
function createServer(state) {
|
|
469
|
+
const server = new McpServer({
|
|
470
|
+
name: "cyberquant-mcp",
|
|
471
|
+
version: "0.1.0"
|
|
472
|
+
});
|
|
473
|
+
registerConfigureTool(server, state);
|
|
474
|
+
registerListRoutesTool(server, state);
|
|
475
|
+
registerQueryDataTool(server, state);
|
|
476
|
+
createUserProfileResource(server, state);
|
|
477
|
+
createRouteListResource(server, state);
|
|
478
|
+
return server;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/index.ts
|
|
482
|
+
async function main() {
|
|
483
|
+
const config = loadConfig();
|
|
484
|
+
const state = {
|
|
485
|
+
config,
|
|
486
|
+
client: config ? new ApiClient(config) : null
|
|
487
|
+
};
|
|
488
|
+
if (config) {
|
|
489
|
+
process.stderr.write(
|
|
490
|
+
`[cyberquant-mcp] \u542F\u52A8\u4E2D... endpoint=${config.endpoint}, pageSize=${config.mcp.pageSize}, timeout=${config.mcp.timeout}
|
|
491
|
+
`
|
|
492
|
+
);
|
|
493
|
+
} else {
|
|
494
|
+
process.stderr.write(
|
|
495
|
+
"[cyberquant-mcp] \u672A\u68C0\u6D4B\u5230\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u901A\u8FC7 configure \u5DE5\u5177\u914D\u7F6E API Key\n"
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
const server = createServer(state);
|
|
499
|
+
const transport = new StdioServerTransport();
|
|
500
|
+
await server.connect(transport);
|
|
501
|
+
process.stderr.write("[cyberquant-mcp] \u5DF2\u542F\u52A8\uFF0C\u901A\u8FC7 stdio \u7B49\u5F85 MCP \u8FDE\u63A5\n");
|
|
502
|
+
}
|
|
503
|
+
main().catch((err) => {
|
|
504
|
+
process.stderr.write(`[cyberquant-mcp] \u542F\u52A8\u5931\u8D25\uFF1A${err}
|
|
505
|
+
`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
});
|
|
508
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lib/config.ts","../src/types/index.ts","../src/lib/errors.ts","../src/lib/api-client.ts","../src/server.ts","../src/tools/configure.ts","../src/lib/state.ts","../src/tools/list.ts","../src/tools/query.ts","../src/resources/user-profile.ts","../src/resources/route-list.ts"],"sourcesContent":["// ============================================================\n// cyberquant-mcp 入口 —— stdio 传输\n// ============================================================\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './lib/config.js';\nimport { ApiClient } from './lib/api-client.js';\nimport type { ServerState } from './lib/state.js';\nimport { createServer } from './server.js';\n\nasync function main() {\n const config = loadConfig();\n\n const state: ServerState = {\n config,\n client: config ? new ApiClient(config) : null,\n };\n\n if (config) {\n process.stderr.write(\n `[cyberquant-mcp] 启动中... endpoint=${config.endpoint}, pageSize=${config.mcp.pageSize}, timeout=${config.mcp.timeout}\\n`,\n );\n } else {\n process.stderr.write(\n '[cyberquant-mcp] 未检测到配置文件,请通过 configure 工具配置 API Key\\n',\n );\n }\n\n const server = createServer(state);\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write('[cyberquant-mcp] 已启动,通过 stdio 等待 MCP 连接\\n');\n}\n\nmain().catch((err) => {\n process.stderr.write(`[cyberquant-mcp] 启动失败:${err}\\n`);\n process.exit(1);\n});\n","// ============================================================\n// 配置管理 —— 共用 ~/.cyberquant/config.json\n// ============================================================\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { AppConfig, McpConfig } from '../types/index.js';\nimport { MCP_DEFAULTS, PAGE_SIZE_MAX } from '../types/index.js';\n\nconst CONFIG_DIR = path.join(os.homedir(), '.cyberquant');\nconst CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\n\n/** 默认 API Gateway 端点 */\nexport const DEFAULT_ENDPOINT = 'https://api.cyberspace2077.com';\n\ninterface RawConfig {\n endpoint?: string;\n apiKey?: string;\n mcp?: Partial<McpConfig>;\n}\n\n/** 读取原始配置文件 */\nfunction readRawConfig(): RawConfig | null {\n try {\n if (!fs.existsSync(CONFIG_FILE)) return null;\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n return JSON.parse(raw) as RawConfig;\n } catch {\n // 配置文件格式错误,视为无配置\n return null;\n }\n}\n\n/** 自动补全 mcp 字段(缺失时写回文件) */\nfunction ensureMcpField(raw: RawConfig): void {\n if (raw.mcp && raw.mcp.pageSize !== undefined && raw.mcp.timeout !== undefined) {\n return;\n }\n raw.mcp = { ...MCP_DEFAULTS, ...raw.mcp };\n try {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(raw, null, 2), 'utf-8');\n } catch {\n // 写入失败不影响运行,使用默认值即可\n }\n}\n\n/** 从原始配置构建 AppConfig(不抛异常) */\nfunction buildConfig(raw: RawConfig): AppConfig | null {\n if (!raw.endpoint || !raw.apiKey) return null;\n\n ensureMcpField(raw);\n\n const mcp: McpConfig = {\n pageSize: raw.mcp?.pageSize ?? MCP_DEFAULTS.pageSize,\n timeout: raw.mcp?.timeout ?? MCP_DEFAULTS.timeout,\n };\n\n if (mcp.pageSize > PAGE_SIZE_MAX) {\n process.stderr.write(\n `[cyberquant-mcp] 警告:mcp.pageSize=${mcp.pageSize} 超过上限 ${PAGE_SIZE_MAX},查询时将返回警告提示\\n`,\n );\n }\n\n return {\n endpoint: raw.endpoint.replace(/\\/+$/, ''),\n apiKey: raw.apiKey,\n mcp,\n };\n}\n\n/** 加载配置,无配置或缺少必填字段时返回 null */\nexport function loadConfig(): AppConfig | null {\n const raw = readRawConfig();\n if (!raw) return null;\n return buildConfig(raw);\n}\n\n/** 保存配置并返回 AppConfig(用于 configure tool) */\nexport function saveConfig(apiKey: string, endpoint?: string): AppConfig {\n const config: RawConfig = {\n endpoint: (endpoint ?? DEFAULT_ENDPOINT).replace(/\\/+$/, ''),\n apiKey,\n mcp: { ...MCP_DEFAULTS },\n };\n\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n }\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n\n return {\n endpoint: config.endpoint!,\n apiKey: config.apiKey,\n mcp: { ...MCP_DEFAULTS },\n };\n}\n","// ============================================================\n// cyberquant-mcp 类型定义\n// ============================================================\n\n/** MCP 专有配置 */\nexport interface McpConfig {\n pageSize: number;\n timeout: number;\n}\n\n/** 应用配置(共用 ~/.cyberquant/config.json) */\nexport interface AppConfig {\n endpoint: string;\n apiKey: string;\n mcp: McpConfig;\n}\n\n/** MCP 配置默认值 */\nexport const MCP_DEFAULTS: McpConfig = {\n pageSize: 200,\n timeout: 30000,\n};\n\n/** pageSize 上限 */\nexport const PAGE_SIZE_MAX = 1000;\n\n// ---- API Gateway 响应类型 ----\n\nexport interface Pagination {\n nextCursor: string | null;\n hasMore: boolean;\n pageSize: number;\n}\n\nexport interface ApiMeta {\n route?: string;\n count?: number;\n total?: number;\n pagination?: Pagination;\n userTier?: number;\n userMarkets?: string[];\n}\n\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n code?: string;\n meta?: ApiMeta;\n}\n\n// ---- 用户信息 ----\n\nexport interface UserProfile {\n email: string;\n tier: { level: number; name: string };\n markets: { code: string; name: string }[];\n expiresAt: string | null;\n isActive: boolean;\n rateLimit: { windowMs: number; maxRequests: number };\n}\n\n// ---- 路由列表 ----\n\nexport interface QueryParam {\n name: string;\n type: 'string' | 'number' | 'date' | 'boolean';\n required: boolean;\n desc: string;\n}\n\nexport interface ResponseParam {\n name: string;\n type: 'string' | 'number' | 'date' | 'boolean';\n desc: string;\n}\n\nexport interface RouteInfo {\n routeSlug: string;\n displayName: string;\n description: string;\n category: string;\n marketType: string;\n requiredTier: number;\n queryParams: QueryParam[];\n responseParams: ResponseParam[];\n}\n\nexport interface RouteListMeta {\n total: number;\n userTier: number;\n userMarkets: string[];\n}\n","// ============================================================\n// 错误类型定义\n// ============================================================\n\nexport class McpError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'McpError';\n }\n}\n\nexport class ConfigError extends McpError {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nexport class ApiError extends McpError {\n constructor(\n public readonly statusCode: number,\n message: string,\n ) {\n super(message);\n this.name = 'ApiError';\n }\n}\n\n/** HTTP 状态码 → 中文错误消息 */\nconst ERROR_MESSAGES: Record<number, string> = {\n 400: '参数校验失败',\n 401: 'API Key 无效或已过期',\n 403: '权限不足',\n 404: '未找到 API 路由',\n 429: '请求频率超限',\n 500: '服务器内部错误',\n 503: '服务暂不可用',\n};\n\n/** 将 HTTP 状态码映射为 ApiError */\nexport function mapApiError(status: number, detail?: string): ApiError {\n const base = ERROR_MESSAGES[status] ?? `请求失败 (${status})`;\n return new ApiError(status, detail ? `${base}: ${detail}` : base);\n}\n","// ============================================================\n// API Gateway HTTP 客户端(含重试 + 超时)\n// ============================================================\n\nimport type {\n AppConfig,\n ApiResponse,\n UserProfile,\n RouteInfo,\n RouteListMeta,\n} from '../types/index.js';\nimport { mapApiError } from './errors.js';\n\nexport class ApiClient {\n private readonly endpoint: string;\n private readonly apiKey: string;\n private readonly timeout: number;\n\n constructor(config: AppConfig) {\n this.endpoint = config.endpoint;\n this.apiKey = config.apiKey;\n this.timeout = config.mcp.timeout;\n }\n\n /** 通用请求(含 429/503 重试) */\n private async request<T>(path: string): Promise<T> {\n const url = `${this.endpoint}${path}`;\n const maxRetries = 3;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const res = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n 'X-Client-Type': 'mcp',\n },\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (res.ok) {\n return (await res.json()) as T;\n }\n\n // 429 / 503 自动重试\n if ((res.status === 429 || res.status === 503) && attempt < maxRetries) {\n // 消耗响应体以避免 Socket 泄漏\n await res.body?.cancel().catch(() => {});\n const retryAfter = res.headers.get('Retry-After');\n const delay = retryAfter\n ? Number(retryAfter) * 1000\n : Math.pow(2, attempt) * 1000;\n await new Promise((r) => setTimeout(r, Math.min(delay, 10000)));\n continue;\n }\n\n // 其他错误:提取错误详情\n let detail = '';\n try {\n const body = (await res.json()) as ApiResponse;\n detail = body.error ?? '';\n } catch {\n // JSON 解析失败,使用空详情\n }\n throw mapApiError(res.status, detail);\n }\n\n // 重试耗尽\n throw mapApiError(503, '重试次数已用完,请稍后再试');\n }\n\n /** 获取用户信息 */\n async getProfile(): Promise<ApiResponse<UserProfile>> {\n return this.request('/api/v1/me');\n }\n\n /** 获取可用路由列表 */\n async listRoutes(): Promise<ApiResponse<RouteInfo[]> & { meta?: RouteListMeta }> {\n return this.request('/api/v1/api-list');\n }\n\n /** 查询数据 */\n async queryData(\n routeSlug: string,\n params: Record<string, string | number | boolean | undefined | (string | number)[]>,\n pageSize: number,\n ): Promise<ApiResponse<Record<string, unknown>[]>> {\n const qs = new URLSearchParams();\n qs.set('pageSize', String(pageSize));\n\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === '') continue;\n if (Array.isArray(v)) {\n for (const item of v) {\n if (item !== undefined && item !== '') qs.append(k, String(item));\n }\n } else {\n qs.set(k, String(v));\n }\n }\n\n const query = qs.toString();\n return this.request(`/api/v1/data/${routeSlug}${query ? '?' + query : ''}`);\n }\n}\n","// ============================================================\n// MCP Server —— 注册 Tools + Resources\n// ============================================================\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerState } from './lib/state.js';\nimport { registerConfigureTool } from './tools/configure.js';\nimport { registerListRoutesTool } from './tools/list.js';\nimport { registerQueryDataTool } from './tools/query.js';\nimport { createUserProfileResource } from './resources/user-profile.js';\nimport { createRouteListResource } from './resources/route-list.js';\n\nexport function createServer(state: ServerState): McpServer {\n const server = new McpServer({\n name: 'cyberquant-mcp',\n version: '0.1.0',\n });\n\n // 注册 Tools\n registerConfigureTool(server, state);\n registerListRoutesTool(server, state);\n registerQueryDataTool(server, state);\n\n // 注册 Resources\n createUserProfileResource(server, state);\n createRouteListResource(server, state);\n\n return server;\n}\n","// ============================================================\n// Tool: configure — 配置 API Key(首次使用时调用)\n// ============================================================\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerState } from '../lib/state.js';\nimport { saveConfig, DEFAULT_ENDPOINT } from '../lib/config.js';\nimport { ApiClient } from '../lib/api-client.js';\n\nexport function registerConfigureTool(server: McpServer, state: ServerState): void {\n server.tool(\n 'configure',\n '配置 API Key 以访问数据服务。首次使用时必须调用此工具完成配置。endpoint 默认为 https://api.cyberspace2077.com。',\n {\n apiKey: z.string().describe('API Key,格式为 sk_live_xxx 或 sk_test_xxx'),\n endpoint: z\n .string()\n .optional()\n .describe(`API Gateway 地址,默认 ${DEFAULT_ENDPOINT}`),\n },\n async ({ apiKey, endpoint }) => {\n try {\n const config = saveConfig(apiKey, endpoint);\n\n // 更新共享状态,后续调用立即可用\n state.config = config;\n state.client = new ApiClient(config);\n\n process.stderr.write(\n `[cyberquant-mcp] 配置已更新:endpoint=${config.endpoint}, pageSize=${config.mcp.pageSize}\\n`,\n );\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `配置成功!endpoint: ${config.endpoint},现在可以使用 list_routes 查看可用数据路由,或使用 query_data 查询数据。`,\n },\n ],\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : '未知错误';\n return {\n content: [\n {\n type: 'text' as const,\n text: `配置保存失败:${msg}。请检查是否有写入 ~/.cyberquant/ 目录的权限。`,\n },\n ],\n };\n }\n },\n );\n}\n","// ============================================================\n// 共享可变状态 —— Tools/Resources 通过此对象访问 ApiClient\n// ============================================================\n\nimport type { AppConfig } from '../types/index.js';\nimport type { ApiClient } from './api-client.js';\n\nexport interface ServerState {\n config: AppConfig | null;\n client: ApiClient | null;\n}\n\n/** 无配置时的统一提示文案 */\nexport const NO_CONFIG_HINT =\n '未检测到 API Key 配置。请先调用 configure 工具,传入 apiKey 参数完成配置。endpoint 默认为 https://api.cyberspace2077.com,也可自定义。';\n","// ============================================================\n// Tool: list_routes — 列出可用路由及参数 Schema\n// ============================================================\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerState } from '../lib/state.js';\nimport { NO_CONFIG_HINT } from '../lib/state.js';\nimport type { RouteInfo } from '../types/index.js';\n\n/** 格式化单个路由为自然语言描述 */\nfunction formatRoute(route: RouteInfo): string {\n const lines: string[] = [\n `【${route.displayName}】routeSlug: ${route.routeSlug}`,\n `说明:${route.description}`,\n `分类:${route.category}`,\n ];\n\n if (route.queryParams.length > 0) {\n lines.push('查询参数:');\n for (const p of route.queryParams) {\n const required = p.required ? '必填' : '可选';\n lines.push(` - ${p.name} (${p.type}, ${required}): ${p.desc}`);\n }\n }\n\n if (route.responseParams.length > 0) {\n lines.push('返回字段:');\n for (const f of route.responseParams) {\n lines.push(` - ${f.name} (${f.type}): ${f.desc}`);\n }\n }\n\n return lines.join('\\n');\n}\n\nexport function registerListRoutesTool(server: McpServer, state: ServerState): void {\n server.tool(\n 'list_routes',\n '列出当前用户可用的数据路由及其参数 Schema,包含每个路由的查询参数和返回字段说明',\n {},\n async () => {\n if (!state.client) {\n return { content: [{ type: 'text' as const, text: NO_CONFIG_HINT }] };\n }\n\n try {\n const res = await state.client.listRoutes();\n\n if (!res.success || !res.data) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `获取路由列表失败:${res.error ?? '未知错误'}。请检查 API Key 配置是否正确。`,\n },\n ],\n };\n }\n\n const routes = res.data;\n const total = res.meta?.total ?? routes.length;\n\n const header = `可用数据路由(共 ${total} 个):\\n`;\n const body = routes.map(formatRoute).join('\\n\\n');\n const footer = total > 0\n ? '\\n\\n使用 query_data 工具查询指定路由的数据,传入 routeSlug 和查询参数。'\n : '';\n\n return {\n content: [{ type: 'text' as const, text: header + body + footer }],\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : '未知错误';\n return {\n content: [\n { type: 'text' as const, text: `获取路由列表失败:${msg}` },\n ],\n };\n }\n },\n );\n}\n","// ============================================================\n// Tool: query_data — 数据查询(CSV 格式输出)\n// ============================================================\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerState } from '../lib/state.js';\nimport { NO_CONFIG_HINT } from '../lib/state.js';\nimport { PAGE_SIZE_MAX } from '../types/index.js';\n\n/** 将 JSON 数组转为 CSV 字符串(表头取首条数据字段名) */\nfunction toCsv(rows: Record<string, unknown>[]): string {\n if (rows.length === 0) return '';\n const headers = Object.keys(rows[0]);\n const headerLine = headers.join(',');\n const dataLines = rows.map((row) =>\n headers\n .map((h) => {\n const val = row[h];\n if (val === null || val === undefined) return '';\n const str = String(val);\n // CSV 转义:含逗号、引号、换行时用双引号包裹\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n })\n .join(','),\n );\n return [headerLine, ...dataLines].join('\\n');\n}\n\n/** pageSize 超限警告 */\nconst PAGE_SIZE_WARNING = (n: number) =>\n `⚠️ 当前配置的单次查询数量为 ${n} 条,超过上限 ${PAGE_SIZE_MAX} 条。大模型不擅长直接在庞大的数值矩阵中做复杂的数学运算(如精确计算长期均线、RSI、布林带等),建议:\n1. 将 mcp.pageSize 调整为 ${PAGE_SIZE_MAX} 以内(推荐 200)\n2. 使用编程脚本(Python/Node.js)配合 cyberquant-cli 处理大数据量任务\n3. 缩小查询参数范围,获取更精准的数据子集`;\n\n/** hasMore 提示 */\nconst HAS_MORE_HINT =\n '⚠️ 当前查询范围下还有更多数据未返回。建议缩小查询参数范围(如缩小日期区间、指定具体代码等)以获取精确的数据子集,大模型更适合分析精准的小数据集。';\n\n/** 无数据提示 */\nconst NO_DATA_HINT =\n '未查询到符合条件的数据。请检查查询参数是否正确,可通过 list_routes 工具查看该路由支持的参数说明。';\n\nexport function registerQueryDataTool(server: McpServer, state: ServerState): void {\n server.tool(\n 'query_data',\n '查询指定路由的数据,返回 CSV 格式。使用 list_routes 查看可用路由和参数说明。',\n {\n routeSlug: z.string().describe('路由标识,如 \"daily-stock\"。通过 list_routes 获取可用路由。'),\n params: z\n .record(z.union([z.string(), z.number(), z.boolean(), z.array(z.union([z.string(), z.number()]))]))\n .optional()\n .describe('查询参数,键值对透传给 API。具体参数参考 list_routes 返回的路由说明。'),\n },\n async ({ routeSlug, params }) => {\n if (!state.client || !state.config) {\n return { content: [{ type: 'text' as const, text: NO_CONFIG_HINT }] };\n }\n\n const pageSize = state.config.mcp.pageSize;\n\n // 前置校验:pageSize 上限拦截\n if (pageSize > PAGE_SIZE_MAX) {\n return {\n content: [{ type: 'text' as const, text: PAGE_SIZE_WARNING(pageSize) }],\n };\n }\n\n try {\n const res = await state.client.queryData(routeSlug, params ?? {}, pageSize);\n\n if (!res.success) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `查询失败:${res.error ?? '未知错误'}。请检查路由标识和参数是否正确。`,\n },\n ],\n };\n }\n\n const rows = res.data;\n const count = res.meta?.count ?? (Array.isArray(rows) ? rows.length : 0);\n const hasMore = res.meta?.pagination?.hasMore ?? false;\n\n // 无数据\n if (!Array.isArray(rows) || rows.length === 0) {\n return {\n content: [{ type: 'text' as const, text: NO_DATA_HINT }],\n };\n }\n\n // 拼接结果:CSV + 统计 + 引导\n const parts: string[] = [toCsv(rows), '', `本次查询返回 ${count} 条数据。`];\n if (hasMore) {\n parts.push(HAS_MORE_HINT);\n }\n\n return {\n content: [{ type: 'text' as const, text: parts.join('\\n') }],\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : '未知错误';\n return {\n content: [{ type: 'text' as const, text: `查询失败:${msg}` }],\n };\n }\n },\n );\n}\n","// ============================================================\n// Resource: cyberquant://user/profile\n// ============================================================\n\nimport type { URL } from 'node:url';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerState } from '../lib/state.js';\n\nexport function createUserProfileResource(server: McpServer, state: ServerState): void {\n server.resource(\n 'user-profile',\n 'cyberquant://user/profile',\n {\n description: '当前用户信息(等级、市场权限、到期时间、速率限制)',\n mimeType: 'application/json',\n },\n async (uri: URL) => {\n if (!state.client) {\n return {\n contents: [\n {\n uri: uri.href,\n text: JSON.stringify({\n error: '未检测到 API Key 配置,请先调用 configure 工具完成配置。',\n }),\n },\n ],\n };\n }\n\n const res = await state.client.getProfile();\n if (!res.success || !res.data) {\n return {\n contents: [\n {\n uri: uri.href,\n text: JSON.stringify({ error: res.error ?? '获取用户信息失败' }),\n },\n ],\n };\n }\n return {\n contents: [\n {\n uri: uri.href,\n text: JSON.stringify(res.data, null, 2),\n },\n ],\n };\n },\n );\n}\n","// ============================================================\n// Resource: cyberquant://routes\n// ============================================================\n\nimport type { URL } from 'node:url';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerState } from '../lib/state.js';\n\nexport function createRouteListResource(server: McpServer, state: ServerState): void {\n server.resource(\n 'routes',\n 'cyberquant://routes',\n {\n description: '当前用户可用的数据路由列表',\n mimeType: 'application/json',\n },\n async (uri: URL) => {\n if (!state.client) {\n return {\n contents: [\n {\n uri: uri.href,\n text: JSON.stringify({\n error: '未检测到 API Key 配置,请先调用 configure 工具完成配置。',\n }),\n },\n ],\n };\n }\n\n const res = await state.client.listRoutes();\n if (!res.success || !res.data) {\n return {\n contents: [\n {\n uri: uri.href,\n text: JSON.stringify({ error: res.error ?? '获取路由列表失败' }),\n },\n ],\n };\n }\n return {\n contents: [\n {\n uri: uri.href,\n text: JSON.stringify(res.data, null, 2),\n },\n ],\n };\n },\n );\n}\n"],"mappings":"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","names":["path","z","z"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cyberquant-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP Server for CyberQuant Data Sharing Platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cyberquant-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"prepublishOnly": "pnpm build"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
19
|
+
"zod": "^3.24.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"tsup": "^8.4.0",
|
|
23
|
+
"tsx": "^4.19.0",
|
|
24
|
+
"typescript": "^5.7.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|