cyberquant-mcp 0.1.1 → 0.1.2

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 CHANGED
@@ -10,12 +10,13 @@ CyberQuant 数据共享平台的 MCP(Model Context Protocol)服务器,让
10
10
 
11
11
  ## 功能特性
12
12
 
13
- ### MCP Tools(3 个)
13
+ ### MCP Tools(4 个)
14
14
 
15
15
  | 工具 | 说明 |
16
16
  |------|------|
17
17
  | `configure` | 配置 API Key,首次使用时调用 |
18
- | `list_routes` | 列出当前用户可用的数据路由及参数 Schema |
18
+ | `list_routes` | 列出当前用户可用的数据路由**目录**(仅元信息,不含入参/返回字段) |
19
+ | `get_route_detail` | 查询单个路由的入参/返回字段详情,由大模型据此自行组织 `query_data` 参数 |
19
20
  | `query_data` | 查询指定路由数据,返回 **CSV 格式**(节省 token) |
20
21
 
21
22
  ### MCP Resources(2 个)
@@ -28,6 +29,7 @@ CyberQuant 数据共享平台的 MCP(Model Context Protocol)服务器,让
28
29
  ### 设计亮点
29
30
 
30
31
  - **CSV 输出**:相比 JSON 节省 40–60% token,表格结构天然适合 AI 分析
32
+ - **路由目录 + 按需详情**:`list_routes` 仅输出目录级元信息,`get_route_detail` 按需取详情,避免上百路由全量入参一次性占满上下文(路由列表内存缓存 2 分钟,list→detail 连贯调用零重复网络)
31
33
  - **自然语言引导**:数据返回附带提示,引导 AI 缩小查询范围而非暴力翻页
32
34
  - **pageSize 上限保护**:超过 1000 条自动拦截,避免 AI 处理超大数据集
33
35
  - **运行时配置**:通过 `configure` 工具动态更新 API Key,无需重启
@@ -85,7 +87,7 @@ MCP Server 与 `cyberquant-cli` 共用配置文件 `~/.cyberquant/config.json`
85
87
  帮我查一下平安银行最近一周的日K线数据
86
88
  ```
87
89
 
88
- AI 会依次调用 `list_routes` → `query_data`,并以表格形式返回分析结果。更多场景见 [使用示例](./examples/usage-examples.md)。
90
+ AI 会依次调用 `list_routes` → `get_route_detail` → `query_data`,并以表格形式返回分析结果。更多场景见 [使用示例](./examples/usage-examples.md)。
89
91
 
90
92
  ## 文档
91
93
 
package/dist/index.js CHANGED
@@ -112,10 +112,13 @@ function mapApiError(status, detail) {
112
112
  }
113
113
 
114
114
  // src/lib/api-client.ts
115
+ var ROUTES_CACHE_TTL_MS = 12e4;
115
116
  var ApiClient = class {
116
117
  endpoint;
117
118
  apiKey;
118
119
  timeout;
120
+ /** 路由列表内存缓存(仅成功响应才回填,失败不污染) */
121
+ routesCache = null;
119
122
  constructor(config) {
120
123
  this.endpoint = config.endpoint;
121
124
  this.apiKey = config.apiKey;
@@ -159,9 +162,17 @@ var ApiClient = class {
159
162
  async getProfile() {
160
163
  return this.request("/api/v1/me");
161
164
  }
162
- /** 获取可用路由列表 */
165
+ /** 获取可用路由列表(命中 2 分钟内存缓存时零网络) */
163
166
  async listRoutes() {
164
- return this.request("/api/v1/api-list");
167
+ const now = Date.now();
168
+ if (this.routesCache && now < this.routesCache.expiresAt) {
169
+ return this.routesCache.data;
170
+ }
171
+ const res = await this.request("/api/v1/api-list");
172
+ if (res.success) {
173
+ this.routesCache = { data: res, expiresAt: now + ROUTES_CACHE_TTL_MS };
174
+ }
175
+ return res;
165
176
  }
166
177
  /** 查询数据 */
167
178
  async queryData(routeSlug, params, pageSize) {
@@ -232,37 +243,22 @@ var NO_CONFIG_HINT = "\u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E\u3002\u8BF7\
232
243
 
233
244
  // src/tools/list.ts
234
245
  function formatRoute(route) {
235
- const lines = [
246
+ return [
236
247
  `\u3010${route.displayName}\u3011routeSlug: ${route.routeSlug}`,
237
248
  `\u8BF4\u660E\uFF1A${route.description}`,
238
249
  `\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");
250
+ ].join("\n");
254
251
  }
