@kinginsun/mcp-drugsea 0.7.0 → 0.8.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 +7 -1
- package/dist/api.js +50 -7
- package/dist/index.js +33 -17
- package/dist/normalize.js +49 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,13 +178,19 @@ xlsx export is not implemented in this MCP (v1 returns JSON samples only).
|
|
|
178
178
|
| Tool | Parameters | Notes |
|
|
179
179
|
|------|------------|--------|
|
|
180
180
|
| `yaohai-catalog` | `category?`, `q?` | List databases |
|
|
181
|
-
| `yaohai-search` | `dbname`, `query?`, `limit?`, `offset?` | Default limit 10, max 50 |
|
|
181
|
+
| `yaohai-search` | `dbname`, `query?`, `limit?`, `offset?` | Default limit 10, max 50; ≤ 1000 rows per query condition (offset+limit window cap) |
|
|
182
182
|
| `yaohai-detail` | `dbname`, `id` | Skip DBs with `has_detail: false` |
|
|
183
183
|
| `yaohai-facets` | `dbname?`, `query?`, `fields?` | Facets for `dbs`-route DBs. Omit `fields` → discover facet-capable DBs/fields; pass `fields` → fetch buckets |
|
|
184
184
|
| `yaohai-global-search` | `q?`, `query?`, `limit?`, `offset?` | `q` fills `query.term` |
|
|
185
185
|
|
|
186
186
|
When the target database is unclear, use `yaohai-catalog` (filter by `category` / `q`) to pick a `dbname`, then `yaohai-search`; or use `yaohai-global-search` for a cross-database panorama query.
|
|
187
187
|
|
|
188
|
+
> **Retrieval cap (anti-scraping, enforced client + server):** one distinct query
|
|
189
|
+
> condition can return at most **1000 rows** (`offset + limit ≤ 1000`). A larger
|
|
190
|
+
> `offset` is rejected; `limit` is auto-shrunk near the edge of the window. To
|
|
191
|
+
> reach deeper slices, narrow the filters (date / province / ATC / enterprise)
|
|
192
|
+
> and query again — each *new* condition gets its own 1000-row window.
|
|
193
|
+
|
|
188
194
|
`yaohai-facets` mirrors the ConditionSearch facet filters of the website for the `dbs`-route databases (医保 `yibao`, 基药 `jiyao`, 集采 `jicai`, sales `drugsales`, …). Two modes:
|
|
189
195
|
|
|
190
196
|
- **Discovery** (no `fields`): omit `dbname` to list all 44 facet-capable databases, or pass `dbname` to list its facet-able fields (with `filter_type`).
|
package/dist/api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { URL } from "node:url";
|
|
4
|
-
import { normalizeRecord, parseFacetList } from "./normalize.js";
|
|
4
|
+
import { normalizeRecord, parseFacetList, stripMcpFields } from "./normalize.js";
|
|
5
5
|
export class MissingTokenError extends Error {
|
|
6
6
|
constructor(message) {
|
|
7
7
|
super(message ??
|
|
@@ -95,6 +95,19 @@ export function clampOffset(offset) {
|
|
|
95
95
|
}
|
|
96
96
|
return Math.max(0, Math.trunc(offset));
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* 单组查询条件最多可获取的行数窗口(与服务端 yaohai_mcp_max_retrieve() 同步,
|
|
100
|
+
* 默认 1000):offset+limit 超出窗口则收缩 limit;offset 已达窗口直接报错,
|
|
101
|
+
* 提示收窄筛选条件而不是继续翻页。
|
|
102
|
+
*/
|
|
103
|
+
export const MAX_RETRIEVE_PER_QUERY = 1000;
|
|
104
|
+
export function enforceRetrievalWindow(limit, offset, max = MAX_RETRIEVE_PER_QUERY) {
|
|
105
|
+
if (offset >= max) {
|
|
106
|
+
throw new ApiError(`A single query condition can return at most ${max} rows(单个查询条件最多获取 ${max} 条): ` +
|
|
107
|
+
`offset=${offset} 已超出窗口上限。请收窄筛选条件(如日期/省份/ATC/企业名)后分页查询。`);
|
|
108
|
+
}
|
|
109
|
+
return { limit: Math.min(limit, max - offset), offset };
|
|
110
|
+
}
|
|
98
111
|
function httpRequest(method, urlStr, headers, body) {
|
|
99
112
|
return new Promise((resolve, reject) => {
|
|
100
113
|
const url = new URL(urlStr);
|
|
@@ -239,11 +252,16 @@ export async function mcpDbSearch(opts) {
|
|
|
239
252
|
if (item && typeof item === "object" && "fields" in item) {
|
|
240
253
|
const row = item;
|
|
241
254
|
const flat = normalizeRecord(row.fields);
|
|
242
|
-
if (
|
|
243
|
-
flat &&
|
|
255
|
+
if (flat &&
|
|
244
256
|
typeof flat === "object" &&
|
|
245
257
|
!Array.isArray(flat)) {
|
|
246
|
-
|
|
258
|
+
const stripped = stripMcpFields(flat, {
|
|
259
|
+
dbname: opts.dbname,
|
|
260
|
+
});
|
|
261
|
+
if (row.detail_url) {
|
|
262
|
+
return { ...stripped, detail_url: row.detail_url };
|
|
263
|
+
}
|
|
264
|
+
return stripped;
|
|
247
265
|
}
|
|
248
266
|
return flat;
|
|
249
267
|
}
|
|
@@ -270,7 +288,14 @@ export async function mcpDbDetail(dbname, id) {
|
|
|
270
288
|
dbname,
|
|
271
289
|
id,
|
|
272
290
|
}));
|
|
273
|
-
|
|
291
|
+
const detail = Array.isArray(content.detail)
|
|
292
|
+
? content.detail.map((row) => row && typeof row === "object"
|
|
293
|
+
? stripMcpFields(row, { dbname, isDetail: true })
|
|
294
|
+
: row)
|
|
295
|
+
: content.detail && typeof content.detail === "object"
|
|
296
|
+
? stripMcpFields(content.detail, { dbname, isDetail: true })
|
|
297
|
+
: content.detail;
|
|
298
|
+
return { dbname, id, ...content, detail };
|
|
274
299
|
}
|
|
275
300
|
export async function listSearch(opts) {
|
|
276
301
|
const params = {
|
|
@@ -285,6 +310,13 @@ export async function listSearch(opts) {
|
|
|
285
310
|
const raw = result.raw;
|
|
286
311
|
const content = raw.content ?? result.content;
|
|
287
312
|
const items = normalizeRecord(content);
|
|
313
|
+
if (Array.isArray(items)) {
|
|
314
|
+
for (const it of items) {
|
|
315
|
+
if (it && typeof it === "object") {
|
|
316
|
+
stripMcpFields(it, { dbname: opts.dbname });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
288
320
|
const totalRaw = raw.tnum;
|
|
289
321
|
const total = typeof totalRaw === "number"
|
|
290
322
|
? totalRaw
|
|
@@ -301,14 +333,25 @@ export async function listSearch(opts) {
|
|
|
301
333
|
query_applied: opts.query,
|
|
302
334
|
};
|
|
303
335
|
}
|
|
304
|
-
export async function fetchDetail(path, id) {
|
|
336
|
+
export async function fetchDetail(path, id, dbname = "") {
|
|
305
337
|
const result = await yaohaiGet(path, {});
|
|
306
338
|
if (!result.ok) {
|
|
307
339
|
throw new ApiError(result.error, result.status, result.raw);
|
|
308
340
|
}
|
|
341
|
+
const detail = normalizeRecord(result.content);
|
|
342
|
+
if (Array.isArray(detail)) {
|
|
343
|
+
for (const it of detail) {
|
|
344
|
+
if (it && typeof it === "object") {
|
|
345
|
+
stripMcpFields(it, { dbname, isDetail: true });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else if (detail && typeof detail === "object") {
|
|
350
|
+
stripMcpFields(detail, { dbname, isDetail: true });
|
|
351
|
+
}
|
|
309
352
|
return {
|
|
310
353
|
id,
|
|
311
|
-
detail
|
|
354
|
+
detail,
|
|
312
355
|
};
|
|
313
356
|
}
|
|
314
357
|
export async function fetchFacets(opts) {
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { clampLimit, clampOffset, fetchDetail, fetchFacets, listSearch, mcpDbDetail, mcpDbSearch, prefersMcpListApi, yaohaiPost, } from "./api.js";
|
|
5
|
+
import { clampLimit, clampOffset, enforceRetrievalWindow, fetchDetail, fetchFacets, listSearch, mcpDbDetail, mcpDbSearch, prefersMcpListApi, yaohaiPost, } from "./api.js";
|
|
6
6
|
import { ATC_HINT, PRODUCT_CN_COMMON_FIELDS, PRODUCT_CN_DETAIL_PATH, PRODUCT_CN_FACET_FIELDS, PRODUCT_CN_FACET_PREFIX, PRODUCT_CN_VIEW_TYPES, REG_CN_COMMON_FIELDS, REG_CN_DETAIL_PATH, REG_CN_FACET_FIELDS, REG_CN_FACET_PREFIX, REG_CN_VIEW_TYPES, applyProductCnDefaults, applyRegCnDefaults, productCnSearchPath, regCnSearchPath, } from "./fields.js";
|
|
7
7
|
import { EmptyObjectSchema, ProductCnDetailSchema, ProductCnFacetsSchema, ProductCnSearchSchema, RegCnDetailSchema, RegCnFacetsSchema, RegCnSearchSchema, YaohaiCatalogSchema, YaohaiDetailSchema, YaohaiFacetsSchema, YaohaiGlobalSearchSchema, YaohaiSearchSchema, } from "./types.js";
|
|
8
8
|
import { DBS_FACET_CATALOG } from "./dbs-facets.js";
|
|
9
9
|
import { checkForUpdate, formatUpdateMessage, } from "./update-check.js";
|
|
10
|
-
const PACKAGE_VERSION = "0.
|
|
10
|
+
const PACKAGE_VERSION = "0.8.0";
|
|
11
11
|
const YAOHAI_LIMIT_MAX = 50;
|
|
12
12
|
const YAOHAI_LIMIT_DEFAULT = 10;
|
|
13
13
|
const CN_LIMIT_MAX = 100;
|
|
@@ -18,6 +18,7 @@ const QUERY_PROP = {
|
|
|
18
18
|
description: "Search/filter fields. Values may be string, number, or string[] (repeat the key for ConditionSearch multiple). Date: \"YYYY-MM-DD to YYYY-MM-DD\". Range: \"min to max\".",
|
|
19
19
|
};
|
|
20
20
|
const PRESENTATION_HINT = "If total > 20, summarize in chat (about 5–10 sample rows) instead of dumping the full table. Include frontend source links when present.";
|
|
21
|
+
const RETRIEVAL_CAP_HINT = "One distinct query condition can return at most 1000 rows total (offset+limit window cap, anti-scraping): paginate within that window, or narrow the filters (date / province / ATC / enterprise) to reach deeper slices — a too-large offset is rejected.";
|
|
21
22
|
const ROUTING_HINT = "Already-marketed China products (国药准字, 批准文号, 上市, 医保/集采) → product-cn-* tools. R&D / CDE pipeline (在研, 受理号, 审评, 尚未上市) → reg-cn-* tools. Other DBs (医保 yibao, 基药 jiyao, 集采 jicai, trials, global) → yaohai-*. Do not use yaohai-search with dbname product_cn or reg_cn when the dedicated tools apply.";
|
|
22
23
|
/**
|
|
23
24
|
* Derived from the generated facet catalog so the tool descriptions can never
|
|
@@ -74,6 +75,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
74
75
|
{
|
|
75
76
|
name: "yaohai-search",
|
|
76
77
|
description: "Search a single Yaohai database by dbname (from yaohai-catalog). Default limit 10, max 50. " +
|
|
78
|
+
RETRIEVAL_CAP_HINT +
|
|
79
|
+
" " +
|
|
77
80
|
"Use query fields from the catalog's search_fields. " +
|
|
78
81
|
ROUTING_HINT +
|
|
79
82
|
" " +
|
|
@@ -90,7 +93,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
90
93
|
type: "number",
|
|
91
94
|
description: `Row cap (default ${YAOHAI_LIMIT_DEFAULT}, max ${YAOHAI_LIMIT_MAX})`,
|
|
92
95
|
},
|
|
93
|
-
offset: { type: "number", description: "Pagination offset (default 0)" },
|
|
96
|
+
offset: { type: "number", description: "Pagination offset (default 0); offset+limit is capped at 1000 rows per query condition" },
|
|
94
97
|
},
|
|
95
98
|
required: ["dbname"],
|
|
96
99
|
},
|
|
@@ -134,6 +137,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
134
137
|
{
|
|
135
138
|
name: "yaohai-global-search",
|
|
136
139
|
description: "Global drug panorama search (global_search). Pass q as the search term, or query.term. " +
|
|
140
|
+
RETRIEVAL_CAP_HINT +
|
|
141
|
+
" " +
|
|
137
142
|
PRESENTATION_HINT,
|
|
138
143
|
inputSchema: {
|
|
139
144
|
type: "object",
|
|
@@ -144,7 +149,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
144
149
|
type: "number",
|
|
145
150
|
description: `Row cap (default ${YAOHAI_LIMIT_DEFAULT}, max ${YAOHAI_LIMIT_MAX})`,
|
|
146
151
|
},
|
|
147
|
-
offset: { type: "number", description: "Pagination offset (default 0)" },
|
|
152
|
+
offset: { type: "number", description: "Pagination offset (default 0); offset+limit is capped at 1000 rows per query condition" },
|
|
148
153
|
},
|
|
149
154
|
},
|
|
150
155
|
},
|
|
@@ -159,6 +164,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
159
164
|
"Default search_mode=3 (partial). first_approve_date = first listing date; approve_date = latest re-registration (not first listing). " +
|
|
160
165
|
ATC_HINT +
|
|
161
166
|
" Default limit 20, max 100. " +
|
|
167
|
+
RETRIEVAL_CAP_HINT +
|
|
168
|
+
" " +
|
|
162
169
|
"Not for R&D pipeline — use reg-cn-search. " +
|
|
163
170
|
PRESENTATION_HINT,
|
|
164
171
|
inputSchema: {
|
|
@@ -173,7 +180,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
173
180
|
type: "number",
|
|
174
181
|
description: `Row cap (default ${CN_LIMIT_DEFAULT}, max ${CN_LIMIT_MAX})`,
|
|
175
182
|
},
|
|
176
|
-
offset: { type: "number", description: "Pagination offset (default 0)" },
|
|
183
|
+
offset: { type: "number", description: "Pagination offset (default 0); offset+limit is capped at 1000 rows per query condition" },
|
|
177
184
|
view_type: {
|
|
178
185
|
type: "string",
|
|
179
186
|
enum: [...PRODUCT_CN_VIEW_TYPES],
|
|
@@ -221,7 +228,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
221
228
|
description: "Search China drug registration / CDE review (reg_cn): 在研, 受理号, 申报, 审评进度, not-yet-listed. " +
|
|
222
229
|
"Default rows_excluded=1 (drop 备案), search_mode=1. " +
|
|
223
230
|
ATC_HINT +
|
|
224
|
-
" Default limit 20, max 100.
|
|
231
|
+
" Default limit 20, max 100. " +
|
|
232
|
+
RETRIEVAL_CAP_HINT +
|
|
233
|
+
" " +
|
|
234
|
+
"Not for already-marketed products — use product-cn-search. " +
|
|
225
235
|
PRESENTATION_HINT,
|
|
226
236
|
inputSchema: {
|
|
227
237
|
type: "object",
|
|
@@ -235,7 +245,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
235
245
|
type: "number",
|
|
236
246
|
description: `Row cap (default ${CN_LIMIT_DEFAULT}, max ${CN_LIMIT_MAX})`,
|
|
237
247
|
},
|
|
238
|
-
offset: { type: "number", description: "Pagination offset (default 0)" },
|
|
248
|
+
offset: { type: "number", description: "Pagination offset (default 0); offset+limit is capped at 1000 rows per query condition" },
|
|
239
249
|
view_type: {
|
|
240
250
|
type: "string",
|
|
241
251
|
enum: [...REG_CN_VIEW_TYPES],
|
|
@@ -319,11 +329,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
319
329
|
}
|
|
320
330
|
case "yaohai-search": {
|
|
321
331
|
const validated = YaohaiSearchSchema.parse(args);
|
|
332
|
+
const window = enforceRetrievalWindow(clampLimit(validated.limit, YAOHAI_LIMIT_DEFAULT, YAOHAI_LIMIT_MAX), clampOffset(validated.offset));
|
|
322
333
|
const content = await yaohaiPost("/g/mcp/yaohai/search", {
|
|
323
334
|
dbname: validated.dbname,
|
|
324
335
|
query: asQuery(validated.query),
|
|
325
|
-
limit:
|
|
326
|
-
offset:
|
|
336
|
+
limit: window.limit,
|
|
337
|
+
offset: window.offset,
|
|
327
338
|
});
|
|
328
339
|
return ok(content);
|
|
329
340
|
}
|
|
@@ -406,10 +417,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
406
417
|
if (validated.q && (query.term === undefined || query.term === "")) {
|
|
407
418
|
query.term = validated.q;
|
|
408
419
|
}
|
|
420
|
+
const window = enforceRetrievalWindow(clampLimit(validated.limit, YAOHAI_LIMIT_DEFAULT, YAOHAI_LIMIT_MAX), clampOffset(validated.offset));
|
|
409
421
|
const content = await yaohaiPost("/g/mcp/yaohai/global-search", {
|
|
410
422
|
query,
|
|
411
|
-
limit:
|
|
412
|
-
offset:
|
|
423
|
+
limit: window.limit,
|
|
424
|
+
offset: window.offset,
|
|
413
425
|
});
|
|
414
426
|
return ok(content);
|
|
415
427
|
}
|
|
@@ -425,8 +437,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
425
437
|
const validated = ProductCnSearchSchema.parse(args ?? {});
|
|
426
438
|
const viewType = validated.view_type ?? "eslist";
|
|
427
439
|
const query = applyProductCnDefaults(asQuery(validated.query));
|
|
428
|
-
const
|
|
429
|
-
const
|
|
440
|
+
const window = enforceRetrievalWindow(clampLimit(validated.limit, CN_LIMIT_DEFAULT, CN_LIMIT_MAX), clampOffset(validated.offset));
|
|
441
|
+
const limit = window.limit;
|
|
442
|
+
const offset = window.offset;
|
|
430
443
|
const content = prefersMcpListApi()
|
|
431
444
|
? await mcpDbSearch({
|
|
432
445
|
dbname: "product_cn",
|
|
@@ -441,6 +454,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
441
454
|
limit,
|
|
442
455
|
offset,
|
|
443
456
|
viewType,
|
|
457
|
+
dbname: "product_cn",
|
|
444
458
|
});
|
|
445
459
|
return ok(content);
|
|
446
460
|
}
|
|
@@ -459,7 +473,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
459
473
|
const validated = ProductCnDetailSchema.parse(args);
|
|
460
474
|
const content = prefersMcpListApi()
|
|
461
475
|
? await mcpDbDetail("product_cn", validated.id)
|
|
462
|
-
: await fetchDetail(`${PRODUCT_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id);
|
|
476
|
+
: await fetchDetail(`${PRODUCT_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id, "product_cn");
|
|
463
477
|
return ok(content);
|
|
464
478
|
}
|
|
465
479
|
case "reg-cn-fields": {
|
|
@@ -474,8 +488,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
474
488
|
const validated = RegCnSearchSchema.parse(args ?? {});
|
|
475
489
|
const viewType = validated.view_type ?? "eslist";
|
|
476
490
|
const query = applyRegCnDefaults(asQuery(validated.query));
|
|
477
|
-
const
|
|
478
|
-
const
|
|
491
|
+
const window = enforceRetrievalWindow(clampLimit(validated.limit, CN_LIMIT_DEFAULT, CN_LIMIT_MAX), clampOffset(validated.offset));
|
|
492
|
+
const limit = window.limit;
|
|
493
|
+
const offset = window.offset;
|
|
479
494
|
const content = prefersMcpListApi()
|
|
480
495
|
? await mcpDbSearch({
|
|
481
496
|
dbname: "reg_cn",
|
|
@@ -490,6 +505,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
490
505
|
limit,
|
|
491
506
|
offset,
|
|
492
507
|
viewType,
|
|
508
|
+
dbname: "reg_cn",
|
|
493
509
|
});
|
|
494
510
|
return ok(content);
|
|
495
511
|
}
|
|
@@ -508,7 +524,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
508
524
|
const validated = RegCnDetailSchema.parse(args);
|
|
509
525
|
const content = prefersMcpListApi()
|
|
510
526
|
? await mcpDbDetail("reg_cn", validated.id)
|
|
511
|
-
: await fetchDetail(`${REG_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id);
|
|
527
|
+
: await fetchDetail(`${REG_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id, "reg_cn");
|
|
512
528
|
return ok(content);
|
|
513
529
|
}
|
|
514
530
|
default:
|
package/dist/normalize.js
CHANGED
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
/** Flatten DrugSea UI cell objects `{ text, title, url }` into plain values. */
|
|
2
|
+
/**
|
|
3
|
+
* Strip MCP-irrelevant fields from result rows (lists and details alike).
|
|
4
|
+
* These are internal join keys, ETL timestamps, request-echo flags, and the
|
|
5
|
+
* `related_drug_names` synonym dump (100+ pipe-separated aliases) — they cost
|
|
6
|
+
* tokens but carry no signal for an agent. Mirrors `yaohai_mcp_strip_fields`
|
|
7
|
+
* in the drugsea_api backend (`src/routes/ai_mcp/mcp.php`); the two layers are
|
|
8
|
+
* idempotent with each other.
|
|
9
|
+
*/
|
|
10
|
+
const STRIP_ALWAYS = [
|
|
11
|
+
"related_drug_names",
|
|
12
|
+
// internal ids / join keys
|
|
13
|
+
"dp2_id", "gcid", "ProductID", "XUI", "DrugUID", "UniqueID", "es_index_key", "company_for_count",
|
|
14
|
+
// maintenance timestamps
|
|
15
|
+
"created_at", "updated_at", "in_sfda_updated_at",
|
|
16
|
+
// request-echo / internal flags
|
|
17
|
+
"rows_excluded", "has_new_data", "has_sms", "is_47", "linked_to_xui", "is_filing_ref_drug", "2018yizhi", "has_detail",
|
|
18
|
+
// raw change-log dump in product detail (semicolon-delimited ETL diffs)
|
|
19
|
+
"timeline",
|
|
20
|
+
];
|
|
21
|
+
/** Stale index column that contradicts is_guojia_jicai (skill docs: ignore it). */
|
|
22
|
+
const STRIP_PRODUCT_CN = ["is_jicai"];
|
|
23
|
+
/** Backup / duplicate copies of conclusion / transact_status / slh. */
|
|
24
|
+
const STRIP_REG_CN = ["conclusion_bak", "orig_transact_status", "slh2"];
|
|
25
|
+
/** Labelled insert text: useless HTML blobs in list rows, informative in detail. */
|
|
26
|
+
const STRIP_PRODUCT_CN_LIST_ONLY = [
|
|
27
|
+
"indications",
|
|
28
|
+
"dosage_and_administration",
|
|
29
|
+
"pharmacological_and_toxicological",
|
|
30
|
+
];
|
|
31
|
+
export function stripMcpFields(row, opts = {}) {
|
|
32
|
+
const { dbname = "", isDetail = false } = opts;
|
|
33
|
+
const strip = new Set(STRIP_ALWAYS);
|
|
34
|
+
if (dbname === "product_cn") {
|
|
35
|
+
for (const f of STRIP_PRODUCT_CN)
|
|
36
|
+
strip.add(f);
|
|
37
|
+
if (!isDetail) {
|
|
38
|
+
for (const f of STRIP_PRODUCT_CN_LIST_ONLY)
|
|
39
|
+
strip.add(f);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (dbname === "reg_cn") {
|
|
43
|
+
for (const f of STRIP_REG_CN)
|
|
44
|
+
strip.add(f);
|
|
45
|
+
}
|
|
46
|
+
for (const f of strip) {
|
|
47
|
+
delete row[f];
|
|
48
|
+
}
|
|
49
|
+
return row;
|
|
50
|
+
}
|
|
2
51
|
function isPlainObject(v) {
|
|
3
52
|
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
4
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kinginsun/mcp-drugsea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "MCP server for DrugSea / Yaohai pharmaceutical databases: cross-db search, China marketed products (product_cn), and CDE registration review (reg_cn).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|