mcp-xdx-schema-ctx 1.1.9

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.
Files changed (60) hide show
  1. package/.env.example +43 -0
  2. package/README.md +269 -0
  3. package/dist/config.d.ts +131 -0
  4. package/dist/config.js +138 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/crypto-utils.d.ts +29 -0
  7. package/dist/crypto-utils.js +118 -0
  8. package/dist/crypto-utils.js.map +1 -0
  9. package/dist/db.d.ts +10 -0
  10. package/dist/db.js +149 -0
  11. package/dist/db.js.map +1 -0
  12. package/dist/diff-utils.d.ts +42 -0
  13. package/dist/diff-utils.js +130 -0
  14. package/dist/diff-utils.js.map +1 -0
  15. package/dist/encrypt-secrets.d.ts +12 -0
  16. package/dist/encrypt-secrets.js +190 -0
  17. package/dist/encrypt-secrets.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +30 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/migrate-utils.d.ts +91 -0
  22. package/dist/migrate-utils.js +399 -0
  23. package/dist/migrate-utils.js.map +1 -0
  24. package/dist/schema-compat.d.ts +46 -0
  25. package/dist/schema-compat.js +169 -0
  26. package/dist/schema-compat.js.map +1 -0
  27. package/dist/shared.d.ts +70 -0
  28. package/dist/shared.js +197 -0
  29. package/dist/shared.js.map +1 -0
  30. package/dist/sql-build.d.ts +121 -0
  31. package/dist/sql-build.js +254 -0
  32. package/dist/sql-build.js.map +1 -0
  33. package/dist/tool-availability.d.ts +35 -0
  34. package/dist/tool-availability.js +90 -0
  35. package/dist/tool-availability.js.map +1 -0
  36. package/dist/tools/data-tools.d.ts +2 -0
  37. package/dist/tools/data-tools.js +243 -0
  38. package/dist/tools/data-tools.js.map +1 -0
  39. package/dist/tools/dataflow-tools.d.ts +2 -0
  40. package/dist/tools/dataflow-tools.js +434 -0
  41. package/dist/tools/dataflow-tools.js.map +1 -0
  42. package/dist/tools/dbo-tools.d.ts +2 -0
  43. package/dist/tools/dbo-tools.js +228 -0
  44. package/dist/tools/dbo-tools.js.map +1 -0
  45. package/dist/tools/diff-tools.d.ts +2 -0
  46. package/dist/tools/diff-tools.js +224 -0
  47. package/dist/tools/diff-tools.js.map +1 -0
  48. package/dist/tools/form-tools.d.ts +2 -0
  49. package/dist/tools/form-tools.js +715 -0
  50. package/dist/tools/form-tools.js.map +1 -0
  51. package/dist/tools/migrate-tools.d.ts +2 -0
  52. package/dist/tools/migrate-tools.js +423 -0
  53. package/dist/tools/migrate-tools.js.map +1 -0
  54. package/dist/tools/system-tools.d.ts +2 -0
  55. package/dist/tools/system-tools.js +1102 -0
  56. package/dist/tools/system-tools.js.map +1 -0
  57. package/dist/tools/table-tools.d.ts +2 -0
  58. package/dist/tools/table-tools.js +1322 -0
  59. package/dist/tools/table-tools.js.map +1 -0
  60. package/package.json +48 -0
