national-stats-mcp 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -187,7 +187,7 @@ enum CategoryCode {
187
187
  - 搜索接口 `/external/query` 用于关键词搜索和快速定位cid
188
188
  - 树遍历接口 `/new/queryIndexTreeAsync` 用于浏览分类树和获取cid
189
189
  - 指标查询接口 `/new/queryIndicatorsByCid` 用于获取指标列表
190
- - 数据查询接口 `/getEsDataByCidAndDt` 用于批量获取时间序列数据
190
+ - 数据查询接口 `/stream/esData` 用于批量获取时间序列数据
191
191
  - 内置缓存机制:树结构24小时,指标列表12小时
192
192
  - UUID标识符系统和时间分片机制
193
193
  - 完整的错误处理和重试逻辑
@@ -408,10 +408,10 @@ class NewStatsApiClient {
408
408
  showType: params.showType || '1',
409
409
  rootId: params.rootId || this.rootId,
410
410
  };
411
- console.error(`Data Request: ${this.baseUrl}/getEsDataByCidAndDt`);
411
+ console.error(`Data Request: ${this.baseUrl}/stream/esData`);
412
412
  console.error(`Payload:`, JSON.stringify(payload, null, 2));
413
413
  try {
414
- const response = await axios_1.default.post(`${this.baseUrl}/getEsDataByCidAndDt`, payload, {
414
+ const response = await axios_1.default.post(`${this.baseUrl}/stream/esData`, payload, {
415
415
  timeout: this.timeout,
416
416
  headers: {
417
417
  'Content-Type': 'application/json',
package/dist/index.js CHANGED
@@ -8,60 +8,151 @@ const mcp_http_server_1 = require("mcp-http-server");
8
8
  const api_client_1 = require("./api-client");
9
9
  const zod_1 = require("zod");
10
10
  const commander_1 = require("commander");
11
- // 获取版本号,这里使用package.json中的版本
12
- const VERSION = '1.0.2';
13
- // 创建API客户端实例
14
- const apiClient = new api_client_1.StatsApiClient();
11
+ // 获取版本号
12
+ const VERSION = '2.0.0';
13
+ // 创建新版API客户端实例
14
+ const apiClient = new api_client_1.NewStatsApiClient();
15
+ // 31省 das 常量
16
+ const PROVINCE_LIST = [
17
+ { text: '北京', value: '110000000000' },
18
+ { text: '天津', value: '120000000000' },
19
+ { text: '河北', value: '130000000000' },
20
+ { text: '山西', value: '140000000000' },
21
+ { text: '内蒙古', value: '150000000000' },
22
+ { text: '辽宁', value: '210000000000' },
23
+ { text: '吉林', value: '220000000000' },
24
+ { text: '黑龙江', value: '230000000000' },
25
+ { text: '上海', value: '310000000000' },
26
+ { text: '江苏', value: '320000000000' },
27
+ { text: '浙江', value: '330000000000' },
28
+ { text: '安徽', value: '340000000000' },
29
+ { text: '福建', value: '350000000000' },
30
+ { text: '江西', value: '360000000000' },
31
+ { text: '山东', value: '370000000000' },
32
+ { text: '河南', value: '410000000000' },
33
+ { text: '湖北', value: '420000000000' },
34
+ { text: '湖南', value: '430000000000' },
35
+ { text: '广东', value: '440000000000' },
36
+ { text: '广西', value: '450000000000' },
37
+ { text: '海南', value: '460000000000' },
38
+ { text: '重庆', value: '500000000000' },
39
+ { text: '四川', value: '510000000000' },
40
+ { text: '贵州', value: '520000000000' },
41
+ { text: '云南', value: '530000000000' },
42
+ { text: '西藏', value: '540000000000' },
43
+ { text: '陕西', value: '610000000000' },
44
+ { text: '甘肃', value: '620000000000' },
45
+ { text: '青海', value: '630000000000' },
46
+ { text: '宁夏', value: '640000000000' },
47
+ { text: '新疆', value: '650000000000' },
48
+ ];
49
+ // 全国
50
+ const NATIONAL = { text: '全国', value: '000000000000' };
51
+ /**
52
+ * 根据 region 参数构造 das 数组
53
+ */
54
+ function buildDas(region) {
55
+ if (!region || region === '全国' || region === 'all') {
56
+ return [NATIONAL];
57
+ }
58
+ if (region === '31省' || region === '分省' || region === 'provinces') {
59
+ return PROVINCE_LIST;
60
+ }
61
+ // 支持单个省名或逗号分隔的多个省名
62
+ const names = region.split(',').map(s => s.trim());
63
+ const result = [];
64
+ for (const name of names) {
65
+ if (name === '全国') {
66
+ result.push(NATIONAL);
67
+ continue;
68
+ }
69
+ const found = PROVINCE_LIST.find(p => p.text === name || p.text === name.replace(/[市省区]$/, ''));
70
+ if (found) {
71
+ result.push(found);
72
+ }
73
+ else {
74
+ // 尝试模糊匹配
75
+ const fuzzy = PROVINCE_LIST.find(p => p.text.includes(name) || name.includes(p.text));
76
+ if (fuzzy) {
77
+ result.push(fuzzy);
78
+ }
79
+ }
80
+ }
81
+ return result.length > 0 ? result : [NATIONAL];
82
+ }
83
+ /**
84
+ * 确定 showType: 多地区用 "2"(按地区分组),单地区用 "1"(按时间分组)
85
+ */
86
+ function getShowType(das) {
87
+ return das.length > 1 ? '2' : '1';
88
+ }
15
89
  // 创建MCP服务器
16
90
  exports.server = new mcp_js_1.McpServer({
17
91
  name: 'national-stats-mcp',
18
92
  version: VERSION,
19
- description: '国家统计局数据查询API'
93
+ description: '国家统计局数据查询API (V2.0 新版)'
20
94
  });
21
95
  // 设置服务器instructions
22
96
  exports.server.instructions = `
23
- 该服务主要用于帮助用户查询国家统计局的统计数据。
97
+ 该服务主要用于帮助用户查询国家统计局的统计数据(使用V2.0新版API)。
24
98
 
25
99
  主要工具包括:
26
- - search_statistics: 通过关键词搜索国家统计局数据(推荐用于快速查询)
27
- - get_statistics_categories: 获取统计指标分类
28
- - get_statistics_leaf_categories: 获取所有叶子指标(递归获取)
29
- - get_statistics_time_options: 获取特定数据库可选的时间范围
30
- - get_statistics_data: 获取特定统计指标的数据
31
- - batch_get_statistics: 批量获取统计数据
100
+ - search_statistics: 通过关键词搜索数据集,获取cid
101
+ - browse_tree: 浏览指标分类树,支持月度/季度/年度/分省数据
102
+ - get_indicators: 根据cid获取可用指标列表
103
+ - get_data: 获取具体统计数据(支持全国/分省/指定省份)
104
+ - search_and_get: 一步到位:搜索 + 获取数据
105
+
106
+ 使用流程:
107
+ 1. 用 search_statistics 搜索关键词找到 cid
108
+ 2. 用 get_indicators 获取该 cid 下的指标 ID
109
+ 3. 用 get_data 传入 cid、indicatorIds、时间范围和地区获取数据
110
+
111
+ 快捷方式:
112
+ - search_and_get 可以一步完成上述流程(适合简单查询)
113
+ - browse_tree 可以浏览完整的分类体系
114
+
115
+ 地区参数说明(region):
116
+ - "全国" 或不传: 查全国数据
117
+ - "31省" 或 "分省": 查所有31省数据
118
+ - "北京" / "广东" 等: 查单个省份
119
+ - "北京,上海,广东": 逗号分隔查多个省份
32
120
 
33
- 使用说明:
34
- - 对于快速查询,直接使用search_statistics根据关键词查找数据和指标code
35
- - 如需浏览分类体系,调用get_statistics_categories获取分类树
36
- - 要获取特定分类下的所有叶子指标,使用get_statistics_leaf_categories
37
- - 查看可用时间范围调用get_statistics_time_options
38
- - 根据指标代码查询具体数据使用get_statistics_data
121
+ 时间格式说明(dts):
122
+ - 年度: "2020YY-2024YY"
123
+ - 季度: "2024ASS-2025BSS" (A=1季度, B=2季度, C=3季度, D=4季度)
124
+ - 月度: "202401MM-202412MM"
39
125
 
40
- 配置选项:
41
- - dbcode: 数据库代码,hgnd (年度数据)、hgjd (季度数据) 等
42
- - zb: 指标代码,通过分类或搜索获取
43
- - sj: 时间范围,如 LAST6 (最近6个季度)、2025 (指定年份) 等
126
+ 分类代码(code):
127
+ - 1: 月度数据
128
+ - 2: 季度数据
129
+ - 3: 年度数据
130
+ - 5: 分省季度数据
131
+ - 6: 分省年度数据
132
+ - 7: 其他/普查数据
44
133
  `;
45
- // 注册搜索统计指标的工具(最推荐使用)
46
- exports.server.tool('search_statistics', '通过关键词搜索国家统计局指标和数据。可以快速查找指标及其最新值。', {
47
- keyword: zod_1.z.string().describe('搜索关键词,如 "GDP"、"人均可支配收入" 等'),
48
- db: zod_1.z.string().optional().default('').describe('数据库筛选,空=全部,可选 "年度数据" 或 "季度数据"'),
49
- page: zod_1.z.number().optional().default(0).describe('页码,从0开始'),
134
+ // ====== 工具1: 搜索数据集 ======
135
+ exports.server.tool('search_statistics', '通过关键词搜索国家统计局数据集,返回匹配的数据集及其cid。用于定位要查询的数据。', {
136
+ keyword: zod_1.z.string().describe('搜索关键词,如 "GDP"、"CPI"、"人口"、"居民消费价格" 等'),
137
+ pageSize: zod_1.z.number().optional().default(10).describe('每页结果数,默认10'),
138
+ pagenum: zod_1.z.number().optional().default(1).describe('页码,从1开始'),
50
139
  }, async (args) => {
51
- const { keyword, db = '', page = 0 } = args;
140
+ const { keyword, pageSize = 10, pagenum = 1 } = args;
52
141
  try {
53
- // 执行搜索
54
- const searchResult = await apiClient.search(keyword, db, page);
55
- // 解析搜索结果
56
- const parsedResults = apiClient.parseSearchResult(searchResult);
142
+ const results = await apiClient.search({ search: keyword, pageSize, pagenum });
57
143
  return {
58
144
  content: [
59
145
  {
60
146
  type: 'text',
61
147
  text: JSON.stringify({
62
- pagecount: searchResult.pagecount,
63
- pagecurrent: searchResult.pagecurrent,
64
- results: parsedResults
148
+ count: results.length,
149
+ results: results.map(r => ({
150
+ name: r.show_name,
151
+ type: r.type_text,
152
+ cid: r.cid || null,
153
+ globalid: r.treeinfo_globalid,
154
+ timeRange: r.sdate && r.edate ? `${r.sdate} - ${r.edate}` : null,
155
+ }))
65
156
  }, null, 2)
66
157
  }
67
158
  ],
@@ -69,169 +160,233 @@ exports.server.tool('search_statistics', '通过关键词搜索国家统计局
69
160
  }
70
161
  catch (error) {
71
162
  return {
72
- content: [
73
- {
74
- type: 'text',
75
- text: `Error: ${error.message}`,
76
- },
77
- ],
163
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
78
164
  };
79
165
  }
80
166
  });
81
- // 注册获取统计分类的工具
82
- exports.server.tool('get_statistics_categories', '获取国家统计局的统计指标分类,返回分类树结构(第一层)', {
83
- dbcode: zod_1.z.string()
84
- .describe('数据库代码,指定数据类型:hgnd(年度数据), hgjd(季度数据)等'),
85
- wdcode: zod_1.z.string()
86
- .default('zb')
87
- .describe('维度代码,默认为zb(指标)'),
167
+ // ====== 工具2: 浏览分类树 ======
168
+ exports.server.tool('browse_tree', '浏览国家统计局指标分类树。可逐层钻取获取子节点,叶子节点的_id即为cid。', {
169
+ code: zod_1.z.string().default('3').describe('分类代码: 1=月度, 2=季度, 3=年度, 5=分省季度, 6=分省年度, 7=其他'),
170
+ pid: zod_1.z.string().optional().describe('父节点ID,不传则获取顶层节点'),
88
171
  }, async (args) => {
89
- const { dbcode, wdcode = 'zb' } = args;
172
+ const { code, pid } = args;
90
173
  try {
91
- const data = await apiClient.getCategories(dbcode, wdcode);
174
+ const nodes = await apiClient.getTree({ code, pid });
92
175
  return {
93
176
  content: [
94
177
  {
95
178
  type: 'text',
96
- text: JSON.stringify(data, null, 2)
179
+ text: JSON.stringify({
180
+ count: nodes.length,
181
+ nodes: nodes.map(n => ({
182
+ id: n._id,
183
+ name: n.name,
184
+ isLeaf: n.isLeaf,
185
+ timeRange: n.sdate && n.edate ? `${n.sdate} - ${n.edate}` : n.sdate || null,
186
+ }))
187
+ }, null, 2)
97
188
  }
98
189
  ],
99
190
  };
100
191
  }
101
192
  catch (error) {
102
193
  return {
103
- content: [
104
- {
105
- type: 'text',
106
- text: `Error: ${error.message}`,
107
- },
108
- ],
194
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
109
195
  };
110
196
  }
111
197
  });
112
- // 注册获取统计指标叶子节点的工具(递归获取所有叶子指标)
113
- exports.server.tool('get_statistics_leaf_categories', '递归获取国家统计局特定数据库的所有叶子指标节点(可查看分类下所有可查询指标)', {
114
- dbcode: zod_1.z.string()
115
- .describe('数据库代码,指定数据类型:hgnd(年度数据), hgjd(季度数据)等'),
198
+ // ====== 工具3: 获取指标列表 ======
199
+ exports.server.tool('get_indicators', '根据cid获取数据集下的所有可用指标(含指标ID、名称、单位、统计口径)。', {
200
+ cid: zod_1.z.string().describe('数据集ID(从搜索结果或树节点的_id获取)'),
116
201
  }, async (args) => {
117
- const { dbcode } = args;
202
+ const { cid } = args;
118
203
  try {
119
- const data = await apiClient.getLeafCategories(dbcode);
204
+ const indicators = await apiClient.getIndicators({ cid });
120
205
  return {
121
206
  content: [
122
207
  {
123
208
  type: 'text',
124
- text: JSON.stringify(data, null, 2)
209
+ text: JSON.stringify({
210
+ cid,
211
+ count: indicators.length,
212
+ indicators: indicators.map(ind => ({
213
+ id: ind._id,
214
+ name: ind.i_showname,
215
+ mark: ind.i_mark || null,
216
+ order: ind.order,
217
+ }))
218
+ }, null, 2)
125
219
  }
126
220
  ],
127
221
  };
128
222
  }
129
223
  catch (error) {
130
224
  return {
131
- content: [
132
- {
133
- type: 'text',
134
- text: `Error: ${error.message}`,
135
- },
136
- ],
225
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
137
226
  };
138
227
  }
139
228
  });
140
- // 注册获取统计时间维度选项的工具
141
- exports.server.tool('get_statistics_time_options', '获取指定数据库可选的时间范围维度', {
142
- dbcode: zod_1.z.string()
143
- .describe('数据库代码,指定数据类型:hgnd(年度数据), hgjd(季度数据)等'),
229
+ // ====== 工具4: 获取数据(核心工具)======
230
+ exports.server.tool('get_data', '获取统计数据。支持全国/31省/指定省份。需提供cid和indicatorIds(通过get_indicators获取)。', {
231
+ cid: zod_1.z.string().describe('数据集ID'),
232
+ indicatorIds: zod_1.z.array(zod_1.z.string()).describe('指标ID数组(从get_indicators获取)'),
233
+ dts: zod_1.z.string().describe('时间范围,如 "2020YY-2024YY"(年度)、"202401MM-202412MM"(月度)'),
234
+ region: zod_1.z.string().optional().default('全国').describe('地区: "全国"(默认), "31省"/"分省"(全部省份), "北京"(单省), "北京,上海,广东"(多省逗号分隔)'),
144
235
  }, async (args) => {
145
- const { dbcode } = args;
236
+ const { cid, indicatorIds, dts, region } = args;
146
237
  try {
147
- const data = await apiClient.getTimeDimensions(dbcode);
238
+ const das = buildDas(region);
239
+ const showType = getShowType(das);
240
+ const data = await apiClient.getData({
241
+ cid,
242
+ indicatorIds,
243
+ das,
244
+ dts: [dts],
245
+ showType,
246
+ });
148
247
  return {
149
248
  content: [
150
249
  {
151
250
  type: 'text',
152
- text: JSON.stringify(data, null, 2)
251
+ text: JSON.stringify({
252
+ query: { cid, indicatorIds, dts, region: region || '全国', showType },
253
+ count: data.length,
254
+ data: data.map(d => ({
255
+ code: d.code,
256
+ name: d.name,
257
+ values: d.values.map(v => ({
258
+ indicator: v.i_showname || v._id,
259
+ value: v.value,
260
+ region: v.da_name || d.name,
261
+ unit: v.du_name,
262
+ }))
263
+ }))
264
+ }, null, 2)
153
265
  }
154
266
  ],
155
267
  };
156
268
  }
157
269
  catch (error) {
158
270
  return {
159
- content: [
160
- {
161
- type: 'text',
162
- text: `Error: ${error.message}`,
163
- },
164
- ],
271
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
165
272
  };
166
273
  }
167
274
  });
168
- // 注册获取统计数据的工具
169
- exports.server.tool('get_statistics_data', '获取国家统计局特定指标的数据', {
170
- zb: zod_1.z.string().describe('指标代码,通过分类或搜索获取'),
171
- dbcode: zod_1.z.string()
172
- .describe('数据库代码,指定数据类型:hgnd(年度数据), hgjd(季度数据)等'),
173
- sj: zod_1.z.string()
174
- .default('LAST30')
175
- .describe('时间范围,例如 LAST6 (最近6季度), 2025 (指定年份), LAST30 (最近30年) 等'),
275
+ // ====== 工具5: 搜索并获取数据(快捷工具)======
276
+ exports.server.tool('search_and_get', '一步到位:根据关键词搜索 → 定位cid → 获取指标 → 查询数据。适合快速查询。支持指定地区。', {
277
+ keyword: zod_1.z.string().describe('搜索关键词,如 "GDP"、"人口"、"CPI"'),
278
+ indicatorName: zod_1.z.string().optional().describe('指标名称过滤(模糊匹配),不传则取第一个指标'),
279
+ startTime: zod_1.z.string().optional().describe('起始时间,如 "2020YY"、"202401MM"'),
280
+ endTime: zod_1.z.string().optional().describe('结束时间,如 "2024YY"、"202412MM"'),
281
+ region: zod_1.z.string().optional().default('全国').describe('地区: "全国"(默认), "31省", "北京", "北京,上海,广东"'),
176
282
  }, async (args) => {
177
- const { zb, dbcode, sj = 'LAST30' } = args;
283
+ const { keyword, indicatorName, startTime, endTime, region } = args;
178
284
  try {
179
- const data = await apiClient.getData(zb, dbcode, sj);
285
+ // 1. 搜索定位cid
286
+ const searchResults = await apiClient.search({ search: keyword, pageSize: 10 });
287
+ if (!searchResults || searchResults.length === 0) {
288
+ throw new Error(`未找到与 "${keyword}" 相关的数据集`);
289
+ }
290
+ // 优先选择最新的数据集
291
+ const target = searchResults.reduce((latest, current) => {
292
+ if (!latest.edate)
293
+ return current;
294
+ if (!current.edate)
295
+ return latest;
296
+ return current.edate > latest.edate ? current : latest;
297
+ });
298
+ // 提取 cid
299
+ const cid = target.cid || extractCidFromGlobalId(target.treeinfo_globalid);
300
+ if (!cid) {
301
+ throw new Error('无法从搜索结果中提取 cid');
302
+ }
303
+ // 2. 获取指标列表
304
+ const indicators = await apiClient.getIndicators({ cid });
305
+ if (!indicators || indicators.length === 0) {
306
+ throw new Error(`cid ${cid} 下没有找到指标`);
307
+ }
308
+ // 筛选指标
309
+ let targetIndicator = indicatorName
310
+ ? indicators.find(ind => ind.i_showname.includes(indicatorName))
311
+ : indicators[0];
312
+ if (!targetIndicator) {
313
+ throw new Error(`未找到包含 "${indicatorName}" 的指标。可用指标: ${indicators.map(i => i.i_showname).join(', ')}`);
314
+ }
315
+ // 3. 构造时间范围
316
+ const timeRange = startTime && endTime
317
+ ? `${startTime}-${endTime}`
318
+ : `${target.sdate || '2020'}YY-${target.edate || '2024'}YY`;
319
+ // 4. 构造地区维度
320
+ const das = buildDas(region);
321
+ const showType = getShowType(das);
322
+ // 5. 查询数据
323
+ const data = await apiClient.getData({
324
+ cid,
325
+ indicatorIds: [targetIndicator._id],
326
+ das,
327
+ dts: [timeRange],
328
+ showType,
329
+ });
180
330
  return {
181
331
  content: [
182
332
  {
183
333
  type: 'text',
184
- text: JSON.stringify(data, null, 2)
334
+ text: JSON.stringify({
335
+ dataset: target.show_name,
336
+ cid,
337
+ indicator: {
338
+ id: targetIndicator._id,
339
+ name: targetIndicator.i_showname,
340
+ mark: targetIndicator.i_mark || null,
341
+ },
342
+ timeRange,
343
+ region: region || '全国',
344
+ count: data.length,
345
+ data: data.map(d => ({
346
+ code: d.code,
347
+ name: d.name,
348
+ values: d.values.map(v => ({
349
+ indicator: v.i_showname || targetIndicator.i_showname,
350
+ value: v.value,
351
+ region: v.da_name || d.name,
352
+ unit: v.du_name,
353
+ }))
354
+ }))
355
+ }, null, 2)
185
356
  }
186
357
  ],
187
358
  };
188
359
  }
189
360
  catch (error) {
190
361
  return {
191
- content: [
192
- {
193
- type: 'text',
194
- text: `Error: ${error.message}`,
195
- },
196
- ],
362
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
197
363
  };
198
364
  }
199
365
  });
200
- // 注册批量获取统计数据的工具
201
- exports.server.tool('batch_get_statistics', '批量获取国家统计局多个指标的数据', {
202
- queries: zod_1.z.array(zod_1.z.object({
203
- zb: zod_1.z.string().describe('指标代码'),
204
- dbcode: zod_1.z.string().describe('数据库代码'),
205
- sj: zod_1.z.string().optional().default('LAST30').describe('时间范围'),
206
- })).describe('查询参数数组')
207
- }, async (args) => {
208
- const { queries } = args;
209
- try {
210
- const results = await apiClient.batchGet(queries.map(q => ({
211
- zb: q.zb,
212
- dbcode: q.dbcode,
213
- sj: q.sj || 'LAST30'
214
- })));
215
- return {
216
- content: [
217
- {
218
- type: 'text',
219
- text: JSON.stringify(results, null, 2)
220
- }
221
- ],
222
- };
223
- }
224
- catch (error) {
225
- return {
226
- content: [
227
- {
228
- type: 'text',
229
- text: `Error: ${error.message}`,
230
- },
231
- ],
232
- };
233
- }
366
+ // ====== 工具6: 获取省份列表 ======
367
+ exports.server.tool('list_provinces', '获取31省份代码列表,用于了解可用的地区参数。', {}, async () => {
368
+ return {
369
+ content: [
370
+ {
371
+ type: 'text',
372
+ text: JSON.stringify({
373
+ national: NATIONAL,
374
+ provinces: PROVINCE_LIST,
375
+ usage: '在 get_data 和 search_and_get 的 region 参数中使用省份名称,如 "北京" 或 "31省"'
376
+ }, null, 2)
377
+ }
378
+ ],
379
+ };
234
380
  });
381
+ /**
382
+ * 从 globalid 提取 cid (最后一段)
383
+ */
384
+ function extractCidFromGlobalId(globalId) {
385
+ if (!globalId)
386
+ return null;
387
+ const parts = globalId.split('.');
388
+ return parts[parts.length - 1] || null;
389
+ }
235
390
  // 启动服务器
236
391
  async function startServer() {
237
392
  const program = new commander_1.Command();
@@ -240,7 +395,6 @@ async function startServer() {
240
395
  .option('-h, --host <host>', 'Host to listen on for HTTP/SSE mode', 'localhost')
241
396
  .parse();
242
397
  const options = program.opts();
243
- // 通过端口参数判断是否使用HTTP模式还是STDIO模式
244
398
  if (options.port) {
245
399
  await (0, mcp_http_server_1.startSseAndStreamableHttpMcpServer)({
246
400
  host: options.host,
@@ -251,7 +405,6 @@ async function startServer() {
251
405
  });
252
406
  }
253
407
  else {
254
- // 使用STDIO模式
255
408
  const transport = new stdio_js_1.StdioServerTransport();
256
409
  await exports.server.connect(transport);
257
410
  }
package/dist/types.d.ts CHANGED
@@ -82,7 +82,7 @@ export interface DataValue {
82
82
  du_name?: string;
83
83
  }
84
84
  /**
85
- * 数据点 - 来自 getEsDataByCidAndDt
85
+ * 数据点 - 来自 stream/esData
86
86
  */
87
87
  export interface DataPoint {
88
88
  code: string;
@@ -95,6 +95,8 @@ export interface DataPoint {
95
95
  export interface DataResponse {
96
96
  data: DataPoint[];
97
97
  success: boolean;
98
+ state?: number;
99
+ message?: string;
98
100
  }
99
101
  /**
100
102
  * 数据查询请求参数
Binary file
@@ -0,0 +1,481 @@
1
+ # 国家统计局新版数据接口使用文档
2
+
3
+ > **基础地址**: `https://data.stats.gov.cn/dg/website/publicrelease/web/external`
4
+ > **鉴权**: 无需鉴权,公开访问
5
+ > **版本**: V2.0 (2026.06.05更新)
6
+ > **特点**: 采用 UUID 标识符,支持时间分片,元数据与数据分离,批量查询效率高。
7
+
8
+ ### 变更记录
9
+
10
+ | 日期 | 变更内容 |
11
+ |------|---------|
12
+ | 2026.06.05 | 数据查询接口从 `/getEsDataByCidAndDt` 迁移至 `/stream/esData`;`showType` 变为必填参数;响应新增 `state`、`message` 字段 |
13
+ | 2026.03.27 | V2.0 发布,采用 UUID 标识符架构 |
14
+
15
+ ---
16
+
17
+ ## 目录
18
+
19
+ - [核心概念](#核心概念)
20
+ - [接口概览](#接口概览)
21
+ - [接口详解](#接口详解)
22
+ - [编码与ID规律](#编码与id规律)
23
+ - [使用示例](#使用示例)
24
+ - [最佳实践](#最佳实践)
25
+
26
+ ---
27
+
28
+ ## 核心概念
29
+
30
+ 新版 API 摒弃了旧版的层级代码(如 `A0101`),转而使用 **UUID** 作为唯一标识。数据获取遵循 **"树形导航 -> 指标元数据 -> 批量取值"** 的三步走策略。
31
+
32
+ ### 1. 关键标识符
33
+
34
+ | 标识符 | 全称 | 含义 | 来源接口 |
35
+ | :--- | :--- | :--- | :--- |
36
+ | **`pid`** | Parent ID | **父节点 ID**。用于在目录树中向下展开。初始请求时为空或根节点 ID。 | `queryIndexTreeAsync` 返回节点的 `_id` |
37
+ | **`cid`** | Catalog ID | **数据集 ID**。代表一个**叶子节点**(Leaf Node)。<br>通常对应:**特定指标 + 特定地区 + 特定时间分段**。<br>*注意:同一业务指标因时间分段不同会有多个 `cid`。* | `queryIndexTreeAsync` 返回中 `isLeaf: true` 节点的 `_id` |
38
+ | **`indicatorId`** | Indicator ID | **具体指标 ID**。代表数据集中的某一列(如"同比增长"、"累计值")。 | `queryIndicatorsByCid` 返回列表中的 `_id` |
39
+ | **`du`** | Unit ID | **单位 ID**。指向数据单位的唯一标识(如 `%`, `亿元`)。 | `queryIndicatorsByCid` 返回 |
40
+
41
+ ### 2. 时间分片机制 (Time Slicing)
42
+
43
+ 这是新版 API 最显著的特征。**同一个业务指标(如 CPI)会被拆分成多个 `cid`**,通常按 5 年或统计制度变革周期分割。
44
+
45
+ * **现象**: 搜索 "CPI" 可能会得到多个 `cid`:
46
+ * `cid_A`: 2016-2020
47
+ * `cid_B`: 2021-2025
48
+ * `cid_C`: 2026-至今
49
+ * **影响**: 获取长期历史数据时,必须**分别请求**这些 `cid`,然后在本地按时间拼接。
50
+
51
+ ### 3. 时间编码格式
52
+
53
+ 在 `stream/esData` 接口中使用:
54
+
55
+ * **月度**: `YYYYMM` (如 `202602`),后缀 `MM` (如 `202602MM`)
56
+ * **季度**: `YYYYQ` (如 `20254` 表示第四季度),后缀 `SS` (如 `202504SS`,注意这里 Q4 可能编码为 04)
57
+ * **年度**: `YYYY` (如 `2025`),后缀 `YY` (如 `2025YY`)
58
+ * **范围**: `Start-End` (如 `202501MM-202602MM`)
59
+
60
+ ---
61
+
62
+ ## 接口概览
63
+
64
+ | 步骤 | 接口路径 | 方法 | 用途 | 关键入参 |
65
+ | :--- | :--- | :--- | :--- | :--- |
66
+ | **1. 遍历目录** | `/new/queryIndexTreeAsync` | GET | 获取分类树,找到目标数据集 (`cid`) | `pid`, `code` |
67
+ | **2. 获取指标** | `/new/queryIndicatorsByCid` | GET | 获取某数据集下的所有指标列表 (`indicatorId`) | `cid` |
68
+ | **3. 查询数据** | `/stream/esData` | POST | 批量获取具体数值 | `cid`, `indicatorIds`, `dts`, `das`, `showType` |
69
+ | *(可选)* | `/external/query` | GET | 关键词搜索,快速定位 `cid` | `search`, `pagenum` |
70
+
71
+ ---
72
+
73
+ ## 接口详解
74
+
75
+ ### 1. 遍历目录树 (Get Tree)
76
+
77
+ 用于从根节点开始,层层下钻,直到找到 `isLeaf: true` 的节点。
78
+
79
+ ```http
80
+ GET /new/queryIndexTreeAsync?pid={parent_id}&code={category_code}
81
+ ```
82
+
83
+ **参数**
84
+
85
+ | 参数 | 类型 | 必填 | 说明 |
86
+ | :--- | :--- | :--- | :--- |
87
+ | `pid` | string | ❌ | 父节点 ID。首次请求可为空字符串 `""` 或根节点 ID。 |
88
+ | `code` | string | ✅ | 顶级分类代码。常见值:<br>`1`: 月度数据<br>`2`: 季度数据<br>`3`: 年度数据<br>`5`: 分省季度<br>`6`: 分省年度<br>`7`: 其他/普查 |
89
+
90
+ **响应关键字段**
91
+
92
+ ```json
93
+ {
94
+ "data": [
95
+ {
96
+ "_id": "fc982599...",
97
+ "name": "价格指数",
98
+ "isLeaf": false,
99
+ "treeinfo_globalid": "...",
100
+ "sdate": "2021",
101
+ "edate": "2025"
102
+ }
103
+ ],
104
+ "success": true
105
+ }
106
+ ```
107
+
108
+ **字段说明**
109
+
110
+ | 字段 | 说明 |
111
+ |------|------|
112
+ | `_id` | 节点唯一标识。若 `isLeaf=false`,则作为下一次请求的 `pid`;若 `isLeaf=true`,则作为 `cid`。 |
113
+ | `isLeaf` | `true`=叶子节点(可查数据),`false`=目录节点(需继续下钻)。 |
114
+ | `sdate/edate` | 该数据集覆盖的时间范围,用于判断是否包含所需年份。 |
115
+
116
+ ---
117
+
118
+ ### 2. 获取指标列表 (Get Indicators)
119
+
120
+ 拿到 `cid` 后,查询该数据集包含哪些具体指标。
121
+
122
+ ```http
123
+ GET /new/queryIndicatorsByCid?cid={catalog_id}&dt=&name=
124
+ ```
125
+
126
+ **参数**
127
+
128
+ | 参数 | 类型 | 必填 | 说明 |
129
+ | :--- | :--- | :--- | :--- |
130
+ | `cid` | string | ✅ | 数据集 ID (来自步骤 1 的叶子节点 `_id`) |
131
+ | `dt` | string | ❌ | 时间过滤,通常留空 |
132
+ | `name` | string | ❌ | 指标名称过滤,通常留空 |
133
+
134
+ **响应关键字段**
135
+
136
+ ```json
137
+ {
138
+ "data": {
139
+ "list": [
140
+ {
141
+ "_id": "6d249959...",
142
+ "i_showname": "规上工业增加值同比增长 (%) ",
143
+ "i_mark": "统计口径说明...",
144
+ "du": "414774...",
145
+ "dp": "11",
146
+ "order": 1
147
+ }
148
+ ]
149
+ }
150
+ }
151
+ ```
152
+
153
+ **字段说明**
154
+
155
+ | 字段 | 说明 |
156
+ |------|------|
157
+ | `_id` | 指标唯一 ID,后续查询数据时必须传入。 |
158
+ | `i_showname` | 指标显示名称,含单位。 |
159
+ | `i_mark` | **统计口径说明**。非常重要,解释了数据的计算方法和适用范围。 |
160
+ | `du` | 单位 ID,需结合其他接口或常识解析(如 `%`)。 |
161
+
162
+ ---
163
+
164
+ ### 3. 查询具体数据 (Get Data)
165
+
166
+ **核心接口**。支持批量查询多个指标、多个时间点的数据。
167
+
168
+ ```http
169
+ POST /stream/esData
170
+ Content-Type: application/json
171
+ ```
172
+
173
+ **请求体 (Body)**
174
+
175
+ ```json
176
+ {
177
+ "cid": "e2d9463aceae483eb122794e53180bf9",
178
+ "indicatorIds": [
179
+ "6d249959166b4b07aad922e2aa51097d",
180
+ "f991aa39485440158f761a71e39b03a1"
181
+ ],
182
+ "das": [
183
+ {
184
+ "text": "全国",
185
+ "value": "000000000000"
186
+ }
187
+ ],
188
+ "dts": ["202501MM-202602MM"],
189
+ "showType": "1",
190
+ "rootId": "fc982599aa684be7969d7b90b1bd0e84"
191
+ }
192
+ ```
193
+
194
+ **参数说明**
195
+
196
+ | 字段 | 类型 | 必填 | 说明 |
197
+ | :--- | :--- | :--- | :--- |
198
+ | `cid` | string | ✅ | 数据集 ID |
199
+ | `indicatorIds` | array | ✅ | 指标 ID 数组 (来自步骤 2) |
200
+ | `das` | array | ✅ | 地区维度。`value`: `000000000000` 代表全国。如果是分省数据,需传入具体省份代码。 |
201
+ | `dts` | array | ✅ | 时间范围数组。格式 `Start-End`。 |
202
+ | `showType` | string | ✅ | 显示类型,固定传 `"1"`(必填,不传会返回 500) |
203
+ | `rootId` | string | ✅ | 根节点 ID。通常固定为月度数据的根 ID,可通过第一次树请求获取。 |
204
+
205
+ **响应结构**
206
+
207
+ ```json
208
+ {
209
+ "data": [
210
+ {
211
+ "code": "202602MM",
212
+ "name": "2026年2月",
213
+ "values": [
214
+ {
215
+ "_id": "6d249959...",
216
+ "i_showname": "规上工业增加值同比增长 (%) ",
217
+ "value": "5.8",
218
+ "da_name": "全国",
219
+ "du_name": "%"
220
+ },
221
+ {
222
+ "_id": "f991aa39...",
223
+ "value": "6.1"
224
+ }
225
+ ]
226
+ },
227
+ {
228
+ "code": "202601MM",
229
+ "name": "2026年1月",
230
+ "values": [ ... ]
231
+ }
232
+ ],
233
+ "success": true,
234
+ "state": 20000,
235
+ "message": "成功"
236
+ }
237
+ ```
238
+
239
+ **注意事项**
240
+ * 如果某个月份数据未发布,`values` 可能为空数组 `[]`。
241
+ * 返回数据按时间倒序或正序排列,需根据 `code` 自行排序。
242
+ * `showType` 为必填参数,不传将返回 HTTP 500 错误。
243
+
244
+ ---
245
+
246
+ ## 编码与ID规律
247
+
248
+ 由于 ID 均为 UUID,无法通过算法推导,必须通过遍历获取。但存在以下业务规律:
249
+
250
+ ### 1. `cid` 与时间的关系
251
+ * 同一类指标(如 CPI),通常会按 `5年` 或 `10年` 切割成不同的 `cid`。
252
+ * 检查 `queryIndexTreeAsync` 返回的 `sdate` 和 `edate` 字段,可以判断该 `cid` 覆盖的时间范围。
253
+
254
+ ### 2. `indicatorId` 的稳定性
255
+ * 在同一个 `cid` 内,`indicatorId` 是稳定的。
256
+ * **跨 `cid` 不保证稳定**:2021-2025 的 "GDP" 指标 ID 可能与 2026- 的 "GDP" 指标 ID 不同。**建议每个 `cid` 都重新调用 `queryIndicatorsByCid` 获取映射。**
257
+
258
+ ### 3. 地区代码 (`da`)
259
+ * `000000000000`: 全国
260
+ * 分省数据的 `cid` 通常位于"分省年度/季度数据"目录下。
261
+
262
+ ### 4. 根节点 ID (`rootId`)
263
+ * 月度数据根节点通常为: `fc982599aa684be7969d7b90b1bd0e84`
264
+ * 可通过 `GET /new/queryIndexTreeAsync?pid=` (空) 获取第一级节点确认。
265
+
266
+ ---
267
+
268
+ ## 使用示例
269
+
270
+ ### 示例一:搜索定位并获取最新 CPI 数据
271
+
272
+ > 场景:快速获取最近几个月的全国居民消费价格指数 (CPI)
273
+
274
+ **Step 1: 关键词搜索定位 `cid`**
275
+
276
+ ```bash
277
+ curl "https://data.stats.gov.cn/dg/website/publicrelease/web/external/query?search=CPI&pagenum=1&pageSize=5"
278
+ ```
279
+
280
+ *解析*: 在返回结果中寻找 `type_text` 为 "月度数据" 且 `show_name` 包含 "居民消费价格" 的项。假设找到最新时间段 (2026-) 的 `cid` 为 `5c745282...`。
281
+
282
+ **Step 2: 获取指标 ID**
283
+
284
+ ```bash
285
+ curl "https://data.stats.gov.cn/dg/website/publicrelease/web/external/new/queryIndicatorsByCid?cid=5c7452825c7c4dcba391db5ca7f335c5"
286
+ ```
287
+
288
+ *解析*: 找到 `i_showname` 为 "居民消费价格指数 (上年同月=100) " 的项,记录其 `_id`,假设为 `ind_cpi_total` (`53180dfb...`)。
289
+
290
+ **Step 3: 查询数据**
291
+
292
+ ```bash
293
+ curl -X POST "https://data.stats.gov.cn/dg/website/publicrelease/web/external/stream/esData" \
294
+ -H "Content-Type: application/json" \
295
+ -d '{
296
+ "cid": "5c7452825c7c4dcba391db5ca7f335c5",
297
+ "indicatorIds": ["53180dfb9c14411ba4b762307c85920c"],
298
+ "das": [{"text": "全国", "value": "000000000000"}],
299
+ "dts": ["202601MM-202603MM"],
300
+ "showType": "1",
301
+ "rootId": "fc982599aa684be7969d7b90b1bd0e84"
302
+ }'
303
+ ```
304
+
305
+ ---
306
+
307
+ ### 示例二:遍历树获取所有月度数据 CID (Python)
308
+
309
+ > 场景:构建本地索引,获取所有可用的月度数据集 ID
310
+
311
+ ```python
312
+ import requests
313
+ import time
314
+ import json
315
+
316
+ BASE_URL = "https://data.stats.gov.cn/dg/website/publicrelease/web/external"
317
+
318
+ def get_tree(pid="", code="1"):
319
+ """递归获取树结构,收集所有叶子节点 (cid)"""
320
+ url = f"{BASE_URL}/new/queryIndexTreeAsync"
321
+ params = {"pid": pid, "code": code}
322
+ try:
323
+ resp = requests.get(url, params=params, timeout=10)
324
+ nodes = resp.json().get('data', [])
325
+ except Exception as e:
326
+ print(f"Error fetching tree for pid={pid}: {e}")
327
+ return []
328
+
329
+ cids = []
330
+ for node in nodes:
331
+ if node.get('isLeaf'):
332
+ cids.append({
333
+ "name": node['name'],
334
+ "cid": node['_id'],
335
+ "sdate": node.get('sdate'),
336
+ "edate": node.get('edate')
337
+ })
338
+ else:
339
+ time.sleep(0.5)
340
+ sub_cids = get_tree(pid=node['_id'], code=code)
341
+ cids.extend(sub_cids)
342
+ return cids
343
+
344
+ # 执行遍历 (慎用,耗时较长)
345
+ # all_cids = get_tree(code="1")
346
+ # with open('nbs_cids_monthly.json', 'w', encoding='utf-8') as f:
347
+ # json.dump(all_cids, f, ensure_ascii=False, indent=2)
348
+ ```
349
+
350
+ ---
351
+
352
+ ## 最佳实践
353
+
354
+ ### 1. 优先使用搜索接口定位 `cid`
355
+
356
+ 全量遍历树非常耗时(可能有数千个节点)。如果已知关键词(如 "GDP", "CPI", "人口"),先用 `/external/query` 搜索,从结果中提取相关的 `cid` 或线索,再精准查询。
357
+
358
+ ```
359
+ ❌ 低效方式:
360
+ 从根节点开始递归遍历整棵树,寻找 "GDP"
361
+
362
+ ✅ 高效方式:
363
+ search.htm?s=GDP
364
+ → 从结果中提取 cid 或相关线索
365
+ → 直接调用 queryIndicatorsByCid
366
+ ```
367
+
368
+ ### 2. 处理"时间分片"
369
+
370
+ **不要假设一个 `cid` 能查到所有历史数据。**
371
+ * **策略**: 当发现数据缺失(如 `values` 为空)或需要更长历史时,检查相邻的 `cid`(通过树结构查找同级节点,或通过搜索查看是否有其他年份范围的同类指标)。
372
+ * **合并**: 在客户端按时间戳合并不同 `cid` 返回的数据。
373
+
374
+ ### 3. 缓存元数据
375
+
376
+ * **树结构 (`queryIndexTreeAsync`)**: 变化极慢,建议本地缓存 24 小时以上。
377
+ * **指标列表 (`queryIndicatorsByCid`)**: 变化较慢,建议按 `cid` 缓存。
378
+ * **数据 (`stream/esData`)**: 每月更新,建议设置较短缓存或按需请求。
379
+
380
+ ### 4. 批量请求减少 IO
381
+
382
+ `stream/esData` 支持 `indicatorIds` 数组。
383
+ * ❌ **错误**: 循环调用接口,每次查 1 个指标。
384
+ * ✅ **正确**: 一次性传入该 `cid` 下所有需要的指标 ID(如 CPI 的总指数、食品、居住等 10 个子类),一次请求拿回所有数据。
385
+
386
+ ### 5. 关注 `i_mark` (统计口径)
387
+
388
+ 在 `queryIndicatorsByCid` 返回的 `i_mark` 字段中,包含了至关重要的统计说明(如"按不变价计算"、"基期为 2020 年"等)。展示数据时务必附带此说明,否则可能导致误读。
389
+
390
+ ### 6. 完整封装示例 (JavaScript/Node.js)
391
+
392
+ ```javascript
393
+ class NbsNewClient {
394
+ constructor() {
395
+ this.base = 'https://data.stats.gov.cn/dg/website/publicrelease/web/external';
396
+ this.rootId = 'fc982599aa684be7969d7b90b1bd0e84';
397
+ }
398
+
399
+ async searchCid(keyword) {
400
+ const url = `${this.base}/query?search=${encodeURIComponent(keyword)}&pagenum=1&pageSize=10`;
401
+ const res = await fetch(url);
402
+ const json = await res.json();
403
+ return json.data?.data?.map(item => ({
404
+ name: item.show_name,
405
+ cid: item.cid || this.extractCidFromGlobalId(item.treeinfo_globalid),
406
+ type: item.type_text
407
+ })) || [];
408
+ }
409
+
410
+ extractCidFromGlobalId(globalId) {
411
+ if (!globalId) return null;
412
+ const parts = globalId.split('.');
413
+ return parts[parts.length - 1];
414
+ }
415
+
416
+ async getIndicators(cid) {
417
+ const url = `${this.base}/new/queryIndicatorsByCid?cid=${cid}`;
418
+ const res = await fetch(url);
419
+ const json = await res.json();
420
+ return json.data?.list || [];
421
+ }
422
+
423
+ async getData(cid, indicatorIds, startTime, endTime, regionCode = "000000000000") {
424
+ const payload = {
425
+ cid,
426
+ indicatorIds,
427
+ das: [{ text: "全国", value: regionCode }],
428
+ dts: [`${startTime}-${endTime}`],
429
+ showType: "1",
430
+ rootId: this.rootId
431
+ };
432
+
433
+ const res = await fetch(`${this.base}/stream/esData`, {
434
+ method: 'POST',
435
+ headers: { 'Content-Type': 'application/json' },
436
+ body: JSON.stringify(payload)
437
+ });
438
+ const json = await res.json();
439
+
440
+ if (!json.success) throw new Error(json.message);
441
+ return json.data;
442
+ }
443
+ }
444
+
445
+ // 使用示例
446
+ const client = new NbsNewClient();
447
+
448
+ async function main() {
449
+ const results = await client.searchCid("居民消费价格");
450
+ console.log("Found CIDs:", results);
451
+
452
+ const targetCid = results[0].cid;
453
+
454
+ const indicators = await client.getIndicators(targetCid);
455
+ const cpiIndicator = indicators.find(i => i.i_showname.includes("居民消费价格指数 (上年同月=100)"));
456
+
457
+ if (cpiIndicator) {
458
+ const data = await client.getData(
459
+ targetCid,
460
+ [cpiIndicator._id],
461
+ "202601MM",
462
+ "202603MM"
463
+ );
464
+ console.log("CPI Data:", data);
465
+ }
466
+ }
467
+
468
+ main();
469
+ ```
470
+
471
+ ---
472
+
473
+ ## 接口速查表
474
+
475
+ | 需求 | 接口 | 关键参数 |
476
+ |------|------|---------|
477
+ | 关键词找数据集 | `GET /query` | `search`, `pagenum` |
478
+ | 浏览分类体系 | `GET /new/queryIndexTreeAsync` | `pid`, `code` |
479
+ | 拿指标 ID | `GET /new/queryIndicatorsByCid` | `cid` |
480
+ | 查历史时间序列 | `POST /stream/esData` | `cid`, `indicatorIds`, `dts`, `showType` |
481
+ | 获取根节点 ID | `GET /new/queryIndexTreeAsync?pid=` | `pid` (空) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "national-stats-mcp",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "MCP server for accessing National Bureau of Statistics data (supports both new V2.0 and legacy API)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {