@cyanheads/pubchem-mcp-server 0.5.1 → 0.6.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.
Files changed (30) hide show
  1. package/README.md +3 -1
  2. package/dist/mcp-server/tools/definitions/get-bioactivity.tool.d.ts.map +1 -1
  3. package/dist/mcp-server/tools/definitions/get-bioactivity.tool.js +1 -0
  4. package/dist/mcp-server/tools/definitions/get-bioactivity.tool.js.map +1 -1
  5. package/dist/mcp-server/tools/definitions/get-compound-interactions.tool.d.ts +14 -0
  6. package/dist/mcp-server/tools/definitions/get-compound-interactions.tool.d.ts.map +1 -1
  7. package/dist/mcp-server/tools/definitions/get-compound-interactions.tool.js +101 -12
  8. package/dist/mcp-server/tools/definitions/get-compound-interactions.tool.js.map +1 -1
  9. package/dist/mcp-server/tools/definitions/get-compound-xrefs.tool.d.ts.map +1 -1
  10. package/dist/mcp-server/tools/definitions/get-compound-xrefs.tool.js +1 -0
  11. package/dist/mcp-server/tools/definitions/get-compound-xrefs.tool.js.map +1 -1
  12. package/dist/mcp-server/tools/definitions/index.d.ts +17 -0
  13. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  14. package/dist/mcp-server/tools/definitions/search-assays.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/search-assays.tool.js +1 -0
  16. package/dist/mcp-server/tools/definitions/search-assays.tool.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/search-compounds.tool.d.ts +3 -0
  18. package/dist/mcp-server/tools/definitions/search-compounds.tool.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/search-compounds.tool.js +66 -33
  20. package/dist/mcp-server/tools/definitions/search-compounds.tool.js.map +1 -1
  21. package/dist/services/pubchem/pubchem-client.d.ts +29 -8
  22. package/dist/services/pubchem/pubchem-client.d.ts.map +1 -1
  23. package/dist/services/pubchem/pubchem-client.js +102 -37
  24. package/dist/services/pubchem/pubchem-client.js.map +1 -1
  25. package/dist/services/pubchem/types.d.ts +51 -0
  26. package/dist/services/pubchem/types.d.ts.map +1 -1
  27. package/dist/services/pubchem/types.js.map +1 -1
  28. package/manifest.json +1 -1
  29. package/package.json +1 -1
  30. package/server.json +3 -3
@@ -19,7 +19,7 @@ const queryTypeEnum = z.enum(['smiles', 'cid']);
19
19
  const propertyEnum = z.enum(COMPOUND_PROPERTIES);