@@ -0,0 +1,1322 @@
1
+ import { z } from "zod";
2
+ import { envDb } from "../db.js";
3
+ import { T, userName, WARNING, escapeSql, escapeLike, paginate, envelope, ok, sqlPagination, } from "../shared.js";
4
+ import { getTableColumns, getTableInfo, tableExists, pickCols, hasCol, resolveSelect, tryQuery, } from "../schema-compat.js";
5
+ import { buildWhere, buildOrderBy, buildPagedQuery, } from "../sql-build.js";
6
+ export function registerTableTools(server) {
7
+ // ============================================================
8
+ // list_tables
9
+ // ============================================================
10
+ server.registerTool("list_tables", {
11
+ description: WARNING +
12
+ "\n\n列出环境库中所有已定义的表。可选按模块或关键词过滤。数据来源: XDWmsCommon.dbo.TableSetTB",
13
+ inputSchema: {
14
+ modular: z.string().max(50).optional().describe("模块: SC/CK/CW"),
15
+ keyword: z.string().max(200).optional().describe("表名/描述关键词"),
16
+ scope: z
17
+ .enum(["registered", "system", "all"])
18
+ .optional()
19
+ .default("registered")
20
+ .describe("registered=TableSetTB 登记表(默认);system=环境库自身物理表;all=两者合并"),
21
+ limit: z
22
+ .number()
23
+ .optional()
24
+ .default(50)
25
+ .describe("已废弃:本工具已改为 SQL 端分页,请用 page/pageSize"),
26
+ page: z.number().int().min(1).default(1).describe("页码"),
27
+ pageSize: z
28
+ .number()
29
+ .int()
30
+ .min(1)
31
+ .max(200)
32
+ .default(50)
33
+ .describe("每页数量"),
34
+ },
35
+ }, async ({ modular, keyword, scope, page = 1, pageSize = 50 }) => {
36
+ const warnings = [];
37
+ // ---- scope=system / all:环境库自身物理表(INFORMATION_SCHEMA) ----
38
+ if (scope === "system" || scope === "all") {
39
+ const sysTable = `[${envDb()}].INFORMATION_SCHEMA.TABLES`;
40
+ const sysWhere = " WHERE TABLE_SCHEMA='dbo' AND TABLE_TYPE='BASE TABLE'" +
41
+ (keyword
42
+ ? ` AND TABLE_NAME LIKE N'%${escapeLike(keyword)}%' ESCAPE '\\'`
43
+ : "");
44
+ const sysRows = await tryQuery("list_tables.system", `SELECT TOP 2000 [TABLE_NAME] FROM ${sysTable}${sysWhere} ORDER BY [TABLE_NAME]`, warnings);
45
+ // 登记表名集合(用于标记 registered)
46
+ let registeredSet = new Set();
47
+ let regRows = [];
48
+ if (await tableExists(T("tableDef"))) {
49
+ const tCols = await getTableColumns(T("tableDef"));
50
+ const selCols = pickCols(tCols, [
51
+ "TableName",
52
+ "TableDesc",
53
+ "TableType",
54
+ "Modular1",
55
+ ]);
56
+ if (selCols.length > 0 && hasCol(tCols, "TableName")) {
57
+ const groups = [];
58
+ if (keyword) {
59
+ const filters = ["TableName", "TableDesc"]
60
+ .filter((c) => hasCol(tCols, c))
61
+ .map((c) => ({ col: c, op: "like", value: keyword }));
62
+ if (filters.length > 0)
63
+ groups.push({ logic: "or", filters });
64
+ }
65
+ if (modular)
66
+ groups.push({
67
+ filters: [{ col: "Modular1", op: "eq", value: modular }],
68
+ });
69
+ const { where } = buildWhere(tCols, groups);
70
+ const order = buildOrderBy(tCols, ["TableName"]);
71
+ regRows = (await tryQuery("list_tables.registered", `SELECT TOP 2000 ${selCols
72
+ .map((c) => `[${c}]`)
73
+ .join(", ")} FROM ${T("tableDef")}${where}${order.orderBy}`, warnings));
74
+ registeredSet = new Set(regRows.map((r) => String(r.TableName)));
75
+ }
76
+ }
77
+ const merged = [
78
+ ...regRows.map((r) => ({
79
+ ...r,
80
+ registered: true,
81
+ source: "TableSetTB",
82
+ })),
83
+ ...sysRows
84
+ .filter((r) => !registeredSet.has(String(r.TABLE_NAME)))
85
+ .map((r) => ({
86
+ TableName: r.TABLE_NAME,
87
+ TableType: "BASE TABLE",
88
+ registered: false,
89
+ source: "INFORMATION_SCHEMA",
90
+ })),
91
+ ].sort((a, b) => String(a.TableName ?? "").localeCompare(String(b.TableName ?? "")));
92
+ const { page: paged, pagination } = paginate(merged, page, pageSize);
93
+ return ok(envelope({
94
+ items: paged,
95
+ pagination,
96
+ warnings,
97
+ scope,
98
+ }));
99
+ }
100
+ const tableDefCols = await getTableColumns(T("tableDef"));
101
+ const cols = pickCols(tableDefCols, [
102
+ "TableName",
103
+ "TableDesc",
104
+ "TableType",
105
+ "Modular1",
106
+ "Modular2",
107
+ "Code",
108
+ "EnglishDesc",
109
+ ]);
110
+ if (cols.length === 0) {
111
+ return ok(envelope({
112
+ items: [],
113
+ pagination: sqlPagination(page, pageSize, 0),
114
+ warnings: [
115
+ { module: "list_tables", message: "环境库 TableSetTB 无可用字段" },
116
+ ],
117
+ }));
118
+ }
119
+ const groups = [];
120
+ if (keyword) {
121
+ const filters = ["TableName", "TableDesc", "EnglishDesc"]
122
+ .filter((c) => hasCol(tableDefCols, c))
123
+ .map((c) => ({ col: c, op: "like", value: keyword }));
124
+ if (filters.length > 0)
125
+ groups.push({ logic: "or", filters });
126
+ }
127
+ if (modular)
128
+ groups.push({
129
+ filters: [{ col: "Modular1", op: "eq", value: modular }],
130
+ });
131
+ const { where, skipped } = buildWhere(tableDefCols, groups);
132
+ const order = buildOrderBy(tableDefCols, ["TableName"]);
133
+ if (skipped.length > 0) {
134
+ warnings.push({
135
+ module: "list_tables",
136
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
137
+ hint: "可用 describe_table 查看环境库实际字段",
138
+ });
139
+ }
140
+ if (order.skipped.length > 0) {
141
+ warnings.push({
142
+ module: "list_tables",
143
+ message: `排序列不存在,已忽略: ${order.skipped.join(", ")}`,
144
+ });
145
+ }
146
+ const table = T("tableDef");
147
+ const { countSql, sql } = buildPagedQuery({
148
+ table,
149
+ cols,
150
+ where,
151
+ orderBy: order.orderBy,
152
+ page,
153
+ pageSize,
154
+ });
155
+ const countRows = await tryQuery("list_tables.count", countSql, warnings);
156
+ const total = Number(countRows[0]?.Total ?? 0);
157
+ const items = total > 0 ? await tryQuery("list_tables", sql, warnings) : [];
158
+ return ok(envelope({
159
+ items,
160
+ pagination: sqlPagination(page, pageSize, total),
161
+ warnings,
162
+ totalItems: total,
163
+ }));
164
+ });
165
+ // ============================================================
166
+ // describe_table
167
+ // ============================================================
168
+ server.registerTool("describe_table", {
169
+ description: WARNING +
170
+ "\n\n获取指定表的完整字段定义。数据来源: XDWmsCommon.dbo.TableSetTB + TableFieldSetTB",
171
+ inputSchema: {
172
+ tableName: z.string().max(128).describe("表名,如 SaleOrderHedTB"),
173
+ fieldKeyword: z
174
+ .string()
175
+ .max(200)
176
+ .optional()
177
+ .describe("按字段名/字段描述模糊过滤(大表时用于避免输出被截断)"),
178
+ columns: z
179
+ .string()
180
+ .max(500)
181
+ .optional()
182
+ .describe("按需返回的字段名,逗号分隔(如 FieldName,FieldType,FieldDesc)"),
183
+ page: z.number().int().min(1).default(1).describe("页码"),
184
+ pageSize: z
185
+ .number()
186
+ .int()
187
+ .min(1)
188
+ .max(200)
189
+ .default(50)
190
+ .describe("每页数量"),
191
+ },
192
+ }, async ({ tableName, fieldKeyword, columns, page = 1, pageSize = 50 }) => {
193
+ const warnings = [];
194
+ const safeName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
195
+ const wantedCols = columns
196
+ ? columns
197
+ .split(",")
198
+ .map((s) => s.trim())
199
+ .filter((s) => /^[A-Za-z0-9_]+$/.test(s))
200
+ : [];
201
+ // ---- 1) 登记表(TableSetTB) ----
202
+ const tableDefCols = await getTableColumns(T("tableDef"));
203
+ const tCols = pickCols(tableDefCols, [
204
+ "TableName",
205
+ "TableDesc",
206
+ "TableType",
207
+ "ParentTable",
208
+ "Modular1",
209
+ "Modular2",
210
+ "Code",
211
+ "EnglishDesc",
212
+ "OtherDesc",
213
+ "Remark",
214
+ ]);
215
+ let table = null;
216
+ if (tCols.length > 0 && hasCol(tableDefCols, "TableName")) {
217
+ const rows = await tryQuery("describe_table.table", `SELECT ${tCols
218
+ .map((c) => `[${c}]`)
219
+ .join(", ")} FROM ${T("tableDef")} WHERE [TableName]='${escapeSql(safeName)}'`, warnings);
220
+ table = rows[0] ?? null;
221
+ }
222
+ if (table) {
223
+ const fieldDefCols = await getTableColumns(T("fieldDef"));
224
+ const defaultWanted = [
225
+ "iLineNo",
226
+ "FieldName",
227
+ "FieldDesc",
228
+ "FieldType",
229
+ "iPrecision",
230
+ "iScale",
231
+ "DefaultValue",
232
+ "bAllowNull",
233
+ "bVirtual",
234
+ "bSearchField",
235
+ "bUnique",
236
+ "cLinkTable",
237
+ "cLinkField",
238
+ "cDstField",
239
+ "EnglishDesc",
240
+ "OtherDesc",
241
+ "Remark",
242
+ ];
243
+ const wanted = wantedCols.length > 0
244
+ ? wantedCols.filter((c) => hasCol(fieldDefCols, c))
245
+ : defaultWanted;
246
+ if (wantedCols.length > 0 && wanted.length === 0) {
247
+ warnings.push({
248
+ module: "describe_table",
249
+ message: `指定的 columns 在本环境库均不存在: ${wantedCols.join(", ")}`,
250
+ });
251
+ }
252
+ const rs = await resolveSelect(T("fieldDef"), wanted.length > 0 ? wanted : ["FieldName", "FieldDesc", "FieldType"]);
253
+ if (rs.missing.length > 0) {
254
+ warnings.push({
255
+ module: "describe_table",
256
+ message: `以下列在本环境库不存在,已置空: ${rs.missing.join(", ")}`,
257
+ });
258
+ }
259
+ const groups = [
260
+ { filters: [{ col: "TableName", op: "eq", value: safeName }] },
261
+ ];
262
+ if (fieldKeyword) {
263
+ const filters = ["FieldName", "FieldDesc"]
264
+ .filter((c) => hasCol(fieldDefCols, c))
265
+ .map((c) => ({ col: c, op: "like", value: fieldKeyword }));
266
+ if (filters.length > 0)
267
+ groups.push({ logic: "or", filters });
268
+ }
269
+ const { where, skipped } = buildWhere(fieldDefCols, groups);
270
+ if (skipped.length > 0) {
271
+ warnings.push({
272
+ module: "describe_table",
273
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
274
+ });
275
+ }
276
+ const order = buildOrderBy(fieldDefCols, ["iLineNo", "FieldName"]);
277
+ const q = buildPagedQuery({
278
+ table: T("fieldDef"),
279
+ cols: rs.present.length > 0
280
+ ? rs.present.map((c) => `[${c}]`)
281
+ : ["[FieldName]"],
282
+ where,
283
+ orderBy: order.orderBy,
284
+ page,
285
+ pageSize,
286
+ });
287
+ const cnt = await tryQuery("describe_table.fields.count", q.countSql, warnings);
288
+ const total = Number(cnt[0]?.Total ?? 0);
289
+ const items = total > 0
290
+ ? await tryQuery("describe_table.fields", q.sql, warnings)
291
+ : [];
292
+ // 全量字段名(不含属性)— 截断后模型仍能知道有哪些字段可查
293
+ let fieldNames = [];
294
+ const nameSel = pickCols(fieldDefCols, ["FieldName"]);
295
+ if (nameSel.length > 0) {
296
+ const nameList = nameSel.map((c) => `[${c}]`).join(", ");
297
+ const nameWhere = buildWhere(fieldDefCols, [
298
+ { filters: [{ col: "TableName", op: "eq", value: safeName }] },
299
+ ]).where;
300
+ const nameRows = await tryQuery("describe_table.fieldNames", `SELECT ${nameList} FROM ${T("fieldDef")}${nameWhere} ORDER BY ${nameList}`, warnings);
301
+ fieldNames = nameRows
302
+ .map((r) => String(r.FieldName ?? ""))
303
+ .filter(Boolean)
304
+ .slice(0, 500);
305
+ }
306
+ return ok(envelope({
307
+ items,
308
+ pagination: sqlPagination(page, pageSize, total),
309
+ warnings,
310
+ table,
311
+ registered: true,
312
+ source: "TableSetTB",
313
+ fieldCount: total,
314
+ remainingFields: Math.max(0, total - items.length),
315
+ fieldNames,
316
+ }));
317
+ }
318
+ // ---- 2) 未登记在 TableSetTB:回退到物理表(环境库 → 用户库) ----
319
+ const dbs = [envDb(), userName].filter(Boolean);
320
+ let physical = [];
321
+ let sourceDb = "";
322
+ for (const db of dbs) {
323
+ const rows = await tryQuery("describe_table.physical", `SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT FROM [${db}].INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='dbo' AND TABLE_NAME='${escapeSql(safeName)}' ORDER BY ORDINAL_POSITION`, warnings);
324
+ if (rows.length > 0) {
325
+ physical = rows;
326
+ sourceDb = db;
327
+ break;
328
+ }
329
+ }
330
+ if (physical.length > 0) {
331
+ const { page: paged, pagination } = paginate(physical, page, pageSize);
332
+ return ok(envelope({
333
+ items: paged,
334
+ pagination,
335
+ warnings,
336
+ table: { TableName: safeName },
337
+ registered: false,
338
+ source: "INFORMATION_SCHEMA",
339
+ sourceDb,
340
+ fieldCount: physical.length,
341
+ fieldNames: physical
342
+ .map((r) => String(r.COLUMN_NAME ?? ""))
343
+ .filter(Boolean)
344
+ .slice(0, 500),
345
+ hint: `该表未登记在 TableSetTB,返回的是 ${sourceDb} 中的物理列定义`,
346
+ }));
347
+ }
348
+ // ---- 3) 都不存在:给出模糊候选,便于定位 ----
349
+ let candidates = [];
350
+ if (tCols.length > 0 && hasCol(tableDefCols, "TableDesc")) {
351
+ const filters = ["TableName", "TableDesc"]
352
+ .filter((c) => hasCol(tableDefCols, c))
353
+ .map((c) => ({ col: c, op: "like", value: tableName }));
354
+ const candWhere = buildWhere(tableDefCols, [
355
+ { logic: "or", filters },
356
+ ]).where;
357
+ if (candWhere) {
358
+ candidates = await tryQuery("describe_table.candidates", `SELECT TOP 10 ${pickCols(tableDefCols, [
359
+ "TableName",
360
+ "TableDesc",
361
+ "TableType",
362
+ ])
363
+ .map((c) => `[${c}]`)
364
+ .join(", ")} FROM ${T("tableDef")}${candWhere}`, warnings);
365
+ }
366
+ }
367
+ warnings.push({
368
+ module: "describe_table",
369
+ message: `表 "${tableName}" 既未登记在 TableSetTB,也不是环境库/用户库中的物理表`,
370
+ hint: "可用 resolve_table(nameOrKeyword=...) 或 list_forms(keyword=...) 按中文名定位",
371
+ fallback: "resolve_table",
372
+ });
373
+ return ok(envelope({
374
+ items: [],
375
+ pagination: sqlPagination(page, pageSize, 0),
376
+ warnings,
377
+ table: null,
378
+ registered: false,
379
+ candidates,
380
+ }));
381
+ });
382
+ // ============================================================
383
+ // search_schema
384
+ // ============================================================
385
+ server.registerTool("search_schema", {
386
+ description: WARNING +
387
+ "\n\n全局搜索表名/字段名/描述/字段使用追踪。返回匹配的表和字段。usage 模式可追踪字段在哪些表和窗体中使用。",
388
+ inputSchema: {
389
+ keyword: z.string().max(200).describe("搜索关键词"),
390
+ searchIn: z
391
+ .enum(["all", "tables", "fields", "usage"])
392
+ .optional()
393
+ .default("all")
394
+ .describe("all/tables/fields/usage(字段使用追踪)"),
395
+ limit: z.number().optional().default(30).describe("返回条数上限"),
396
+ page: z.number().int().min(1).default(1).describe("页码"),
397
+ pageSize: z
398
+ .number()
399
+ .int()
400
+ .min(1)
401
+ .max(200)
402
+ .default(50)
403
+ .describe("每页数量"),
404
+ },
405
+ }, async ({ keyword, searchIn, page = 1, pageSize = 50 }) => {
406
+ const warnings = [];
407
+ if (searchIn === "usage") {
408
+ const safeName = keyword.replace(/[^a-zA-Z0-9_]/g, "");
409
+ const fieldDefCols = await getTableColumns(T("fieldDef"));
410
+ const fdCols = pickCols(fieldDefCols, [
411
+ "TableName",
412
+ "FieldName",
413
+ "FieldDesc",
414
+ "FieldType",
415
+ "cLinkTable",
416
+ "cLinkField",
417
+ ]);
418
+ const fieldDefs = fdCols.length > 0 && hasCol(fieldDefCols, "FieldName")
419
+ ? await tryQuery("search_schema.usage.fieldDefs", `SELECT ${fdCols.map((c) => `[${c}]`).join(", ")} FROM ${T("fieldDef")} WHERE FieldName='${escapeSql(safeName)}'` +
420
+ (hasCol(fieldDefCols, "TableName") ? " ORDER BY [TableName]" : ""), warnings)
421
+ : [];
422
+ const tableNames = [
423
+ ...new Set(fieldDefs
424
+ .map((f) => f.TableName)
425
+ .filter(Boolean)),
426
+ ];
427
+ let tableInfos = [];
428
+ if (tableNames.length > 0) {
429
+ const tableDefCols = await getTableColumns(T("tableDef"));
430
+ const tCols = pickCols(tableDefCols, [
431
+ "TableName",
432
+ "TableDesc",
433
+ "TableType",
434
+ "Modular1",
435
+ ]);
436
+ if (tCols.length > 0) {
437
+ const names = tableNames
438
+ .map((n) => `'${escapeSql(String(n))}'`)
439
+ .join(",");
440
+ tableInfos = await tryQuery("search_schema.usage.tables", `SELECT ${tCols.map((c) => `[${c}]`).join(", ")} FROM ${T("tableDef")} WHERE TableName IN (${names})`, warnings);
441
+ }
442
+ }
443
+ const formUsage = await tryQuery("search_schema.usage.forms", `SELECT DISTINCT f.FormID, f.FormName, f.LinkTable
444
+ FROM ${T("formDef")} f
445
+ INNER JOIN ${T("formRuleHed")} rh ON f.FormID = rh.FormID
446
+ INNER JOIN ${T("formRuleDet")} rd ON rh.XDID = rd.PXDID
447
+ WHERE rd.FieldName='${escapeSql(safeName)}'
448
+ ORDER BY f.FormID`, warnings);
449
+ const { page: pagedTables, pagination: pagTables } = paginate(tableInfos, page, pageSize);
450
+ return ok(envelope({
451
+ items: pagedTables,
452
+ fieldDefs,
453
+ formUsage,
454
+ pagination: { tables: pagTables },
455
+ warnings,
456
+ fieldName: safeName,
457
+ usageCount: fieldDefs.length,
458
+ }));
459
+ }
460
+ // ---- tables / fields:SQL 端 COUNT + OFFSET/FETCH 真实分页 ----
461
+ const tableDefCols = await getTableColumns(T("tableDef"));
462
+ const fieldDefCols = await getTableColumns(T("fieldDef"));
463
+ const result = {};
464
+ const pagination = {};
465
+ if (searchIn === "all" || searchIn === "tables") {
466
+ const tCols = pickCols(tableDefCols, [
467
+ "TableName",
468
+ "TableDesc",
469
+ "TableType",
470
+ "Modular1",
471
+ "EnglishDesc",
472
+ ]);
473
+ const filters = ["TableName", "TableDesc", "EnglishDesc"]
474
+ .filter((c) => hasCol(tableDefCols, c))
475
+ .map((c) => ({ col: c, op: "like", value: keyword }));
476
+ const { where, skipped } = buildWhere(tableDefCols, filters.length > 0 ? [{ logic: "or", filters }] : []);
477
+ const order = buildOrderBy(tableDefCols, ["TableName"]);
478
+ if (skipped.length > 0) {
479
+ warnings.push({
480
+ module: "search_schema.tables",
481
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
482
+ });
483
+ }
484
+ if (tCols.length > 0 && where) {
485
+ const q = buildPagedQuery({
486
+ table: T("tableDef"),
487
+ cols: tCols,
488
+ where,
489
+ orderBy: order.orderBy,
490
+ page,
491
+ pageSize,
492
+ });
493
+ const cnt = await tryQuery("search_schema.tables.count", q.countSql, warnings);
494
+ const total = Number(cnt[0]?.Total ?? 0);
495
+ result.tables =
496
+ total > 0
497
+ ? await tryQuery("search_schema.tables", q.sql, warnings)
498
+ : [];
499
+ pagination.tables = sqlPagination(page, pageSize, total);
500
+ }
501
+ else {
502
+ result.tables = [];
503
+ pagination.tables = sqlPagination(page, pageSize, 0);
504
+ if (!where) {
505
+ warnings.push({
506
+ module: "search_schema.tables",
507
+ message: "环境库 TableSetTB 无可用搜索列(TableName/TableDesc/EnglishDesc 均不存在)",
508
+ hint: "可用 describe_table 查看实际字段",
509
+ });
510
+ }
511
+ }
512
+ }
513
+ if (searchIn === "all" || searchIn === "fields") {
514
+ const fCols = pickCols(fieldDefCols, [
515
+ "TableName",
516
+ "FieldName",
517
+ "FieldDesc",
518
+ "FieldType",
519
+ "EnglishDesc",
520
+ ]);
521
+ const filters = ["FieldName", "FieldDesc", "EnglishDesc"]
522
+ .filter((c) => hasCol(fieldDefCols, c))
523
+ .map((c) => ({ col: c, op: "like", value: keyword }));
524
+ const { where, skipped } = buildWhere(fieldDefCols, filters.length > 0 ? [{ logic: "or", filters }] : []);
525
+ const order = buildOrderBy(fieldDefCols, ["TableName", "iLineNo"]);
526
+ if (skipped.length > 0) {
527
+ warnings.push({
528
+ module: "search_schema.fields",
529
+ message: `以下列在本环境库不存在,条件已跳过: ${skipped.join(", ")}`,
530
+ });
531
+ }
532
+ if (fCols.length > 0 && where) {
533
+ const q = buildPagedQuery({
534
+ table: T("fieldDef"),
535
+ cols: fCols,
536
+ where,
537
+ orderBy: order.orderBy,
538
+ page,
539
+ pageSize,
540
+ });
541
+ const cnt = await tryQuery("search_schema.fields.count", q.countSql, warnings);
542
+ const total = Number(cnt[0]?.Total ?? 0);
543
+ result.fields =
544
+ total > 0
545
+ ? await tryQuery("search_schema.fields", q.sql, warnings)
546
+ : [];
547
+ pagination.fields = sqlPagination(page, pageSize, total);
548
+ }
549
+ else {
550
+ result.fields = [];
551
+ pagination.fields = sqlPagination(page, pageSize, 0);
552
+ if (!where) {
553
+ warnings.push({
554
+ module: "search_schema.fields",
555
+ message: "环境库 TableFieldSetTB 无可用搜索列(FieldName/FieldDesc/EnglishDesc 均不存在)",
556
+ hint: "可用 describe_table 查看实际字段",
557
+ });
558
+ }
559
+ }
560
+ }
561
+ return ok(envelope({
562
+ items: result.tables ?? [],
563
+ fields: result.fields ?? [],
564
+ pagination,
565
+ warnings,
566
+ searchIn,
567
+ keyword,
568
+ }));
569
+ });
570
+ // ============================================================
571
+ // get_table_relations
572
+ // ============================================================
573
+ server.registerTool("get_table_relations", {
574
+ description: WARNING + "\n\n分析表的主从关系和外键关联。返回父表、子表、关联字段。",
575
+ inputSchema: { tableName: z.string().max(128).describe("表名") },
576
+ }, async ({ tableName }) => {
577
+ const warnings = [];
578
+ const safeName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
579
+ if (!(await tableExists(T("tableDef")))) {
580
+ return ok(envelope({
581
+ items: [],
582
+ pagination: null,
583
+ warnings: [
584
+ {
585
+ module: "get_table_relations",
586
+ message: `环境库缺少 ${T("tableDef")}`,
587
+ fallback: "get_capabilities",
588
+ },
589
+ ],
590
+ }));
591
+ }
592
+ const rs = await resolveSelect(T("tableDef"), [
593
+ "TableName",
594
+ "TableDesc",
595
+ "TableType",
596
+ "ParentTable",
597
+ ]);
598
+ const parentTables = await tryQuery("get_table_relations.parents", `SELECT ${rs.select} FROM ${T("tableDef")} WHERE [TableName]='${escapeSql(safeName)}' AND [ParentTable] IS NOT NULL AND [ParentTable] != ''`, warnings);
599
+ const childTables = await tryQuery("get_table_relations.children", `SELECT ${rs.select} FROM ${T("tableDef")} WHERE [ParentTable]='${escapeSql(safeName)}' ORDER BY [TableName]`, warnings);
600
+ let fieldLinks;
601
+ if (await tableExists(T("fieldDef"))) {
602
+ const frs = await resolveSelect(T("fieldDef"), [
603
+ "TableName",
604
+ "FieldName",
605
+ "FieldDesc",
606
+ "cLinkTable",
607
+ "cLinkField",
608
+ "cDstField",
609
+ ]);
610
+ fieldLinks = {
611
+ module: "fieldLinks",
612
+ available: true,
613
+ items: await tryQuery("get_table_relations.fieldLinks", `SELECT ${frs.select} FROM ${T("fieldDef")} WHERE [TableName]='${escapeSql(safeName)}' AND [cLinkTable] IS NOT NULL AND [cLinkTable] != ''`, warnings),
614
+ };
615
+ }
616
+ else {
617
+ fieldLinks = {
618
+ module: "fieldLinks",
619
+ available: false,
620
+ reason: `环境库缺少 ${T("fieldDef")}`,
621
+ };
622
+ }
623
+ return ok(envelope({
624
+ items: parentTables,
625
+ pagination: null,
626
+ warnings,
627
+ table: safeName,
628
+ children: childTables,
629
+ fieldLinks,
630
+ }));
631
+ });
632
+ // ============================================================
633
+ // check_user_table
634
+ // ============================================================
635
+ server.registerTool("check_user_table", {
636
+ description: WARNING +
637
+ "\n\n检查用户库中表是否实际存在,返回实际字段列表并与环境库定义对比。",
638
+ inputSchema: {
639
+ tableName: z.string().max(128).describe("表名"),
640
+ page: z.number().int().min(1).default(1).describe("页码"),
641
+ pageSize: z
642
+ .number()
643
+ .int()
644
+ .min(1)
645
+ .max(200)
646
+ .default(50)
647
+ .describe("每页数量"),
648
+ },
649
+ }, async ({ tableName, page = 1, pageSize = 50 }) => {
650
+ const warnings = [];
651
+ const safeName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
652
+ if (!userName) {
653
+ return ok(envelope({
654
+ items: [],
655
+ pagination: null,
656
+ warnings: [
657
+ {
658
+ module: "check_user_table",
659
+ message: "用户库未配置(DB_TARGET_DATABASES 为空)",
660
+ },
661
+ ],
662
+ }));
663
+ }
664
+ const existsRows = await tryQuery("check_user_table.exists", `SELECT TABLE_NAME FROM [${userName}].INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='dbo' AND TABLE_NAME='${escapeSql(safeName)}'`, warnings);
665
+ if (existsRows.length === 0) {
666
+ return ok(envelope({
667
+ items: [],
668
+ pagination: null,
669
+ warnings,
670
+ table: safeName,
671
+ exists: false,
672
+ userDb: userName,
673
+ hint: `表 "${tableName}" 在用户库 ${userName} 中不存在`,
674
+ }));
675
+ }
676
+ const actualFields = await tryQuery("check_user_table.actual", `SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT FROM [${userName}].INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='dbo' AND TABLE_NAME='${escapeSql(safeName)}' ORDER BY ORDINAL_POSITION`, warnings);
677
+ let definedFields = [];
678
+ let definedModule = {
679
+ module: "definedFields",
680
+ available: true,
681
+ items: [],
682
+ };
683
+ if (await tableExists(T("fieldDef"))) {
684
+ const info = await getTableInfo(T("fieldDef"));
685
+ const rs = await resolveSelect(T("fieldDef"), [
686
+ "iLineNo",
687
+ "FieldName",
688
+ "FieldType",
689
+ "bAllowNull",
690
+ "DefaultValue",
691
+ ]);
692
+ const where = buildWhere(info.columns, [
693
+ { filters: [{ col: "TableName", op: "eq", value: safeName }] },
694
+ ]).where;
695
+ const order = buildOrderBy(info.columns, ["iLineNo", "FieldName"]);
696
+ definedFields = await tryQuery("check_user_table.defined", `SELECT ${rs.select} FROM ${T("fieldDef")}${where}${order.orderBy}`, warnings);
697
+ definedModule = {
698
+ module: "definedFields",
699
+ available: true,
700
+ missing: rs.missing,
701
+ };
702
+ }
703
+ else {
704
+ definedModule = {
705
+ module: "definedFields",
706
+ available: false,
707
+ reason: `环境库缺少 ${T("fieldDef")}`,
708
+ };
709
+ }
710
+ const { page: pagedActual, pagination: pagActual } = paginate(actualFields, page, pageSize);
711
+ return ok(envelope({
712
+ items: pagedActual,
713
+ pagination: pagActual,
714
+ warnings,
715
+ table: safeName,
716
+ exists: true,
717
+ userDb: userName,
718
+ actualFieldCount: actualFields.length,
719
+ definedFieldCount: definedFields.length,
720
+ definedFields,
721
+ definedFieldsModule: definedModule,
722
+ }));
723
+ });
724
+ // ============================================================
725
+ // get_table_indexes
726
+ // ============================================================
727
+ server.registerTool("get_table_indexes", {
728
+ description: WARNING +
729
+ "\n\n查询指定表的索引和外键信息。数据来源: 用户库 sys.indexes + sys.foreign_keys",
730
+ inputSchema: {
731
+ tableName: z.string().max(128).describe("表名"),
732
+ page: z.number().int().min(1).default(1).describe("页码"),
733
+ pageSize: z
734
+ .number()
735
+ .int()
736
+ .min(1)
737
+ .max(200)
738
+ .default(50)
739
+ .describe("每页数量"),
740
+ },
741
+ }, async ({ tableName, page = 1, pageSize = 50 }) => {
742
+ const warnings = [];
743
+ const safeName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
744
+ if (!userName) {
745
+ return ok(envelope({
746
+ items: [],
747
+ pagination: null,
748
+ warnings: [
749
+ {
750
+ module: "get_table_indexes",
751
+ message: "用户库未配置(DB_TARGET_DATABASES 为空)",
752
+ },
753
+ ],
754
+ }));
755
+ }
756
+ const indexes = await tryQuery("get_table_indexes.indexes", `
757
+ SELECT
758
+ i.name AS IndexName,
759
+ i.type_desc AS IndexType,
760
+ i.is_unique AS IsUnique,
761
+ i.is_primary_key AS IsPrimaryKey,
762
+ c.name AS ColumnName,
763
+ ic.is_included_column AS IsIncluded,
764
+ ic.key_ordinal AS KeyOrdinal
765
+ FROM [${userName}].sys.indexes i
766
+ INNER JOIN [${userName}].sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
767
+ INNER JOIN [${userName}].sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
768
+ INNER JOIN [${userName}].sys.tables t ON i.object_id = t.object_id
769
+ WHERE t.name = '${escapeSql(safeName)}'
770
+ ORDER BY i.name, ic.key_ordinal
771
+ `, warnings);
772
+ const foreignKeys = await tryQuery("get_table_indexes.foreignKeys", `
773
+ SELECT
774
+ fk.name AS FKName,
775
+ OBJECT_NAME(fk.parent_object_id) AS ParentTable,
776
+ col.name AS ParentColumn,
777
+ OBJECT_NAME(fk.referenced_object_id) AS ReferencedTable,
778
+ rcol.name AS ReferencedColumn
779
+ FROM [${userName}].sys.foreign_keys fk
780
+ INNER JOIN [${userName}].sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
781
+ INNER JOIN [${userName}].sys.columns col ON fkc.parent_object_id = col.object_id AND fkc.parent_column_id = col.column_id
782
+ INNER JOIN [${userName}].sys.columns rcol ON fkc.referenced_object_id = rcol.object_id AND fkc.referenced_column_id = rcol.column_id
783
+ WHERE OBJECT_NAME(fk.parent_object_id) = '${escapeSql(safeName)}'
784
+ OR OBJECT_NAME(fk.referenced_object_id) = '${escapeSql(safeName)}'
785
+ ORDER BY fk.name
786
+ `, warnings);
787
+ const { page: pagedIndexes, pagination: pag } = paginate(indexes, page, pageSize);
788
+ return ok(envelope({
789
+ items: pagedIndexes,
790
+ pagination: pag,
791
+ warnings,
792
+ tableName: safeName,
793
+ foreignKeys,
794
+ indexCount: indexes.length,
795
+ }));
796
+ });
797
+ // ============================================================
798
+ // suggest_indexes
799
+ // ============================================================
800
+ server.registerTool("suggest_indexes", {
801
+ description: WARNING +
802
+ "\n\n基于环境库字段定义,对指定表进行索引建议分析。根据字段类型(bSearchField/bUnique/cLinkTable/bVirtual等)给出索引优化建议。数据来源: XDWmsCommon.dbo.TableFieldSetTB",
803
+ inputSchema: {
804
+ tableName: z.string().max(128).describe("表名"),
805
+ page: z.number().int().min(1).default(1).describe("页码"),
806
+ pageSize: z
807
+ .number()
808
+ .int()
809
+ .min(1)
810
+ .max(200)
811
+ .default(50)
812
+ .describe("每页数量"),
813
+ },
814
+ }, async ({ tableName, page = 1, pageSize = 50 }) => {
815
+ const safeName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
816
+ const warnings = [];
817
+ if (!(await tableExists(T("fieldDef")))) {
818
+ return ok(envelope({
819
+ items: [],
820
+ pagination: null,
821
+ warnings: [
822
+ {
823
+ module: "suggest_indexes",
824
+ message: `环境库缺少 ${T("fieldDef")}`,
825
+ fallback: "get_capabilities",
826
+ },
827
+ ],
828
+ }));
829
+ }
830
+ const rs = await resolveSelect(T("fieldDef"), [
831
+ "FieldName",
832
+ "FieldDesc",
833
+ "FieldType",
834
+ "bSearchField",
835
+ "bUnique",
836
+ "bAllowNull",
837
+ "cLinkTable",
838
+ "cLinkField",
839
+ "bVirtual",
840
+ ]);
841
+ const info = await getTableInfo(T("fieldDef"));
842
+ const where = buildWhere(info.columns, [
843
+ { filters: [{ col: "TableName", op: "eq", value: safeName }] },
844
+ ]).where;
845
+ const fields = await tryQuery("suggest_indexes.fields", `SELECT ${rs.select} FROM ${T("fieldDef")}${where}`, warnings);
846
+ if (fields.length === 0) {
847
+ return ok(envelope({
848
+ items: [],
849
+ pagination: null,
850
+ warnings,
851
+ tableName: safeName,
852
+ hint: `表 "${tableName}" 在环境库中未定义,无法给出索引建议`,
853
+ }));
854
+ }
855
+ const suggestions = [];
856
+ const singleColIndexes = [];
857
+ const compositeCandidate = [];
858
+ for (const f of fields) {
859
+ if (f.bSearchField && f.cLinkTable) {
860
+ suggestions.push(`外键关联字段 ${f.FieldName}(${f.FieldDesc}) → ${f.cLinkTable}.${f.cLinkField},建议建非聚集索引`);
861
+ singleColIndexes.push(f.FieldName);
862
+ }
863
+ else if (f.bSearchField) {
864
+ suggestions.push(`搜索字段 ${f.FieldName}(${f.FieldDesc}),建议建非聚集索引`);
865
+ singleColIndexes.push(f.FieldName);
866
+ }
867
+ if (f.bUnique) {
868
+ suggestions.push(`唯一字段 ${f.FieldName}(${f.FieldDesc}),建议建唯一索引`);
869
+ }
870
+ if (f.bVirtual) {
871
+ suggestions.push(`虚拟字段 ${f.FieldName}(${f.FieldDesc}),不建议建索引(计算字段)`);
872
+ }
873
+ if (f.cLinkTable) {
874
+ compositeCandidate.push(f.FieldName);
875
+ }
876
+ }
877
+ if (compositeCandidate.length >= 2) {
878
+ suggestions.push(`复合索引候选: (${compositeCandidate.join(", ")})`);
879
+ }
880
+ const fieldDetails = fields.map((f) => ({
881
+ FieldName: f.FieldName,
882
+ FieldDesc: f.FieldDesc,
883
+ bSearchField: f.bSearchField,
884
+ bUnique: f.bUnique,
885
+ cLinkTable: f.cLinkTable,
886
+ bVirtual: f.bVirtual,
887
+ }));
888
+ const { page: pagedFields, pagination: pag } = paginate(fieldDetails, page, pageSize);
889
+ return ok(envelope({
890
+ items: pagedFields,
891
+ pagination: pag,
892
+ warnings,
893
+ tableName: safeName,
894
+ fieldCount: fields.length,
895
+ suggestions,
896
+ }));
897
+ });
898
+ // ============================================================
899
+ // get_virtual_tables
900
+ // ============================================================
901
+ server.registerTool("get_virtual_tables", {
902
+ description: WARNING +
903
+ "\n\n查询系统虚拟表定义及其关联的物理表。虚拟表通过SQL动态生成表结构。数据来源: XDWmsCommon.dbo.VirtualTableTB",
904
+ inputSchema: {
905
+ tableName: z
906
+ .string()
907
+ .max(128)
908
+ .optional()
909
+ .describe("虚拟表名,精确查询"),
910
+ keyword: z.string().max(200).optional().describe("按表名模糊搜索"),
911
+ page: z.number().int().min(1).default(1).describe("页码"),
912
+ pageSize: z
913
+ .number()
914
+ .int()
915
+ .min(1)
916
+ .max(200)
917
+ .default(50)
918
+ .describe("每页数量"),
919
+ },
920
+ }, async ({ tableName, keyword, page = 1, pageSize = 50 }) => {
921
+ const warnings = [];
922
+ if (!(await tableExists(T("virtualTable")))) {
923
+ return ok(envelope({
924
+ items: [],
925
+ pagination: sqlPagination(page, pageSize, 0),
926
+ warnings: [
927
+ {
928
+ module: "get_virtual_tables",
929
+ message: `环境库缺少 ${T("virtualTable")}`,
930
+ fallback: "get_capabilities",
931
+ },
932
+ ],
933
+ }));
934
+ }
935
+ const info = await getTableInfo(T("virtualTable"));
936
+ const rs = await resolveSelect(T("virtualTable"), [
937
+ "TableName",
938
+ "VirtualTable",
939
+ "FixTableName",
940
+ ]);
941
+ const groups = [];
942
+ if (tableName)
943
+ groups.push({ filters: [{ col: "TableName", op: "eq", value: tableName }] });
944
+ if (keyword)
945
+ groups.push({ filters: [{ col: "TableName", op: "like", value: keyword }] });
946
+ const { where } = buildWhere(info.columns, groups);
947
+ const order = buildOrderBy(info.columns, ["TableName"]);
948
+ const q = buildPagedQuery({
949
+ table: T("virtualTable"),
950
+ cols: rs.present.length > 0
951
+ ? rs.present.map((c) => `[${c}]`)
952
+ : ["[TableName]"],
953
+ where,
954
+ orderBy: order.orderBy,
955
+ page,
956
+ pageSize,
957
+ });
958
+ const cnt = await tryQuery("get_virtual_tables.count", q.countSql, warnings);
959
+ const total = Number(cnt[0]?.Total ?? 0);
960
+ const items = total > 0 ? await tryQuery("get_virtual_tables", q.sql, warnings) : [];
961
+ return ok(envelope({
962
+ items,
963
+ pagination: sqlPagination(page, pageSize, total),
964
+ warnings,
965
+ }));
966
+ });
967
+ // ============================================================
968
+ // get_module_tree
969
+ // ============================================================
970
+ server.registerTool("get_module_tree", {
971
+ description: WARNING +
972
+ "\n\n获取系统模块树结构。ModuleManageTB 定义了系统的模块层次(如销售管理→销售订单→销售出库)。",
973
+ inputSchema: {
974
+ keyword: z.string().max(200).optional().describe("按模块名称搜索"),
975
+ page: z.number().int().min(1).default(1).describe("页码"),
976
+ pageSize: z
977
+ .number()
978
+ .int()
979
+ .min(1)
980
+ .max(200)
981
+ .default(50)
982
+ .describe("每页数量"),
983
+ },
984
+ }, async ({ keyword, page = 1, pageSize = 50 }) => {
985
+ const warnings = [];
986
+ // 环境库可能没有 ModuleManageTB,缺失时优雅降级而非报错
987
+ if (!(await tableExists(T("moduleManage")))) {
988
+ return ok(envelope({
989
+ items: [],
990
+ pagination: sqlPagination(page, pageSize, 0),
991
+ warnings: [
992
+ {
993
+ module: "get_module_tree",
994
+ message: `环境库缺少 ${T("moduleManage")}`,
995
+ hint: "可用 get_capabilities 查看本环境支持的元数据表",
996
+ fallback: "get_capabilities",
997
+ },
998
+ ],
999
+ }));
1000
+ }
1001
+ const info = await getTableInfo(T("moduleManage"));
1002
+ const rs = await resolveSelect(T("moduleManage"), [
1003
+ "XDID",
1004
+ "Name",
1005
+ "ModuleDesc",
1006
+ "ModuleAlias",
1007
+ "Code",
1008
+ "ID",
1009
+ "ParentXDID",
1010
+ "LevelCode",
1011
+ "HelpCode",
1012
+ ]);
1013
+ if (rs.present.length === 0) {
1014
+ return ok(envelope({
1015
+ items: [],
1016
+ pagination: sqlPagination(page, pageSize, 0),
1017
+ warnings: [
1018
+ {
1019
+ module: "get_module_tree",
1020
+ message: "ModuleManageTB 无可用字段",
1021
+ },
1022
+ ],
1023
+ }));
1024
+ }
1025
+ const groups = [];
1026
+ if (keyword) {
1027
+ const filters = ["Name", "ModuleDesc"]
1028
+ .filter((c) => hasCol(info.columns, c))
1029
+ .map((c) => ({ col: c, op: "like", value: keyword }));
1030
+ if (filters.length > 0)
1031
+ groups.push({ logic: "or", filters });
1032
+ }
1033
+ const { where } = buildWhere(info.columns, groups);
1034
+ const order = buildOrderBy(info.columns, ["LevelCode"]);
1035
+ const q = buildPagedQuery({
1036
+ table: T("moduleManage"),
1037
+ cols: rs.present.map((c) => `[${c}]`),
1038
+ where,
1039
+ orderBy: order.orderBy,
1040
+ page,
1041
+ pageSize,
1042
+ });
1043
+ const cnt = await tryQuery("get_module_tree.count", q.countSql, warnings);
1044
+ const total = Number(cnt[0]?.Total ?? 0);
1045
+ const modules = total > 0 ? await tryQuery("get_module_tree", q.sql, warnings) : [];
1046
+ const moduleNames = [
1047
+ ...new Set(modules
1048
+ .map((m) => m.Name)
1049
+ .filter((name) => typeof name === "string")
1050
+ .map((name) => `'${escapeSql(String(name))}'`)),
1051
+ ];
1052
+ let funcCounts = [];
1053
+ if (moduleNames.length > 0 && (await tableExists(T("function")))) {
1054
+ funcCounts = await tryQuery("get_module_tree.funcCounts", `SELECT [ParentFName] AS ModuleName, COUNT(*) AS funcCount FROM ${T("function")} WHERE [ParentFName] IN (${moduleNames.join(",")}) GROUP BY [ParentFName]`, warnings);
1055
+ }
1056
+ return ok(envelope({
1057
+ items: modules,
1058
+ pagination: sqlPagination(page, pageSize, total),
1059
+ warnings,
1060
+ funcCounts,
1061
+ }));
1062
+ });
1063
+ // ============================================================
1064
+ // get_inoutkind_detail
1065
+ // ============================================================
1066
+ server.registerTool("get_inoutkind_detail", {
1067
+ description: WARNING +
1068
+ "\n\n查看出入库类型表(InOutKindTB)的完整定义与明细数据。返回表头、字段定义,以及按编号排序的实际数据行。数据来源: XDWmsCommon.dbo.InOutKindTB。",
1069
+ inputSchema: {
1070
+ keyword: z
1071
+ .string()
1072
+ .max(200)
1073
+ .optional()
1074
+ .describe("按编号/名称/助记码模糊筛选"),
1075
+ includeData: z
1076
+ .boolean()
1077
+ .optional()
1078
+ .default(true)
1079
+ .describe("是否返回实际数据行"),
1080
+ page: z.number().int().min(1).default(1).describe("页码"),
1081
+ pageSize: z
1082
+ .number()
1083
+ .int()
1084
+ .min(1)
1085
+ .max(200)
1086
+ .default(50)
1087
+ .describe("每页数量"),
1088
+ },
1089
+ }, async ({ keyword, includeData = true, page = 1, pageSize = 50 }) => {
1090
+ const warnings = [];
1091
+ // 表头定义(TableSetTB 可能没有 InOutKindTB 登记)
1092
+ let tableDef = null;
1093
+ if (await tableExists(T("tableDef"))) {
1094
+ const trs = await resolveSelect(T("tableDef"), [
1095
+ "TableName",
1096
+ "TableDesc",
1097
+ "TableType",
1098
+ "ParentTable",
1099
+ "Modular1",
1100
+ "Modular2",
1101
+ "Remark",
1102
+ ]);
1103
+ const rows = await tryQuery("get_inoutkind_detail.tableDef", `SELECT ${trs.select} FROM ${T("tableDef")} WHERE [TableName]='InOutKindTB'`, warnings);
1104
+ tableDef = rows[0] ?? null;
1105
+ }
1106
+ // 字段定义
1107
+ let fields = { module: "fields", available: true, items: [] };
1108
+ if (await tableExists(T("fieldDef"))) {
1109
+ const info = await getTableInfo(T("fieldDef"));
1110
+ const rs = await resolveSelect(T("fieldDef"), [
1111
+ "iLineNo",
1112
+ "FieldName",
1113
+ "FieldDesc",
1114
+ "FieldType",
1115
+ "iPrecision",
1116
+ "iScale",
1117
+ "DefaultValue",
1118
+ "bAllowNull",
1119
+ "bVirtual",
1120
+ "bSearchField",
1121
+ "bUnique",
1122
+ "cLinkTable",
1123
+ "cLinkField",
1124
+ "cDstField",
1125
+ ]);
1126
+ const where = buildWhere(info.columns, [
1127
+ { filters: [{ col: "TableName", op: "eq", value: "InOutKindTB" }] },
1128
+ ]).where;
1129
+ const order = buildOrderBy(info.columns, ["iLineNo", "FieldName"]);
1130
+ fields = {
1131
+ module: "fields",
1132
+ available: true,
1133
+ items: await tryQuery("get_inoutkind_detail.fields", `SELECT ${rs.select} FROM ${T("fieldDef")}${where}${order.orderBy}`, warnings),
1134
+ };
1135
+ }
1136
+ else {
1137
+ fields = {
1138
+ module: "fields",
1139
+ available: false,
1140
+ reason: `环境库缺少 ${T("fieldDef")}`,
1141
+ };
1142
+ }
1143
+ // 实际数据行(InOutKindTB 可能不存在)
1144
+ let data = [];
1145
+ let dataModule = { module: "data", available: true, items: [] };
1146
+ let pagination = null;
1147
+ if (includeData) {
1148
+ if (await tableExists(T("inOutKind"))) {
1149
+ const info = await getTableInfo(T("inOutKind"));
1150
+ const rs = await resolveSelect(T("inOutKind"), [
1151
+ "ID",
1152
+ "Code",
1153
+ "Name",
1154
+ "XDFormID",
1155
+ "IOFlag",
1156
+ "IsOther",
1157
+ "IsInOther",
1158
+ "IsChangePrice",
1159
+ "bDefault",
1160
+ "IoAcc",
1161
+ "bEnableDet",
1162
+ "DefaultPState",
1163
+ "Prefix",
1164
+ "bZZ",
1165
+ "IoAccName",
1166
+ "bIoPzShow",
1167
+ "ParentID",
1168
+ "LevelCode",
1169
+ "HelpCode",
1170
+ "MakeMan",
1171
+ "MakeDate",
1172
+ "EditMan",
1173
+ "EditDate",
1174
+ "Remark",
1175
+ ]);
1176
+ const groups = [];
1177
+ if (keyword) {
1178
+ const filters = ["Code", "Name", "HelpCode"]
1179
+ .filter((c) => hasCol(info.columns, c))
1180
+ .map((c) => ({ col: c, op: "like", value: keyword }));
1181
+ if (filters.length > 0)
1182
+ groups.push({ logic: "or", filters });
1183
+ }
1184
+ const { where } = buildWhere(info.columns, groups);
1185
+ const order = buildOrderBy(info.columns, ["Code"]);
1186
+ const q = buildPagedQuery({
1187
+ table: T("inOutKind"),
1188
+ cols: rs.present.length > 0
1189
+ ? rs.present.map((c) => `[${c}]`)
1190
+ : ["[Code]"],
1191
+ where,
1192
+ orderBy: order.orderBy,
1193
+ page,
1194
+ pageSize,
1195
+ });
1196
+ const cnt = await tryQuery("get_inoutkind_detail.count", q.countSql, warnings);
1197
+ const total = Number(cnt[0]?.Total ?? 0);
1198
+ data =
1199
+ total > 0
1200
+ ? await tryQuery("get_inoutkind_detail.data", q.sql, warnings)
1201
+ : [];
1202
+ pagination = sqlPagination(page, pageSize, total);
1203
+ dataModule = { module: "data", available: true };
1204
+ }
1205
+ else {
1206
+ dataModule = {
1207
+ module: "data",
1208
+ available: false,
1209
+ reason: `环境库缺少 ${T("inOutKind")}`,
1210
+ };
1211
+ warnings.push({
1212
+ module: "get_inoutkind_detail.data",
1213
+ message: `环境库缺少 ${T("inOutKind")}`,
1214
+ fallback: "get_capabilities",
1215
+ });
1216
+ pagination = sqlPagination(page, pageSize, 0);
1217
+ }
1218
+ }
1219
+ return ok(envelope({
1220
+ items: data,
1221
+ pagination,
1222
+ warnings,
1223
+ table: tableDef,
1224
+ fields,
1225
+ dataModule,
1226
+ totalData: data.length,
1227
+ }));
1228
+ });
1229
+ // ============================================================
1230
+ // resolve_table(中文别名 / 关键词 → 实际表名)
1231
+ // ============================================================
1232
+ server.registerTool("resolve_table", {
1233
+ description: WARNING +
1234
+ "\n\n把中文表名/关键词解析为实际表名。先精确匹配 TableName,未命中再按 TableName/TableDesc/EnglishDesc 模糊匹配,返回带 matchType=exact|fuzzy 的候选。",
1235
+ inputSchema: {
1236
+ nameOrKeyword: z.string().max(200).describe("表名或中文关键词,如 发货通知"),
1237
+ page: z.number().int().min(1).default(1).describe("页码"),
1238
+ pageSize: z
1239
+ .number()
1240
+ .int()
1241
+ .min(1)
1242
+ .max(200)
1243
+ .default(50)
1244
+ .describe("每页数量"),
1245
+ },
1246
+ }, async ({ nameOrKeyword, page = 1, pageSize = 50 }) => {
1247
+ const warnings = [];
1248
+ if (!(await tableExists(T("tableDef")))) {
1249
+ return ok(envelope({
1250
+ items: [],
1251
+ pagination: null,
1252
+ warnings: [
1253
+ {
1254
+ module: "resolve_table",
1255
+ message: `环境库缺少 ${T("tableDef")}`,
1256
+ fallback: "get_capabilities",
1257
+ },
1258
+ ],
1259
+ }));
1260
+ }
1261
+ const tCols = await getTableColumns(T("tableDef"));
1262
+ const sel = pickCols(tCols, [
1263
+ "TableName",
1264
+ "TableDesc",
1265
+ "TableType",
1266
+ "Modular1",
1267
+ "EnglishDesc",
1268
+ ]);
1269
+ if (sel.length === 0) {
1270
+ return ok(envelope({
1271
+ items: [],
1272
+ pagination: null,
1273
+ warnings: [
1274
+ { module: "resolve_table", message: "TableSetTB 无可用字段" },
1275
+ ],
1276
+ }));
1277
+ }
1278
+ const selectList = sel.map((c) => `[${c}]`).join(", ");
1279
+ const safe = nameOrKeyword.replace(/[^a-zA-Z0-9_]/g, "");
1280
+ // 1) 精确匹配 TableName
1281
+ let matchType = "exact";
1282
+ let rows = [];
1283
+ if (safe && hasCol(tCols, "TableName")) {
1284
+ const { where } = buildWhere(tCols, [
1285
+ { filters: [{ col: "TableName", op: "eq", value: safe }] },
1286
+ ]);
1287
+ rows = await tryQuery("resolve_table.exact", `SELECT TOP 50 ${selectList} FROM ${T("tableDef")}${where} ORDER BY [TableName]`, warnings);
1288
+ }
1289
+ // 2) 未命中 → 模糊匹配
1290
+ if (rows.length === 0) {
1291
+ matchType = "fuzzy";
1292
+ const filters = ["TableName", "TableDesc", "EnglishDesc"]
1293
+ .filter((c) => hasCol(tCols, c))
1294
+ .map((c) => ({ col: c, op: "like", value: nameOrKeyword }));
1295
+ const { where } = buildWhere(tCols, filters.length > 0 ? [{ logic: "or", filters }] : []);
1296
+ if (where) {
1297
+ rows = await tryQuery("resolve_table.fuzzy", `SELECT TOP 50 ${selectList} FROM ${T("tableDef")}${where} ORDER BY [TableName]`, warnings);
1298
+ }
1299
+ }
1300
+ const items = rows.map((r) => ({
1301
+ ...r,
1302
+ matchType,
1303
+ }));
1304
+ if (items.length === 0) {
1305
+ warnings.push({
1306
+ module: "resolve_table",
1307
+ message: `未找到与 "${nameOrKeyword}" 匹配的表`,
1308
+ hint: "可用 list_tables(keyword=...) 或 search_schema(keyword=...) 进一步查找",
1309
+ fallback: "list_tables",
1310
+ });
1311
+ }
1312
+ const { page: paged, pagination } = paginate(items, page, pageSize);
1313
+ return ok(envelope({
1314
+ items: paged,
1315
+ pagination,
1316
+ warnings,
1317
+ query: nameOrKeyword,
1318
+ matchType,
1319
+ }));
1320
+ });
1321
+ }
1322
+ //# sourceMappingURL=table-tools.js.map