@cyanheads/pubmed-mcp-server 2.10.8 → 2.10.10

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 (67) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/README.md +6 -4
  4. package/dist/mcp-server/tools/definitions/_text.d.ts +23 -1
  5. package/dist/mcp-server/tools/definitions/_text.d.ts.map +1 -1
  6. package/dist/mcp-server/tools/definitions/_text.js +25 -1
  7. package/dist/mcp-server/tools/definitions/_text.js.map +1 -1
  8. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts +35 -0
  9. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts.map +1 -1
  10. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js +657 -44
  11. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js.map +1 -1
  12. package/dist/mcp-server/tools/definitions/find-related.tool.d.ts +8 -0
  13. package/dist/mcp-server/tools/definitions/find-related.tool.d.ts.map +1 -1
  14. package/dist/mcp-server/tools/definitions/find-related.tool.js +131 -23
  15. package/dist/mcp-server/tools/definitions/find-related.tool.js.map +1 -1
  16. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts +6 -0
  17. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.js +14 -3
  19. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/search-articles.tool.d.ts +6 -0
  21. package/dist/mcp-server/tools/definitions/search-articles.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/search-articles.tool.js +15 -3
  23. package/dist/mcp-server/tools/definitions/search-articles.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts +6 -0
  25. package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts.map +1 -1
  26. package/dist/mcp-server/tools/definitions/spell-check.tool.js +15 -3
  27. package/dist/mcp-server/tools/definitions/spell-check.tool.js.map +1 -1
  28. package/dist/services/error-contracts.d.ts +22 -2
  29. package/dist/services/error-contracts.d.ts.map +1 -1
  30. package/dist/services/error-contracts.js +24 -2
  31. package/dist/services/error-contracts.js.map +1 -1
  32. package/dist/services/europe-pmc/europe-pmc-service.d.ts +3 -3
  33. package/dist/services/europe-pmc/europe-pmc-service.d.ts.map +1 -1
  34. package/dist/services/europe-pmc/europe-pmc-service.js +8 -16
  35. package/dist/services/europe-pmc/europe-pmc-service.js.map +1 -1
  36. package/dist/services/ncbi/parsing/ordered-xml-parser-options.d.ts +44 -0
  37. package/dist/services/ncbi/parsing/ordered-xml-parser-options.d.ts.map +1 -0
  38. package/dist/services/ncbi/parsing/ordered-xml-parser-options.js +41 -0
  39. package/dist/services/ncbi/parsing/ordered-xml-parser-options.js.map +1 -0
  40. package/dist/services/ncbi/parsing/pmc-article-parser.d.ts +70 -10
  41. package/dist/services/ncbi/parsing/pmc-article-parser.d.ts.map +1 -1
  42. package/dist/services/ncbi/parsing/pmc-article-parser.js +795 -79
  43. package/dist/services/ncbi/parsing/pmc-article-parser.js.map +1 -1
  44. package/dist/services/ncbi/parsing/pmc-xml-helpers.d.ts +18 -0
  45. package/dist/services/ncbi/parsing/pmc-xml-helpers.d.ts.map +1 -1
  46. package/dist/services/ncbi/parsing/pmc-xml-helpers.js +39 -3
  47. package/dist/services/ncbi/parsing/pmc-xml-helpers.js.map +1 -1
  48. package/dist/services/ncbi/response-handler.d.ts +3 -9
  49. package/dist/services/ncbi/response-handler.d.ts.map +1 -1
  50. package/dist/services/ncbi/response-handler.js +6 -29
  51. package/dist/services/ncbi/response-handler.js.map +1 -1
  52. package/dist/services/ncbi/types.d.ts +82 -0
  53. package/dist/services/ncbi/types.d.ts.map +1 -1
  54. package/dist/services/openalex/api-client.d.ts +13 -4
  55. package/dist/services/openalex/api-client.d.ts.map +1 -1
  56. package/dist/services/openalex/api-client.js +19 -8
  57. package/dist/services/openalex/api-client.js.map +1 -1
  58. package/dist/services/openalex/openalex-service.d.ts +34 -17
  59. package/dist/services/openalex/openalex-service.d.ts.map +1 -1
  60. package/dist/services/openalex/openalex-service.js +119 -35
  61. package/dist/services/openalex/openalex-service.js.map +1 -1
  62. package/dist/services/openalex/types.d.ts +34 -1
  63. package/dist/services/openalex/types.d.ts.map +1 -1
  64. package/dist/services/openalex/types.js +20 -0
  65. package/dist/services/openalex/types.js.map +1 -1
  66. package/package.json +1 -1
  67. package/server.json +3 -3
