@apocdata-info/mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/LICENSE +191 -0
- package/README.md +230 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.js +51 -0
- package/dist/cache.js.map +1 -0
- package/dist/client.js +113 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.js +56 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +259 -0
- package/dist/index.js.map +1 -0
- package/dist/resources.js +229 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +69 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/factor-registry.d.ts +17 -0
- package/dist/tools/factor-registry.js +36 -0
- package/dist/tools/factor-registry.js.map +1 -0
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.js +45 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/macro-dashboard.d.ts +17 -0
- package/dist/tools/macro-dashboard.js +30 -0
- package/dist/tools/macro-dashboard.js.map +1 -0
- package/dist/tools/macro-indicator.d.ts +17 -0
- package/dist/tools/macro-indicator.js +45 -0
- package/dist/tools/macro-indicator.js.map +1 -0
- package/dist/tools/quotes-snapshot.d.ts +17 -0
- package/dist/tools/quotes-snapshot.js +39 -0
- package/dist/tools/quotes-snapshot.js.map +1 -0
- package/dist/tools/stock-bars-daily.d.ts +17 -0
- package/dist/tools/stock-bars-daily.js +45 -0
- package/dist/tools/stock-bars-daily.js.map +1 -0
- package/dist/tools/stock-detail.d.ts +17 -0
- package/dist/tools/stock-detail.js +38 -0
- package/dist/tools/stock-detail.js.map +1 -0
- package/dist/tools/stock-financials.d.ts +17 -0
- package/dist/tools/stock-financials.js +36 -0
- package/dist/tools/stock-financials.js.map +1 -0
- package/dist/tools/stock-list.d.ts +17 -0
- package/dist/tools/stock-list.js +39 -0
- package/dist/tools/stock-list.js.map +1 -0
- package/dist/tools/stock-news.d.ts +17 -0
- package/dist/tools/stock-news.js +36 -0
- package/dist/tools/stock-news.js.map +1 -0
- package/dist/tools/stock-quote.d.ts +17 -0
- package/dist/tools/stock-quote.js +32 -0
- package/dist/tools/stock-quote.js.map +1 -0
- package/dist/tools/top10-holders.d.ts +17 -0
- package/dist/tools/top10-holders.js +35 -0
- package/dist/tools/top10-holders.js.map +1 -0
- package/dist/tools/trade-days.d.ts +17 -0
- package/dist/tools/trade-days.js +36 -0
- package/dist/tools/trade-days.js.map +1 -0
- package/dist/tools/types.d.ts +29 -0
- package/dist/tools/types.js +16 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools.js +540 -0
- package/dist/transport.d.ts +44 -0
- package/dist/transport.js +87 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -0
- package/scripts/client-unit-test.mjs +147 -0
- package/scripts/contract-test.mjs +167 -0
- package/scripts/coverage-test.mjs +126 -0
- package/scripts/error-path-test.mjs +110 -0
- package/scripts/freshness-probe.mjs +144 -0
- package/scripts/integration-test.mjs +284 -0
- package/scripts/mcp-e2e-test.mjs +196 -0
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
*
|
|
14
|
+
* @author Chill
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 后端 TdcResponse / blade R 错误码 → LLM-friendly 中文翻译。
|
|
18
|
+
* 任何未匹配的 code 返原始 message,前缀"后端错误"。
|
|
19
|
+
*/
|
|
20
|
+
export declare const ERROR_TRANSLATIONS: Record<string, string>;
|
|
21
|
+
/**
|
|
22
|
+
* HTTP 状态码 → 错误码兜底映射。
|
|
23
|
+
*/
|
|
24
|
+
export declare const HTTP_STATUS_FALLBACK: Record<number, string>;
|
|
25
|
+
/**
|
|
26
|
+
* 翻译后端错误为 LLM-friendly ToolResult 错误。
|
|
27
|
+
*/
|
|
28
|
+
export declare function translateError(opts: {
|
|
29
|
+
httpStatus?: number;
|
|
30
|
+
backendCode?: string;
|
|
31
|
+
backendMessage?: string;
|
|
32
|
+
cause?: unknown;
|
|
33
|
+
}): {
|
|
34
|
+
error: true;
|
|
35
|
+
code: string;
|
|
36
|
+
message: string;
|
|
37
|
+
};
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
*
|
|
14
|
+
* @author Chill
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 后端 TdcResponse / blade R 错误码 → LLM-friendly 中文翻译。
|
|
18
|
+
* 任何未匹配的 code 返原始 message,前缀"后端错误"。
|
|
19
|
+
*/
|
|
20
|
+
export const ERROR_TRANSLATIONS = {
|
|
21
|
+
AUTH_INVALID: "API Key 无效,请联系运维更新 TQ_API_KEY",
|
|
22
|
+
AUTH_REVOKED: "API Key 已吞吊,请联系运维补发",
|
|
23
|
+
AUTH_EXPIRED: "API Key 已过期,请联系运维续期",
|
|
24
|
+
SKU_INSUFFICIENT: "当前 SKU 等级不足以调用此 tool(需要 SKU-PRO+),请联系运维升级",
|
|
25
|
+
QPS_EXCEEDED: "调用频率超限,请等待数秒后重试",
|
|
26
|
+
QUOTA_EXCEEDED: "今日配额已用尽,明日 00:00 重置或联系运维升级",
|
|
27
|
+
NOT_FOUND: "未找到该 symbol/indicator 的数据,请检查参数(symbol 应为 6 位代码,如 '000001')",
|
|
28
|
+
INVALID_PARAM: "参数错误,请检查 symbol/date/range 格式",
|
|
29
|
+
UPSTREAM_TIMEOUT: "数据源响应超时,建议重试或缩小查询范围",
|
|
30
|
+
UPSTREAM_ERROR: "数据源临时不可用,稍后重试",
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* HTTP 状态码 → 错误码兜底映射。
|
|
34
|
+
*/
|
|
35
|
+
export const HTTP_STATUS_FALLBACK = {
|
|
36
|
+
401: "AUTH_INVALID",
|
|
37
|
+
403: "SKU_INSUFFICIENT",
|
|
38
|
+
404: "NOT_FOUND",
|
|
39
|
+
429: "QPS_EXCEEDED",
|
|
40
|
+
500: "UPSTREAM_ERROR",
|
|
41
|
+
502: "UPSTREAM_ERROR",
|
|
42
|
+
503: "UPSTREAM_ERROR",
|
|
43
|
+
504: "UPSTREAM_TIMEOUT",
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* 翻译后端错误为 LLM-friendly ToolResult 错误。
|
|
47
|
+
*/
|
|
48
|
+
export function translateError(opts) {
|
|
49
|
+
const code = opts.backendCode ?? (opts.httpStatus ? HTTP_STATUS_FALLBACK[opts.httpStatus] : null) ?? "UNKNOWN";
|
|
50
|
+
const friendly = ERROR_TRANSLATIONS[code];
|
|
51
|
+
if (friendly)
|
|
52
|
+
return { error: true, code, message: friendly };
|
|
53
|
+
if (opts.backendMessage)
|
|
54
|
+
return { error: true, code, message: `后端错误: ${opts.backendMessage}` };
|
|
55
|
+
return { error: true, code, message: `未知错误(HTTP ${opts.httpStatus ?? "?"})` };
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,YAAY,EAAE,+BAA+B;IAC7C,YAAY,EAAE,qBAAqB;IACnC,YAAY,EAAE,qBAAqB;IACnC,gBAAgB,EAAE,2CAA2C;IAC7D,YAAY,EAAE,iBAAiB;IAC/B,cAAc,EAAE,4BAA4B;IAC5C,SAAS,EAAE,6DAA6D;IACxE,aAAa,EAAE,+BAA+B;IAC9C,gBAAgB,EAAE,qBAAqB;IACvC,cAAc,EAAE,eAAe;CAChC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA2B;IAC1D,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,kBAAkB;IACvB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,kBAAkB;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAK9B;IACC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;IAC/G,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAC9D,IAAI,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;IAC/F,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,IAAI,CAAC,UAAU,IAAI,GAAG,GAAG,EAAE,CAAC;AAChF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
*
|
|
15
|
+
* @author Chill
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 天启至数 ApocData - MCP Server
|
|
4
|
+
*
|
|
5
|
+
* stdio transport,46 个股票数据工具。
|
|
6
|
+
*
|
|
7
|
+
* CLI:
|
|
8
|
+
* apocdata-mcp 启动 MCP server
|
|
9
|
+
* apocdata-mcp --version | -v 打印版本号后退出
|
|
10
|
+
* apocdata-mcp --help | -h 打印用法后退出
|
|
11
|
+
*
|
|
12
|
+
* 环境变量:
|
|
13
|
+
* APOCDATA_BASE_URL 覆盖默认 base URL(默认指向 data.tianqis.com)
|
|
14
|
+
* APOCDATA_DEBUG 设为 "1" 时把每次调用元信息打到 stderr
|
|
15
|
+
* APOCDATA_TIMEOUT_MS 单次请求超时(默认 30000)
|
|
16
|
+
* APOCDATA_MAX_RETRIES 5xx/网络错误重试次数(默认 2)
|
|
17
|
+
*
|
|
18
|
+
* 信号:
|
|
19
|
+
* SIGTERM / SIGINT 优雅退出(等 in-flight 请求结束,最多 5s)
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync } from 'node:fs';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { dirname, resolve } from 'node:path';
|
|
24
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
25
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
26
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
27
|
+
import { ApocDataClient, NetworkError } from './client.js';
|
|
28
|
+
import { ALL_RESOURCES } from './resources.js';
|
|
29
|
+
import { ALL_TOOLS } from './tools.js';
|
|
30
|
+
const PKG_VERSION = readPackageVersion();
|
|
31
|
+
const DEFAULT_BASE_URL = 'https://data.tianqis.com/api/blade-dataplatform/open/data';
|
|
32
|
+
const SHUTDOWN_GRACE_MS = 5_000;
|
|
33
|
+
// === CLI 处理(必须在创建 transport 之前,否则会污染 stdout) ===
|
|
34
|
+
handleCliFlags(process.argv.slice(2));
|
|
35
|
+
const baseUrl = process.env.APOCDATA_BASE_URL ?? DEFAULT_BASE_URL;
|
|
36
|
+
const debug = process.env.APOCDATA_DEBUG === '1';
|
|
37
|
+
const client = new ApocDataClient(baseUrl, {
|
|
38
|
+
timeoutMs: parsePositiveInt(process.env.APOCDATA_TIMEOUT_MS, 30_000),
|
|
39
|
+
maxRetries: parsePositiveInt(process.env.APOCDATA_MAX_RETRIES, 2),
|
|
40
|
+
});
|
|
41
|
+
const toolByName = new Map(ALL_TOOLS.map((t) => [t.name, t]));
|
|
42
|
+
const server = new Server({
|
|
43
|
+
name: 'apocdata-mcp-server',
|
|
44
|
+
version: PKG_VERSION,
|
|
45
|
+
}, {
|
|
46
|
+
capabilities: {
|
|
47
|
+
tools: {},
|
|
48
|
+
resources: {},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const resourceByUri = new Map(ALL_RESOURCES.map((r) => [r.uri, r]));
|
|
52
|
+
// 优雅退出状态
|
|
53
|
+
let inflight = 0;
|
|
54
|
+
let shuttingDown = false;
|
|
55
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
56
|
+
return {
|
|
57
|
+
tools: ALL_TOOLS.map((t) => ({
|
|
58
|
+
name: t.name,
|
|
59
|
+
description: t.description,
|
|
60
|
+
inputSchema: buildInputSchema(t),
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
65
|
+
return {
|
|
66
|
+
resources: ALL_RESOURCES.map((r) => ({
|
|
67
|
+
uri: r.uri,
|
|
68
|
+
name: r.name,
|
|
69
|
+
description: r.description,
|
|
70
|
+
mimeType: r.mimeType,
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
75
|
+
const uri = request.params.uri;
|
|
76
|
+
const resource = resourceByUri.get(uri);
|
|
77
|
+
if (!resource) {
|
|
78
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
contents: [
|
|
82
|
+
{
|
|
83
|
+
uri: resource.uri,
|
|
84
|
+
mimeType: resource.mimeType,
|
|
85
|
+
text: resource.text,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
91
|
+
if (shuttingDown) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: 'Server shutting down, request rejected' }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const { name, arguments: args = {} } = request.params;
|
|
98
|
+
const tool = toolByName.get(name);
|
|
99
|
+
if (!tool) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
inflight++;
|
|
106
|
+
try {
|
|
107
|
+
const result = await client.call(tool.path, args);
|
|
108
|
+
if (debug) {
|
|
109
|
+
process.stderr.write(`[apocdata-mcp] ${tool.path} status=${result.status} meta=${JSON.stringify(result.meta)}\n`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: 'text',
|
|
115
|
+
text: formatResponse(result.body, result.meta),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
isError: result.status >= 400 || isApiError(result.body),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const prefix = err instanceof NetworkError ? 'Network error' : 'Request failed';
|
|
123
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
124
|
+
return {
|
|
125
|
+
content: [{ type: 'text', text: `${prefix}: ${msg}` }],
|
|
126
|
+
isError: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
inflight--;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
function buildInputSchema(tool) {
|
|
134
|
+
const properties = {};
|
|
135
|
+
for (const [name, def] of Object.entries(tool.params)) {
|
|
136
|
+
const schema = {
|
|
137
|
+
type: def.type,
|
|
138
|
+
description: def.description,
|
|
139
|
+
};
|
|
140
|
+
if (def.enum)
|
|
141
|
+
schema.enum = def.enum;
|
|
142
|
+
if (def.default !== undefined)
|
|
143
|
+
schema.default = def.default;
|
|
144
|
+
properties[name] = schema;
|
|
145
|
+
}
|
|
146
|
+
const schema = {
|
|
147
|
+
type: 'object',
|
|
148
|
+
properties,
|
|
149
|
+
};
|
|
150
|
+
if (tool.required && tool.required.length > 0) {
|
|
151
|
+
schema.required = tool.required;
|
|
152
|
+
}
|
|
153
|
+
return schema;
|
|
154
|
+
}
|
|
155
|
+
function isApiError(body) {
|
|
156
|
+
if (body && typeof body === 'object' && 'success' in body) {
|
|
157
|
+
return body.success === false;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 把响应 body + meta 合并成给 Agent 看的文本。
|
|
163
|
+
* meta 里的 X-Tdc-* 头放在前面(限流/截断/错误码线索),body 紧跟其后。
|
|
164
|
+
*/
|
|
165
|
+
function formatResponse(body, meta) {
|
|
166
|
+
const interesting = Object.entries(meta).filter(([k]) => {
|
|
167
|
+
const lower = k.toLowerCase();
|
|
168
|
+
return lower.startsWith('x-tdc-') || lower === 'cache-control';
|
|
169
|
+
});
|
|
170
|
+
const metaText = interesting.length
|
|
171
|
+
? `<!-- meta\n${interesting.map(([k, v]) => `${k}: ${v}`).join('\n')}\n-->\n`
|
|
172
|
+
: '';
|
|
173
|
+
return metaText + JSON.stringify(body, null, 2);
|
|
174
|
+
}
|
|
175
|
+
function parsePositiveInt(value, fallback) {
|
|
176
|
+
if (!value)
|
|
177
|
+
return fallback;
|
|
178
|
+
const n = Number.parseInt(value, 10);
|
|
179
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
180
|
+
}
|
|
181
|
+
function readPackageVersion() {
|
|
182
|
+
try {
|
|
183
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
184
|
+
// dist/index.js → ../package.json
|
|
185
|
+
const pkg = JSON.parse(readFileSync(resolve(dir, '..', 'package.json'), 'utf-8'));
|
|
186
|
+
return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return '0.0.0';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function handleCliFlags(argv) {
|
|
193
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
194
|
+
process.stdout.write(`apocdata-mcp-server ${PKG_VERSION}\n`);
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
197
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
198
|
+
process.stdout.write(`apocdata-mcp-server ${PKG_VERSION}\n` +
|
|
199
|
+
`\n` +
|
|
200
|
+
`天启至数 ApocData - MCP Server,stdio transport,46 个股票数据工具。\n` +
|
|
201
|
+
`\n` +
|
|
202
|
+
`Usage:\n` +
|
|
203
|
+
` apocdata-mcp Start MCP server on stdio\n` +
|
|
204
|
+
` apocdata-mcp --version|-v Print version and exit\n` +
|
|
205
|
+
` apocdata-mcp --help|-h Show this help\n` +
|
|
206
|
+
`\n` +
|
|
207
|
+
`Environment:\n` +
|
|
208
|
+
` APOCDATA_BASE_URL Override base URL (default ${DEFAULT_BASE_URL})\n` +
|
|
209
|
+
` APOCDATA_DEBUG=1 Log call meta to stderr\n` +
|
|
210
|
+
` APOCDATA_TIMEOUT_MS=30000 Per-request timeout in ms\n` +
|
|
211
|
+
` APOCDATA_MAX_RETRIES=2 Retries on 5xx / network error\n` +
|
|
212
|
+
`\n` +
|
|
213
|
+
`Signals:\n` +
|
|
214
|
+
` SIGTERM, SIGINT Graceful shutdown (wait up to 5s for inflight)\n` +
|
|
215
|
+
`\n` +
|
|
216
|
+
`Docs: https://github.com/ApocData/ApocData-skill\n`);
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
// 未识别参数:警告但继续启动
|
|
220
|
+
const unknown = argv.filter((a) => a.startsWith('-'));
|
|
221
|
+
if (unknown.length > 0) {
|
|
222
|
+
process.stderr.write(`[apocdata-mcp] Unknown flags ignored: ${unknown.join(' ')}\n`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function shutdown(signal) {
|
|
226
|
+
if (shuttingDown)
|
|
227
|
+
return;
|
|
228
|
+
shuttingDown = true;
|
|
229
|
+
if (debug) {
|
|
230
|
+
process.stderr.write(`[apocdata-mcp] ${signal} received, draining ${inflight} in-flight…\n`);
|
|
231
|
+
}
|
|
232
|
+
// 等 in-flight 完成(最多 SHUTDOWN_GRACE_MS)
|
|
233
|
+
const deadline = Date.now() + SHUTDOWN_GRACE_MS;
|
|
234
|
+
while (inflight > 0 && Date.now() < deadline) {
|
|
235
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
await server.close();
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
if (debug) {
|
|
242
|
+
process.stderr.write(`[apocdata-mcp] server.close() error: ${String(err)}\n`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
247
|
+
async function main() {
|
|
248
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
249
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
250
|
+
const transport = new StdioServerTransport();
|
|
251
|
+
await server.connect(transport);
|
|
252
|
+
if (debug) {
|
|
253
|
+
process.stderr.write(`[apocdata-mcp] server ready, version=${PKG_VERSION}, base=${baseUrl}, tools=${ALL_TOOLS.length}\n`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
main().catch((err) => {
|
|
257
|
+
process.stderr.write(`Fatal: ${err instanceof Error ? err.stack : err}\n`);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,gCAAgC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP resources:暴露给 Agent 的静态文档。
|
|
3
|
+
*
|
|
4
|
+
* Agent 通过 resources/list 发现可读资源,通过 resources/read 拉取内容。
|
|
5
|
+
* 比 tool description 更详细,但比挂全文档更精简——目标是让 Agent 一上来就能:
|
|
6
|
+
* 1. 看懂全局能力(guide)
|
|
7
|
+
* 2. 知道场景到工具的映射(scenarios)
|
|
8
|
+
* 3. 查到 limit 上限/默认值(limits)
|
|
9
|
+
*/
|
|
10
|
+
const GUIDE = `# ApocData MCP - 接入与工具速查
|
|
11
|
+
|
|
12
|
+
## 是什么
|
|
13
|
+
|
|
14
|
+
天启至数 ApocData 是 A 股免鉴权数据 API(\`https://data.tianqis.com/api/blade-dataplatform/open/data/*\`),本 MCP server 把 46 个端点包装成可直接调用的工具。
|
|
15
|
+
|
|
16
|
+
## 工具分组(46 个)
|
|
17
|
+
|
|
18
|
+
| 类别 | 工具 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| **A 行情与估值** | quote / quotes / daily / stock / stocks / st / ranking / indexes / index-daily / hot-rank |
|
|
21
|
+
| **B 财务与股东** | financial / express / dividend / holders / holder-number / share-float / repurchase / block-trade |
|
|
22
|
+
| **C 资金流向** | moneyflow / hsgt / hk-hold / hk-daily / margin / dragon-tiger / hot-money / hot-money-detail |
|
|
23
|
+
| **D 涨跌停与板块** | limit-list / limit-step / sector-flow / cyq-perf |
|
|
24
|
+
| **E 公告/调研** | announcements / survey |
|
|
25
|
+
| **F 板块成分** | concepts / concept-stocks / ths-boards / ths-board-stocks |
|
|
26
|
+
| **G 可转债** | convertible-bonds / cb-price-chg |
|
|
27
|
+
| **H 因子** | factors / tech-factor |
|
|
28
|
+
| **I 宏观** | macro / macro-latest / macro-definition |
|
|
29
|
+
| **J 日历** | calendar |
|
|
30
|
+
| **K 综合(推荐用于多维分析)** | profile-full / factor-categories |
|
|
31
|
+
|
|
32
|
+
## 关键约定
|
|
33
|
+
|
|
34
|
+
- **symbol 格式**:A 股传 6 位代码(如 \`600519\`),后端自动补全交易所后缀;港股/指数/可转债传完整 \`tsCode\`(如 \`00700.HK\` / \`000300.SH\` / \`127026.SZ\`)
|
|
35
|
+
- **延迟**:免鉴权接口走 FREE 套餐,盘中实时数据有 15min 延迟
|
|
36
|
+
- **数据稀疏≠错误**:express/hk-daily/survey/share-float 返回空数组属正常,不代表接口故障
|
|
37
|
+
- **错误协议**:业务错误用 HTTP 200 + \`success=false\` + \`msg\`,不走 HTTP 4xx
|
|
38
|
+
- **格式优化**:返回数组的工具支持 \`format=compact\`(columns+rows 列式,省 60-70% token);**所有工具都支持 \`fields\` 字段白名单**(在 query 里加 fields=col1,col2 即可,工具 schema 只在 financial/announcements 显式列出但全局生效)
|
|
39
|
+
|
|
40
|
+
## 元信息透传
|
|
41
|
+
|
|
42
|
+
每次调用返回前会有一段 \`<!-- meta -->\` 注释块,含:
|
|
43
|
+
- \`X-Tdc-RateLimit-Remaining\`:剩余限流配额(每 IP 60 req/min)
|
|
44
|
+
- \`X-Tdc-Truncated=true\`:limit 超上限被截断
|
|
45
|
+
- \`X-Tdc-Error-Code\`:标准错误码(如 \`RESOURCE_NOT_FOUND\`)
|
|
46
|
+
- \`X-Tdc-Freshness-Tier\`:数据时效分类(**用这个判断"数据多新"**)
|
|
47
|
+
- \`intraday\`:盘中实时(FREE 套餐 15min 延迟)
|
|
48
|
+
- \`post-close\`:盘后批量(16:30 / 17:00-18:00 / 20:00)
|
|
49
|
+
- \`t0-morning\`:T+0 当天 08:00(公告/新闻)
|
|
50
|
+
- \`quarterly\`:季报,报告期后约 1 个月
|
|
51
|
+
- \`metadata\`:元数据/低频
|
|
52
|
+
- \`aggregated\`:聚合接口(取最严约束)
|
|
53
|
+
- \`X-Tdc-Freshness-Detail\`:上面 tier 的人类可读说明
|
|
54
|
+
- \`Cache-Control\`:服务端缓存策略,避免重复调用
|
|
55
|
+
|
|
56
|
+
Agent 可读这些头主动调整行为,避免无谓重试。**典型用法**:看到 \`Freshness-Tier=intraday\` 且当前已收盘 → 数据是 14:55 的快照;看到 \`post-close\` 且当前 19:00 → 数据应是当日 17:00 的。
|
|
57
|
+
|
|
58
|
+
## 详细文档
|
|
59
|
+
|
|
60
|
+
完整使用指南:https://github.com/ApocData/ApocData-skill
|
|
61
|
+
`;
|
|
62
|
+
const SCENARIOS = `# 常见场景到工具的映射
|
|
63
|
+
|
|
64
|
+
按用户意图找最优工具组合。**优先用一次能拿到多维数据的工具**(profile-full)。
|
|
65
|
+
|
|
66
|
+
## 单股分析
|
|
67
|
+
|
|
68
|
+
| 用户意图 | 推荐 |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| 全面了解一只股票 | **profile-full(symbol)** — 一次 8 维 |
|
|
71
|
+
| 当前股价/涨跌 | quote(symbol) |
|
|
72
|
+
| 基本信息(行业/PE/市值) | stock(symbol) |
|
|
73
|
+
| 是否 ST | st(symbol) |
|
|
74
|
+
| 历史 K 线 | daily(symbol, limit) 或 daily(symbol, start, end) |
|
|
75
|
+
| 技术指标(MACD/RSI/KDJ) | tech-factor(symbol) |
|
|
76
|
+
| 财务摘要 | financial(symbol, limit) |
|
|
77
|
+
| 业绩快报(季报披露窗口) | express(symbol) |
|
|
78
|
+
| 十大股东 | holders(symbol) |
|
|
79
|
+
| 股东户数变化 | holder-number(symbol) |
|
|
80
|
+
| 资金流(主力/超大单) | moneyflow(symbol) |
|
|
81
|
+
| 北向持仓 | hk-hold(symbol) |
|
|
82
|
+
| 筹码分布 | cyq-perf(symbol) |
|
|
83
|
+
| 公司公告(正式披露) | announcements(symbol, fields=title,summary,ann_date) |
|
|
84
|
+
| 机构调研 | survey(symbol) |
|
|
85
|
+
| 分红送配 | dividend(symbol) |
|
|
86
|
+
| 限售解禁 | share-float(symbol) |
|
|
87
|
+
| 大宗交易 | block-trade(symbol) |
|
|
88
|
+
| 回购 | repurchase(symbol) |
|
|
89
|
+
|
|
90
|
+
## 全市场排行/筛选
|
|
91
|
+
|
|
92
|
+
| 用户意图 | 推荐 |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| 今天涨幅榜/跌幅榜 | ranking(direction=gain\\|loss) |
|
|
95
|
+
| 当日热度榜 | hot-rank(type=A股市场) |
|
|
96
|
+
| 今天涨停/跌停个股 | limit-list(kind=U\\|D\\|Z) |
|
|
97
|
+
| 连板天梯 | limit-step() |
|
|
98
|
+
| 板块资金流榜 | sector-flow(type=industry\\|concept\\|region) |
|
|
99
|
+
| 龙虎榜 | dragon-tiger(date) 或按个股 dragon-tiger(symbol) |
|
|
100
|
+
| 游资席位清单 | hot-money() |
|
|
101
|
+
| 游资交易明细 | hot-money-detail(symbol\\|date) |
|
|
102
|
+
| 沪深港通总流向 | hsgt() |
|
|
103
|
+
| 融资融券 | margin(exchange=SSE\\|SZSE\\|BSE) |
|
|
104
|
+
| 名称/行业搜股票 | stocks(q\\|industry\\|market) |
|
|
105
|
+
|
|
106
|
+
## 板块成分
|
|
107
|
+
|
|
108
|
+
| 用户意图 | 推荐 |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| 找东财概念板块 | concepts(q) → concept-stocks(themeCode) |
|
|
111
|
+
| 找同花顺板块(行业+概念) | ths-boards(q) → ths-board-stocks(tsCode) |
|
|
112
|
+
|
|
113
|
+
## 指数与港股可转债
|
|
114
|
+
|
|
115
|
+
| 用户意图 | 推荐 |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| 指数日 K(沪深 300 等) | indexes(q) → index-daily(tsCode 如 000300.SH) |
|
|
118
|
+
| 港股日 K | hk-daily(tsCode 如 00700.HK) — 数据稀疏 |
|
|
119
|
+
| 可转债列表 | convertible-bonds(q\\|stkCode) |
|
|
120
|
+
| 转股价变动 | cb-price-chg(tsCode) |
|
|
121
|
+
|
|
122
|
+
## 宏观与日历
|
|
123
|
+
|
|
124
|
+
| 用户意图 | 推荐 |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| CPI/PPI/GDP/PMI 历史 | macro(type) |
|
|
127
|
+
| 最新一期值 | macro-latest(type) |
|
|
128
|
+
| 指标定义说明 | macro-definition(type) |
|
|
129
|
+
| 某区间交易日 | calendar(start, end) |
|
|
130
|
+
|
|
131
|
+
## 因子
|
|
132
|
+
|
|
133
|
+
| 用户意图 | 推荐 |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| 平台支持哪些因子 | factor-categories() — 看 11 个分类概览 |
|
|
136
|
+
| 全部 154 个因子明细 | factors() |
|
|
137
|
+
| 个股技术指标值 | tech-factor(symbol) |
|
|
138
|
+
|
|
139
|
+
## 反模式(不要这样用)
|
|
140
|
+
|
|
141
|
+
- ❌ 用 quote + stock + holders + ... 串调 8 个接口 → ✅ 一次 profile-full
|
|
142
|
+
- ❌ 用 stock + st 拼出"ST 状态" → ✅ 直接 st(symbol)
|
|
143
|
+
- ❌ 拿到 array[0] 就重试 → ✅ 检查 meta 里 X-Tdc-Sparse 注释,数据稀疏属正常
|
|
144
|
+
`;
|
|
145
|
+
const LIMITS = `# 工具参数 limit 速查表(超出会静默截断)
|
|
146
|
+
|
|
147
|
+
| 工具 | 默认 | 最大 |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| quote | — | — |
|
|
150
|
+
| quotes (symbols 列表长度) | — | 10 |
|
|
151
|
+
| daily | 30 | 30 |
|
|
152
|
+
| stocks | 20 | 50 |
|
|
153
|
+
| ranking | 20 | 50 |
|
|
154
|
+
| indexes | 20 | 50 |
|
|
155
|
+
| index-daily | 30 | 30 |
|
|
156
|
+
| hot-rank | 30 | 50 |
|
|
157
|
+
| financial | 4 | 4 |
|
|
158
|
+
| express | 4 | 8 |
|
|
159
|
+
| dividend | 10 | 20 |
|
|
160
|
+
| holder-number | 10 | 20 |
|
|
161
|
+
| share-float | 10 | 20 |
|
|
162
|
+
| repurchase | 10 | 20 |
|
|
163
|
+
| block-trade | 10 | 30 |
|
|
164
|
+
| moneyflow | 10 | 30 |
|
|
165
|
+
| hsgt | 10 | 30 |
|
|
166
|
+
| hk-hold | 10 | 30 |
|
|
167
|
+
| hk-daily | 30 | 30 |
|
|
168
|
+
| margin | 10 | 30 |
|
|
169
|
+
| dragon-tiger | 30 | 50 |
|
|
170
|
+
| hot-money | 50 | 50 |
|
|
171
|
+
| hot-money-detail | 30 | 50 |
|
|
172
|
+
| limit-list | 30 | 50 |
|
|
173
|
+
| limit-step | 30 | 50 |
|
|
174
|
+
| sector-flow | 20 | 50 |
|
|
175
|
+
| cyq-perf | 5 | 20 |
|
|
176
|
+
| announcements | 5 | 5 |
|
|
177
|
+
| survey | 5 | 10 |
|
|
178
|
+
| concepts | 30 | 50 |
|
|
179
|
+
| concept-stocks | 50 | 50 |
|
|
180
|
+
| ths-boards | 30 | 50 |
|
|
181
|
+
| ths-board-stocks | 50 | 50 |
|
|
182
|
+
| convertible-bonds | 20 | 50 |
|
|
183
|
+
| cb-price-chg | 10 | 20 |
|
|
184
|
+
| tech-factor | 1 | 10 |
|
|
185
|
+
| macro | 12 | 12 |
|
|
186
|
+
| calendar 跨度 | — | 366 天 |
|
|
187
|
+
|
|
188
|
+
## 字段裁剪 fields(全局支持)
|
|
189
|
+
|
|
190
|
+
**所有 46 个工具都支持** \`?fields=col1,col2,...\`,响应只保留指定字段。请求列序保持。响应头会带 \`X-Tdc-Fields-Applied\`。
|
|
191
|
+
|
|
192
|
+
最受益的几个(默认字段多、token 贵):
|
|
193
|
+
|
|
194
|
+
- **financial** — 默认返回 60+ 字段,建议 \`fields=symbol,end_date,roe,revenue,net_profit\`
|
|
195
|
+
- **announcements** — 默认含 Markdown 全文(很长),建议 \`fields=title,summary,ann_date,url\`
|
|
196
|
+
- **quote** — 16 字段,紧凑场景可 \`fields=symbol,close,pct_chg\`
|
|
197
|
+
- **stock** — 12 字段,估值场景可 \`fields=symbol,name,pe,pb,total_mv\`
|
|
198
|
+
- **profile-full** — 8 维聚合,按需只要某些 section(注意是子 Map,行为待验证)
|
|
199
|
+
|
|
200
|
+
不存在的字段自动忽略(不报错),空 fields 返回原数据。
|
|
201
|
+
|
|
202
|
+
## format=compact
|
|
203
|
+
|
|
204
|
+
所有返回数组的工具支持。响应从 \`[{a:1,b:2}, {a:3,b:4}]\` 变成 \`{columns:["a","b"], rows:[[1,2],[3,4]]}\`,省 60-70% token。
|
|
205
|
+
单条 Map 返回的工具(quote/stock/st/macro-latest/macro-definition/profile-full/calendar)不支持。
|
|
206
|
+
`;
|
|
207
|
+
export const ALL_RESOURCES = [
|
|
208
|
+
{
|
|
209
|
+
uri: 'apocdata://guide',
|
|
210
|
+
name: 'ApocData 接入指南',
|
|
211
|
+
description: '全局能力速览 + 46 工具分组 + 关键约定(symbol 格式 / 延迟 / 错误协议 / 元信息头)',
|
|
212
|
+
mimeType: 'text/markdown',
|
|
213
|
+
text: GUIDE,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
uri: 'apocdata://scenarios',
|
|
217
|
+
name: '场景速查',
|
|
218
|
+
description: '常见用户意图到工具组合的映射;含反模式(应该一次画像就别串调 8 个接口)',
|
|
219
|
+
mimeType: 'text/markdown',
|
|
220
|
+
text: SCENARIOS,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
uri: 'apocdata://limits',
|
|
224
|
+
name: 'limit / fields / compact 速查表',
|
|
225
|
+
description: '所有工具的 limit 默认值与上限;哪些工具支持 fields 字段裁剪和 format=compact',
|
|
226
|
+
mimeType: 'text/markdown',
|
|
227
|
+
text: LIMITS,
|
|
228
|
+
},
|
|
229
|
+
];
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com).
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
*
|
|
14
|
+
* @author Chill
|
|
15
|
+
*/
|
|
16
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
17
|
+
/**
|
|
18
|
+
* 构造 MCP server 实例,注册所有 tool。
|
|
19
|
+
*/
|
|
20
|
+
export declare function createMcpServer(): Server;
|