cyberquant-mcp 0.1.0 → 0.1.1
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/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -252,6 +252,13 @@ function formatRoute(route) {
|
|
|
252
252
|
}
|
|
253
253
|
return lines.join("\n");
|
|
254
254
|
}
|
|
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"
|
|
261
|
+
].join("\n");
|
|
255
262
|
function registerListRoutesTool(server, state) {
|
|
256
263
|
server.tool(
|
|
257
264
|
"list_routes",
|
|
@@ -278,9 +285,12 @@ function registerListRoutesTool(server, state) {
|
|
|
278
285
|
const header = `\u53EF\u7528\u6570\u636E\u8DEF\u7531\uFF08\u5171 ${total} \u4E2A\uFF09\uFF1A
|
|
279
286
|
`;
|
|
280
287
|
const body = routes.map(formatRoute).join("\n\n");
|
|
288
|
+
const guide = total > 0 ? `
|
|
289
|
+
|
|
290
|
+
${PARAM_FORMAT_GUIDE}` : "";
|
|
281
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" : "";
|
|
282
292
|
return {
|
|
283
|
-
content: [{ type: "text", text: header + body + footer }]
|
|
293
|
+
content: [{ type: "text", text: header + body + guide + footer }]
|
|
284
294
|
};
|
|
285
295
|
} catch (err) {
|
|
286
296
|
const msg = err instanceof Error ? err.message : "\u672A\u77E5\u9519\u8BEF";
|
|
@@ -325,7 +335,14 @@ function registerQueryDataTool(server, state) {
|
|
|
325
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",
|
|
326
336
|
{
|
|
327
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'),
|
|
328
|
-
params: z2.record(z2.union([z2.string(), z2.number(), z2.boolean(), z2.array(z2.union([z2.string(), z2.number()]))])).optional().describe(
|
|
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"))
|
|
329
346
|
},
|
|
330
347
|
async ({ routeSlug, params }) => {
|
|
331
348
|
if (!state.client || !state.config) {
|
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\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"]}
|
|
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":"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","names":["path","z","z"]}
|