20
20
  export const searchCompounds = tool('pubchem_search_compounds', {
21
21
  title: 'Search Compounds',
22
- description: 'Search PubChem for chemical compounds by identifier (name, SMILES, or InChIKey, batched up to 25), molecular formula in Hill notation, substructure or superstructure containment, or 2D Tanimoto similarity. Optionally hydrate results with properties to avoid a follow-up pubchem_get_compound_details call.',
22
+ description: 'Search PubChem for chemical compounds by identifier (name, SMILES, or InChIKey, batched up to 25), molecular formula in Hill notation, substructure or superstructure containment, or 2D Tanimoto similarity. Returns a page of CIDs — reach matches past maxResults with offset. Optionally hydrate results with properties to avoid a follow-up pubchem_get_compound_details call.',
23
23
  annotations: {
24
24
  readOnlyHint: true,
25
25
  idempotentHint: true,
@@ -60,12 +60,20 @@ export const searchCompounds = tool('pubchem_search_compounds', {
60
60
  .max(100)
61
61
  .default(90)
62
62
  .describe('Similarity search only. Minimum Tanimoto similarity (70-100). 90+ for close analogs, 70-80 for scaffold hops. Default: 90.'),
63
+ offset: z
64
+ .number()
65
+ .int()
66
+ .min(0)
67
+ .max(10000)
68
+ .default(0)
69
+ .describe('Zero-based index of the first CID to return. Pass the nextOffset from a previous call to read the following page. Identifier lookups resolve every match up front, so paging them is free; formula, substructure, superstructure, and similarity searches have to ask PubChem for offset + maxResults records to reach a page, so deep pages cost progressively more upstream — hence the 10000 ceiling. Default: 0.'),
63
70
  maxResults: z
64
71
  .number()
72
+ .int()
65
73
  .min(1)
66
74
  .max(200)
67
75
  .default(20)
68
- .describe('Maximum CIDs to return (1-200). Default: 20.'),
76
+ .describe('Maximum CIDs to return per page (1-200). Use offset to reach matches past this page. Default: 20.'),
69
77
  properties: z
70
78
  .array(propertyEnum)
71
79
  .optional()
@@ -92,8 +100,9 @@ export const searchCompounds = tool('pubchem_search_compounds', {
92
100
  .optional()
93
101
  .describe('Identifier-mode only: input identifiers that resolved to no CID. Omitted when every identifier resolved and for non-identifier searches.'),
94
102
  }),
95
- // Agent-facing context — search strategy echo, total before cap, and an empty-result
96
- // notice. Reaches structuredContent and content[] automatically; not in the domain return.
103
+ // Agent-facing context — search strategy echo, total, the page boundary, and a notice
104
+ // covering unresolved identifiers, an empty page, and further pages. Reaches
105
+ // structuredContent and content[] automatically; not in the domain return.
97
106
  //
98
107
  // Bounded-total convention: a reported total states only what the server actually
99
108
  // observed. `totalFound` carries a true count and appears only when the whole match set
@@ -108,21 +117,23 @@ export const searchCompounds = tool('pubchem_search_compounds', {
108
117
  totalFound: z
109
118
  .number()
110
119
  .optional()
111
- .describe('Exact number of matching CIDs, before the maxResults cap. Omitted when a formula, substructure, superstructure, or similarity search filled its page — PubChem returns no match count for those, so totalFoundAtLeast reports a floor instead.'),
120
+ .describe('Exact number of matching CIDs across all pages. Omitted when a formula, substructure, superstructure, or similarity search saturated the records it requested — PubChem returns no match count for those, so totalFoundAtLeast reports a floor instead.'),
112
121
  totalFoundAtLeast: z
113
122
  .number()
114
123
  .optional()
115
- .describe('Lower bound on matching CIDs, reported in place of totalFound when the exact count is unavailable. At least this many match, and the true total may be higher; raise maxResults to retrieve more.'),
116
- truncated: z
117
- .boolean()
124
+ .describe('Lower bound on matching CIDs, reported in place of totalFound when the exact count is unavailable. At least this many match, and the true total may be higher; page further with offset to observe more.'),
125
+ offset: z.number().describe('Zero-based index of the first CID returned.'),
126
+ nextOffset: z
127
+ .number()
118
128
  .optional()
119
- .describe('True when CIDs were capped at maxResults — more matches exist than returned.'),
120
- shown: z.number().optional().describe('CIDs returned after the maxResults cap.'),
129
+ .describe('Offset to pass on the next call to continue past this page. Omitted when no further matches remain.'),
130
+ truncated: z.boolean().optional().describe('True when matching CIDs remain past this page.'),
131
+ shown: z.number().optional().describe('CIDs returned on this page.'),
121
132
  cap: z.number().optional().describe('The maxResults cap that was applied.'),
122
133
  notice: z
123
134
  .string()
124
135
  .optional()
125
- .describe('Recovery guidance when no compounds matched — echoes search strategy and suggests how to broaden. Absent when results were returned.'),
136
+ .describe('Recovery guidance when no compounds matched, when the offset runs past the matches observed, when identifiers failed to resolve, or when further pages remain. Absent when this page is complete and every identifier resolved.'),
126
137
  },
127
138
  errors: [
128
139
  {
@@ -153,10 +164,12 @@ export const searchCompounds = tool('pubchem_search_compounds', {
153
164
  async handler(input, ctx) {
154
165
  const client = getPubChemClient();
155
166
  let allCids = [];
156
- // Formula and structure searches are bounded server-side. One record past the cap is
157
- // enough to prove more matches exist without moving the rest of PubChem's match set,
158
- // which for a common substructure runs to 16.3 MB of CIDs.
159
- const recordCap = input.maxResults + 1;
167
+ // Formula and structure searches are bounded server-side. One record past the requested
168
+ // page is enough to prove more matches exist without moving the rest of PubChem's match
169
+ // set, which for a common substructure runs to 16.3 MB of CIDs. The window has to cover
170
+ // the offset too — these endpoints take a record count, not a start position, and their
171
+ // ordering is a stable prefix, so reaching page N means asking for everything up to it.
172
+ const recordCap = input.offset + input.maxResults + 1;
160
173
  let boundedSearch = false;
161
174
  const identifierMap = new Map();
162
175
  // Identifier-mode resolution tracking (#29): inputs that resolved to no CID, and
@@ -243,23 +256,33 @@ export const searchCompounds = tool('pubchem_search_compounds', {
243
256
  break;
244
257
  }
245
258
  }
246
- // Deduplicate and cap
259
+ // Deduplicate, then take the requested page.
247
260
  const uniqueCids = [...new Set(allCids)];
248
261
  const observedTotal = uniqueCids.length;
249
- const cappedCids = uniqueCids.slice(0, input.maxResults);
262
+ const pagedCids = uniqueCids.slice(input.offset, input.offset + input.maxResults);
250
263
  // A bounded search that came back short of its cap returned everything PubChem had, so
251
264
  // the count is exact. A saturated one only proves a floor.
252
265
  const totalIsExact = !boundedSearch || allCids.length < recordCap;
266
+ // The stride comes from the page that was returned, never from maxResults — that input
267
+ // accepts a fractional value, which would emit a nextOffset this tool's own integer
268
+ // offset validator then rejects.
269
+ const nextOffset = input.offset + pagedCids.length;
270
+ // A saturated bounded search proved a record beyond the window it asked for, so more
271
+ // remain even though observedTotal cannot say how many.
272
+ const hasMore = !totalIsExact || nextOffset < observedTotal;
253
273
  ctx.log.info('Search completed', {
254
274
  searchType: input.searchType,
255
275
  observedTotal,
256
276
  totalIsExact,
257
- returned: cappedCids.length,
277
+ offset: input.offset,
278
+ returned: pagedCids.length,
258
279
  });
259
- // Agent-facing context: search strategy echo, total, and empty-result notice.
280
+ // Agent-facing context: search strategy echo, total, page boundary, and notices.
260
281
  ctx.enrich(totalIsExact
261
- ? { searchType: input.searchType, totalFound: observedTotal }
262
- : { searchType: input.searchType, totalFoundAtLeast: observedTotal });
282
+ ? { searchType: input.searchType, totalFound: observedTotal, offset: input.offset }
283
+ : { searchType: input.searchType, totalFoundAtLeast: observedTotal, offset: input.offset });
284
+ if (hasMore)
285
+ ctx.enrich({ nextOffset });
263
286
  // Identifier-mode signals (#29): name inputs that did not resolve and flag CID
264
287
  // collisions, so a partial miss is never silent. Empty for other search types.
265
288
  const noticeParts = [];
@@ -270,20 +293,30 @@ export const searchCompounds = tool('pubchem_search_compounds', {
270
293
  for (const c of collisions) {
271
294
  noticeParts.push(`Identifiers ${c.identifiers.join(', ')} all resolved to CID ${c.cid}; the result row echoes only "${identifierMap.get(c.cid)}" for it.`);
272
295
  }
273
- if (cappedCids.length === 0) {
274
- ctx.enrich.notice(noticeParts.length > 0
275
- ? noticeParts.join(' ')
276
- : `No compounds matched the ${input.searchType} search. Try a different identifier, broaden the formula, lower the similarity threshold, or verify the SMILES/CID.`);
296
+ if (pagedCids.length === 0) {
297
+ // An empty page is either "nothing matched" or "the offset ran past the matches" —
298
+ // say which, and name the bound the caller can page back inside.
299
+ if (observedTotal === 0) {
300
+ if (noticeParts.length === 0) {
301
+ noticeParts.push(`No compounds matched the ${input.searchType} search. Try a different identifier, broaden the formula, lower the similarity threshold, or verify the SMILES/CID.`);
302
+ }
303
+ }
304
+ else {
305
+ // Reachable only with an exact total: a saturated bounded search observed
306
+ // offset + maxResults + 1 records, so its page is never empty.
307
+ noticeParts.push(`offset ${input.offset} is past the ${observedTotal} match(es) found. Pass an offset below ${observedTotal}.`);
308
+ }
309
+ ctx.enrich.notice(noticeParts.join(' '));
277
310
  }
278
- else if (observedTotal > cappedCids.length) {
311
+ else if (hasMore) {
279
312
  // Truncated: fold any identifier notice into the truncation guidance (notice is
280
313
  // last-wins, so compose rather than emit two). Without an exact total there is no
281
- // "of N" to quote — say more exist and stop.
314
+ // "of N" to quote — say more exist, and still hand over the next offset.
282
315
  const truncationGuidance = totalIsExact
283
- ? `Showing ${cappedCids.length} of ${observedTotal} matches — raise maxResults (max 200) for more.`
284
- : `Showing ${cappedCids.length} matches; more exist, but PubChem reports no match count for a ${input.searchType} search — raise maxResults (max 200) for more.`;
316
+ ? `Showing matches ${input.offset + 1}-${nextOffset} of ${observedTotal}. Pass offset=${nextOffset} for the next page.`
317
+ : `Showing matches ${input.offset + 1}-${nextOffset}; more exist, but PubChem reports no match count for a ${input.searchType} search. Pass offset=${nextOffset} for the next page.`;
285
318
  ctx.enrich.truncated({
286
- shown: cappedCids.length,
319
+ shown: pagedCids.length,
287
320
  cap: input.maxResults,
288
321
  guidance: noticeParts.length > 0
289
322
  ? `${noticeParts.join(' ')} ${truncationGuidance}`
@@ -295,11 +328,11 @@ export const searchCompounds = tool('pubchem_search_compounds', {
295
328
  }
296
329
  // Optionally hydrate with properties
297
330
  let propsMap;
298
- if (input.properties && input.properties.length > 0 && cappedCids.length > 0) {
299
- const rows = await client.getProperties(cappedCids, input.properties);
331
+ if (input.properties && input.properties.length > 0 && pagedCids.length > 0) {
332
+ const rows = await client.getProperties(pagedCids, input.properties);
300
333
  propsMap = new Map(rows.map((r) => [r.CID, r]));
301
334
  }
302
- const results = cappedCids.map((cid) => {
335
+ const results = pagedCids.map((cid) => {
303
336
  const result = { cid };
304
337
  const identifier = identifierMap.get(cid);
305
338
  if (identifier)
@@ -1 +1 @@
1
- {"version":3,"file":"search-compounds.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-compounds.tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search-compounds.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-compounds.tool.ts"],"names":[],"mappings":"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"}
@@ -73,10 +73,11 @@ export declare class PubChemClient {
73
73
  searchAssaysByTarget(targetType: string, query: string): Promise<number[]>;
74
74
  getEntitySummary(entityType: string, identifier: string | number): Promise<Record<string, unknown> | null>;
75
75
  /** Fetch drug-drug, drug-food, and/or chemical-target interactions for a compound.
76
- * Drug-drug and target data live in PubChem SDQ external tables (drugbankddi,
77
- * consolidatedcompoundtarget); drug-food is inline PUG View text. Each kind is capped
78
- * at maxEntries; absent data for a kind contributes nothing rather than erroring. */
79
- getInteractions(cid: number, kinds: Array<'drug-drug' | 'drug-food' | 'target'>, maxEntries: number): Promise<InteractionsResult>;
76
+ * Drug-drug and target data live in PubChem SDQ external tables (drugbankddi, bioactivity);
77
+ * drug-food is inline PUG View text. Each kind reads a window of `maxEntries` entries
78
+ * starting at `offset` records into that kind's own stream; absent data for a kind
79
+ * contributes an empty page rather than erroring. */
80
+ getInteractions(cid: number, kinds: Array<'drug-drug' | 'drug-food' | 'target'>, maxEntries: number, offset: number): Promise<InteractionsResult>;
80
81
  private getInteractionsForKind;
81
82
  private getDrugDrugInteractions;
82
83
  /** Chemical-target binding/activity from PubChem's `bioactivity` SDQ collection (BindingDB,
@@ -84,15 +85,35 @@ export declare class PubChemClient {
84
85
  * compound; the `consolidatedcompoundtarget` collection used previously is gene-indexed, so
85
86
  * its `cid` filter was silently ignored — every CID returned the same default compound (#20).
86
87
  * Only rows naming a molecular target are returned; untargeted assay outcomes are the domain
87
- * of pubchem_get_bioactivity. */
88
+ * of pubchem_get_bioactivity.
89
+ *
90
+ * Because most rows are dropped, the page position is an index into the activity records, not
91
+ * into the entries returned — the walk records exactly how many rows it read so the next page
92
+ * resumes at the first unread one. */
88
93
  private getTargetInteractions;
89
94
  private getDrugFoodInteractions;
90
95
  /** Query a PubChem SDQ external table for a single CID, projecting only the columns the
91
96
  * caller maps. Projection keeps payloads small and excludes the free-text `citations` column
92
- * whose unescaped quotes make PubChem emit invalid JSON (#20). Returns [] on not-found; on an
93
- * unparseable body throws a contextful error rather than the bare `Failed to parse JSON`, so
94
- * the caller and per-kind isolation can report it cleanly. */
97
+ * whose unescaped quotes make PubChem emit invalid JSON (#20).
98
+ *
99
+ * Uses SDQ's `select` projection rather than `download`: the `SDQOutputSet` envelope it
100
+ * answers with carries `totalCount` — the records matching the query across all pages —
101
+ * beside the requested window, which is the only way to tell a full page from the last one.
102
+ * `start` is 1-based upstream; `offset` here is zero-based like every other paging input.
103
+ *
104
+ * Returns an empty page on not-found. A rejected query is reported, never read as "no data":
105
+ * SDQ answers a malformed query with a 5xx that the shared HTTP core already classifies and
106
+ * throws, and the `status.error` check covers the same rejection arriving inside a 2xx body.
107
+ * An unparseable body throws with the collection and a body snippet attached, so per-kind
108
+ * isolation can name what failed. */
95
109
  private fetchSdq;
110
+ /** Resolve a kind's record total for the page just fetched.
111
+ *
112
+ * SDQ reports `totalCount` alongside every non-empty page, but a `start` past the last record
113
+ * answers `totalCount` 0 — so an offset that overshoots would otherwise report "no records"
114
+ * for a compound that has thousands. A one-row probe from the top recovers the real bound,
115
+ * and only runs on that case. */
116
+ private resolveSdqTotal;
96
117
  /** Fetch the default 3D conformer as raw V2000 SDF text. Throws a typed not-found when
97
118
  * PubChem has no computed 3D coordinates (large molecules, mixtures, undefined salts). */
98
119
  getSdf3d(cid: number): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"pubchem-client.d.ts","sourceRoot":"","sources":["../../../src/services/pubchem/pubchem-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pubchem-client.d.ts","sourceRoot":"","sources":["../../../src/services/pubchem/pubchem-client.ts"],"names":[],"mappings":"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"}
@@ -908,19 +908,25 @@ export class PubChemClient {
908
908
  }
909
909
  // ── Interactions ────────────────────────────────────────────────
910
910
  /** Fetch drug-drug, drug-food, and/or chemical-target interactions for a compound.
911
- * Drug-drug and target data live in PubChem SDQ external tables (drugbankddi,
912
- * consolidatedcompoundtarget); drug-food is inline PUG View text. Each kind is capped
913
- * at maxEntries; absent data for a kind contributes nothing rather than erroring. */
914
- async getInteractions(cid, kinds, maxEntries) {
911
+ * Drug-drug and target data live in PubChem SDQ external tables (drugbankddi, bioactivity);
912
+ * drug-food is inline PUG View text. Each kind reads a window of `maxEntries` entries
913
+ * starting at `offset` records into that kind's own stream; absent data for a kind
914
+ * contributes an empty page rather than erroring. */
915
+ async getInteractions(cid, kinds, maxEntries, offset) {
915
916
  // Per-kind isolation: a failure in one source (upstream parse error, timeout, network)
916
917
  // must not discard the kinds that succeeded. Failures are reported, not thrown (#21).
917
- const settled = await Promise.allSettled(kinds.map((kind) => this.getInteractionsForKind(cid, kind, maxEntries)));
918
+ // Page state is recorded per kind from that kind's own result, so a failing kind leaves
919
+ // the others' continuation signals untouched rather than zeroing them.
920
+ const settled = await Promise.allSettled(kinds.map((kind) => this.getInteractionsForKind(cid, kind, maxEntries, offset)));
918
921
  const entries = [];
922
+ const pages = [];
919
923
  const failedKinds = [];
920
924
  kinds.forEach((kind, i) => {
921
925
  const result = settled[i];
922
926
  if (result?.status === 'fulfilled') {
923
- entries.push(...result.value);
927
+ const { entries: kindEntries, totalRecords, recordsConsumed } = result.value;
928
+ entries.push(...kindEntries);
929
+ pages.push({ kind, returnedCount: kindEntries.length, totalRecords, recordsConsumed });
924
930
  }
925
931
  else if (result?.status === 'rejected') {
926
932
  const reason = result.reason;
@@ -930,20 +936,20 @@ export class PubChemClient {
930
936
  });
931
937
  }
932
938
  });
933
- return { entries, failedKinds };
939
+ return { entries, pages, failedKinds };
934
940
  }
935
- getInteractionsForKind(cid, kind, maxEntries) {
941
+ getInteractionsForKind(cid, kind, maxEntries, offset) {
936
942
  switch (kind) {
937
943
  case 'drug-drug':
938
- return this.getDrugDrugInteractions(cid, maxEntries);
944
+ return this.getDrugDrugInteractions(cid, maxEntries, offset);
939
945
  case 'drug-food':
940
- return this.getDrugFoodInteractions(cid, maxEntries);
946
+ return this.getDrugFoodInteractions(cid, maxEntries, offset);
941
947
  case 'target':
942
- return this.getTargetInteractions(cid, maxEntries);
948
+ return this.getTargetInteractions(cid, maxEntries, offset);
943
949
  }
944
950
  }
945
- async getDrugDrugInteractions(cid, maxEntries) {
946
- const rows = await this.fetchSdq('drugbankddi', cid, ['cid', 'name2', 'descr'], maxEntries);
951
+ async getDrugDrugInteractions(cid, maxEntries, offset) {
952
+ const { rows, totalCount } = await this.fetchSdq('drugbankddi', cid, ['cid', 'name2', 'descr'], maxEntries, offset);
947
953
  const entries = [];
948
954
  for (const row of rows) {
949
955
  const text = typeof row.descr === 'string' ? row.descr : '';
@@ -954,21 +960,35 @@ export class PubChemClient {
954
960
  entry.partner = row.name2;
955
961
  entries.push(entry);
956
962
  }
957
- return entries;
963
+ return {
964
+ entries,
965
+ totalRecords: await this.resolveSdqTotal('drugbankddi', cid, rows.length, totalCount, offset),
966
+ recordsConsumed: rows.length,
967
+ };
958
968
  }
959
969
  /** Chemical-target binding/activity from PubChem's `bioactivity` SDQ collection (BindingDB,
960
970
  * ChEMBL, and others). That collection is cid-keyed and scopes correctly to the requested
961
971
  * compound; the `consolidatedcompoundtarget` collection used previously is gene-indexed, so
962
972
  * its `cid` filter was silently ignored — every CID returned the same default compound (#20).
963
973
  * Only rows naming a molecular target are returned; untargeted assay outcomes are the domain
964
- * of pubchem_get_bioactivity. */
965
- async getTargetInteractions(cid, maxEntries) {
974
+ * of pubchem_get_bioactivity.
975
+ *
976
+ * Because most rows are dropped, the page position is an index into the activity records, not
977
+ * into the entries returned — the walk records exactly how many rows it read so the next page
978
+ * resumes at the first unread one. */
979
+ async getTargetInteractions(cid, maxEntries, offset) {
966
980
  // `targetname` is sparse (most rows are untargeted assay outcomes), so oversample and keep
967
981
  // the target-bearing rows, most-potent-first.
968
982
  const window = Math.min(Math.max(maxEntries * 4, 20), 100);
969
- const rows = await this.fetchSdq('bioactivity', cid, ['cid', 'targetname', 'acname', 'acqualifier', 'acvalue', 'aidsrcname'], window, ['acvalue,asc']);
983
+ const { rows, totalCount } = await this.fetchSdq('bioactivity', cid, ['cid', 'targetname', 'acname', 'acqualifier', 'acvalue', 'aidsrcname'], window, offset, ['acvalue,asc']);
970
984
  const entries = [];
985
+ // Collapse duplicate measurements of the same target/value reported across assays.
986
+ const seen = new Set();
987
+ let recordsConsumed = 0;
971
988
  for (const row of rows) {
989
+ if (entries.length === maxEntries)
990
+ break;
991
+ recordsConsumed++;
972
992
  const target = typeof row.targetname === 'string' ? row.targetname.trim() : '';
973
993
  if (!target)
974
994
  continue;
@@ -981,53 +1001,79 @@ export class PubChemClient {
981
1001
  // assaysummary endpoint's "Activity Value [uM]" column. We assert that documented µM
982
1002
  // convention here — it is not a per-row unit fetched from SDQ (none exists to fetch).
983
1003
  const activity = acname && acvalue ? `${acname} ${qualifier} ${acvalue} uM`.replace(/\s+/g, ' ').trim() : '';
1004
+ const text = activity || 'Activity reported';
1005
+ const key = `${target}|${text}`;
1006
+ if (seen.has(key))
1007
+ continue;
1008
+ seen.add(key);
984
1009
  entries.push({
985
1010
  kind: 'target',
986
1011
  source: typeof row.aidsrcname === 'string' && row.aidsrcname ? row.aidsrcname : 'PubChem',
987
1012
  partner: target,
988
- text: activity || 'Activity reported',
1013
+ text,
989
1014
  });
990
1015
  }
991
- // Collapse duplicate measurements of the same target/value reported across assays.
992
- return dedupByKey(entries, (e) => `${e.partner ?? ''}|${e.text}`).slice(0, maxEntries);
1016
+ return {
1017
+ entries,
1018
+ totalRecords: await this.resolveSdqTotal('bioactivity', cid, rows.length, totalCount, offset),
1019
+ recordsConsumed,
1020
+ };
993
1021
  }
994
- async getDrugFoodInteractions(cid, maxEntries) {
1022
+ async getDrugFoodInteractions(cid, maxEntries, offset) {
1023
+ const empty = { entries: [], totalRecords: 0, recordsConsumed: 0 };
995
1024
  try {
996
1025
  const data = await this.fetchJson(`${this.viewBase}/data/compound/${cid}/JSON?heading=Drug-Food+Interactions`);
997
1026
  const sections = data.Record.Section;
998
1027
  if (!sections)
999
- return [];
1028
+ return empty;
1000
1029
  const section = findSection(sections, 'Drug-Food Interactions');
1001
1030
  if (!section)
1002
- return [];
1031
+ return empty;
1003
1032
  const refToSource = new Map();
1004
1033
  for (const ref of data.Record.Reference ?? []) {
1005
1034
  refToSource.set(ref.ReferenceNumber, ref.SourceName);
1006
1035
  }
1036
+ // PUG View returns the whole section inline, so the deduped item list is the complete
1037
+ // record stream and the page is a plain slice of it.
1007
1038
  const items = dedupByKey(extractDescriptionItems(section), descriptionKey);
1008
- return items.slice(0, maxEntries).map((item) => {
1009
- const source = item.refNum != null ? refToSource.get(item.refNum) : undefined;
1010
- return { kind: 'drug-food', source: source ?? 'PubChem', text: item.text };
1011
- });
1039
+ const page = items.slice(offset, offset + maxEntries);
1040
+ return {
1041
+ entries: page.map((item) => {
1042
+ const source = item.refNum != null ? refToSource.get(item.refNum) : undefined;
1043
+ return { kind: 'drug-food', source: source ?? 'PubChem', text: item.text };
1044
+ }),
1045
+ totalRecords: items.length,
1046
+ recordsConsumed: page.length,
1047
+ };
1012
1048
  }
1013
1049
  catch (error) {
1014
1050
  if (isNotFound(error))
1015
- return [];
1051
+ return empty;
1016
1052
  throw error;
1017
1053
  }
1018
1054
  }
1019
1055
  /** Query a PubChem SDQ external table for a single CID, projecting only the columns the
1020
1056
  * caller maps. Projection keeps payloads small and excludes the free-text `citations` column
1021
- * whose unescaped quotes make PubChem emit invalid JSON (#20). Returns [] on not-found; on an
1022
- * unparseable body throws a contextful error rather than the bare `Failed to parse JSON`, so
1023
- * the caller and per-kind isolation can report it cleanly. */
1024
- async fetchSdq(collection, cid, columns, limit, order) {
1057
+ * whose unescaped quotes make PubChem emit invalid JSON (#20).
1058
+ *
1059
+ * Uses SDQ's `select` projection rather than `download`: the `SDQOutputSet` envelope it
1060
+ * answers with carries `totalCount` — the records matching the query across all pages —
1061
+ * beside the requested window, which is the only way to tell a full page from the last one.
1062
+ * `start` is 1-based upstream; `offset` here is zero-based like every other paging input.
1063
+ *
1064
+ * Returns an empty page on not-found. A rejected query is reported, never read as "no data":
1065
+ * SDQ answers a malformed query with a 5xx that the shared HTTP core already classifies and
1066
+ * throws, and the `status.error` check covers the same rejection arriving inside a 2xx body.
1067
+ * An unparseable body throws with the collection and a body snippet attached, so per-kind
1068
+ * isolation can name what failed. */
1069
+ async fetchSdq(collection, cid, columns, limit, offset = 0, order) {
1070
+ const empty = { rows: [], totalCount: 0 };
1025
1071
  const query = JSON.stringify({
1026
- download: columns,
1072
+ select: columns,
1027
1073
  collection,
1028
1074
  where: { ands: [{ cid: String(cid) }] },
1029
1075
  ...(order ? { order } : {}),
1030
- start: 1,
1076
+ start: offset + 1,
1031
1077
  limit,
1032
1078
  });
1033
1079
  const url = `${this.sdqBase}?infmt=json&outfmt=json&query=${encodeURIComponent(query)}`;
@@ -1037,16 +1083,35 @@ export class PubChemClient {
1037
1083
  }
1038
1084
  catch (error) {
1039
1085
  if (isNotFound(error))
1040
- return [];
1086
+ return empty;
1041
1087
  throw error;
1042
1088
  }
1089
+ let parsed;
1043
1090
  try {
1044
- const data = JSON.parse(body);
1045
- return Array.isArray(data) ? data : [];
1091
+ parsed = JSON.parse(body);
1046
1092
  }
1047
1093
  catch {
1048
1094
  throw new McpError(JsonRpcErrorCode.ServiceUnavailable, `PubChem SDQ returned unparseable JSON for collection "${collection}"`, { collection, cid, snippet: body.slice(0, 200) });
1049
1095
  }
1096
+ const set = parsed.SDQOutputSet?.[0];
1097
+ if (!set)
1098
+ return empty;
1099
+ if (set.status?.error) {
1100
+ throw new McpError(JsonRpcErrorCode.ServiceUnavailable, `PubChem SDQ rejected the query for collection "${collection}": ${set.status.error}`, { collection, cid, sdqError: set.status.error });
1101
+ }
1102
+ return { rows: set.rows ?? [], totalCount: set.totalCount ?? 0 };
1103
+ }
1104
+ /** Resolve a kind's record total for the page just fetched.
1105
+ *
1106
+ * SDQ reports `totalCount` alongside every non-empty page, but a `start` past the last record
1107
+ * answers `totalCount` 0 — so an offset that overshoots would otherwise report "no records"
1108
+ * for a compound that has thousands. A one-row probe from the top recovers the real bound,
1109
+ * and only runs on that case. */
1110
+ async resolveSdqTotal(collection, cid, rowsReturned, reportedTotal, offset) {
1111
+ if (rowsReturned > 0 || offset === 0)
1112
+ return reportedTotal;
1113
+ const { totalCount } = await this.fetchSdq(collection, cid, ['cid'], 1);
1114
+ return totalCount;
1050
1115
  }
1051
1116
  // ── 3D Structure ────────────────────────────────────────────────
1052
1117
  /** Fetch the default 3D conformer as raw V2000 SDF text. Throws a typed not-found when