gangtise-mcp 0.1.21 → 0.1.22

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
@@ -4,6 +4,16 @@
4
4
 
5
5
  ## Changelog
6
6
 
7
+ ### 0.1.22 (2026-06-12)
8
+ - 同步 CLI reference 常量/题材/板块 API(CLI v0.15.1 之后的 f2d2a00):
9
+ - 新增 `gangtise_constant_category`:常量分类清单及各分类适用的接口参数(usageScopes)
10
+ - 新增 `gangtise_constant_list`:按分类查全部常量值(中信/申万/Gangtise 行业、国内城市、A股/港股公告分类、区域),树形分类含 `children` 嵌套;`constants` 响应自动归一化为 `list`
11
+ - 新增 `gangtise_concept_search`:按关键词(中文名/拼音/分组名)搜索题材 ID,供 `gangtise_concept_info` / `gangtise_concept_securities` / `gangtise_theme_tracking` 使用
12
+ - 新增 `gangtise_sector_search` / `gangtise_sector_constituents`:板块 ID 搜索与全量成分股名单
13
+ - `gangtise_lookup` 收窄为常量 API 未覆盖的 3 类本地表(券商机构 / 会议机构 / 申万行业代码),移除研究方向/行业/地区/公告类别/主题 ID 本地数据(约 -2700 行静态表,改由上述 API 实时提供)
14
+ - 日程类工具(路演/调研/策略会/论坛)新增 `locationList` 筛选(domesticCity 常量 ID)
15
+ - 同步 CLI v0.15.1 错误码提示:补充 410110(异步生成中)/ 410111(生成失败终态)/ 410004(数据未找到)/ 430004(下载失败)/ 430007(行情超限)/ 433007(数据源不匹配)/ 10011401(白名单未开通)
16
+
7
17
  ### 0.1.21 (2026-06-10)
8
18
  - 全部 69 个工具声明 MCP `annotations: { readOnlyHint: true }`:本服务只做数据查询,支持该注解的客户端(如 VS Code Copilot)可跳过确认弹窗;并加集成测试防止后续新工具漏标
9
19
  - 补齐核心模块单测:`pollAsyncContent` 轮询(退避序列 / 410110 / 410111 / 超时)、`normalizeRows` 矩阵转换、异步工具对 submit→poll 流程,测试 85 → 98
@@ -112,7 +122,7 @@
112
122
  | 类别 | 工具 |
113
123
  |---|---|
114
124
  | 上下文 | `gangtise_current_date` — 查询运行时当前日期、年份、时间和时区 |
115
- | 参考数据 | `gangtise_lookup` — 研究方向、券商、行业、地区、公告类别、申万行业代码、主题 ID |
125
+ | 参考数据 | `gangtise_constant_category` / `gangtise_constant_list` 行业、城市、公告分类、区域等常量;`gangtise_concept_search` — 题材 ID 搜索;`gangtise_sector_search` / `gangtise_sector_constituents` — 板块及成分股;`gangtise_lookup` — 券商机构、会议机构、申万行业代码(本地表) |
116
126
  | 证券检索 | `gangtise_securities_search` |
117
127
  | 观点/研报 | 国内首席观点、纪要、券商研报、外资研报、外资独立观点、公告(A股/港股) |
118
128
  | 路演/调研 | 路演、调研、策略会、论坛 |
@@ -100,14 +100,9 @@ export class GangtiseClient {
100
100
  }