@@ -7,7 +7,7 @@
7
7
  import { JsonRpcErrorCode, McpError, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
8
8
  import { fetchWithTimeout, logger, requestContextService } from '@cyanheads/mcp-ts-core/utils';
9
9
  import { recoveryFor } from '../error-contracts.js';
10
- import { OPENALEX_API_BASE } from './types.js';
10
+ import { OPENALEX_API_BASE, OPENALEX_MAX_PAGE_SIZE, } from './types.js';
11
11
  const USER_AGENT = 'pubmed-mcp-server (+https://github.com/cyanheads/pubmed-mcp-server)';
12
12
  /**
13
13
  * Low-level HTTP client for OpenAlex. Single-attempt — retries upstream.
@@ -72,7 +72,12 @@ export class OpenAlexApiClient {
72
72
  /**
73
73
  * Batch-resolve a list of OpenAlex work IDs to their PMIDs.
74
74
  * Uses the filter=openalex:W1|W2|... endpoint with select=id,ids.
75
- * Returns only records that carry a `pmid` field.
75
+ *
76
+ * One call resolves one batch: the OR-filter accepts at most
77
+ * `OPENALEX_MAX_FILTER_VALUES` values and the endpoint serves at most
78
+ * `OPENALEX_MAX_PAGE_SIZE` rows, so splitting a longer list is the caller's
79
+ * job (`OpenAlexService`). Results arrive in OpenAlex's order, not the
80
+ * requested one.
76
81
  */
77
82
  resolveOaIdsToPmids(oaIds, signal) {
78
83
  if (oaIds.length === 0)
@@ -82,7 +87,7 @@ export class OpenAlexApiClient {
82
87
  const params = new URLSearchParams({
83
88
  filter: `openalex:${bareIds.join('|')}`,
84
89
  select: 'id,ids',
85
- per_page: String(Math.min(oaIds.length, 200)),
90
+ per_page: String(Math.min(oaIds.length, OPENALEX_MAX_PAGE_SIZE)),
86
91
  });
87
92
  if (this.config.email)
88
93
  params.set('mailto', this.config.email);
@@ -90,10 +95,14 @@ export class OpenAlexApiClient {
90
95
  return this.fetchWorksList(url, 'OpenAlexResolveOaIds', signal);
91
96
  }
92
97
  /**
93
- * Fetch works that cite a given OpenAlex work ID (the cited_by relationship).
94
- * Returns up to `perPage` records with PMIDs.
98
+ * Fetch one page of works that cite a given OpenAlex work ID (the cited_by
99
+ * relationship). Returns up to `perPage` records plus OpenAlex's own total.
100
+ *
101
+ * `page` is 1-based. Basic paging is bounded by `page × per_page ≤ 10,000`;
102
+ * past that OpenAlex rejects the request and requires cursor paging, so the
103
+ * caller's page cap must keep the walk inside that ceiling.
95
104
  */
96
- async getCitedBy(oaId, perPage, signal) {
105
+ async getCitedBy(oaId, perPage, page, signal) {
97
106
  // Strip full URL prefix if needed
98
107
  const bareId = oaId.startsWith('https://openalex.org/')
99
108
  ? oaId.slice('https://openalex.org/'.length)
@@ -101,14 +110,15 @@ export class OpenAlexApiClient {
101
110
  const params = new URLSearchParams({
102
111
  filter: `cites:${bareId}`,
103
112
  select: 'id,ids',
104
- per_page: String(Math.min(perPage, 200)),
113
+ per_page: String(Math.min(perPage, OPENALEX_MAX_PAGE_SIZE)),
114
+ page: String(page),
105
115
  });
106
116
  if (this.config.email)
107
117
  params.set('mailto', this.config.email);
108
118
  const url = `${OPENALEX_API_BASE}/works?${params.toString()}`;
109
119
  const ctx = requestContextService.createRequestContext({
110
120
  operation: 'OpenAlexGetCitedBy',
111
- additionalContext: { oaId: bareId },
121
+ additionalContext: { oaId: bareId, page },
112
122
  });
113
123
  let response;
114
124
  try {
@@ -135,6 +145,7 @@ export class OpenAlexApiClient {
135
145
  operation: 'OpenAlexGetCitedByDone',
136
146
  additionalContext: {
137
147
  oaId: bareId,
148
+ page,
138
149
  totalCount: parsed.meta?.count,
139
150
  resultCount: parsed.results?.length,
140
151
  },
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../../src/services/openalex/api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../../src/services/openalex/api-client.ts"],"names":[],"mappings":"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"}
@@ -5,13 +5,20 @@
5
5
  * - `citedBy(pmid, n)`: cites:W<id> filter → PMIDs (mirrors pubmed_pubmed_citedin)
6
6
  * - `references(pmid, n)`: referenced_works → PMIDs (mirrors pubmed_pubmed_refs)
7
7
  *
8
- * All three methods drop records with no PMID — never mints fake IDs.
8
+ * Each method returns at least `n` PubMed-addressable rows when OpenAlex has
9
+ * them: `citedBy` walks pages of the `cites:` filter, and the other two resolve
10
+ * the work's ID list in batches, both stopping once the window is covered,
11
+ * upstream runs out, or a cap is reached. Records with no PMID are dropped —
12
+ * never minted — and counted, so callers can disclose the shortfall instead of
13
+ * serving an unexplained empty window.
14
+ *
9
15
  * Uses the NCBI_ADMIN_EMAIL config (adminEmail) as the OpenAlex polite-pool
10
16
  * `mailto=` parameter when set; omits it when unset.
11
17
  *
12
18
  * @module src/services/openalex/openalex-service
13
19
  */
14
20
  import { OpenAlexApiClient } from './api-client.js';
21
+ import { type OpenAlexRelatedResult } from './types.js';
15
22
  /** Service facade over the OpenAlex API for the find-related provider chain. */
16
23
  export declare class OpenAlexService {
17
24
  private readonly client;
@@ -20,33 +27,43 @@ export declare class OpenAlexService {
20
27
  /**
21
28
  * Find works with similar content to the given PMID via OpenAlex `related_works`.
22
29
  * Returns PMIDs only; drops any record with no PMID.
23
- * `n` is the number of PMIDs to return (OpenAlex caps related_works at ~10).
30
+ * `n` is the size of the window the caller needs covered.
24
31
  */
25
- similar(pmid: string, n: number, signal?: AbortSignal): Promise<{
26
- pmids: string[];
27
- totalCount: number;
28
- }>;
32
+ similar(pmid: string, n: number, signal?: AbortSignal): Promise<OpenAlexRelatedResult>;
29
33
  /**
30
34
  * Find works that cite the given PMID via OpenAlex `cites:W<id>` filter.
31
35
  * Returns PMIDs only; drops any record with no PMID.
36
+ *
37
+ * Pages are all one size and are pulled until the filtered array covers `n`,
38
+ * upstream runs out, or `OPENALEX_MAX_UPSTREAM_REQUESTS` is hit — the rows OpenAlex
39
+ * serves count every citing work, but only those with a PubMed PMID are
40
+ * addressable, so a single page sized on the raw window can under-fill it.
32
41
  */
33
- citedBy(pmid: string, n: number, signal?: AbortSignal): Promise<{
34
- pmids: string[];
35
- totalCount: number;
36
- }>;
42
+ citedBy(pmid: string, n: number, signal?: AbortSignal): Promise<OpenAlexRelatedResult>;
37
43
  /**
38
44
  * Find works referenced by the given PMID via OpenAlex `referenced_works`.
39
45
  * Returns PMIDs only; drops any record with no PMID.
40
46
  */
41
- references(pmid: string, n: number, signal?: AbortSignal): Promise<{
42
- pmids: string[];
43
- totalCount: number;
44
- }>;
47
+ references(pmid: string, n: number, signal?: AbortSignal): Promise<OpenAlexRelatedResult>;
48
+ /** Look up the source work by PMID; `null` when OpenAlex doesn't index it. */
49
+ private fetchWork;
50
+ /**
51
+ * Resolve an OpenAlex ID list (a work's references or related works) to PMIDs,
52
+ * in batches, until the window is covered, the list is exhausted, or the
53
+ * request cap is reached. The work record carries the whole list, so this
54
+ * paginates resolution rather than an upstream query.
55
+ *
56
+ * Each batch's rows are re-ordered to the requested IDs, so the result follows
57
+ * the source list — a window at any offset stays stable across calls, whatever
58
+ * order OpenAlex answers a batch in.
59
+ */
60
+ private resolveIdsToPmids;
45
61
  /**
46
- * Extract unique numeric PMIDs from a list of works, excluding the source PMID.
47
- * Any work with no PMID is silently dropped (never minted).
62
+ * Shape a walk's outcome. Exhausting the fetchable set makes the addressable
63
+ * count exact; short of that only OpenAlex's own total is known, and a window
64
+ * the walk never reached is flagged rather than served as a silent empty.
48
65
  */
49
- private extractPmids;
66
+ private finish;
50
67
  /** Retry wrapper for transient errors, mirroring the EPMC service pattern. */
51
68
  private withRetry;
52
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAyCpD,gFAAgF;AAChF,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAF7B,YACmB,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,MAAM,EACjC;IAEJ;;;;OAIG;IACG,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAmBlD;IAED;;;OAGG;IACG,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAgBlD;IAED;;;OAGG;IACG,UAAU,CACd,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAmBlD;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY;IAYpB,8EAA8E;YAChE,SAAS;CAmDxB;AAMD,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,IAAI,CAmB1C;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,eAAe,CAIpD;AAED,kFAAkF;AAClF,wBAAgB,0BAA0B,IAAI,eAAe,GAAG,SAAS,CAExE"}
1
+ {"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAIL,KAAK,qBAAqB,EAE3B,MAAM,YAAY,CAAC;AAmFpB,gFAAgF;AAChF,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAF7B,YACmB,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,MAAM,EACjC;IAEJ;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAW3F;IAED;;;;;;;;OAQG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA4B3F;IAED;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAW9F;IAED,8EAA8E;IAC9E,OAAO,CAAC,SAAS;IAQjB;;;;;;;;;OASG;YACW,iBAAiB;IAiC/B;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAcd,8EAA8E;YAChE,SAAS;CAmDxB;AAMD,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,IAAI,CAmB1C;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,eAAe,CAIpD;AAED,kFAAkF;AAClF,wBAAgB,0BAA0B,IAAI,eAAe,GAAG,SAAS,CAExE"}
@@ -5,7 +5,13 @@
5
5
  * - `citedBy(pmid, n)`: cites:W<id> filter → PMIDs (mirrors pubmed_pubmed_citedin)
6
6
  * - `references(pmid, n)`: referenced_works → PMIDs (mirrors pubmed_pubmed_refs)
7
7
  *
8
- * All three methods drop records with no PMID — never mints fake IDs.
8
+ * Each method returns at least `n` PubMed-addressable rows when OpenAlex has
9
+ * them: `citedBy` walks pages of the `cites:` filter, and the other two resolve
10
+ * the work's ID list in batches, both stopping once the window is covered,
11
+ * upstream runs out, or a cap is reached. Records with no PMID are dropped —
12
+ * never minted — and counted, so callers can disclose the shortfall instead of
13
+ * serving an unexplained empty window.
14
+ *
9
15
  * Uses the NCBI_ADMIN_EMAIL config (adminEmail) as the OpenAlex polite-pool
10
16
  * `mailto=` parameter when set; omits it when unset.
11
17
  *
@@ -17,6 +23,7 @@ import { getServerConfig } from '../../config/server-config.js';
17
23
  import { recoveryFor } from '../error-contracts.js';
18
24
  import { isTransient } from '../retry-policy.js';
19
25
  import { OpenAlexApiClient } from './api-client.js';
26
+ import { OPENALEX_MAX_FILTER_VALUES, OPENALEX_MAX_PAGE_SIZE, OPENALEX_MAX_UPSTREAM_REQUESTS, } from './types.js';
20
27
  /** Transient codes eligible for retry. */
21
28
  const RETRYABLE_CODES = new Set([
22
29
  JsonRpcErrorCode.ServiceUnavailable,
@@ -24,6 +31,10 @@ const RETRYABLE_CODES = new Set([
24
31
  JsonRpcErrorCode.RateLimited,
25
32
  ]);
26
33
  const MAX_BACKOFF_MS = 30_000;
34
+ /** Strip the `https://openalex.org/` prefix an OA ID may carry. */
35
+ function bareOaId(id) {
36
+ return id.startsWith('https://openalex.org/') ? id.slice('https://openalex.org/'.length) : id;
37
+ }
27
38
  function abortableSleep(ms, signal) {
28
39
  if (!signal)
29
40
  return new Promise((r) => setTimeout(r, ms));
@@ -54,6 +65,44 @@ function extractPmid(work) {
54
65
  const match = /(\d+)\s*$/.exec(raw);
55
66
  return match?.[1] ?? null;
56
67
  }
68
+ /**
69
+ * Nothing to page through: no source work, or an empty ID list. Built per call
70
+ * rather than shared — the `pmids` array escapes to the caller by identity, and
71
+ * one module-level instance would hand every empty answer the same array.
72
+ */
73
+ function emptyResult() {
74
+ return { pmids: [], totalCount: 0, droppedNoPmid: 0, reachCapped: false };
75
+ }
76
+ /**
77
+ * Accumulates PubMed-addressable rows across pages or batches: dedupes, drops
78
+ * the source work, and counts the rows that carry no PMID. Kept as one object
79
+ * so a multi-request walk cannot reset the `seen` set between requests and
80
+ * re-emit a PMID an earlier page already returned.
81
+ */
82
+ class PmidCollector {
83
+ excludePmid;
84
+ pmids = [];
85
+ droppedNoPmid = 0;
86
+ seen = new Set();
87
+ constructor(excludePmid) {
88
+ this.excludePmid = excludePmid;
89
+ }
90
+ /**
91
+ * Add one candidate row. `undefined` means OpenAlex returned nothing for a
92
+ * requested ID — unaddressable in PubMed exactly like a row with no PMID.
93
+ */
94
+ add(work) {
95
+ const pmid = work ? extractPmid(work) : null;
96
+ if (!pmid) {
97
+ this.droppedNoPmid++;
98
+ return;
99
+ }
100
+ if (pmid === this.excludePmid || this.seen.has(pmid))
101
+ return;
102
+ this.seen.add(pmid);
103
+ this.pmids.push(pmid);
104
+ }
105
+ }
57
106
  /** Service facade over the OpenAlex API for the find-related provider chain. */
58
107
  export class OpenAlexService {
59
108
  client;
@@ -65,61 +114,96 @@ export class OpenAlexService {
65
114
  /**
66
115
  * Find works with similar content to the given PMID via OpenAlex `related_works`.
67
116
  * Returns PMIDs only; drops any record with no PMID.
68
- * `n` is the number of PMIDs to return (OpenAlex caps related_works at ~10).
117
+ * `n` is the size of the window the caller needs covered.
69
118
  */
70
119
  async similar(pmid, n, signal) {
71
- const work = await this.withRetry(() => this.client.getWorkByPmid(pmid, signal), `getWorkByPmid(${pmid})`, signal);
120
+ const work = await this.fetchWork(pmid, signal);
72
121
  if (!work)
73
- return { pmids: [], totalCount: 0 };
74
- const relatedIds = (work.related_works ?? []).slice(0, Math.min(n * 3, 50));
75
- if (relatedIds.length === 0)
76
- return { pmids: [], totalCount: 0 };
77
- const resolved = await this.withRetry(() => this.client.resolveOaIdsToPmids(relatedIds, signal), `resolveRelatedWorks(${pmid})`, signal);
78
- const pmids = this.extractPmids(resolved, pmid).slice(0, n);
79
- return { pmids, totalCount: work.related_works?.length ?? 0 };
122
+ return emptyResult();
123
+ return this.resolveIdsToPmids(pmid, work.related_works ?? [], n, `resolveRelatedWorks(${pmid})`, signal);
80
124
  }
81
125
  /**
82
126
  * Find works that cite the given PMID via OpenAlex `cites:W<id>` filter.
83
127
  * Returns PMIDs only; drops any record with no PMID.
128
+ *
129
+ * Pages are all one size and are pulled until the filtered array covers `n`,
130
+ * upstream runs out, or `OPENALEX_MAX_UPSTREAM_REQUESTS` is hit — the rows OpenAlex
131
+ * serves count every citing work, but only those with a PubMed PMID are
132
+ * addressable, so a single page sized on the raw window can under-fill it.
84
133
  */
85
134
  async citedBy(pmid, n, signal) {
86
- const work = await this.withRetry(() => this.client.getWorkByPmid(pmid, signal), `getWorkByPmid(${pmid})`, signal);
135
+ const work = await this.fetchWork(pmid, signal);
87
136
  if (!work)
88
- return { pmids: [], totalCount: 0 };
89
- const { works, totalCount } = await this.withRetry(() => this.client.getCitedBy(work.id, n, signal), `getCitedBy(${work.id})`, signal);
90
- const pmids = this.extractPmids(works, pmid);
91
- return { pmids, totalCount };
137
+ return emptyResult();
138
+ const perPage = Math.min(n, OPENALEX_MAX_PAGE_SIZE);
139
+ const collector = new PmidCollector(pmid);
140
+ let upstreamCount = 0;
141
+ let exhausted = false;
142
+ for (let page = 1; page <= OPENALEX_MAX_UPSTREAM_REQUESTS && collector.pmids.length < n; page++) {
143
+ const { works, totalCount } = await this.withRetry(() => this.client.getCitedBy(work.id, perPage, page, signal), `getCitedBy(${work.id}, page ${page})`, signal);
144
+ upstreamCount = totalCount;
145
+ for (const w of works)
146
+ collector.add(w);
147
+ if (works.length < perPage || page * perPage >= totalCount) {
148
+ exhausted = true;
149
+ break;
150
+ }
151
+ }
152
+ return this.finish(collector, exhausted, upstreamCount, n);
92
153
  }
93
154
  /**
94
155
  * Find works referenced by the given PMID via OpenAlex `referenced_works`.
95
156
  * Returns PMIDs only; drops any record with no PMID.
96
157
  */
97
158
  async references(pmid, n, signal) {
98
- const work = await this.withRetry(() => this.client.getWorkByPmid(pmid, signal), `getWorkByPmid(${pmid})`, signal);
159
+ const work = await this.fetchWork(pmid, signal);
99
160
  if (!work)
100
- return { pmids: [], totalCount: 0 };
101
- const refIds = (work.referenced_works ?? []).slice(0, Math.min(n * 3, 200));
102
- if (refIds.length === 0)
103
- return { pmids: [], totalCount: 0 };
104
- const resolved = await this.withRetry(() => this.client.resolveOaIdsToPmids(refIds, signal), `resolveReferencedWorks(${pmid})`, signal);
105
- const pmids = this.extractPmids(resolved, pmid).slice(0, n);
106
- return { pmids, totalCount: work.referenced_works?.length ?? 0 };
161
+ return emptyResult();
162
+ return this.resolveIdsToPmids(pmid, work.referenced_works ?? [], n, `resolveReferencedWorks(${pmid})`, signal);
163
+ }
164
+ /** Look up the source work by PMID; `null` when OpenAlex doesn't index it. */
165
+ fetchWork(pmid, signal) {
166
+ return this.withRetry(() => this.client.getWorkByPmid(pmid, signal), `getWorkByPmid(${pmid})`, signal);
107
167
  }
108
168
  /**
109
- * Extract unique numeric PMIDs from a list of works, excluding the source PMID.
110
- * Any work with no PMID is silently dropped (never minted).
169
+ * Resolve an OpenAlex ID list (a work's references or related works) to PMIDs,
170
+ * in batches, until the window is covered, the list is exhausted, or the
171
+ * request cap is reached. The work record carries the whole list, so this
172
+ * paginates resolution rather than an upstream query.
173
+ *
174
+ * Each batch's rows are re-ordered to the requested IDs, so the result follows
175
+ * the source list — a window at any offset stays stable across calls, whatever
176
+ * order OpenAlex answers a batch in.
111
177
  */
112
- extractPmids(works, excludePmid) {
113
- const seen = new Set();
114
- const result = [];
115
- for (const w of works) {
116
- const pmid = extractPmid(w);
117
- if (!pmid || pmid === excludePmid || seen.has(pmid))
118
- continue;
119
- seen.add(pmid);
120
- result.push(pmid);
178
+ async resolveIdsToPmids(pmid, oaIds, n, label, signal) {
179
+ if (oaIds.length === 0)
180
+ return emptyResult();
181
+ const collector = new PmidCollector(pmid);
182
+ let consumed = 0;
183
+ for (let batch = 0; batch < OPENALEX_MAX_UPSTREAM_REQUESTS &&
184
+ consumed < oaIds.length &&
185
+ collector.pmids.length < n; batch++) {
186
+ const ids = oaIds.slice(consumed, consumed + OPENALEX_MAX_FILTER_VALUES);
187
+ const resolved = await this.withRetry(() => this.client.resolveOaIdsToPmids(ids, signal), label, signal);
188
+ const byId = new Map(resolved.map((w) => [bareOaId(w.id), w]));
189
+ for (const id of ids)
190
+ collector.add(byId.get(bareOaId(id)));
191
+ consumed += ids.length;
121
192
  }
122
- return result;
193
+ return this.finish(collector, consumed >= oaIds.length, oaIds.length, n);
194
+ }
195
+ /**
196
+ * Shape a walk's outcome. Exhausting the fetchable set makes the addressable
197
+ * count exact; short of that only OpenAlex's own total is known, and a window
198
+ * the walk never reached is flagged rather than served as a silent empty.
199
+ */
200
+ finish(collector, exhausted, upstreamCount, n) {
201
+ return {
202
+ pmids: collector.pmids,
203
+ totalCount: exhausted ? collector.pmids.length : upstreamCount,
204
+ droppedNoPmid: collector.droppedNoPmid,
205
+ reachCapped: !exhausted && collector.pmids.length < n,
206
+ };
123
207
  }
124
208
  /** Retry wrapper for transient errors, mirroring the EPMC service pattern. */
125
209
  async withRetry(execute, label, signal) {
@@ -1 +1 @@
1
- {"version":3,"file":"openalex-service.js","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openalex-service.js","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,26 @@
12
12
  */
13
13
  /** Base URL for the OpenAlex public API. */
14
14
  export declare const OPENALEX_API_BASE = "https://api.openalex.org";
15
+ /**
16
+ * Rows OpenAlex serves per request. The documented `per_page` range is 1-100,
17
+ * and basic paging is additionally bounded by `page × per_page ≤ 10,000` —
18
+ * beyond that the API returns HTTP 400 and demands cursor paging.
19
+ */
20
+ export declare const OPENALEX_MAX_PAGE_SIZE = 100;
21
+ /**
22
+ * Values one `filter=openalex:W1|W2|…` OR-clause accepts. A 101st value is
23
+ * rejected with HTTP 400 ("Maximum number of values exceeded"), so a caller
24
+ * resolving a long ID list must split it into batches of this size.
25
+ */
26
+ export declare const OPENALEX_MAX_FILTER_VALUES = 100;
27
+ /**
28
+ * Upstream requests one service call may spend reaching the window — pages of
29
+ * the `cites:` filter, or ID-resolution batches. Ten pages of 100 reach 1,000
30
+ * upstream rows: past the 50-row `maxResults` ceiling at any realistic offset,
31
+ * inside the `page × per_page ≤ 10,000` basic-paging limit, and ~3s of
32
+ * sequential requests at the ~0.3s a page costs (~0.7s for a 100-ID batch).
33
+ */
34
+ export declare const OPENALEX_MAX_UPSTREAM_REQUESTS = 10;
15
35
  /**
16
36
  * Identifier block on an OpenAlex Work. Only `openalex` (the OA ID) is always
17
37
  * present; `pmid` is absent for non-PubMed records. We only care about PMIDs —
@@ -48,9 +68,22 @@ export interface OpenAlexWorksResponse {
48
68
  results?: OpenAlexWork[];
49
69
  [key: string]: unknown;
50
70
  }
51
- /** Resolved PMID list returned by service methods. */
71
+ /**
72
+ * Resolved PMID list returned by service methods. `pmids` is indexed in
73
+ * PubMed-addressable rows only — upstream records with no PMID are dropped and
74
+ * counted in `droppedNoPmid`, so a caller can disclose the gap between
75
+ * OpenAlex's own totals and what it can actually return.
76
+ */
52
77
  export interface OpenAlexRelatedResult {
78
+ /** Candidate rows fetched for this request that carry no PubMed PMID. */
79
+ droppedNoPmid: number;
53
80
  pmids: string[];
81
+ /** Paging/batching stopped at its cap before the requested window was covered. */
82
+ reachCapped: boolean;
83
+ /**
84
+ * Exact PubMed-addressable count once the fetchable set is exhausted;
85
+ * OpenAlex's own upstream count while rows remain unfetched.
86
+ */
54
87
  totalCount: number;
55
88
  }
56
89
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,iBAAiB,6BAA6B,CAAC;AAE5D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,oEAAoE;AACpE,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,sDAAsD;AACtD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,iBAAiB,6BAA6B,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,oEAAoE;AACpE,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kFAAkF;IAClF,WAAW,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -12,4 +12,24 @@
12
12
  */
13
13
  /** Base URL for the OpenAlex public API. */
14
14
  export const OPENALEX_API_BASE = 'https://api.openalex.org';
15
+ /**
16
+ * Rows OpenAlex serves per request. The documented `per_page` range is 1-100,
17
+ * and basic paging is additionally bounded by `page × per_page ≤ 10,000` —
18
+ * beyond that the API returns HTTP 400 and demands cursor paging.
19
+ */
20
+ export const OPENALEX_MAX_PAGE_SIZE = 100;
21
+ /**
22
+ * Values one `filter=openalex:W1|W2|…` OR-clause accepts. A 101st value is
23
+ * rejected with HTTP 400 ("Maximum number of values exceeded"), so a caller
24
+ * resolving a long ID list must split it into batches of this size.
25
+ */
26
+ export const OPENALEX_MAX_FILTER_VALUES = 100;
27
+ /**
28
+ * Upstream requests one service call may spend reaching the window — pages of
29
+ * the `cites:` filter, or ID-resolution batches. Ten pages of 100 reach 1,000
30
+ * upstream rows: past the 50-row `maxResults` ceiling at any realistic offset,
31
+ * inside the `page × per_page ≤ 10,000` basic-paging limit, and ~3s of
32
+ * sequential requests at the ~0.3s a page costs (~0.7s for a 100-ID batch).
33
+ */
34
+ export const OPENALEX_MAX_UPSTREAM_REQUESTS = 10;
15
35
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/openalex/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,4CAA4C;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,0BAA0B,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/openalex/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,4CAA4C;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAE5D;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/pubmed-mcp-server",
3
- "version": "2.10.8",
3
+ "version": "2.10.10",
4
4
  "mcpName": "io.github.cyanheads/pubmed-mcp-server",
5
5
  "description": "Search PubMed/Europe PMC, fetch articles and full text (PMC/EPMC/Unpaywall), citations, MeSH terms via MCP. STDIO or Streamable HTTP.",
6
6
  "type": "module",
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/cyanheads/pubmed-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.10.8",
9
+ "version": "2.10.10",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",
@@ -19,7 +19,7 @@
19
19
  "registryBaseUrl": "https://registry.npmjs.org",
20
20
  "identifier": "@cyanheads/pubmed-mcp-server",
21
21
  "runtimeHint": "bun",
22
- "version": "2.10.8",
22
+ "version": "2.10.10",
23
23
  "packageArguments": [
24
24
  {
25
25
  "type": "positional",
@@ -66,7 +66,7 @@
66
66
  "registryBaseUrl": "https://registry.npmjs.org",
67
67
  "identifier": "@cyanheads/pubmed-mcp-server",
68
68
  "runtimeHint": "bun",
69
- "version": "2.10.8",
69
+ "version": "2.10.10",
70
70
  "packageArguments": [
71
71
  {
72
72
  "type": "positional",