@kinginsun/mcp-drugsea 0.7.0 → 0.7.1
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/dist/api.js +37 -7
- package/dist/index.js +5 -3
- package/dist/normalize.js +49 -0
- package/package.json +1 -1
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 ??
|
|
@@ -239,11 +239,16 @@ export async function mcpDbSearch(opts) {
|
|
|
239
239
|
if (item && typeof item === "object" && "fields" in item) {
|
|
240
240
|
const row = item;
|
|
241
241
|
const flat = normalizeRecord(row.fields);
|
|
242
|
-
if (
|
|
243
|
-
flat &&
|
|
242
|
+
if (flat &&
|
|
244
243
|
typeof flat === "object" &&
|
|
245
244
|
!Array.isArray(flat)) {
|
|
246
|
-
|
|
245
|
+
const stripped = stripMcpFields(flat, {
|
|
246
|
+
dbname: opts.dbname,
|
|
247
|
+
});
|
|
248
|
+
if (row.detail_url) {
|
|
249
|
+
return { ...stripped, detail_url: row.detail_url };
|
|
250
|
+
}
|
|
251
|
+
return stripped;
|
|
247
252
|
}
|
|
248
253
|
return flat;
|
|
249
254
|
}
|
|
@@ -270,7 +275,14 @@ export async function mcpDbDetail(dbname, id) {
|
|
|
270
275
|
dbname,
|
|
271
276
|
id,
|
|
272
277
|
}));
|
|
273
|
-
|
|
278
|
+
const detail = Array.isArray(content.detail)
|
|
279
|
+
? content.detail.map((row) => row && typeof row === "object"
|
|
280
|
+
? stripMcpFields(row, { dbname, isDetail: true })
|
|
281
|
+
: row)
|
|
282
|
+
: content.detail && typeof content.detail === "object"
|
|
283
|
+
? stripMcpFields(content.detail, { dbname, isDetail: true })
|
|
284
|
+
: content.detail;
|
|
285
|
+
return { dbname, id, ...content, detail };
|
|
274
286
|
}
|
|
275
287
|
export async function listSearch(opts) {
|
|
276
288
|
const params = {
|
|
@@ -285,6 +297,13 @@ export async function listSearch(opts) {
|
|
|
285
297
|
const raw = result.raw;
|
|
286
298
|
const content = raw.content ?? result.content;
|
|
287
299
|
const items = normalizeRecord(content);
|
|
300
|
+
if (Array.isArray(items)) {
|
|
301
|
+
for (const it of items) {
|
|
302
|
+
if (it && typeof it === "object") {
|
|
303
|
+
stripMcpFields(it, { dbname: opts.dbname });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
288
307
|
const totalRaw = raw.tnum;
|
|
289
308
|
const total = typeof totalRaw === "number"
|
|
290
309
|
? totalRaw
|
|
@@ -301,14 +320,25 @@ export async function listSearch(opts) {
|
|
|
301
320
|
query_applied: opts.query,
|
|
302
321
|
};
|
|
303
322
|
}
|
|
304
|
-
export async function fetchDetail(path, id) {
|
|
323
|
+
export async function fetchDetail(path, id, dbname = "") {
|
|
305
324
|
const result = await yaohaiGet(path, {});
|
|
306
325
|
if (!result.ok) {
|
|
307
326
|
throw new ApiError(result.error, result.status, result.raw);
|
|
308
327
|
}
|
|
328
|
+
const detail = normalizeRecord(result.content);
|
|
329
|
+
if (Array.isArray(detail)) {
|
|
330
|
+
for (const it of detail) {
|
|
331
|
+
if (it && typeof it === "object") {
|
|
332
|
+
stripMcpFields(it, { dbname, isDetail: true });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else if (detail && typeof detail === "object") {
|
|
337
|
+
stripMcpFields(detail, { dbname, isDetail: true });
|
|
338
|
+
}
|
|
309
339
|
return {
|
|
310
340
|
id,
|
|
311
|
-
detail
|
|
341
|
+
detail,
|
|
312
342
|
};
|
|
313
343
|
}
|
|
314
344
|
export async function fetchFacets(opts) {
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ATC_HINT, PRODUCT_CN_COMMON_FIELDS, PRODUCT_CN_DETAIL_PATH, PRODUCT_CN_
|
|
|
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.7.
|
|
10
|
+
const PACKAGE_VERSION = "0.7.1";
|
|
11
11
|
const YAOHAI_LIMIT_MAX = 50;
|
|
12
12
|
const YAOHAI_LIMIT_DEFAULT = 10;
|
|
13
13
|
const CN_LIMIT_MAX = 100;
|
|
@@ -441,6 +441,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
441
441
|
limit,
|
|
442
442
|
offset,
|
|
443
443
|
viewType,
|
|
444
|
+
dbname: "product_cn",
|
|
444
445
|
});
|
|
445
446
|
return ok(content);
|
|
446
447
|
}
|
|
@@ -459,7 +460,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
459
460
|
const validated = ProductCnDetailSchema.parse(args);
|
|
460
461
|
const content = prefersMcpListApi()
|
|
461
462
|
? await mcpDbDetail("product_cn", validated.id)
|
|
462
|
-
: await fetchDetail(`${PRODUCT_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id);
|
|
463
|
+
: await fetchDetail(`${PRODUCT_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id, "product_cn");
|
|
463
464
|
return ok(content);
|
|
464
465
|
}
|
|
465
466
|
case "reg-cn-fields": {
|
|
@@ -490,6 +491,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
490
491
|
limit,
|
|
491
492
|
offset,
|
|
492
493
|
viewType,
|
|
494
|
+
dbname: "reg_cn",
|
|
493
495
|
});
|
|
494
496
|
return ok(content);
|
|
495
497
|
}
|
|
@@ -508,7 +510,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
508
510
|
const validated = RegCnDetailSchema.parse(args);
|
|
509
511
|
const content = prefersMcpListApi()
|
|
510
512
|
? await mcpDbDetail("reg_cn", validated.id)
|
|
511
|
-
: await fetchDetail(`${REG_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id);
|
|
513
|
+
: await fetchDetail(`${REG_CN_DETAIL_PATH}/${encodeId(validated.id)}`, validated.id, "reg_cn");
|
|
512
514
|
return ok(content);
|
|
513
515
|
}
|
|
514
516
|
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.7.
|
|
3
|
+
"version": "0.7.1",
|
|
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",
|