101
101
  async readLocalLookup(endpoint) {
102
102
  const keyMapping = {
103
- "lookup.research-areas.list": "research-areas",
104
103
  "lookup.broker-orgs.list": "broker-orgs",
105
104
  "lookup.meeting-orgs.list": "meeting-orgs",
106
- "lookup.industries.list": "industries",
107
- "lookup.regions.list": "regions",
108
- "lookup.announcement-categories.list": "announcement-categories",
109
105
  "lookup.industry-codes.list": "industry-codes",
110
- "lookup.theme-ids.list": "theme-ids",
111
106
  };
112
107
  const lookupKey = keyMapping[endpoint.key];
113
108
  if (lookupKey) {
@@ -8,13 +8,6 @@ export const ENDPOINTS = {
8
8
  description: "Get access token",
9
9
  },
10
10
  // ─── lookup (served from local data, not HTTP) ───
11
- "lookup.research-areas.list": {
12
- key: "lookup.research-areas.list",
13
- method: "GET",
14
- path: "/guide/research-area-local",
15
- kind: "json",
16
- description: "List research areas from local docs",
17
- },
18
11
  "lookup.broker-orgs.list": {
19
12
  key: "lookup.broker-orgs.list",
20
13
  method: "GET",
@@ -29,27 +22,6 @@ export const ENDPOINTS = {
29
22
  kind: "json",
30
23
  description: "List meeting orgs from local docs",
31
24
  },
32
- "lookup.industries.list": {
33
- key: "lookup.industries.list",
34
- method: "GET",
35
- path: "/guide/industries-local",
36
- kind: "json",
37
- description: "List industries from local docs",
38
- },
39
- "lookup.regions.list": {
40
- key: "lookup.regions.list",
41
- method: "GET",
42
- path: "/guide/regions-local",
43
- kind: "json",
44
- description: "List regions from local docs",
45
- },
46
- "lookup.announcement-categories.list": {
47
- key: "lookup.announcement-categories.list",
48
- method: "GET",
49
- path: "/guide/announcement-categories-local",
50
- kind: "json",
51
- description: "List announcement categories from local docs",
52
- },
53
25
  "lookup.industry-codes.list": {
54
26
  key: "lookup.industry-codes.list",
55
27
  method: "GET",
@@ -57,13 +29,6 @@ export const ENDPOINTS = {
57
29
  kind: "json",
58
30
  description: "List Shenwan industry codes from local docs",
59
31
  },
60
- "lookup.theme-ids.list": {
61
- key: "lookup.theme-ids.list",
62
- method: "GET",
63
- path: "/guide/theme-ids-local",
64
- kind: "json",
65
- description: "List theme IDs from local docs",
66
- },
67
32
  // ─── insight ───
68
33
  "insight.opinion.list": {
69
34
  key: "insight.opinion.list",
@@ -211,6 +176,41 @@ export const ENDPOINTS = {
211
176
  kind: "json",
212
177
  description: "Search GTS codes (securities)",
213
178
  },
179
+ "reference.constant-category": {
180
+ key: "reference.constant-category",
181
+ method: "GET",
182
+ path: "/application/open-reference/constants/category",
183
+ kind: "json",
184
+ description: "List constant categories and their API usage scopes",
185
+ },
186
+ "reference.constant-list": {
187
+ key: "reference.constant-list",
188
+ method: "POST",
189
+ path: "/application/open-reference/constants/getList",
190
+ kind: "json",
191
+ description: "List all constant values of a category",
192
+ },
193
+ "reference.concept-search": {
194
+ key: "reference.concept-search",
195
+ method: "POST",
196
+ path: "/application/open-reference/concepts/search",
197
+ kind: "json",
198
+ description: "Search concept (theme) IDs by keyword",
199
+ },
200
+ "reference.sector-search": {
201
+ key: "reference.sector-search",
202
+ method: "POST",
203
+ path: "/application/open-reference/sectors/search",
204
+ kind: "json",
205
+ description: "Search sector IDs by keyword",
206
+ },
207
+ "reference.sector-constituents": {
208
+ key: "reference.sector-constituents",
209
+ method: "POST",
210
+ path: "/application/open-reference/sectors/constituents",
211
+ kind: "json",
212
+ description: "List constituent securities of a sector",
213
+ },
214
214
  // ─── quote ───
215
215
  "quote.day-kline": {
216
216
  key: "quote.day-kline",
@@ -29,6 +29,13 @@ const ERROR_HINTS = {
29
29
  "8000016": "开发账号状态异常。",
30
30
  "8000018": "开发账号已到期。",
31
31
  "903301": "今日调用次数已达上限。",
32
+ "410110": "异步内容生成中,稍后用对应 *_check 工具查询。",
33
+ "410111": "异步内容生成失败(终态),请更换参数后重新提交。",
34
+ "410004": "数据未找到,请检查查询条件。",
35
+ "430004": "下载失败(官方未文档化错误码),请确认 reportId 有效或更换 fileType 重试。",
36
+ "430007": "行情查询超出限制,请缩短日期范围。",
37
+ "433007": "数据源不匹配,请检查 resourceType 与 sourceId 组合。",
38
+ "10011401": "白名单未开通,请联系管理员。",
32
39
  };
33
40
  export class ApiError extends CliError {
34
41
  code;
@@ -1,13 +1,8 @@
1
1
  const cache = new Map();
2
2
  const loaders = {
3
- "research-areas": () => import("./research-areas.js"),
4
3
  "broker-orgs": () => import("./broker-orgs.js"),
5
4
  "meeting-orgs": () => import("./meeting-orgs.js"),
6
- "industries": () => import("./industries.js"),
7
- "regions": () => import("./regions.js"),
8
- "announcement-categories": () => import("./announcement-categories.js"),
9
5
  "industry-codes": () => import("./industry-codes.js"),
10
- "theme-ids": () => import("./theme-ids.js"),
11
6
  };
12
7
  export async function getLookupData(key) {
13
8
  if (cache.has(key))
@@ -29,5 +29,10 @@ export function normalizeRows(value) {
29
29
  const hasMeta = Object.keys(meta).length > 0;
30
30
  return hasMeta ? { ...meta, list: chatRoomList } : chatRoomList;
31
31
  }
32
+ if (Array.isArray(record.constants)) {
33
+ const { constants, ...meta } = record;
34
+ const hasMeta = Object.keys(meta).length > 0;
35
+ return hasMeta ? { ...meta, list: constants } : constants;
36
+ }
32
37
  return value;
33
38
  }
package/dist/tools/ai.js CHANGED
@@ -145,7 +145,7 @@ export function registerAiTools(server, client, opts) {
145
145
  server.registerTool("gangtise_theme_tracking", {
146
146
  description: "获取指定主题的每日跟踪报告(早报或晚报版),需传入主题 ID 和日期。",
147
147
  inputSchema: {
148
- themeId: z.string().describe("主题 ID,来自 gangtise_lookup type=theme-ids(必填)"),
148
+ themeId: z.string().describe("主题 ID,来自 gangtise_concept_search(必填)"),
149
149
  date: z.string().describe("YYYY-MM-DD,仅支持最近 30 天(必填)"),
150
150
  type: z.union([z.string(), z.array(z.string())]).optional().describe("morning=早报 | night=晚报;可传单个值或数组"),
151
151
  },
@@ -16,20 +16,20 @@ const specs = [
16
16
  },
17
17
  {
18
18
  name: "gangtise_concept_info",
19
- description: "查询题材指数(概念/主题)基本信息:返回题材整体画像(定义 / 投资逻辑 / 行业空间 / 竞争格局 / 催化事件)。仅返回最新截面数据,不支持历史回溯。conceptId 与主题跟踪 gangtise_theme_tracking 的 themeId 为同一套 ID 体系,可用 gangtise_lookup(type=theme-ids) 按名称查询(如 机器人 → 121000130)。",
19
+ description: "查询题材指数(概念/主题)基本信息:返回题材整体画像(定义 / 投资逻辑 / 行业空间 / 竞争格局 / 催化事件)。仅返回最新截面数据,不支持历史回溯。conceptId 与主题跟踪 gangtise_theme_tracking 的 themeId 为同一套 ID 体系,可用 gangtise_concept_search 按名称查询(如 机器人 → 121000130)。",
20
20
  endpointKey: "alternative.concept-info",
21
21
  paginated: false,
22
22
  inputSchema: {
23
- conceptId: z.string().describe("题材指数 ID,如 '121000130'(机器人)。来自 gangtise_lookup(type=theme-ids)(必填)"),
23
+ conceptId: z.string().describe("题材指数 ID,如 '121000130'(机器人)。来自 gangtise_concept_search(必填)"),
24
24
  },
25
25
  },
26
26
  {
27
27
  name: "gangtise_concept_securities",
28
- description: "查询题材指数(概念/主题)成分股(题材深度 F8):按分组结构返回当前成分股,每只含是否重点个股 isKey 与纳入理由 inclusionReason。conceptId 与主题跟踪 gangtise_theme_tracking 的 themeId 为同一套 ID 体系,可用 gangtise_lookup(type=theme-ids) 按名称查询(如 机器人 → 121000130)。",
28
+ description: "查询题材指数(概念/主题)成分股(题材深度 F8):按分组结构返回当前成分股,每只含是否重点个股 isKey 与纳入理由 inclusionReason。conceptId 与主题跟踪 gangtise_theme_tracking 的 themeId 为同一套 ID 体系,可用 gangtise_concept_search 按名称查询(如 机器人 → 121000130)。",
29
29
  endpointKey: "alternative.concept-securities",
30
30
  paginated: false,
31
31
  inputSchema: {
32
- conceptId: z.string().describe("题材指数 ID,如 '121000130'(机器人)。来自 gangtise_lookup(type=theme-ids)(必填)"),
32
+ conceptId: z.string().describe("题材指数 ID,如 '121000130'(机器人)。来自 gangtise_concept_search(必填)"),
33
33
  },
34
34
  },
35
35
  ];
@@ -17,11 +17,12 @@ const scheduleListSchema = {
17
17
  brokerTypeList: z.array(z.string()).optional(),
18
18
  objectList: z.array(z.string()).optional().describe("company=公司 | industry=行业"),
19
19
  permission: z.array(z.number().int()).optional(),
20
+ locationList: z.array(z.string()).optional().describe("地点 ID(domesticCity 常量),来自 gangtise_constant_list category=domesticCity"),
20
21
  };
21
22
  function scheduleSpec(name, label, endpointKey) {
22
23
  return {
23
24
  name,
24
- description: `查询${label}日程列表,支持按研究方向、机构、证券、类别、市场、参会角色等筛选。`,
25
+ description: `查询${label}日程列表,支持按研究方向、机构、证券、类别、市场、参会角色、地点等筛选。`,
25
26
  endpointKey,
26
27
  paginated: true,
27
28
  inputSchema: scheduleListSchema,
@@ -39,7 +40,7 @@ const listSpecs = [
39
40
  endTime: z.string().optional().describe(dateTimeDesc()),
40
41
  keyword: z.string().optional(),
41
42
  rankType: z.number().int().optional().describe("1=综合排序(默认)| 2=时间倒序"),
42
- researchAreaList: z.array(z.string()).optional().describe("研究方向 ID,来自 gangtise_lookup type=research-areas"),
43
+ researchAreaList: z.array(z.string()).optional().describe("研究方向 ID,来自 gangtise_constant_list(citicIndustry / gangtiseIndustry 分类)"),
43
44
  chiefList: z.array(z.string()).optional().describe("首席分析师 ID 列表"),
44
45
  securityList: z.array(z.string()).optional().describe("证券代码列表,如 ['600519.SH']"),
45
46
  brokerList: z.array(z.string()).optional().describe("券商机构 ID,来自 gangtise_lookup type=broker-orgs"),
@@ -111,7 +112,7 @@ const listSpecs = [
111
112
  searchType: z.number().int().optional().describe("1=标题搜索 | 2=全文搜索"),
112
113
  rankType: z.number().int().optional().describe("1=综合排序 | 2=时间倒序"),
113
114
  securityList: z.array(z.string()).optional(),
114
- regionList: z.array(z.string()).optional().describe("地区 ID,来自 gangtise_lookup type=regions"),
115
+ regionList: z.array(z.string()).optional().describe("地区 ID,来自 gangtise_constant_list category=regionCategory"),
115
116
  categoryList: z.array(z.string()).optional(),
116
117
  industryList: z.array(z.string()).optional(),
117
118
  brokerList: z.array(z.string()).optional(),
@@ -136,7 +137,7 @@ const listSpecs = [
136
137
  rankType: z.number().int().optional().describe("1=综合排序 | 2=时间倒序"),
137
138
  securityList: z.array(z.string()).optional(),
138
139
  announcementTypeList: z.array(z.string()).optional(),
139
- categoryList: z.array(z.string()).optional().describe("公告类别 ID,来自 gangtise_lookup type=announcement-categories"),
140
+ categoryList: z.array(z.string()).optional().describe("公告类别 ID,来自 gangtise_constant_list category=aShareAnnouncementCategory"),
140
141
  },
141
142
  },
142
143
  {
@@ -152,7 +153,7 @@ const listSpecs = [
152
153
  searchType: z.number().int().optional().describe("1=标题搜索 | 2=全文搜索"),
153
154
  rankType: z.number().int().optional().describe("1=综合排序 | 2=时间倒序"),
154
155
  securityList: z.array(z.string()).optional(),
155
- categoryList: z.array(z.string()).optional(),
156
+ categoryList: z.array(z.string()).optional().describe("公告类别 ID,来自 gangtise_constant_list category=hkShareAnnouncementCategory"),
156
157
  },
157
158
  },
158
159
  {
@@ -166,7 +167,7 @@ const listSpecs = [
166
167
  endTime: z.string().optional().describe(dateTimeDesc()),
167
168
  keyword: z.string().optional(),
168
169
  rankType: z.number().int().optional().describe("1=综合排序 | 2=时间倒序"),
169
- regionList: z.array(z.string()).optional().describe("地区 ID,来自 gangtise_lookup type=regions"),
170
+ regionList: z.array(z.string()).optional().describe("地区 ID,来自 gangtise_constant_list category=regionCategory"),
170
171
  industryList: z.array(z.string()).optional(),
171
172
  securityList: z.array(z.string()).optional(),
172
173
  brokerList: z.array(z.string()).optional(),
@@ -2,20 +2,15 @@ import { z } from "zod";
2
2
  import { getLookupData } from "../core/lookupData/index.js";
3
3
  import { errorMessage } from "../core/errors.js";
4
4
  const LOOKUP_TYPES = [
5
- "research-areas",
6
5
  "broker-orgs",
7
6
  "meeting-orgs",
8
- "industries",
9
- "regions",
10
- "announcement-categories",
11
7
  "industry-codes",
12
- "theme-ids",
13
8
  ];
14
9
  export function registerLookupTools(server, _client) {
15
10
  server.registerTool("gangtise_lookup", {
16
- description: "查询本地静态参考数据:研究方向、券商机构、会议机构、行业、地区、公告类别、申万行业代码、主题 ID。无需调用 API,直接返回本地数据。",
11
+ description: "查询本地静态参考数据(常量 API 未覆盖的 ID):券商机构、会议机构、申万行业代码。无需调用 API,直接返回本地数据。行业/地区/公告类别 ID 用 gangtise_constant_list,主题 ID 用 gangtise_concept_search。",
17
12
  inputSchema: {
18
- type: z.enum(LOOKUP_TYPES).describe("research-areas=研究方向 | broker-orgs=券商机构 | meeting-orgs=会议机构 | industries=行业 | regions=地区 | announcement-categories=公告类别 | industry-codes=申万行业代码 | theme-ids=主题ID"),
13
+ type: z.enum(LOOKUP_TYPES).describe("broker-orgs=券商机构 | meeting-orgs=会议机构 | industry-codes=申万行业代码"),
19
14
  },
20
15
  annotations: { readOnlyHint: true },
21
16
  }, async ({ type }) => {
@@ -1,7 +1,51 @@
1
1
  import { z } from "zod";
2
2
  import { normalizeRows } from "../core/normalize.js";
3
- import { buildToolContent } from "./registry.js";
3
+ import { buildToolContent, registerJsonTool } from "./registry.js";
4
4
  import { toolHandler, contentResult } from "./helpers.js";
5
+ const referenceSpecs = [
6
+ {
7
+ name: "gangtise_constant_category",
8
+ description: "查询常量分类列表:返回所有常量分类及每个分类适用于哪些接口的哪些参数(usageScopes)。当前分类:citicIndustry=中信一级行业 | swIndustry=申万一级行业 | gangtiseIndustry=Gangtise行业 | domesticCity=国内城市 | aShareAnnouncementCategory=A股公告分类 | hkShareAnnouncementCategory=港股公告分类 | regionCategory=区域分类。",
9
+ endpointKey: "reference.constant-category",
10
+ inputSchema: {},
11
+ },
12
+ {
13
+ name: "gangtise_constant_list",
14
+ description: "查询某个常量分类下的全部常量值(constantId / constantName / level),树形分类(公告分类)的父节点含 children 嵌套。行业、城市、公告类别、区域等筛选参数的 ID 都从这里查。",
15
+ endpointKey: "reference.constant-list",
16
+ inputSchema: {
17
+ category: z
18
+ .string()
19
+ .describe("分类代码(必填):citicIndustry | swIndustry | gangtiseIndustry | domesticCity | aShareAnnouncementCategory | hkShareAnnouncementCategory | regionCategory,完整清单见 gangtise_constant_category"),
20
+ },
21
+ },
22
+ {
23
+ name: "gangtise_concept_search",
24
+ description: "按关键词搜索题材(概念/主题)ID,支持中文名、简称、拼音首字母(如 jqr)、分组名。返回 conceptId / conceptName / matchScore。该 ID 供 gangtise_concept_info / gangtise_concept_securities 的 conceptId 和 gangtise_theme_tracking 的 themeId 使用(同一套 ID)。",
25
+ endpointKey: "reference.concept-search",
26
+ inputSchema: {
27
+ keyword: z.string().describe("搜索词:题材中文名/简称、拼音首字母(如 jqr)、分组名(如 灵巧手)"),
28
+ top: z.number().int().min(1).max(10).optional().describe("最大返回条数(默认 10,上限 10)"),
29
+ },
30
+ },
31
+ {
32
+ name: "gangtise_sector_search",
33
+ description: "按关键词搜索板块 ID(行业/概念/指数成份等分类树节点),返回 sectorId / sectorName / hierarchy(层级路径)/ matchScore。同名板块可能出现在多个层级,用 hierarchy 区分。sectorId 供 gangtise_sector_constituents 使用,与题材 conceptId 是两套 ID,不通用。",
34
+ endpointKey: "reference.sector-search",
35
+ inputSchema: {
36
+ keyword: z.string().optional().describe("搜索词:板块中文名/简称、拼音首字母"),
37
+ top: z.number().int().min(1).max(10).optional().describe("最大返回条数(默认 10,上限 10)"),
38
+ },
39
+ },
40
+ {
41
+ name: "gangtise_sector_constituents",
42
+ description: "查询板块的全量成分股名单(gtsCode / gtsName)。sectorId 必须来自 gangtise_sector_search;返回 0 条通常是误用了题材 conceptId。题材成分股(含分组/重点标记)用 gangtise_concept_securities。",
43
+ endpointKey: "reference.sector-constituents",
44
+ inputSchema: {
45
+ sectorId: z.string().describe("板块 ID,来自 gangtise_sector_search(必填)"),
46
+ },
47
+ },
48
+ ];
5
49
  export function registerReferenceTools(server, client) {
6
50
  server.registerTool("gangtise_securities_search", {
7
51
  description: "按关键词搜索证券,支持股票名称、代码(如 600519)、拼音或英文名。返回匹配证券及其 GTS 代码。",
@@ -15,4 +59,7 @@ export function registerReferenceTools(server, client) {
15
59
  const result = await client.call("reference.securities-search", args);
16
60
  return contentResult(await buildToolContent(normalizeRows(result)));
17
61
  }));
62
+ for (const spec of referenceSpecs) {
63
+ registerJsonTool(server, client, spec);
64
+ }
18
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gangtise-mcp",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "MCP server for Gangtise OpenAPI",
5
5
  "license": "MIT",
6
6
  "author": "gangtiser",