@cyanheads/pubchem-mcp-server 0.4.3 → 0.5.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.
Files changed (34) hide show
  1. package/README.md +5 -4
  2. package/dist/mcp-server/resources/definitions/compound-bioactivity.resource.js +1 -1
  3. package/dist/mcp-server/resources/definitions/compound-bioactivity.resource.js.map +1 -1
  4. package/dist/mcp-server/resources/definitions/compound-xrefs.resource.js +1 -1
  5. package/dist/mcp-server/resources/definitions/compound-xrefs.resource.js.map +1 -1
  6. package/dist/mcp-server/tools/definitions/get-bioactivity.tool.d.ts +3 -0
  7. package/dist/mcp-server/tools/definitions/get-bioactivity.tool.d.ts.map +1 -1
  8. package/dist/mcp-server/tools/definitions/get-bioactivity.tool.js +41 -13
  9. package/dist/mcp-server/tools/definitions/get-bioactivity.tool.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/get-compound-details.tool.d.ts +11 -1
  11. package/dist/mcp-server/tools/definitions/get-compound-details.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/get-compound-details.tool.js +147 -21
  13. package/dist/mcp-server/tools/definitions/get-compound-details.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/get-compound-xrefs.tool.d.ts +3 -0
  15. package/dist/mcp-server/tools/definitions/get-compound-xrefs.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/get-compound-xrefs.tool.js +60 -16
  17. package/dist/mcp-server/tools/definitions/get-compound-xrefs.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/index.d.ts +22 -2
  19. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/search-assays.tool.d.ts +3 -0
  21. package/dist/mcp-server/tools/definitions/search-assays.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/search-assays.tool.js +41 -16
  23. package/dist/mcp-server/tools/definitions/search-assays.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/search-compounds.tool.d.ts +2 -1
  25. package/dist/mcp-server/tools/definitions/search-compounds.tool.d.ts.map +1 -1
  26. package/dist/mcp-server/tools/definitions/search-compounds.tool.js +46 -18
  27. package/dist/mcp-server/tools/definitions/search-compounds.tool.js.map +1 -1
  28. package/dist/services/pubchem/pubchem-client.d.ts +26 -4
  29. package/dist/services/pubchem/pubchem-client.d.ts.map +1 -1
  30. package/dist/services/pubchem/pubchem-client.js +43 -15
  31. package/dist/services/pubchem/pubchem-client.js.map +1 -1
  32. package/manifest.json +1 -1
  33. package/package.json +1 -1
  34. package/server.json +3 -3
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.4.3-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/pubchem-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/pubchem-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/pubchem-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-^1.3.0-f472b6.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.5.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/pubchem-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/pubchem-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/pubchem-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-^1.3.0-f472b6.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -62,8 +62,9 @@ Get detailed compound information by CID.
62
62
 
63
63
  - Batches up to 100 CIDs in a single request
64
64
  - 27 available properties: molecular weight, SMILES, InChIKey, XLogP, TPSA, complexity, stereo counts, and more
65
- - Optionally includes textual descriptions (pharmacology, mechanism, therapeutic use) from PUG View
66
- - Optionally includes all known synonyms (trade names, systematic names, registry numbers)
65
+ - Optionally includes textual descriptions (pharmacology, mechanism, therapeutic use) from PUG View — fetched for the first 10 CIDs of a batch, with the skipped CIDs named in the response
66
+ - Optionally includes known synonyms (trade names, systematic names, registry numbers)
67
+ - Synonyms and descriptions are paged: `synonymOffset` and `descriptionOffset` window every compound in the batch at the same position, reaching the entries past a page
67
68
  - Optionally computes drug-likeness assessment (Lipinski Rule of Five + Veber rules) from fetched properties
68
69
  - Optionally fetches pharmacological classification (FDA classes, mechanisms of action, MeSH classes, ATC codes)
69
70
 
@@ -75,7 +76,7 @@ Get a compound's bioactivity profile from PubChem BioAssay.
75
76
 
76
77
  - Returns assay outcomes (Active/Inactive/Inconclusive), target info (protein accessions, NCBI Gene IDs), and quantitative values (IC50, EC50, Ki)
77
78
  - Filter by outcome and/or a specific molecular target (NCBI Gene ID or protein accession)
78
- - Caps at 100 results per request (well-studied compounds may have thousands)
79
+ - Caps at 100 results per page; `offset` reaches the rest (well-studied compounds may have thousands)
79
80
 
80
81
  ---
81
82
 
@@ -8,7 +8,7 @@ import { getPubChemClient } from '../../../services/pubchem/pubchem-client.js';
8
8
  const MAX_RESULTS = 25;