255
- var PARAM_FORMAT_GUIDE = [
256
- "\u901A\u7528\u4F20\u503C\u683C\u5F0F\uFF08\u6309\u53C2\u6570 type\uFF09\uFF1A",
257
- "- string\uFF1A\u5355\u503C key=v\uFF1B\u591A\u503C\u7528\u9017\u53F7 key=v1,v2,v3\uFF08\u2264100\uFF09\u6216 URL \u6570\u7EC4 key=v1&key=v2",
258
- "- number\uFF1A\u5355\u503C key=1\uFF1B\u591A\u503C\u7528\u9017\u53F7 key=1,5,30\uFF08\u2264100 \u9879\uFF09",
259
- "- date\uFF1A\u5355\u503C key=yyyy-MM-dd\uFF1B\u8303\u56F4 key=d1&key=d2\uFF08>= AND <=\uFF09\uFF1B\u540C\u65F6\u652F\u6301 yyyy-MM-dd HH:mm:ss",
260
- "\u6570\u7EC4/\u8303\u56F4\u8BF7\u4F20 JS \u6570\u7EC4\uFF0C\u9017\u53F7\u5206\u9694\u5219\u4F20\u5B57\u7B26\u4E32\u3002"
252
+ var NEXT_STEP_HINT = [
253
+ "\u4EE5\u4E0A\u4E3A\u8DEF\u7531\u76EE\u5F55\uFF0C**\u4E0D\u542B\u5165\u53C2/\u8FD4\u56DE\u5B57\u6BB5\u8BE6\u60C5**\u3002",
254
+ "\u8BF7\u6309\u4EE5\u4E0B\u6D41\u7A0B\u64CD\u4F5C\uFF1A",
255
+ '1. \u8C03\u7528 get_route_detail(routeSlug="...") \u83B7\u53D6\u76EE\u6807\u8DEF\u7531\u7684\u67E5\u8BE2\u53C2\u6570\u4E0E\u8FD4\u56DE\u5B57\u6BB5\u8BF4\u660E',
256
+ "2. \u6839\u636E get_route_detail \u8FD4\u56DE\u7684\u53C2\u6570\u8BF4\u660E\u4E0E\u4F20\u503C\u683C\u5F0F\uFF0C\u81EA\u884C\u7EC4\u7EC7 params \u540E\u8C03\u7528 query_data \u67E5\u8BE2\u6570\u636E"
261
257
  ].join("\n");
262
258
  function registerListRoutesTool(server, state) {
263
259
  server.tool(
264
260
  "list_routes",
265
- "\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",
261
+ "\u5217\u51FA\u5F53\u524D\u7528\u6237\u53EF\u7528\u7684\u6570\u636E\u8DEF\u7531\u76EE\u5F55\uFF08\u4EC5\u542B routeSlug\u3001\u540D\u79F0\u3001\u8BF4\u660E\u3001\u5206\u7C7B\uFF0C\u4E0D\u542B\u5165\u53C2/\u8FD4\u56DE\u5B57\u6BB5\uFF09\u3002\u5982\u9700\u5165\u53C2/\u8FD4\u56DE\u5B57\u6BB5\u8BE6\u60C5\uFF0C\u8BF7\u4F7F\u7528 get_route_detail \u5DE5\u5177\u3002",
266
262
  {},
267
263
  async () => {
268
264
  if (!state.client) {
@@ -285,12 +281,11 @@ function registerListRoutesTool(server, state) {
285
281
  const header = `\u53EF\u7528\u6570\u636E\u8DEF\u7531\uFF08\u5171 ${total} \u4E2A\uFF09\uFF1A
286
282
  `;
287
283
  const body = routes.map(formatRoute).join("\n\n");
288
- const guide = total > 0 ? `
284
+ const footer = total > 0 ? `
289
285
 
290
- ${PARAM_FORMAT_GUIDE}` : "";
291
- 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" : "";
286
+ ${NEXT_STEP_HINT}` : "";
292
287
  return {
293
- content: [{ type: "text", text: header + body + guide + footer }]
288
+ content: [{ type: "text", text: header + body + footer }]
294
289
  };
295
290
  } catch (err) {
296
291
  const msg = err instanceof Error ? err.message : "\u672A\u77E5\u9519\u8BEF";
@@ -304,8 +299,103 @@ ${PARAM_FORMAT_GUIDE}` : "";
304
299
  );
305
300
  }
306
301
 
307
- // src/tools/query.ts
302
+ // src/tools/detail.ts
308
303
  import { z as z2 } from "zod";
304
+ var PARAM_FORMAT_GUIDE = [
305
+ "\u4F20\u503C\u683C\u5F0F\uFF08\u6309\u53C2\u6570 type\uFF09\uFF1A",
306
+ '- string\uFF1A\u5355\u503C\u4F20\u5B57\u7B26\u4E32 "v"\uFF1B\u591A\u503C\u4F20\u5B57\u7B26\u4E32\u6570\u7EC4 ["v1","v2"]\uFF0C\u6216\u9017\u53F7\u5206\u9694\u5B57\u7B26\u4E32 "v1,v2,v3"\uFF08\u2264100 \u9879\uFF09',
307
+ '- number\uFF1A\u5355\u503C\u4F20\u6570\u5B57 1\uFF1B\u591A\u503C\u4F20\u6570\u5B57\u6570\u7EC4 [1,5,30]\uFF0C\u6216\u9017\u53F7\u5206\u9694\u5B57\u7B26\u4E32 "1,5,30"\uFF08\u2264100 \u9879\uFF09',
308
+ '- date\uFF1A\u5355\u503C\u4F20\u5B57\u7B26\u4E32 "2026-05-01"\uFF1B\u8303\u56F4\u4F20\u4E24\u5143\u7D20\u6570\u7EC4 ["2026-05-01","2026-05-07"]\uFF08\u8BED\u4E49 >= AND <=\uFF09',
309
+ " \u652F\u6301\u683C\u5F0F\uFF1Ayyyy-MM-dd \u6216 yyyy-MM-dd HH:mm:ss"
310
+ ].join("\n");
311
+ var CALL_HINT = [
312
+ "\u8BF7\u6839\u636E\u4E0A\u8FF0\u67E5\u8BE2\u53C2\u6570\u8BF4\u660E\u4E0E\u901A\u7528\u4F20\u503C\u683C\u5F0F\uFF0C\u7ED3\u5408\u7528\u6237\u5B9E\u9645\u610F\u56FE\uFF08\u4F8B\u5982\u6307\u5B9A\u7684\u4EE3\u7801\u3001\u65F6\u95F4\u8303\u56F4\u7B49\uFF09\uFF1A",
313
+ "1. \u81EA\u884C\u5224\u65AD\u9700\u8981\u4F20\u54EA\u4E9B\u53C2\u6570\uFF08\u5FC5\u586B\u7684\u52A1\u5FC5\u5E26\u4E0A\uFF0C\u53EF\u9009\u7684\u6309\u9700\uFF09",
314
+ "2. \u6309\u5404\u53C2\u6570\u5BF9\u5E94\u7684 type \u9009\u62E9\u6B63\u786E\u7684\u4F20\u503C\u5F62\u5F0F\uFF08\u5355\u503C / \u591A\u503C / \u8303\u56F4\uFF09",
315
+ "3. \u8C03\u7528 query_data(routeSlug, params) \u83B7\u53D6\u6570\u636E",
316
+ "\u6CE8\uFF1ApageSize \u7531 mcp.pageSize \u914D\u7F6E\u63A7\u5236\uFF0C\u4E0D\u8981\u5199\u5165 params\u3002"
317
+ ].join("\n");
318
+ function formatQueryParams(params) {
319
+ if (params.length === 0) return "\u67E5\u8BE2\u53C2\u6570\uFF1A\uFF08\u65E0\uFF09";
320
+ const lines = ["\u67E5\u8BE2\u53C2\u6570\uFF1A"];
321
+ for (const p of params) {
322
+ const required = p.required ? "\u5FC5\u586B" : "\u53EF\u9009";
323
+ lines.push(` - ${p.name} (${p.type}, ${required}): ${p.desc}`);
324
+ }
325
+ return lines.join("\n");
326
+ }
327
+ function formatResponseParams(params) {
328
+ if (params.length === 0) return "\u8FD4\u56DE\u5B57\u6BB5\uFF1A\uFF08\u65E0\uFF09";
329
+ const lines = ["\u8FD4\u56DE\u5B57\u6BB5\uFF1A"];
330
+ for (const f of params) {
331
+ lines.push(` - ${f.name} (${f.type}): ${f.desc}`);
332
+ }
333
+ return lines.join("\n");
334
+ }
335
+ function registerGetRouteDetailTool(server, state) {
336
+ server.tool(
337
+ "get_route_detail",
338
+ "\u67E5\u8BE2\u6307\u5B9A\u8DEF\u7531\u7684\u5165\u53C2\u4E0E\u8FD4\u56DE\u5B57\u6BB5\u8BE6\u60C5\u3002\u5148\u7528 list_routes \u627E\u5230 routeSlug\uFF0C\u518D\u7528\u672C\u5DE5\u5177\u62FF\u53C2\u6570\u8BF4\u660E\uFF0C\u6700\u540E\u7531\u5927\u6A21\u578B\u6839\u636E\u8BF4\u660E\u81EA\u884C\u7EC4\u7EC7 params \u8C03\u7528 query_data\u3002",
339
+ {
340
+ routeSlug: z2.string().describe('\u8DEF\u7531\u6807\u8BC6\uFF0C\u5982 "daily-stock"\u3002\u901A\u8FC7 list_routes \u83B7\u53D6\u53EF\u7528 routeSlug\u3002')
341
+ },
342
+ async ({ routeSlug }) => {
343
+ if (!state.client) {
344
+ return { content: [{ type: "text", text: NO_CONFIG_HINT }] };
345
+ }
346
+ try {
347
+ const res = await state.client.listRoutes();
348
+ if (!res.success || !res.data) {
349
+ return {
350
+ content: [
351
+ {
352
+ type: "text",
353
+ text: `\u83B7\u53D6\u8DEF\u7531\u8BE6\u60C5\u5931\u8D25\uFF1A${res.error ?? "\u672A\u77E5\u9519\u8BEF"}\u3002\u8BF7\u68C0\u67E5 API Key \u914D\u7F6E\u662F\u5426\u6B63\u786E\u3002`
354
+ }
355
+ ]
356
+ };
357
+ }
358
+ const route = res.data.find((r) => r.routeSlug === routeSlug);
359
+ if (!route) {
360
+ return {
361
+ content: [
362
+ {
363
+ type: "text",
364
+ text: `\u672A\u627E\u5230 routeSlug="${routeSlug}" \u5BF9\u5E94\u7684\u8DEF\u7531\u3002\u8BF7\u8C03\u7528 list_routes \u5DE5\u5177\u786E\u8BA4 routeSlug \u662F\u5426\u6B63\u786E\uFF08\u6CE8\u610F\u5927\u5C0F\u5199\u4E0E\u8FDE\u5B57\u7B26\uFF09\u3002`
365
+ }
366
+ ]
367
+ };
368
+ }
369
+ const sections = [
370
+ `\u3010${route.displayName}\u3011routeSlug: ${route.routeSlug}`,
371
+ `\u8BF4\u660E\uFF1A${route.description}`,
372
+ `\u5206\u7C7B\uFF1A${route.category}`,
373
+ "",
374
+ formatQueryParams(route.queryParams),
375
+ "",
376
+ formatResponseParams(route.responseParams),
377
+ "",
378
+ PARAM_FORMAT_GUIDE,
379
+ "",
380
+ CALL_HINT
381
+ ];
382
+ return {
383
+ content: [{ type: "text", text: sections.join("\n") }]
384
+ };
385
+ } catch (err) {
386
+ const msg = err instanceof Error ? err.message : "\u672A\u77E5\u9519\u8BEF";
387
+ return {
388
+ content: [
389
+ { type: "text", text: `\u83B7\u53D6\u8DEF\u7531\u8BE6\u60C5\u5931\u8D25\uFF1A${msg}` }
390
+ ]
391
+ };
392
+ }
393
+ }
394
+ );
395
+ }
396
+
397
+ // src/tools/query.ts
398
+ import { z as z3 } from "zod";
309
399
  function toCsv(rows) {
310
400
  if (rows.length === 0) return "";
311
401
  const headers = Object.keys(rows[0]);
@@ -328,21 +418,14 @@ var PAGE_SIZE_WARNING = (n) => `\u26A0\uFE0F \u5F53\u524D\u914D\u7F6E\u7684\u535
328
418
  2. \u4F7F\u7528\u7F16\u7A0B\u811A\u672C\uFF08Python/Node.js\uFF09\u914D\u5408 cyberquant-cli \u5904\u7406\u5927\u6570\u636E\u91CF\u4EFB\u52A1
329
419
  3. \u7F29\u5C0F\u67E5\u8BE2\u53C2\u6570\u8303\u56F4\uFF0C\u83B7\u53D6\u66F4\u7CBE\u51C6\u7684\u6570\u636E\u5B50\u96C6`;
330
420
  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";
331
- 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";
421
+ 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 get_route_detail \u5DE5\u5177\u67E5\u770B\u8BE5\u8DEF\u7531\u652F\u6301\u7684\u53C2\u6570\u8BF4\u660E\u3002";
332
422
  function registerQueryDataTool(server, state) {
333
423
  server.tool(
334
424
  "query_data",
335
- "\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",
425
+ "\u67E5\u8BE2\u6307\u5B9A\u8DEF\u7531\u7684\u6570\u636E\uFF0C\u8FD4\u56DE CSV \u683C\u5F0F\u3002\u5148\u7528 list_routes \u627E\u8DEF\u7531\uFF0C\u518D\u7528 get_route_detail \u83B7\u53D6\u5165\u53C2/\u8FD4\u56DE\u5B57\u6BB5\u8BF4\u660E\uFF0C\u6700\u540E\u8C03\u7528\u672C\u5DE5\u5177\u67E5\u8BE2\u3002",
336
426
  {
337
- routeSlug: z2.string().describe('\u8DEF\u7531\u6807\u8BC6\uFF0C\u5982 "daily-stock"\u3002\u901A\u8FC7 list_routes \u83B7\u53D6\u53EF\u7528\u8DEF\u7531\u3002'),
338
- params: z2.record(z2.union([z2.string(), z2.number(), z2.boolean(), z2.array(z2.union([z2.string(), z2.number()]))])).optional().describe([
339
- "\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",
340
- "\u4F20\u503C\u683C\u5F0F\u6309\u53C2\u6570 type \u800C\u5B9A\uFF1A",
341
- "- string\uFF1A\u5355\u503C key=v\uFF1B\u591A\u503C\u7528\u9017\u53F7 key=v1,v2,v3\uFF08\u2264100 \u9879\uFF09\u6216 URL \u6570\u7EC4 key=v1&key=v2",
342
- "- number\uFF1A\u5355\u503C key=1\uFF1B\u591A\u503C\u7528\u9017\u53F7 key=1,5,30\uFF08\u2264100 \u9879\uFF09",
343
- "- date\uFF1A\u5355\u503C key=2026-05-01\uFF1B\u8303\u56F4 key=2026-05-01&key=2026-05-07\uFF08\u8BED\u4E49 >= AND <=\uFF09\uFF1B\u652F\u6301 yyyy-MM-dd \u4E0E yyyy-MM-dd HH:mm:ss \u4E24\u79CD\u683C\u5F0F",
344
- "\uFF08\u591A\u503C/\u8303\u56F4\u8BF7\u4F20 JS \u6570\u7EC4\uFF0C\u5E95\u5C42\u4F1A\u5C55\u5F00\u4E3A\u91CD\u590D key\uFF1B\u9017\u53F7\u5206\u9694\u5219\u4F20\u5B57\u7B26\u4E32\uFF09"
345
- ].join("\n"))
427
+ routeSlug: z3.string().describe('\u8DEF\u7531\u6807\u8BC6\uFF0C\u5982 "daily-stock"\u3002\u901A\u8FC7 list_routes \u83B7\u53D6\u53EF\u7528\u8DEF\u7531\u3002'),
428
+ params: z3.record(z3.union([z3.string(), z3.number(), z3.boolean(), z3.array(z3.union([z3.string(), z3.number()]))])).optional().describe("\u67E5\u8BE2\u53C2\u6570\uFF0C\u952E\u503C\u5BF9\u900F\u4F20\u7ED9 API\u3002\u5177\u4F53\u53C2\u6570\u4E0E\u4F20\u503C\u683C\u5F0F\u8BF7\u5148\u8C03\u7528 get_route_detail(routeSlug) \u83B7\u53D6\uFF0C\u518D\u636E\u6B64\u7EC4\u7EC7\u672C\u5BF9\u8C61\u3002")
346
429
  },
347
430
  async ({ routeSlug, params }) => {
348
431
  if (!state.client || !state.config) {
@@ -489,6 +572,7 @@ function createServer(state) {
489
572
  });
490
573
  registerConfigureTool(server, state);
491
574
  registerListRoutesTool(server, state);
575
+ registerGetRouteDetailTool(server, state);
492
576
  registerQueryDataTool(server, state);
493
577
  createUserProfileResource(server, state);
494
578
  createRouteListResource(server, state);
package/dist/index.js.map CHANGED
@@ -1 +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\n/** 通用参数传值格式说明(按参数 type,所有路由共用) */\nconst PARAM_FORMAT_GUIDE = [\n '通用传值格式(按参数 type):',\n '- string:单值 key=v;多值用逗号 key=v1,v2,v3(≤100)或 URL 数组 key=v1&key=v2',\n '- number:单值 key=1;多值用逗号 key=1,5,30(≤100 项)',\n '- date:单值 key=yyyy-MM-dd;范围 key=d1&key=d2(>= AND <=);同时支持 yyyy-MM-dd HH:mm:ss',\n '数组/范围请传 JS 数组,逗号分隔则传字符串。',\n].join('\\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 guide = total > 0 ? `\\n\\n${PARAM_FORMAT_GUIDE}` : '';\n const footer = total > 0\n ? '\\n\\n使用 query_data 工具查询指定路由的数据,传入 routeSlug 和查询参数。'\n : '';\n\n return {\n content: [{ type: 'text' as const, text: header + body + guide + 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([\n '查询参数,键值对透传给 API。具体参数参考 list_routes 返回的路由说明。',\n '传值格式按参数 type 而定:',\n '- string:单值 key=v;多值用逗号 key=v1,v2,v3(≤100 项)或 URL 数组 key=v1&key=v2',\n '- number:单值 key=1;多值用逗号 key=1,5,30(≤100 项)',\n '- date:单值 key=2026-05-01;范围 key=2026-05-01&key=2026-05-07(语义 >= AND <=);支持 yyyy-MM-dd 与 yyyy-MM-dd HH:mm:ss 两种格式',\n '(多值/范围请传 JS 数组,底层会展开为重复 key;逗号分隔则传字符串)',\n ].join('\\n')),\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":";;;AAIA,SAAS,4BAA4B;;;ACArC,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;;;ACYR,IAAM,eAA0B;AAAA,EACrC,UAAU;AAAA,EACV,SAAS;AACX;AAGO,IAAM,gBAAgB;;;ADd7B,IAAM,aAAa,KAAK,KAAK,GAAG,QAAQ,GAAG,aAAa;AACxD,IAAM,cAAc,KAAK,KAAK,YAAY,aAAa;AAGhD,IAAM,mBAAmB;AAShC,SAAS,gBAAkC;AACzC,MAAI;AACF,QAAI,CAAC,GAAG,WAAW,WAAW,EAAG,QAAO;AACxC,UAAM,MAAM,GAAG,aAAa,aAAa,OAAO;AAChD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,eAAe,KAAsB;AAC5C,MAAI,IAAI,OAAO,IAAI,IAAI,aAAa,UAAa,IAAI,IAAI,YAAY,QAAW;AAC9E;AAAA,EACF;AACA,MAAI,MAAM,EAAE,GAAG,cAAc,GAAG,IAAI,IAAI;AACxC,MAAI;AACF,QAAI,CAAC,GAAG,WAAW,UAAU,GAAG;AAC9B,SAAG,UAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,IAC9C;AACA,OAAG,cAAc,aAAa,KAAK,UAAU,KAAK,MAAM,CAAC,GAAG,OAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,YAAY,KAAkC;AACrD,MAAI,CAAC,IAAI,YAAY,CAAC,IAAI,OAAQ,QAAO;AAEzC,iBAAe,GAAG;AAElB,QAAM,MAAiB;AAAA,IACrB,UAAU,IAAI,KAAK,YAAY,aAAa;AAAA,IAC5C,SAAS,IAAI,KAAK,WAAW,aAAa;AAAA,EAC5C;AAEA,MAAI,IAAI,WAAW,eAAe;AAChC,YAAQ,OAAO;AAAA,MACb,mDAAoC,IAAI,QAAQ,6BAAS,aAAa;AAAA;AAAA,IACxE;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,IAAI,SAAS,QAAQ,QAAQ,EAAE;AAAA,IACzC,QAAQ,IAAI;AAAA,IACZ;AAAA,EACF;AACF;AAGO,SAAS,aAA+B;AAC7C,QAAM,MAAM,cAAc;AAC1B,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,YAAY,GAAG;AACxB;AAGO,SAAS,WAAW,QAAgB,UAA8B;AACvE,QAAM,SAAoB;AAAA,IACxB,WAAW,YAAY,kBAAkB,QAAQ,QAAQ,EAAE;AAAA,IAC3D;AAAA,IACA,KAAK,EAAE,GAAG,aAAa;AAAA,EACzB;AAEA,MAAI,CAAC,GAAG,WAAW,UAAU,GAAG;AAC9B,OAAG,UAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC9C;AACA,KAAG,cAAc,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;AAEtE,SAAO;AAAA,IACL,UAAU,OAAO;AAAA,IACjB,QAAQ,OAAO;AAAA,IACf,KAAK,EAAE,GAAG,aAAa;AAAA,EACzB;AACF;;;AE/FO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAClC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AASO,IAAM,WAAN,cAAuB,SAAS;AAAA,EACrC,YACkB,YAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAGA,IAAM,iBAAyC;AAAA,EAC7C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGO,SAAS,YAAY,QAAgB,QAA2B;AACrE,QAAM,OAAO,eAAe,MAAM,KAAK,6BAAS,MAAM;AACtD,SAAO,IAAI,SAAS,QAAQ,SAAS,GAAG,IAAI,KAAK,MAAM,KAAK,IAAI;AAClE;;;AC9BO,IAAM,YAAN,MAAgB;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAAmB;AAC7B,SAAK,WAAW,OAAO;AACvB,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO,IAAI;AAAA,EAC5B;AAAA;AAAA,EAGA,MAAc,QAAWA,OAA0B;AACjD,UAAM,MAAM,GAAG,KAAK,QAAQ,GAAGA,KAAI;AACnC,UAAM,aAAa;AAEnB,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B,SAAS;AAAA,UACP,eAAe,UAAU,KAAK,MAAM;AAAA,UACpC,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,QACnB;AAAA,QACA,QAAQ,YAAY,QAAQ,KAAK,OAAO;AAAA,MAC1C,CAAC;AAED,UAAI,IAAI,IAAI;AACV,eAAQ,MAAM,IAAI,KAAK;AAAA,MACzB;AAGA,WAAK,IAAI,WAAW,OAAO,IAAI,WAAW,QAAQ,UAAU,YAAY;AAEtE,cAAM,IAAI,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACvC,cAAM,aAAa,IAAI,QAAQ,IAAI,aAAa;AAChD,cAAM,QAAQ,aACV,OAAO,UAAU,IAAI,MACrB,KAAK,IAAI,GAAG,OAAO,IAAI;AAC3B,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,IAAI,OAAO,GAAK,CAAC,CAAC;AAC9D;AAAA,MACF;AAGA,UAAI,SAAS;AACb,UAAI;AACF,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,iBAAS,KAAK,SAAS;AAAA,MACzB,QAAQ;AAAA,MAER;AACA,YAAM,YAAY,IAAI,QAAQ,MAAM;AAAA,IACtC;AAGA,UAAM,YAAY,KAAK,gFAAe;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,aAAgD;AACpD,WAAO,KAAK,QAAQ,YAAY;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,aAA2E;AAC/E,WAAO,KAAK,QAAQ,kBAAkB;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,UACJ,WACA,QACA,UACiD;AACjD,UAAM,KAAK,IAAI,gBAAgB;AAC/B,OAAG,IAAI,YAAY,OAAO,QAAQ,CAAC;AAEnC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,UAAI,MAAM,UAAa,MAAM,GAAI;AACjC,UAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,mBAAW,QAAQ,GAAG;AACpB,cAAI,SAAS,UAAa,SAAS,GAAI,IAAG,OAAO,GAAG,OAAO,IAAI,CAAC;AAAA,QAClE;AAAA,MACF,OAAO;AACL,WAAG,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,MACrB;AAAA,IACF;AAEA,UAAM,QAAQ,GAAG,SAAS;AAC1B,WAAO,KAAK,QAAQ,gBAAgB,SAAS,GAAG,QAAQ,MAAM,QAAQ,EAAE,EAAE;AAAA,EAC5E;AACF;;;ACnGA,SAAS,iBAAiB;;;ACA1B,SAAS,SAAS;AAMX,SAAS,sBAAsB,QAAmB,OAA0B;AACjF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQ,EAAE,OAAO,EAAE,SAAS,gEAAuC;AAAA,MACnE,UAAU,EACP,OAAO,EACP,SAAS,EACT,SAAS,8CAAqB,gBAAgB,EAAE;AAAA,IACrD;AAAA,IACA,OAAO,EAAE,QAAQ,SAAS,MAAM;AAC9B,UAAI;AACF,cAAM,SAAS,WAAW,QAAQ,QAAQ;AAG1C,cAAM,SAAS;AACf,cAAM,SAAS,IAAI,UAAU,MAAM;AAEnC,gBAAQ,OAAO;AAAA,UACb,iEAAmC,OAAO,QAAQ,cAAc,OAAO,IAAI,QAAQ;AAAA;AAAA,QACrF;AAEA,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,2CAAkB,OAAO,QAAQ;AAAA,YACzC;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,6CAAU,GAAG;AAAA,YACrB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACzCO,IAAM,iBACX;;;ACHF,SAAS,YAAY,OAA0B;AAC7C,QAAM,QAAkB;AAAA,IACtB,SAAI,MAAM,WAAW,oBAAe,MAAM,SAAS;AAAA,IACnD,qBAAM,MAAM,WAAW;AAAA,IACvB,qBAAM,MAAM,QAAQ;AAAA,EACtB;AAEA,MAAI,MAAM,YAAY,SAAS,GAAG;AAChC,UAAM,KAAK,gCAAO;AAClB,eAAW,KAAK,MAAM,aAAa;AACjC,YAAM,WAAW,EAAE,WAAW,iBAAO;AACrC,YAAM,KAAK,OAAO,EAAE,IAAI,KAAK,EAAE,IAAI,KAAK,QAAQ,MAAM,EAAE,IAAI,EAAE;AAAA,IAChE;AAAA,EACF;AAEA,MAAI,MAAM,eAAe,SAAS,GAAG;AACnC,UAAM,KAAK,gCAAO;AAClB,eAAW,KAAK,MAAM,gBAAgB;AACpC,YAAM,KAAK,OAAO,EAAE,IAAI,KAAK,EAAE,IAAI,MAAM,EAAE,IAAI,EAAE;AAAA,IACnD;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAGA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAEJ,SAAS,uBAAuB,QAAmB,OAA0B;AAClF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC;AAAA,IACD,YAAY;AACV,UAAI,CAAC,MAAM,QAAQ;AACjB,eAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,eAAe,CAAC,EAAE;AAAA,MACtE;AAEA,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,OAAO,WAAW;AAE1C,YAAI,CAAC,IAAI,WAAW,CAAC,IAAI,MAAM;AAC7B,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,yDAAY,IAAI,SAAS,0BAAM;AAAA,cACvC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,cAAM,SAAS,IAAI;AACnB,cAAM,QAAQ,IAAI,MAAM,SAAS,OAAO;AAExC,cAAM,SAAS,oDAAY,KAAK;AAAA;AAChC,cAAM,OAAO,OAAO,IAAI,WAAW,EAAE,KAAK,MAAM;AAChD,cAAM,QAAQ,QAAQ,IAAI;AAAA;AAAA,EAAO,kBAAkB,KAAK;AACxD,cAAM,SAAS,QAAQ,IACnB,oKACA;AAEJ,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,SAAS,OAAO,QAAQ,OAAO,CAAC;AAAA,QAC3E;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,eAAO;AAAA,UACL,SAAS;AAAA,YACP,EAAE,MAAM,QAAiB,MAAM,yDAAY,GAAG,GAAG;AAAA,UACnD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACxFA,SAAS,KAAAC,UAAS;AAOlB,SAAS,MAAM,MAAyC;AACtD,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,UAAU,OAAO,KAAK,KAAK,CAAC,CAAC;AACnC,QAAM,aAAa,QAAQ,KAAK,GAAG;AACnC,QAAM,YAAY,KAAK;AAAA,IAAI,CAAC,QAC1B,QACG,IAAI,CAAC,MAAM;AACV,YAAM,MAAM,IAAI,CAAC;AACjB,UAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,YAAM,MAAM,OAAO,GAAG;AAEtB,UAAI,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,IAAI,GAAG;AAChE,eAAO,IAAI,IAAI,QAAQ,MAAM,IAAI,CAAC;AAAA,MACpC;AACA,aAAO;AAAA,IACT,CAAC,EACA,KAAK,GAAG;AAAA,EACb;AACA,SAAO,CAAC,YAAY,GAAG,SAAS,EAAE,KAAK,IAAI;AAC7C;AAGA,IAAM,oBAAoB,CAAC,MACzB,yFAAmB,CAAC,yCAAW,aAAa;AAAA,4CACtB,aAAa;AAAA;AAAA;AAKrC,IAAM,gBACJ;AAGF,IAAM,eACJ;AAEK,SAAS,sBAAsB,QAAmB,OAA0B;AACjF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,WAAWC,GAAE,OAAO,EAAE,SAAS,6HAA6C;AAAA,MAC5E,QAAQA,GACL,OAAOA,GAAE,MAAM,CAACA,GAAE,OAAO,GAAGA,GAAE,OAAO,GAAGA,GAAE,QAAQ,GAAGA,GAAE,MAAMA,GAAE,MAAM,CAACA,GAAE,OAAO,GAAGA,GAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACjG,SAAS,EACT,SAAS;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI,CAAC;AAAA,IAChB;AAAA,IACA,OAAO,EAAE,WAAW,OAAO,MAAM;AAC/B,UAAI,CAAC,MAAM,UAAU,CAAC,MAAM,QAAQ;AAClC,eAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,eAAe,CAAC,EAAE;AAAA,MACtE;AAEA,YAAM,WAAW,MAAM,OAAO,IAAI;AAGlC,UAAI,WAAW,eAAe;AAC5B,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,kBAAkB,QAAQ,EAAE,CAAC;AAAA,QACxE;AAAA,MACF;AAEA,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,OAAO,UAAU,WAAW,UAAU,CAAC,GAAG,QAAQ;AAE1E,YAAI,CAAC,IAAI,SAAS;AAChB,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,iCAAQ,IAAI,SAAS,0BAAM;AAAA,cACnC;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,cAAM,OAAO,IAAI;AACjB,cAAM,QAAQ,IAAI,MAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,KAAK,SAAS;AACtE,cAAM,UAAU,IAAI,MAAM,YAAY,WAAW;AAGjD,YAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,GAAG;AAC7C,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,aAAa,CAAC;AAAA,UACzD;AAAA,QACF;AAGA,cAAM,QAAkB,CAAC,MAAM,IAAI,GAAG,IAAI,wCAAU,KAAK,2BAAO;AAChE,YAAI,SAAS;AACX,gBAAM,KAAK,aAAa;AAAA,QAC1B;AAEA,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,KAAK,IAAI,EAAE,CAAC;AAAA,QAC7D;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,iCAAQ,GAAG,GAAG,CAAC;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACjHO,SAAS,0BAA0B,QAAmB,OAA0B;AACrF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO,QAAa;AAClB,UAAI,CAAC,MAAM,QAAQ;AACjB,eAAO;AAAA,UACL,UAAU;AAAA,YACR;AAAA,cACE,KAAK,IAAI;AAAA,cACT,MAAM,KAAK,UAAU;AAAA,gBACnB,OAAO;AAAA,cACT,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,MAAM,MAAM,MAAM,OAAO,WAAW;AAC1C,UAAI,CAAC,IAAI,WAAW,CAAC,IAAI,MAAM;AAC7B,eAAO;AAAA,UACL,UAAU;AAAA,YACR;AAAA,cACE,KAAK,IAAI;AAAA,cACT,MAAM,KAAK,UAAU,EAAE,OAAO,IAAI,SAAS,mDAAW,CAAC;AAAA,YACzD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK,IAAI;AAAA,YACT,MAAM,KAAK,UAAU,IAAI,MAAM,MAAM,CAAC;AAAA,UACxC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC3CO,SAAS,wBAAwB,QAAmB,OAA0B;AACnF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO,QAAa;AAClB,UAAI,CAAC,MAAM,QAAQ;AACjB,eAAO;AAAA,UACL,UAAU;AAAA,YACR;AAAA,cACE,KAAK,IAAI;AAAA,cACT,MAAM,KAAK,UAAU;AAAA,gBACnB,OAAO;AAAA,cACT,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,MAAM,MAAM,MAAM,OAAO,WAAW;AAC1C,UAAI,CAAC,IAAI,WAAW,CAAC,IAAI,MAAM;AAC7B,eAAO;AAAA,UACL,UAAU;AAAA,YACR;AAAA,cACE,KAAK,IAAI;AAAA,cACT,MAAM,KAAK,UAAU,EAAE,OAAO,IAAI,SAAS,mDAAW,CAAC;AAAA,YACzD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK,IAAI;AAAA,YACT,MAAM,KAAK,UAAU,IAAI,MAAM,MAAM,CAAC;AAAA,UACxC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ANvCO,SAAS,aAAa,OAA+B;AAC1D,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AAGD,wBAAsB,QAAQ,KAAK;AACnC,yBAAuB,QAAQ,KAAK;AACpC,wBAAsB,QAAQ,KAAK;AAGnC,4BAA0B,QAAQ,KAAK;AACvC,0BAAwB,QAAQ,KAAK;AAErC,SAAO;AACT;;;ALlBA,eAAe,OAAO;AACpB,QAAM,SAAS,WAAW;AAE1B,QAAM,QAAqB;AAAA,IACzB;AAAA,IACA,QAAQ,SAAS,IAAI,UAAU,MAAM,IAAI;AAAA,EAC3C;AAEA,MAAI,QAAQ;AACV,YAAQ,OAAO;AAAA,MACb,mDAAoC,OAAO,QAAQ,cAAc,OAAO,IAAI,QAAQ,aAAa,OAAO,IAAI,OAAO;AAAA;AAAA,IACrH;AAAA,EACF,OAAO;AACL,YAAQ,OAAO;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,aAAa,KAAK;AAEjC,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAE9B,UAAQ,OAAO,MAAM,6FAA2C;AAClE;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,kDAAyB,GAAG;AAAA,CAAI;AACrD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["path","z","z"]}
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/detail.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\n/** 路由列表缓存 TTL(进程内、实例级)\n *\n * 覆盖典型链路:list_routes → 大模型读元信息并挑选路由 → get_route_detail。\n * 2 分钟足以容纳网络/模型慢的真实节奏,且路由元数据陈旧风险可忽略。\n */\nconst ROUTES_CACHE_TTL_MS = 120_000;\n\ntype RoutesResponse = ApiResponse<RouteInfo[]> & { meta?: RouteListMeta };\n\nexport class ApiClient {\n private readonly endpoint: string;\n private readonly apiKey: string;\n private readonly timeout: number;\n\n /** 路由列表内存缓存(仅成功响应才回填,失败不污染) */\n private routesCache: { data: RoutesResponse; expiresAt: number } | null = null;\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 /** 获取可用路由列表(命中 2 分钟内存缓存时零网络) */\n async listRoutes(): Promise<RoutesResponse> {\n const now = Date.now();\n if (this.routesCache && now < this.routesCache.expiresAt) {\n return this.routesCache.data;\n }\n\n const res = await this.request<RoutesResponse>('/api/v1/api-list');\n\n // 仅成功响应才写缓存,错误响应原样透传给上层处理\n if (res.success) {\n this.routesCache = { data: res, expiresAt: now + ROUTES_CACHE_TTL_MS };\n }\n return res;\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 { registerGetRouteDetailTool } from './tools/detail.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 registerGetRouteDetailTool(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 — 列出可用路由(仅目录级元信息,不含入参/返回字段)\n// ============================================================\n\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 return [\n `【${route.displayName}】routeSlug: ${route.routeSlug}`,\n `说明:${route.description}`,\n `分类:${route.category}`,\n ].join('\\n');\n}\n\n/** 列表后的引导:指向 get_route_detail */\nconst NEXT_STEP_HINT = [\n '以上为路由目录,**不含入参/返回字段详情**。',\n '请按以下流程操作:',\n '1. 调用 get_route_detail(routeSlug=\"...\") 获取目标路由的查询参数与返回字段说明',\n '2. 根据 get_route_detail 返回的参数说明与传值格式,自行组织 params 后调用 query_data 查询数据',\n].join('\\n');\n\nexport function registerListRoutesTool(server: McpServer, state: ServerState): void {\n server.tool(\n 'list_routes',\n '列出当前用户可用的数据路由目录(仅含 routeSlug、名称、说明、分类,不含入参/返回字段)。如需入参/返回字段详情,请使用 get_route_detail 工具。',\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${NEXT_STEP_HINT}` : '';\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: get_route_detail — 查询单个路由的入参/返回字段详情\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 { QueryParam, ResponseParam } from '../types/index.js';\n\n/** 通用参数传值格式说明(按参数 type,所有路由共用) */\nconst PARAM_FORMAT_GUIDE = [\n '传值格式(按参数 type):',\n '- string:单值传字符串 \"v\";多值传字符串数组 [\"v1\",\"v2\"],或逗号分隔字符串 \"v1,v2,v3\"(≤100 项)',\n '- number:单值传数字 1;多值传数字数组 [1,5,30],或逗号分隔字符串 \"1,5,30\"(≤100 项)',\n '- date:单值传字符串 \"2026-05-01\";范围传两元素数组 [\"2026-05-01\",\"2026-05-07\"](语义 >= AND <=)',\n ' 支持格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss',\n].join('\\n');\n\n/** 调用 query_data 的引导:让大模型自行分析需要传哪些参数 */\nconst CALL_HINT = [\n '请根据上述查询参数说明与通用传值格式,结合用户实际意图(例如指定的代码、时间范围等):',\n '1. 自行判断需要传哪些参数(必填的务必带上,可选的按需)',\n '2. 按各参数对应的 type 选择正确的传值形式(单值 / 多值 / 范围)',\n '3. 调用 query_data(routeSlug, params) 获取数据',\n '注:pageSize 由 mcp.pageSize 配置控制,不要写入 params。',\n].join('\\n');\n\n/** 渲染查询参数表 */\nfunction formatQueryParams(params: QueryParam[]): string {\n if (params.length === 0) return '查询参数:(无)';\n const lines = ['查询参数:'];\n for (const p of params) {\n const required = p.required ? '必填' : '可选';\n lines.push(` - ${p.name} (${p.type}, ${required}): ${p.desc}`);\n }\n return lines.join('\\n');\n}\n\n/** 渲染返回字段表 */\nfunction formatResponseParams(params: ResponseParam[]): string {\n if (params.length === 0) return '返回字段:(无)';\n const lines = ['返回字段:'];\n for (const f of params) {\n lines.push(` - ${f.name} (${f.type}): ${f.desc}`);\n }\n return lines.join('\\n');\n}\n\nexport function registerGetRouteDetailTool(server: McpServer, state: ServerState): void {\n server.tool(\n 'get_route_detail',\n '查询指定路由的入参与返回字段详情。先用 list_routes 找到 routeSlug,再用本工具拿参数说明,最后由大模型根据说明自行组织 params 调用 query_data。',\n {\n routeSlug: z.string().describe('路由标识,如 \"daily-stock\"。通过 list_routes 获取可用 routeSlug。'),\n },\n async ({ routeSlug }) => {\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 route = res.data.find((r) => r.routeSlug === routeSlug);\n if (!route) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `未找到 routeSlug=\"${routeSlug}\" 对应的路由。请调用 list_routes 工具确认 routeSlug 是否正确(注意大小写与连字符)。`,\n },\n ],\n };\n }\n\n const sections = [\n `【${route.displayName}】routeSlug: ${route.routeSlug}`,\n `说明:${route.description}`,\n `分类:${route.category}`,\n '',\n formatQueryParams(route.queryParams),\n '',\n formatResponseParams(route.responseParams),\n '',\n PARAM_FORMAT_GUIDE,\n '',\n CALL_HINT,\n ];\n\n return {\n content: [{ type: 'text' as const, text: sections.join('\\n') }],\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 '未查询到符合条件的数据。请检查查询参数是否正确,可通过 get_route_detail 工具查看该路由支持的参数说明。';\n\nexport function registerQueryDataTool(server: McpServer, state: ServerState): void {\n server.tool(\n 'query_data',\n '查询指定路由的数据,返回 CSV 格式。先用 list_routes 找路由,再用 get_route_detail 获取入参/返回字段说明,最后调用本工具查询。',\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。具体参数与传值格式请先调用 get_route_detail(routeSlug) 获取,再据此组织本对象。'),\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","z","z"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberquant-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "MCP Server for CyberQuant Data Sharing Platform",
5
5
  "type": "module",
6
6
  "bin": {