9
9
  export const compoundBioactivityResource = resource('pubchem://compound/{cid}/bioactivity', {
10
10
  name: 'pubchem-compound-bioactivity',
11
- description: 'Bioassay activity profile for a PubChem compound by CID. Returns up to 25 assays; use pubchem_get_bioactivity to filter by outcome or molecular target and to raise the cap.',
11
+ description: 'Bioassay activity profile for a PubChem compound by CID. Returns up to 25 assays; use pubchem_get_bioactivity to filter by outcome or molecular target, raise the cap, and page through the rest with offset.',
12
12
  mimeType: 'application/json',
13
13
  params: z.object({
14
14
  cid: z.coerce.number().int().positive().describe('PubChem Compound ID.'),
@@ -1 +1 @@
1
- {"version":3,"file":"compound-bioactivity.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/compound-bioactivity.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAExE,gGAAgG;AAChG,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,MAAM,CAAC,MAAM,2BAA2B,GAAG,QAAQ,CAAC,sCAAsC,EAAE;IAC1F,IAAI,EAAE,8BAA8B;IACpC,WAAW,EACT,8KAA8K;IAChL,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;KACzE,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,MAAM;QAClB,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;QACtE,OAAO;YACL,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,WAAW,EAAE,IAAI,CAAC,MAAM;YACxB,WAAW;YACX,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC;SACpC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"compound-bioactivity.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/compound-bioactivity.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAExE,gGAAgG;AAChG,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,MAAM,CAAC,MAAM,2BAA2B,GAAG,QAAQ,CAAC,sCAAsC,EAAE;IAC1F,IAAI,EAAE,8BAA8B;IACpC,WAAW,EACT,+MAA+M;IACjN,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;KACzE,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,MAAM;QAClB,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;QACtE,OAAO;YACL,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,WAAW,EAAE,IAAI,CAAC,MAAM;YACxB,WAAW;YACX,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC;SACpC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -9,7 +9,7 @@ const RESOURCE_XREF_TYPES = ['RN', 'RegistryID', 'PubMedID'];
9
9
  const MAX_PER_TYPE = 25;
10
10
  export const compoundXrefsResource = resource('pubchem://compound/{cid}/xrefs', {
11
11
  name: 'pubchem-compound-xrefs',
12
- description: 'External cross-references (CAS RN, registry IDs, PubMed) for a PubChem compound by CID, up to 25 per type. Use pubchem_get_compound_xrefs for the full set of xref types and a higher per-type cap.',
12
+ description: 'External cross-references (CAS RN, registry IDs, PubMed) for a PubChem compound by CID, up to 25 per type. Use pubchem_get_compound_xrefs for the full set of xref types, a higher per-type cap, and to page through the rest with offset.',
13
13
  mimeType: 'application/json',
14
14
  params: z.object({
15
15
  cid: z.coerce.number().int().positive().describe('PubChem Compound ID.'),
@@ -1 +1 @@
1
- {"version":3,"file":"compound-xrefs.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/compound-xrefs.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAExE,0FAA0F;AAC1F,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,CAAU,CAAC;AACtE,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC,gCAAgC,EAAE;IAC9E,IAAI,EAAE,wBAAwB;IAC9B,WAAW,EACT,qMAAqM;IACvM,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;KACzE,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,MAAM;QAClB,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAClC,MAAM,KAAK,GAA8E,EAAE,CAAC;QAC5F,KAAK,MAAM,IAAI,IAAI,mBAAmB,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACpD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"compound-xrefs.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/compound-xrefs.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAExE,0FAA0F;AAC1F,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,CAAU,CAAC;AACtE,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC,gCAAgC,EAAE;IAC9E,IAAI,EAAE,wBAAwB;IAC9B,WAAW,EACT,4OAA4O;IAC9O,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;KACzE,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,MAAM;QAClB,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAClC,MAAM,KAAK,GAA8E,EAAE,CAAC;QAC5F,KAAK,MAAM,IAAI,IAAI,mBAAmB,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACpD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;CACF,CAAC,CAAC"}
@@ -13,6 +13,7 @@ export declare const getBioactivity: import("@cyanheads/mcp-ts-core").ToolDefini
13
13
  }>>;
14
14
  targetGeneId: z.ZodOptional<z.ZodNumber>;
15
15
  targetAccession: z.ZodOptional<z.ZodString>;
16
+ offset: z.ZodDefault<z.ZodNumber>;
16
17
  maxResults: z.ZodDefault<z.ZodNumber>;
17
18
  }, z.core.$strip>, z.ZodObject<{
18
19
  cid: z.ZodNumber;
@@ -36,6 +37,8 @@ export declare const getBioactivity: import("@cyanheads/mcp-ts-core").ToolDefini
36
37
  readonly targetFilter: z.ZodOptional<z.ZodString>;
37
38
  readonly filteredCount: z.ZodNumber;
38
39
  readonly returnedCount: z.ZodNumber;
40
+ readonly offset: z.ZodNumber;
41
+ readonly nextOffset: z.ZodOptional<z.ZodNumber>;
39
42
  readonly truncated: z.ZodOptional<z.ZodBoolean>;
40
43
  readonly shown: z.ZodOptional<z.ZodNumber>;
41
44
  readonly cap: z.ZodOptional<z.ZodNumber>;
@@ -1 +1 @@
1
- {"version":3,"file":"get-bioactivity.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-bioactivity.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAIjD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkNzB,CAAC"}
1
+ {"version":3,"file":"get-bioactivity.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-bioactivity.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAIjD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoPzB,CAAC"}
@@ -34,12 +34,18 @@ export const getBioactivity = tool('pubchem_get_bioactivity', {
34
34
  .string()
35
35
  .optional()
36
36
  .describe('Filter to assays against this target protein accession (UniProt/GenBank), e.g. "P35354". Obtain accessions from pubchem_search_assays or the targetAccession field of an unfiltered result here.'),
37
+ offset: z
38
+ .number()
39
+ .int()
40
+ .min(0)
41
+ .default(0)
42
+ .describe('Zero-based index of the first assay to return, applied after the outcome and target filters. Pass the nextOffset from a previous call to read the following page. Default: 0.'),
37
43
  maxResults: z
38
44
  .number()
39
45
  .min(1)
40
46
  .max(100)
41
47
  .default(20)
42
- .describe('Max assay results to return (1-100). Well-studied compounds have thousands of records. Default: 20.'),
48
+ .describe('Max assay results to return per page (1-100). Well-studied compounds have thousands of records; use offset to reach the ones past this page. Default: 20.'),
43
49
  }),
44
50
  output: z.object({
45
51
  cid: z.number().describe('PubChem Compound ID.'),
@@ -78,9 +84,14 @@ export const getBioactivity = tool('pubchem_get_bioactivity', {
78
84
  .describe('Assay result entry.'))
79
85
  .describe('Assay results matching the filter.'),
80
86
  }),
81
- // Agent-facing context — filter echo, the filtered/returned cap boundary, and a notice
82
- // distinguishing "no data" from "filter excluded everything". Reaches structuredContent
83
- // and content[]; keys disjoint from output (cid/totalAssays live there).
87
+ // Agent-facing context — filter echo, the page boundary, and a notice distinguishing
88
+ // "no data" from "filter excluded everything". Reaches structuredContent and content[];
89
+ // keys disjoint from output (cid/totalAssays live there).
90
+ //
91
+ // Bounded-total convention: a reported total states only what the server actually
92
+ // observed, and pairs with an `…AtLeast` floor when the exact figure is unknowable.
93
+ // Every count here is exact and needs no such twin — PubChem's assay summary has no
94
+ // pagination of its own, so the full table is in hand before any filter runs.
84
95
  enrichment: {
85
96
  outcomeFilter: z.string().describe('Outcome filter applied: active, inactive, or all.'),
86
97
  targetFilter: z
@@ -89,13 +100,15 @@ export const getBioactivity = tool('pubchem_get_bioactivity', {
89
100
  .describe('Target filter applied (gene ID and/or protein accession), when set.'),
90
101
  filteredCount: z
91
102
  .number()
92
- .describe('Assays matching the outcome and target filters, before the maxResults cap.'),
93
- returnedCount: z.number().describe('Assays returned after the maxResults cap.'),
94
- truncated: z
95
- .boolean()
103
+ .describe('Exact number of assays matching the outcome and target filters, across all pages.'),
104
+ returnedCount: z.number().describe('Assays returned on this page.'),
105
+ offset: z.number().describe('Zero-based index of the first assay returned.'),
106
+ nextOffset: z
107
+ .number()
96
108
  .optional()
97
- .describe('True when results were capped at maxResults more matching assays exist.'),
98
- shown: z.number().optional().describe('Assays returned after the maxResults cap.'),
109
+ .describe('Offset to pass on the next call to continue past this page. Omitted when no further assays match.'),
110
+ truncated: z.boolean().optional().describe('True when matching assays remain past this page.'),
111
+ shown: z.number().optional().describe('Assays returned on this page.'),
99
112
  cap: z.number().optional().describe('The maxResults cap that was applied.'),
100
113
  notice: z
101
114
  .string()
@@ -128,21 +141,29 @@ export const getBioactivity = tool('pubchem_get_bioactivity', {
128
141
  if (input.targetAccession) {
129
142
  filtered = filtered.filter((r) => r.targetAccession === input.targetAccession);
130
143
  }
131
- const results = filtered.slice(0, input.maxResults);
144
+ // Offset is applied client-side: PubChem's assay summary endpoint accepts no
145
+ // offset/start/limit/page parameter, so getAssaySummary already holds every row.
146
+ const results = filtered.slice(input.offset, input.offset + input.maxResults);
147
+ const nextOffset = input.offset + results.length;
148
+ const hasMore = nextOffset < filtered.length;
132
149
  ctx.log.info('Bioactivity fetched', {
133
150
  cid: input.cid,
134
151
  total: allRows.length,
135
152
  active: activeCount,
136
153
  filtered: filtered.length,
154
+ offset: input.offset,
137
155
  returned: results.length,
138
156
  });
139
157
  ctx.enrich({
140
158
  outcomeFilter: input.outcomeFilter,
141
159
  filteredCount: filtered.length,
142
160
  returnedCount: results.length,
161
+ offset: input.offset,
143
162
  });
144
163
  if (targetLabel)
145
164
  ctx.enrich({ targetFilter: targetLabel });
165
+ if (hasMore)
166
+ ctx.enrich({ nextOffset });
146
167
  if (allRows.length === 0) {
147
168
  ctx.enrich.notice(`No bioactivity data found for CID ${input.cid}. The compound may be uncharacterized, or verify the CID with pubchem_search_compounds.`);
148
169
  }
@@ -151,8 +172,15 @@ export const getBioactivity = tool('pubchem_get_bioactivity', {
151
172
  ? `CID ${input.cid} has ${allRows.length} assay(s) but none match the target filter (${targetLabel})${input.outcomeFilter !== 'all' ? ` with outcomeFilter="${input.outcomeFilter}"` : ''}. Verify the target identifier appears in this compound's assays, or widen the filter.`
152
173
  : `CID ${input.cid} has ${allRows.length} assay(s) but none match outcomeFilter="${input.outcomeFilter}". Use outcomeFilter="all" to see them.`);
153
174
  }
154
- else if (filtered.length > results.length) {
155
- ctx.enrich.truncated({ shown: results.length, cap: input.maxResults });
175
+ else if (results.length === 0) {
176
+ ctx.enrich.notice(`offset ${input.offset} is past the ${filtered.length} assay(s) matching the filter. Pass an offset below ${filtered.length}.`);
177
+ }
178
+ else if (hasMore) {
179
+ ctx.enrich.truncated({
180
+ shown: results.length,
181
+ cap: input.maxResults,
182
+ guidance: `Showing assays ${input.offset + 1}-${nextOffset} of ${filtered.length}. Pass offset=${nextOffset} for the next page.`,
183
+ });
156
184
  }
157
185
  return {
158
186
  cid: input.cid,
@@ -1 +1 @@
1
- {"version":3,"file":"get-bioactivity.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-bioactivity.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,yBAAyB,EAAE;IAC5D,KAAK,EAAE,iBAAiB;IACxB,WAAW,EACT,kYAAkY;IACpY,WAAW,EAAE;QACX,YAAY,EAAE,IAAI;QAClB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;KACpB;IACD,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,GAAG,EAAE,CAAC;aACH,MAAM,EAAE;aACR,GAAG,EAAE;aACL,QAAQ,EAAE;aACV,QAAQ,CAAC,8EAA8E,CAAC;QAC3F,aAAa,EAAE,CAAC;aACb,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;aACnC,OAAO,CAAC,KAAK,CAAC;aACd,QAAQ,CACP,+JAA+J,CAChK;QACH,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,GAAG,EAAE;aACL,QAAQ,EAAE;aACV,QAAQ,EAAE;aACV,QAAQ,CACP,2OAA2O,CAC5O;QACH,eAAe,EAAE,CAAC;aACf,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,kMAAkM,CACnM;QACH,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,GAAG,CAAC;aACR,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CACP,qGAAqG,CACtG;KACJ,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QAChD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;QAC1E,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;QACjE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QACrE,OAAO,EAAE,CAAC;aACP,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YAC7C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YACnD,OAAO,EAAE,CAAC;iBACP,MAAM,EAAE;iBACR,QAAQ,CAAC,gEAAgE,CAAC;YAC7E,eAAe,EAAE,CAAC;iBACf,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;YAC1D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;YACpE,cAAc,EAAE,CAAC;iBACd,KAAK,CACJ,CAAC;iBACE,MAAM,CAAC;gBACN,IAAI,EAAE,CAAC;qBACJ,MAAM,EAAE;qBACR,QAAQ,EAAE;qBACV,QAAQ,CACP,oEAAoE,CACrE;gBACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;gBAC7C,IAAI,EAAE,CAAC;qBACJ,MAAM,EAAE;qBACR,QAAQ,EAAE;qBACV,QAAQ,CAAC,+DAA+D,CAAC;aAC7E,CAAC;iBACD,QAAQ,CAAC,0CAA0C,CAAC,CACxD;iBACA,QAAQ,CAAC,qCAAqC,CAAC;SACnD,CAAC;aACD,QAAQ,CAAC,qBAAqB,CAAC,CACnC;aACA,QAAQ,CAAC,oCAAoC,CAAC;KAClD,CAAC;IACF,uFAAuF;IACvF,wFAAwF;IACxF,yEAAyE;IACzE,UAAU,EAAE;QACV,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mDAAmD,CAAC;QACvF,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,qEAAqE,CAAC;QAClF,aAAa,EAAE,CAAC;aACb,MAAM,EAAE;aACR,QAAQ,CAAC,4EAA4E,CAAC;QACzF,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QAC/E,SAAS,EAAE,CAAC;aACT,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,2EAA2E,CAAC;QACxF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QAClF,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;QAC3E,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,8FAA8F,CAC/F;KACJ;IAED,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;QACtB,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAExD,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;QACzE,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;QAE7E,2EAA2E;QAC3E,yFAAyF;QACzF,IAAI,QAAQ,GAAG,OAAO,CAAC;QACvB,IAAI,KAAK,CAAC,aAAa,KAAK,QAAQ,EAAE,CAAC;YACrC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC;QAC5D,CAAC;aAAM,IAAI,KAAK,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;YAC9C,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,WAAW,GAAG;YAClB,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS;YACvE,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,SAAS;SACzE;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,IAAI,KAAK,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;YAC/B,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,KAAK,CAAC,YAAY,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;YAC1B,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAEpD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,qBAAqB,EAAE;YAClC,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,KAAK,EAAE,OAAO,CAAC,MAAM;YACrB,MAAM,EAAE,WAAW;YACnB,QAAQ,EAAE,QAAQ,CAAC,MAAM;YACzB,QAAQ,EAAE,OAAO,CAAC,MAAM;SACzB,CAAC,CAAC;QAEH,GAAG,CAAC,MAAM,CAAC;YACT,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,aAAa,EAAE,QAAQ,CAAC,MAAM;YAC9B,aAAa,EAAE,OAAO,CAAC,MAAM;SAC9B,CAAC,CAAC;QACH,IAAI,WAAW;YAAE,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAC3D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,MAAM,CAAC,MAAM,CACf,qCAAqC,KAAK,CAAC,GAAG,yFAAyF,CACxI,CAAC;QACJ,CAAC;aAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,GAAG,CAAC,MAAM,CAAC,MAAM,CACf,WAAW;gBACT,CAAC,CAAC,OAAO,KAAK,CAAC,GAAG,QAAQ,OAAO,CAAC,MAAM,+CAA+C,WAAW,IAAI,KAAK,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,wBAAwB,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,EAAE,wFAAwF;gBACjR,CAAC,CAAC,OAAO,KAAK,CAAC,GAAG,QAAQ,OAAO,CAAC,MAAM,2CAA2C,KAAK,CAAC,aAAa,yCAAyC,CAClJ,CAAC;QACJ,CAAC;aAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAC5C,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,OAAO;YACL,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,WAAW,EAAE,OAAO,CAAC,MAAM;YAC3B,WAAW;YACX,aAAa;YACb,OAAO;SACR,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,MAAM;QACX,MAAM,KAAK,GAAa;YACtB,wBAAwB,MAAM,CAAC,GAAG,EAAE;YACpC,iBAAiB,MAAM,CAAC,WAAW,cAAc,MAAM,CAAC,WAAW,gBAAgB,MAAM,CAAC,aAAa,EAAE;YACzG,EAAE;SACH,CAAC;QAEF,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;YACzC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,eAAe,CAAC;iBACxF,MAAM,CAAC,OAAO,CAAC;iBACf,IAAI,CAAC,KAAK,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,QAAQ,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;YAC3E,IAAI,MAAM;gBAAE,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,EAAE,CAAC,CAAC;YAC9C,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,EAAE,CAAC,IAAI,IAAI,OAAO,CAAC;gBACjC,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjE,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QAED,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpD,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"get-bioactivity.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-bioactivity.tool.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,10 @@ export declare const getCompoundDetails: import("@cyanheads/mcp-ts-core").ToolDe
10
10
  [x: string]: string;
11
11
  }>>>;
12
12
  includeDescription: z.ZodDefault<z.ZodBoolean>;
13
+ descriptionOffset: z.ZodDefault<z.ZodNumber>;
13
14
  maxDescriptions: z.ZodDefault<z.ZodNumber>;
14
15
  includeSynonyms: z.ZodDefault<z.ZodBoolean>;
16
+ synonymOffset: z.ZodDefault<z.ZodNumber>;
15
17
  maxSynonyms: z.ZodDefault<z.ZodNumber>;
16
18
  includeDrugLikeness: z.ZodDefault<z.ZodBoolean>;
17
19
  includeClassification: z.ZodDefault<z.ZodBoolean>;
@@ -76,5 +78,13 @@ export declare const getCompoundDetails: import("@cyanheads/mcp-ts-core").ToolDe
76
78
  meshClasses: z.ZodArray<z.ZodString>;
77
79
  }, z.core.$strip>>;
78
80
  }, z.core.$strip>>;
79
- }, z.core.$strip>, undefined, undefined>;
81
+ }, z.core.$strip>, undefined, {
82
+ readonly enrichedCids: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
83
+ readonly skippedCids: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
84
+ readonly synonymOffset: z.ZodOptional<z.ZodNumber>;
85
+ readonly nextSynonymOffset: z.ZodOptional<z.ZodNumber>;
86
+ readonly descriptionOffset: z.ZodOptional<z.ZodNumber>;
87
+ readonly nextDescriptionOffset: z.ZodOptional<z.ZodNumber>;
88
+ readonly notice: z.ZodOptional<z.ZodString>;
89
+ }>;
80
90
  //# sourceMappingURL=get-compound-details.tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-compound-details.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-compound-details.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AA4GjD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAuZ7B,CAAC"}
1
+ {"version":3,"file":"get-compound-details.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-compound-details.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAmHjD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAijB7B,CAAC"}
@@ -93,10 +93,16 @@ function computeDrugLikeness(properties) {
93
93
  veber: { rotatableBonds, tpsa, violations: veberViolations },
94
94
  };
95
95
  }
96
+ /**
97
+ * PUG View is a per-CID endpoint, so descriptions and classification fan out one request per
98
+ * compound. The batch accepts up to 100 CIDs; this bounds that fan-out. CIDs past it come back
99
+ * without descriptions or classification — disclosed via enrichment (#40).
100
+ */
101
+ const PUG_VIEW_CID_CAP = 10;
96
102
  // ── Tool definition ────────────────────────────────────────────────────
97
103
  export const getCompoundDetails = tool('pubchem_get_compound_details', {
98
104
  title: 'Get Compound Details',
99
- description: 'Get detailed compound information by CID. Returns physicochemical properties (molecular weight, SMILES, InChIKey, XLogP, TPSA, etc.), optionally with a textual description (pharmacology, mechanism, therapeutic use), all known synonyms, drug-likeness assessment (Lipinski/Veber rules), and/or pharmacological classification (FDA classes, MeSH classes, ATC codes). Accepts up to 100 CIDs per call.',
105
+ description: 'Get detailed compound information by CID. Returns physicochemical properties (molecular weight, SMILES, InChIKey, XLogP, TPSA, etc.), optionally with a textual description (pharmacology, mechanism, therapeutic use), known synonyms, drug-likeness assessment (Lipinski/Veber rules), and/or pharmacological classification (FDA classes, MeSH classes, ATC codes). Accepts up to 100 CIDs per call.',
100
106
  annotations: {
101
107
  readOnlyHint: true,
102
108
  idempotentHint: true,
@@ -115,25 +121,37 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
115
121
  includeDescription: z
116
122
  .boolean()
117
123
  .default(false)
118
- .describe('Include textual descriptions (pharmacology, mechanism, therapeutic use) attributed by source. Well-studied compounds have many overlapping summaries — capped via maxDescriptions. Fetched only for the first 10 CIDs in the batch; remaining CIDs return without descriptions.'),
124
+ .describe(`Include textual descriptions (pharmacology, mechanism, therapeutic use) attributed by source. Well-studied compounds have many overlapping summaries — paged via descriptionOffset/maxDescriptions. Fetched only for the first ${PUG_VIEW_CID_CAP} CIDs in the batch; remaining CIDs return without descriptions and are listed in the response's skippedCids.`),
125
+ descriptionOffset: z
126
+ .number()
127
+ .int()
128
+ .min(0)
129
+ .default(0)
130
+ .describe("Zero-based index of the first description to return within each compound's description list. The same offset is applied to every compound in the batch. Pass the nextDescriptionOffset from a previous call to read the following page. Default: 0."),
119
131
  maxDescriptions: z
120
132
  .number()
121
133
  .int()
122
134
  .min(1)
123
135
  .max(20)
124
136
  .default(3)
125
- .describe('Max number of distinct description entries per compound (1-20). PubChem returns near-duplicate summaries from many depositors; duplicates are collapsed before this cap applies. Default: 3.'),
137
+ .describe('Max number of distinct description entries per compound per page (1-20). PubChem returns near-duplicate summaries from many depositors; duplicates are collapsed before this cap applies. Default: 3.'),
126
138
  includeSynonyms: z
127
139
  .boolean()
128
140
  .default(false)
129
- .describe('Fetch all known names and synonyms (trade names, systematic names, registry numbers). Slower for large CID lists.'),
141
+ .describe('Fetch known names and synonyms (trade names, systematic names, registry numbers), paged via synonymOffset/maxSynonyms. Fetched for every found CID in the batch. Slower for large CID lists.'),
142
+ synonymOffset: z
143
+ .number()
144
+ .int()
145
+ .min(0)
146
+ .default(0)
147
+ .describe("Zero-based index of the first synonym to return within each compound's synonym list. The same offset is applied to every compound in the batch. Pass the nextSynonymOffset from a previous call to read the following page. Default: 0."),
130
148
  maxSynonyms: z
131
149
  .number()
132
150
  .int()
133
151
  .min(1)
134
152
  .max(100)
135
153
  .default(20)
136
- .describe('Max synonyms returned per compound (1-100). PubChem lists hundreds for common drugs; capped to keep the response focused. Default: 20.'),
154
+ .describe('Max synonyms returned per compound per page (1-100). PubChem lists hundreds for common drugs; use synonymOffset to reach the ones past this page. Default: 20.'),
137
155
  includeDrugLikeness: z
138
156
  .boolean()
139
157
  .default(false)
@@ -141,7 +159,7 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
141
159
  includeClassification: z
142
160
  .boolean()
143
161
  .default(false)
144
- .describe('Include pharmacological classification: FDA Established Pharmacologic Classes, mechanisms of action, MeSH classes, and ATC codes. Fetched only for the first 10 CIDs in the batch; remaining CIDs return without classification.'),
162
+ .describe(`Include pharmacological classification: FDA Established Pharmacologic Classes, mechanisms of action, MeSH classes, and ATC codes. Fetched only for the first ${PUG_VIEW_CID_CAP} CIDs in the batch; remaining CIDs return without classification and are listed in the response's skippedCids.`),
145
163
  }),
146
164
  output: z.object({
147
165
  compounds: z
@@ -165,16 +183,19 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
165
183
  })
166
184
  .describe('Description entry with optional source attribution.'))
167
185
  .optional()
168
- .describe('Textual descriptions, deduplicated and capped at maxDescriptions. Each entry carries optional source attribution.'),
186
+ .describe('Textual descriptions on this page, deduplicated then windowed by descriptionOffset/maxDescriptions. Each entry carries optional source attribution. Empty when descriptionOffset runs past descriptionsTotal.'),
169
187
  descriptionsTotal: z
170
188
  .number()
171
189
  .optional()
172
- .describe('Total distinct descriptions available before truncation. Larger than descriptions.length when more sources exist — increase maxDescriptions to see them.'),
173
- synonyms: z.array(z.string()).optional().describe('Known names and synonyms.'),
190
+ .describe('Total distinct descriptions available for this compound, across all pages. Larger than descriptions.length when more sources exist — raise maxDescriptions or page with descriptionOffset to see them.'),
191
+ synonyms: z
192
+ .array(z.string())
193
+ .optional()
194
+ .describe('Known names and synonyms on this page, windowed by synonymOffset/maxSynonyms. Empty when synonymOffset runs past synonymsTotal.'),
174
195
  synonymsTotal: z
175
196
  .number()
176
197
  .optional()
177
- .describe('Total synonyms available before truncation. Larger than synonyms.length when more exist — increase maxSynonyms to see them.'),
198
+ .describe('Total synonyms available for this compound, across all pages. Larger than synonyms.length when more exist — raise maxSynonyms or page with synonymOffset to see them.'),
178
199
  drugLikeness: drugLikenessSchema
179
200
  .optional()
180
201
  .describe('Drug-likeness assessment. lipinski.violations ≤ 1 and veber.violations = 0 → pass.'),
@@ -185,6 +206,54 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
185
206
  .describe('Per-CID compound detail record.'))
186
207
  .describe('Compound detail records.'),
187
208
  }),
209
+ // Agent-facing context for the two things a per-compound record cannot say on its own:
210
+ // which CIDs the PUG View fan-out reached (#40), and where the synonym/description windows
211
+ // sit within each compound's full list (#38). Both are batch-wide — one offset and one cap
212
+ // apply to every compound — so they belong here rather than repeated per record. Reaches
213
+ // structuredContent and content[]; keys disjoint from output (compounds lives there).
214
+ //
215
+ // A full page is exactly maxSynonyms/maxDescriptions long, so one nextOffset serves every
216
+ // compound that still has entries left, whatever its individual total.
217
+ enrichment: {
218
+ enrichedCids: z
219
+ .array(z.number())
220
+ .optional()
221
+ .describe('CIDs whose descriptions and classification were fetched. Present only when the batch exceeded the per-call fan-out limit and other CIDs were skipped.'),
222
+ skippedCids: z
223
+ .array(z.number())
224
+ .optional()
225
+ .describe('CIDs found in PubChem whose descriptions and classification were NOT fetched because the batch exceeded the per-call fan-out limit. Their absence from a record means "not requested", not "PubChem has none" — re-request these CIDs in a follow-up call. Present only when CIDs were skipped.'),
226
+ synonymOffset: z
227
+ .number()
228
+ .optional()
229
+ .describe("Zero-based index of the first synonym returned within each compound's list. Present when includeSynonyms is true."),
230
+ nextSynonymOffset: z
231
+ .number()
232
+ .optional()
233
+ .describe('synonymOffset to pass on the next call to continue past this page. Omitted when no compound in the batch has further synonyms.'),
234
+ descriptionOffset: z
235
+ .number()
236
+ .optional()
237
+ .describe("Zero-based index of the first description returned within each compound's list. Present when includeDescription is true."),
238
+ nextDescriptionOffset: z
239
+ .number()
240
+ .optional()
241
+ .describe('descriptionOffset to pass on the next call to continue past this page. Omitted when no compound in the batch has further descriptions.'),
242
+ notice: z
243
+ .string()
244
+ .optional()
245
+ .describe('Recovery guidance covering the skipped CIDs, an offset that runs past every compound, and pages that remain. Absent when nothing was skipped or truncated.'),
246
+ },
247
+ // Both fields are optional, so `render` sees `number[] | undefined` at the type level; the
248
+ // framework only calls it for a populated field.
249
+ enrichmentTrailer: {
250
+ enrichedCids: {
251
+ render: (cids) => `**Descriptions/classification fetched for:** CID ${(cids ?? []).join(', ')}`,
252
+ },
253
+ skippedCids: {
254
+ render: (cids) => `**Descriptions/classification skipped for:** CID ${(cids ?? []).join(', ')}`,
255
+ },
256
+ },
188
257
  async handler(input, ctx) {
189
258
  const client = getPubChemClient();
190
259
  const requestedProps = input.properties ?? [...DEFAULT_PROPERTIES];
@@ -207,12 +276,14 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
207
276
  return false;
208
277
  return Object.keys(row).some((k) => k !== 'CID');
209
278
  };
210
- // PUG View calls (per-CID, capped at 10) — skip CIDs that aren't in PubChem
279
+ // PUG View calls (per-CID, capped) — skip CIDs that aren't in PubChem
211
280
  const foundCids = input.cids.filter(isFound);
212
- const viewCids = foundCids.slice(0, 10);
213
- if ((input.includeDescription || input.includeClassification) &&
214
- viewCids.length < foundCids.length) {
215
- ctx.log.info('PUG View fetch capped at 10 CIDs', {
281
+ const viewCids = foundCids.slice(0, PUG_VIEW_CID_CAP);
282
+ const skippedViewCids = foundCids.slice(PUG_VIEW_CID_CAP);
283
+ const viewCapEngaged = (input.includeDescription || input.includeClassification) && skippedViewCids.length > 0;
284
+ if (viewCapEngaged) {
285
+ ctx.log.info('PUG View fetch capped', {
286
+ cap: PUG_VIEW_CID_CAP,
216
287
  requested: foundCids.length,
217
288
  fetching: viewCids.length,
218
289
  });
@@ -251,12 +322,12 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
251
322
  return compound;
252
323
  const allDescs = descMap?.get(cid);
253
324
  if (allDescs && allDescs.length > 0) {
254
- compound.descriptions = allDescs.slice(0, input.maxDescriptions);
325
+ compound.descriptions = allDescs.slice(input.descriptionOffset, input.descriptionOffset + input.maxDescriptions);
255
326
  compound.descriptionsTotal = allDescs.length;
256
327
  }
257
328
  const syns = synMap?.get(cid);
258
329
  if (syns) {
259
- compound.synonyms = syns.slice(0, input.maxSynonyms);
330
+ compound.synonyms = syns.slice(input.synonymOffset, input.synonymOffset + input.maxSynonyms);
260
331
  compound.synonymsTotal = syns.length;
261
332
  }
262
333
  if (input.includeDrugLikeness)
@@ -266,6 +337,40 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
266
337
  compound.classification = cls;
267
338
  return compound;
268
339
  });
340
+ const notices = [];
341
+ // #40 — the PUG View fan-out cap is otherwise invisible: a capped-out CID looks exactly
342
+ // like a compound PubChem has no description for.
343
+ if (viewCapEngaged) {
344
+ ctx.enrich({ enrichedCids: viewCids, skippedCids: skippedViewCids });
345
+ notices.push(`Descriptions and classification were fetched for the first ${viewCids.length} of ${foundCids.length} found CIDs (limit: ${PUG_VIEW_CID_CAP} per call). CID ${skippedViewCids.join(', ')} returned without them — re-request those CIDs in a follow-up call.`);
346
+ }
347
+ if (input.includeSynonyms) {
348
+ const page = listPage([...(synMap?.values() ?? [])].map((s) => s.length), input.synonymOffset, input.maxSynonyms);
349
+ ctx.enrich({ synonymOffset: input.synonymOffset });
350
+ if (page.hasMore)
351
+ ctx.enrich({ nextSynonymOffset: page.nextOffset });
352
+ if (page.allEmpty) {
353
+ notices.push(`synonymOffset ${input.synonymOffset} is past every compound in this batch — the longest synonym list has ${page.largestTotal} entries. Pass a synonymOffset below ${page.largestTotal}.`);
354
+ }
355
+ else if (page.hasMore) {
356
+ notices.push(`More synonyms remain — pass synonymOffset=${page.nextOffset} to continue.`);
357
+ }
358
+ }
359
+ if (input.includeDescription) {
360
+ const page = listPage([...(descMap?.values() ?? [])].map((d) => d.length), input.descriptionOffset, input.maxDescriptions);
361
+ ctx.enrich({ descriptionOffset: input.descriptionOffset });
362
+ if (page.hasMore)
363
+ ctx.enrich({ nextDescriptionOffset: page.nextOffset });
364
+ if (page.allEmpty) {
365
+ notices.push(`descriptionOffset ${input.descriptionOffset} is past every compound in this batch — the longest description list has ${page.largestTotal} entries. Pass a descriptionOffset below ${page.largestTotal}.`);
366
+ }
367
+ else if (page.hasMore) {
368
+ notices.push(`More descriptions remain — pass descriptionOffset=${page.nextOffset} to continue.`);
369
+ }
370
+ }
371
+ // One notice field, so the sources are composed rather than overwriting one another.
372
+ if (notices.length > 0)
373
+ ctx.enrich.notice(notices.join(' '));
269
374
  return { compounds };
270
375
  },
271
376
  format(result) {
@@ -391,7 +496,12 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
391
496
  }
392
497
  blocks.push(lines.join('\n'));
393
498
  }
394
- // Descriptions (with source attribution; truncated to maxDescriptions)
499
+ // An empty list with a non-zero total means the offset ran past this compound —
500
+ // distinct from having no descriptions at all, which omits the field entirely.
501
+ if (c.descriptions?.length === 0) {
502
+ blocks.push(`\n**Descriptions** (${c.descriptionsTotal ?? 0} total): none at this descriptionOffset`);
503
+ }
504
+ // Descriptions (with source attribution; windowed by descriptionOffset/maxDescriptions)
395
505
  if (c.descriptions && c.descriptions.length > 0) {
396
506
  const total = c.descriptionsTotal ?? c.descriptions.length;
397
507
  const shown = c.descriptions.length;
@@ -409,15 +519,18 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
409
519
  }
410
520
  const more = total - shown;
411
521
  if (more > 0) {
412
- descLines.push(`_+${more} more description${more === 1 ? '' : 's'} from other sources — increase maxDescriptions to see them._`);
522
+ descLines.push(`_+${more} more description${more === 1 ? '' : 's'} from other sources — raise maxDescriptions or page with descriptionOffset to see them._`);
413
523
  }
414
524
  blocks.push(descLines.join('\n\n'));
415
525
  }
416
- // Synonyms (capped at maxSynonyms in the handler; total reported)
526
+ if (c.synonyms?.length === 0) {
527
+ blocks.push(`\n**Synonyms** (${c.synonymsTotal ?? 0} total): none at this synonymOffset`);
528
+ }
529
+ // Synonyms (windowed by synonymOffset/maxSynonyms in the handler; total reported)
417
530
  if (c.synonyms && c.synonyms.length > 0) {
418
531
  const total = c.synonymsTotal ?? c.synonyms.length;
419
532
  const more = total - c.synonyms.length;
420
- const suffix = more > 0 ? ` (+${more} more)` : '';
533
+ const suffix = more > 0 ? ` (+${more} more not on this page)` : '';
421
534
  /**
422
535
  * Pipe-separated: CAS-style inverted names ("Benzoic acid, 2-(acetyloxy)-") carry
423
536
  * their own ", ", which would split one synonym into two. " | " is this formatter's
@@ -434,6 +547,19 @@ export const getCompoundDetails = tool('pubchem_get_compound_details', {
434
547
  return [{ type: 'text', text: blocks.join('\n') }];
435
548
  },
436
549
  });
550
+ /**
551
+ * Batch-wide page state for one of the per-compound lists. `totals` holds each compound's
552
+ * full list length; a page is windowed at the same offset and cap for every compound.
553
+ */
554
+ function listPage(totals, offset, cap) {
555
+ const largestTotal = totals.reduce((max, t) => Math.max(max, t), 0);
556
+ return {
557
+ allEmpty: largestTotal > 0 && totals.every((t) => t <= offset),
558
+ hasMore: totals.some((t) => t > offset + cap),
559
+ largestTotal,
560
+ nextOffset: offset + cap,
561
+ };
562
+ }
437
563
  function formatRules(rules) {
438
564
  return rules
439
565
  .map(([label, r]) => {