@cyanheads/pubmed-mcp-server 2.6.11 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/README.md +36 -14
- package/dist/config/server-config.d.ts +6 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +52 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +19 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts +71 -23
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js +671 -141
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/find-related.tool.d.ts +0 -1
- package/dist/mcp-server/tools/definitions/find-related.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/find-related.tool.js +21 -41
- package/dist/mcp-server/tools/definitions/find-related.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts +87 -0
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js +217 -0
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js.map +1 -0
- package/dist/services/error-contracts.d.ts +18 -1
- package/dist/services/error-contracts.d.ts.map +1 -1
- package/dist/services/error-contracts.js +21 -4
- package/dist/services/error-contracts.js.map +1 -1
- package/dist/services/europe-pmc/api-client.d.ts +47 -0
- package/dist/services/europe-pmc/api-client.d.ts.map +1 -0
- package/dist/services/europe-pmc/api-client.js +123 -0
- package/dist/services/europe-pmc/api-client.js.map +1 -0
- package/dist/services/europe-pmc/europe-pmc-service.d.ts +71 -0
- package/dist/services/europe-pmc/europe-pmc-service.d.ts.map +1 -0
- package/dist/services/europe-pmc/europe-pmc-service.js +243 -0
- package/dist/services/europe-pmc/europe-pmc-service.js.map +1 -0
- package/dist/services/europe-pmc/request-queue.d.ts +33 -0
- package/dist/services/europe-pmc/request-queue.d.ts.map +1 -0
- package/dist/services/europe-pmc/request-queue.js +107 -0
- package/dist/services/europe-pmc/request-queue.js.map +1 -0
- package/dist/services/europe-pmc/types.d.ts +123 -0
- package/dist/services/europe-pmc/types.d.ts.map +1 -0
- package/dist/services/europe-pmc/types.js +16 -0
- package/dist/services/europe-pmc/types.js.map +1 -0
- package/dist/services/ncbi/ncbi-service.d.ts +6 -7
- package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
- package/dist/services/ncbi/ncbi-service.js +11 -11
- package/dist/services/ncbi/ncbi-service.js.map +1 -1
- package/dist/services/ncbi/request-queue.d.ts +26 -11
- package/dist/services/ncbi/request-queue.d.ts.map +1 -1
- package/dist/services/ncbi/request-queue.js +94 -52
- package/dist/services/ncbi/request-queue.js.map +1 -1
- package/dist/services/ncbi/response-handler.js +1 -1
- package/dist/services/unpaywall/unpaywall-service.d.ts.map +1 -1
- package/dist/services/unpaywall/unpaywall-service.js +14 -13
- package/dist/services/unpaywall/unpaywall-service.js.map +1 -1
- package/package.json +7 -6
- package/server.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubmed-europepmc-search.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/pubmed-europepmc-search.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,12 +62,29 @@ export declare const UNPAYWALL_SERVICE_ERRORS: readonly [{
|
|
|
62
62
|
readonly recovery: "Retry after a brief delay; Unpaywall was unreachable. The PMC source remains the primary path.";
|
|
63
63
|
readonly retryable: true;
|
|
64
64
|
}];
|
|
65
|
+
/**
|
|
66
|
+
* Failure modes the Europe PMC service layer can surface. Tools that consume
|
|
67
|
+
* `getEuropePmcService()` should spread these into their `errors[]`.
|
|
68
|
+
*/
|
|
69
|
+
export declare const EUROPEPMC_SERVICE_ERRORS: readonly [{
|
|
70
|
+
readonly reason: "europepmc_unreachable";
|
|
71
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
72
|
+
readonly when: "Europe PMC was unreachable after all retry attempts.";
|
|
73
|
+
readonly recovery: "Retry after a brief delay; Europe PMC was unreachable. NCBI PMC and Unpaywall remain available.";
|
|
74
|
+
readonly retryable: true;
|
|
75
|
+
}, {
|
|
76
|
+
readonly reason: "europepmc_invalid_response";
|
|
77
|
+
readonly code: JsonRpcErrorCode.SerializationError;
|
|
78
|
+
readonly when: "Europe PMC returned a body that could not be parsed (invalid JSON or XML).";
|
|
79
|
+
readonly recovery: "Retry the request; Europe PMC returned a malformed response that could not be parsed.";
|
|
80
|
+
readonly retryable: true;
|
|
81
|
+
}];
|
|
65
82
|
/**
|
|
66
83
|
* Reason identifier union for type-safe authoring on the service layer.
|
|
67
84
|
* Tool handlers get a tighter typed union from `ctx.fail` / `ctx.recoveryFor`
|
|
68
85
|
* via the framework — this is the service-side fallback.
|
|
69
86
|
*/
|
|
70
|
-
export type ServiceErrorReason = (typeof NCBI_SERVICE_ERRORS)[number]['reason'] | (typeof UNPAYWALL_SERVICE_ERRORS)[number]['reason'];
|
|
87
|
+
export type ServiceErrorReason = (typeof NCBI_SERVICE_ERRORS)[number]['reason'] | (typeof UNPAYWALL_SERVICE_ERRORS)[number]['reason'] | (typeof EUROPEPMC_SERVICE_ERRORS)[number]['reason'];
|
|
71
88
|
/**
|
|
72
89
|
* Service-layer counterpart to `ctx.recoveryFor`. Returns `{ recovery: { hint } }`
|
|
73
90
|
* for the contract reason. Use at every service throw that stamps a `reason` so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-contracts.d.ts","sourceRoot":"","sources":["../../src/services/error-contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCtB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;;;;EAS3B,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,GAC9C,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"error-contracts.d.ts","sourceRoot":"","sources":["../../src/services/error-contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCtB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;;;;EAS3B,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;EAiB3B,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,GAC9C,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,GACnD,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC;AAQxD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG;IAAE,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAMtF"}
|
|
@@ -70,10 +70,27 @@ export const UNPAYWALL_SERVICE_ERRORS = [
|
|
|
70
70
|
retryable: true,
|
|
71
71
|
},
|
|
72
72
|
];
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Failure modes the Europe PMC service layer can surface. Tools that consume
|
|
75
|
+
* `getEuropePmcService()` should spread these into their `errors[]`.
|
|
76
|
+
*/
|
|
77
|
+
export const EUROPEPMC_SERVICE_ERRORS = [
|
|
78
|
+
{
|
|
79
|
+
reason: 'europepmc_unreachable',
|
|
80
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
81
|
+
when: 'Europe PMC was unreachable after all retry attempts.',
|
|
82
|
+
recovery: 'Retry after a brief delay; Europe PMC was unreachable. NCBI PMC and Unpaywall remain available.',
|
|
83
|
+
retryable: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
reason: 'europepmc_invalid_response',
|
|
87
|
+
code: JsonRpcErrorCode.SerializationError,
|
|
88
|
+
when: 'Europe PMC returned a body that could not be parsed (invalid JSON or XML).',
|
|
89
|
+
recovery: 'Retry the request; Europe PMC returned a malformed response that could not be parsed.',
|
|
90
|
+
retryable: true,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
const REASON_TO_RECOVERY = new Map([...NCBI_SERVICE_ERRORS, ...UNPAYWALL_SERVICE_ERRORS, ...EUROPEPMC_SERVICE_ERRORS].map((entry) => [entry.reason, entry.recovery]));
|
|
77
94
|
/**
|
|
78
95
|
* Service-layer counterpart to `ctx.recoveryFor`. Returns `{ recovery: { hint } }`
|
|
79
96
|
* for the contract reason. Use at every service throw that stamps a `reason` so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-contracts.js","sourceRoot":"","sources":["../../src/services/error-contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC;QACE,MAAM,EAAE,YAAY;QACpB,IAAI,EAAE,gBAAgB,CAAC,WAAW;QAClC,IAAI,EAAE,0CAA0C;QAChD,QAAQ,EAAE,qEAAqE;QAC/E,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,kBAAkB;QAC1B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,2DAA2D;QACjE,QAAQ,EAAE,4EAA4E;QACtF,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,wBAAwB;QAChC,IAAI,EAAE,gBAAgB,CAAC,OAAO;QAC9B,IAAI,EAAE,iEAAiE;QACvE,QAAQ,EAAE,+DAA+D;QACzE,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,uBAAuB;QAC/B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,mEAAmE;QACzE,QAAQ,EAAE,iFAAiF;QAC3F,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,yBAAyB;QACjC,IAAI,EAAE,gBAAgB,CAAC,QAAQ;QAC/B,IAAI,EAAE,uEAAuE;QAC7E,QAAQ,EACN,gGAAgG;QAClG,SAAS,EAAE,KAAK;KACjB;CACO,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC;QACE,MAAM,EAAE,uBAAuB;QAC/B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,qEAAqE;QAC3E,QAAQ,EACN,gGAAgG;QAClG,SAAS,EAAE,IAAI;KAChB;CACO,CAAC;
|
|
1
|
+
{"version":3,"file":"error-contracts.js","sourceRoot":"","sources":["../../src/services/error-contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC;QACE,MAAM,EAAE,YAAY;QACpB,IAAI,EAAE,gBAAgB,CAAC,WAAW;QAClC,IAAI,EAAE,0CAA0C;QAChD,QAAQ,EAAE,qEAAqE;QAC/E,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,kBAAkB;QAC1B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,2DAA2D;QACjE,QAAQ,EAAE,4EAA4E;QACtF,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,wBAAwB;QAChC,IAAI,EAAE,gBAAgB,CAAC,OAAO;QAC9B,IAAI,EAAE,iEAAiE;QACvE,QAAQ,EAAE,+DAA+D;QACzE,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,uBAAuB;QAC/B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,mEAAmE;QACzE,QAAQ,EAAE,iFAAiF;QAC3F,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,yBAAyB;QACjC,IAAI,EAAE,gBAAgB,CAAC,QAAQ;QAC/B,IAAI,EAAE,uEAAuE;QAC7E,QAAQ,EACN,gGAAgG;QAClG,SAAS,EAAE,KAAK;KACjB;CACO,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC;QACE,MAAM,EAAE,uBAAuB;QAC/B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,qEAAqE;QAC3E,QAAQ,EACN,gGAAgG;QAClG,SAAS,EAAE,IAAI;KAChB;CACO,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC;QACE,MAAM,EAAE,uBAAuB;QAC/B,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,sDAAsD;QAC5D,QAAQ,EACN,iGAAiG;QACnG,SAAS,EAAE,IAAI;KAChB;IACD;QACE,MAAM,EAAE,4BAA4B;QACpC,IAAI,EAAE,gBAAgB,CAAC,kBAAkB;QACzC,IAAI,EAAE,4EAA4E;QAClF,QAAQ,EACN,uFAAuF;QACzF,SAAS,EAAE,IAAI;KAChB;CACO,CAAC;AAYX,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAChC,CAAC,GAAG,mBAAmB,EAAE,GAAG,wBAAwB,EAAE,GAAG,wBAAwB,CAAC,CAAC,GAAG,CACpF,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAC1C,CACF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,MAA0B;IACpD,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,wDAAwD,MAAM,GAAG,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Low-level HTTP client for Europe PMC's REST API. Builds URLs,
|
|
3
|
+
* injects the optional contact email, and exposes single-attempt search and
|
|
4
|
+
* fullTextXML calls. Retry logic lives in `EuropePmcService`.
|
|
5
|
+
* @module src/services/europe-pmc/api-client
|
|
6
|
+
*/
|
|
7
|
+
import { type EuropePmcSearchParams } from './types.js';
|
|
8
|
+
export interface EuropePmcApiClientConfig {
|
|
9
|
+
email?: string;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Outcome of a fullTextXML fetch attempt. Wraps the typed contract so service
|
|
14
|
+
* callers don't need to inspect raw `Response` objects.
|
|
15
|
+
*/
|
|
16
|
+
export type EuropePmcFullTextFetchResult = {
|
|
17
|
+
kind: 'found';
|
|
18
|
+
xml: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'not-available';
|
|
21
|
+
reason: string;
|
|
22
|
+
};
|
|
23
|
+
/** Low-level HTTP client for Europe PMC. Single-attempt — retries upstream. */
|
|
24
|
+
export declare class EuropePmcApiClient {
|
|
25
|
+
private readonly config;
|
|
26
|
+
constructor(config: EuropePmcApiClientConfig);
|
|
27
|
+
/**
|
|
28
|
+
* Execute a search. Returns the raw JSON response body as a string so
|
|
29
|
+
* `EuropePmcService` can parse and surface SerializationError consistently
|
|
30
|
+
* when the body is malformed.
|
|
31
|
+
*/
|
|
32
|
+
search(params: EuropePmcSearchParams): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Fetch the JATS full-text XML for an EPMC record by its internal id.
|
|
35
|
+
* Returns `{ kind: 'not-available' }` for 404 — EPMC has the record but
|
|
36
|
+
* doesn't publish a full-text XML for it (very common for preprints).
|
|
37
|
+
*/
|
|
38
|
+
fullTextXml(epmcId: string, signal?: AbortSignal): Promise<EuropePmcFullTextFetchResult>;
|
|
39
|
+
private buildSearchUrl;
|
|
40
|
+
/**
|
|
41
|
+
* Combine the caller's query with an optional source filter. EPMC's query
|
|
42
|
+
* syntax supports `SRC:"X"` field tokens — we OR-join the requested sources
|
|
43
|
+
* into a parenthesized clause and AND it with the user's query.
|
|
44
|
+
*/
|
|
45
|
+
private buildQueryString;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../../src/services/europe-pmc/api-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,EAAsB,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAI5E,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,+EAA+E;AAC/E,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,wBAAwB;IAE7D;;;;OAIG;IACG,MAAM,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IAiC5D;;;;OAIG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAmD9F,OAAO,CAAC,cAAc;IActB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CAMzB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Low-level HTTP client for Europe PMC's REST API. Builds URLs,
|
|
3
|
+
* injects the optional contact email, and exposes single-attempt search and
|
|
4
|
+
* fullTextXML calls. Retry logic lives in `EuropePmcService`.
|
|
5
|
+
* @module src/services/europe-pmc/api-client
|
|
6
|
+
*/
|
|
7
|
+
import { McpError, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { fetchWithTimeout, httpErrorFromResponse, logger, requestContextService, } from '@cyanheads/mcp-ts-core/utils';
|
|
9
|
+
import { recoveryFor } from '../../services/error-contracts.js';
|
|
10
|
+
import { EUROPEPMC_API_BASE } from './types.js';
|
|
11
|
+
const USER_AGENT = 'pubmed-mcp-server (+https://github.com/cyanheads/pubmed-mcp-server)';
|
|
12
|
+
/** Low-level HTTP client for Europe PMC. Single-attempt — retries upstream. */
|
|
13
|
+
export class EuropePmcApiClient {
|
|
14
|
+
config;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Execute a search. Returns the raw JSON response body as a string so
|
|
20
|
+
* `EuropePmcService` can parse and surface SerializationError consistently
|
|
21
|
+
* when the body is malformed.
|
|
22
|
+
*/
|
|
23
|
+
async search(params) {
|
|
24
|
+
const url = this.buildSearchUrl(params);
|
|
25
|
+
const ctx = requestContextService.createRequestContext({
|
|
26
|
+
operation: 'EuropePmcSearch',
|
|
27
|
+
query: params.query,
|
|
28
|
+
});
|
|
29
|
+
let response;
|
|
30
|
+
try {
|
|
31
|
+
response = await fetchWithTimeout(url, this.config.timeoutMs, ctx, {
|
|
32
|
+
headers: { Accept: 'application/json', 'User-Agent': USER_AGENT },
|
|
33
|
+
...(params.signal && { signal: params.signal }),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof McpError)
|
|
38
|
+
throw error;
|
|
39
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
40
|
+
throw serviceUnavailable(`Europe PMC search request failed: ${msg}`, { reason: 'europepmc_unreachable', ...recoveryFor('europepmc_unreachable') }, { cause: error });
|
|
41
|
+
}
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw await httpErrorFromResponse(response, {
|
|
44
|
+
service: 'Europe PMC',
|
|
45
|
+
data: { url, reason: 'europepmc_unreachable', ...recoveryFor('europepmc_unreachable') },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return response.text();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetch the JATS full-text XML for an EPMC record by its internal id.
|
|
52
|
+
* Returns `{ kind: 'not-available' }` for 404 — EPMC has the record but
|
|
53
|
+
* doesn't publish a full-text XML for it (very common for preprints).
|
|
54
|
+
*/
|
|
55
|
+
async fullTextXml(epmcId, signal) {
|
|
56
|
+
const url = `${EUROPEPMC_API_BASE}/${encodeURIComponent(epmcId)}/fullTextXML`;
|
|
57
|
+
const ctx = requestContextService.createRequestContext({
|
|
58
|
+
operation: 'EuropePmcFullTextXml',
|
|
59
|
+
epmcId,
|
|
60
|
+
});
|
|
61
|
+
let response;
|
|
62
|
+
try {
|
|
63
|
+
response = await fetchWithTimeout(url, this.config.timeoutMs, ctx, {
|
|
64
|
+
headers: {
|
|
65
|
+
Accept: 'application/xml, text/xml, */*;q=0.5',
|
|
66
|
+
'User-Agent': USER_AGENT,
|
|
67
|
+
},
|
|
68
|
+
...(signal && { signal }),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof McpError)
|
|
73
|
+
throw error;
|
|
74
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
75
|
+
throw serviceUnavailable(`Europe PMC fullTextXML request failed: ${msg}`, { reason: 'europepmc_unreachable', epmcId, ...recoveryFor('europepmc_unreachable') }, { cause: error });
|
|
76
|
+
}
|
|
77
|
+
if (response.status === 404) {
|
|
78
|
+
return { kind: 'not-available', reason: 'EPMC has no fullTextXML for this record' };
|
|
79
|
+
}
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw await httpErrorFromResponse(response, {
|
|
82
|
+
service: 'Europe PMC fullTextXML',
|
|
83
|
+
data: { epmcId, reason: 'europepmc_unreachable', ...recoveryFor('europepmc_unreachable') },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const xml = await response.text();
|
|
87
|
+
if (!xml.trim()) {
|
|
88
|
+
logger.debug('Europe PMC returned an empty fullTextXML body.', requestContextService.createRequestContext({
|
|
89
|
+
operation: 'EuropePmcFullTextXmlEmpty',
|
|
90
|
+
epmcId,
|
|
91
|
+
}));
|
|
92
|
+
return { kind: 'not-available', reason: 'EPMC returned an empty fullTextXML body' };
|
|
93
|
+
}
|
|
94
|
+
return { kind: 'found', xml };
|
|
95
|
+
}
|
|
96
|
+
buildSearchUrl(params) {
|
|
97
|
+
const finalParams = {
|
|
98
|
+
query: this.buildQueryString(params),
|
|
99
|
+
format: 'json',
|
|
100
|
+
resultType: params.resultType ?? 'core',
|
|
101
|
+
pageSize: String(params.pageSize ?? 25),
|
|
102
|
+
cursorMark: params.cursorMark ?? '*',
|
|
103
|
+
};
|
|
104
|
+
if (params.sort)
|
|
105
|
+
finalParams.sort = params.sort;
|
|
106
|
+
if (this.config.email)
|
|
107
|
+
finalParams.email = this.config.email;
|
|
108
|
+
return `${EUROPEPMC_API_BASE}/search?${new URLSearchParams(finalParams).toString()}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Combine the caller's query with an optional source filter. EPMC's query
|
|
112
|
+
* syntax supports `SRC:"X"` field tokens — we OR-join the requested sources
|
|
113
|
+
* into a parenthesized clause and AND it with the user's query.
|
|
114
|
+
*/
|
|
115
|
+
buildQueryString(params) {
|
|
116
|
+
const base = params.query.trim();
|
|
117
|
+
if (!params.sources || params.sources.length === 0)
|
|
118
|
+
return base;
|
|
119
|
+
const sourceClause = params.sources.map((s) => `SRC:"${s}"`).join(' OR ');
|
|
120
|
+
return `(${base}) AND (${sourceClause})`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../../src/services/europe-pmc/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Europe PMC service. Wraps the EPMC REST API with rate-limiting,
|
|
3
|
+
* retries, and JATS XML parsing. Two methods: `search()` for keyword discovery
|
|
4
|
+
* across the EPMC corpus (MED/PMC/PPR/PAT/AGR) and `fullTextXml()` for fetching
|
|
5
|
+
* a record's full-text JATS. The XML parser matches NCBI's ordered config so
|
|
6
|
+
* `parsePmcArticle` consumes the result without modification.
|
|
7
|
+
*
|
|
8
|
+
* Optional service: only constructed when `EUROPEPMC_ENABLED=true` (the
|
|
9
|
+
* default). `getEuropePmcService()` returns `undefined` when disabled so
|
|
10
|
+
* callers can skip the chain step gracefully.
|
|
11
|
+
*
|
|
12
|
+
* @module src/services/europe-pmc/europe-pmc-service
|
|
13
|
+
*/
|
|
14
|
+
import type { JatsNode } from '../../services/ncbi/parsing/pmc-xml-helpers.js';
|
|
15
|
+
import { EuropePmcApiClient } from './api-client.js';
|
|
16
|
+
import { EuropePmcRequestQueue } from './request-queue.js';
|
|
17
|
+
import type { EuropePmcFullTextResult, EuropePmcSearchParams, EuropePmcSearchResult, EuropePmcSource } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Facade over the Europe PMC REST API. Two methods:
|
|
20
|
+
* - `search()` — keyword search across MED/PMC/PPR/PAT/AGR.
|
|
21
|
+
* - `fullTextXml()` — JATS full text for an EPMC record.
|
|
22
|
+
*
|
|
23
|
+
* Both honor `ctx.signal` for cancellation and retry transient failures with
|
|
24
|
+
* capped exponential backoff plus jitter.
|
|
25
|
+
*/
|
|
26
|
+
export declare class EuropePmcService {
|
|
27
|
+
private readonly client;
|
|
28
|
+
private readonly queue;
|
|
29
|
+
private readonly maxRetries;
|
|
30
|
+
private readonly orderedXmlParser;
|
|
31
|
+
constructor(client: EuropePmcApiClient, queue: EuropePmcRequestQueue, maxRetries: number);
|
|
32
|
+
/**
|
|
33
|
+
* Search Europe PMC. Cursor-based pagination — pass `cursorMark: '*'` (or
|
|
34
|
+
* omit) for the first page; pass the returned `nextCursorMark` for the next.
|
|
35
|
+
*/
|
|
36
|
+
search(params: EuropePmcSearchParams): Promise<EuropePmcSearchResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Fetch the JATS full text for an EPMC record. Returns:
|
|
39
|
+
* - `{ kind: 'found', xml, epmcId, source }` — JATS XML string usable
|
|
40
|
+
* directly by tool callers that hold their own parser, or via
|
|
41
|
+
* `parseFullTextXml()` for the parsed tree.
|
|
42
|
+
* - `{ kind: 'not-available', reason }` — EPMC has the record but
|
|
43
|
+
* publishes no fullTextXML (404 or empty body).
|
|
44
|
+
*/
|
|
45
|
+
fullTextXml(epmcId: string, source: EuropePmcSource, signal?: AbortSignal): Promise<EuropePmcFullTextResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Parse a JATS XML string into the ordered node tree consumed by
|
|
48
|
+
* `parsePmcArticle`. Returns the `<article>` JatsNode, or `undefined` when
|
|
49
|
+
* the body doesn't contain an article element (malformed / empty).
|
|
50
|
+
*
|
|
51
|
+
* Throws `SerializationError` only for fundamentally invalid XML; an
|
|
52
|
+
* article-free but well-formed body returns `undefined` so callers can
|
|
53
|
+
* surface a `no-epmc-fulltext` outcome without a hard failure.
|
|
54
|
+
*/
|
|
55
|
+
parseFullTextXml(xml: string): JatsNode | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Retry wrapper for transient errors. Mirrors NCBI's `withRetry` minus the
|
|
58
|
+
* service-level deadline — EPMC requests are cheaper individually and the
|
|
59
|
+
* caller (typically `ctx.signal`) bounds the total chain.
|
|
60
|
+
*/
|
|
61
|
+
private withRetry;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Initialize the Europe PMC service when enabled. Safe to call regardless of
|
|
65
|
+
* config — `EUROPEPMC_ENABLED=false` leaves the service unset so callers see
|
|
66
|
+
* `undefined` and skip the chain step.
|
|
67
|
+
*/
|
|
68
|
+
export declare function initEuropePmcService(): void;
|
|
69
|
+
/** Returns the initialized service, or `undefined` when EPMC is disabled. */
|
|
70
|
+
export declare function getEuropePmcService(): EuropePmcService | undefined;
|
|
71
|
+
//# sourceMappingURL=europe-pmc-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"europe-pmc-service.d.ts","sourceRoot":"","sources":["../../../src/services/europe-pmc/europe-pmc-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,4CAA4C,CAAC;AAEzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EACV,uBAAuB,EAEvB,qBAAqB,EAErB,qBAAqB,EACrB,eAAe,EAChB,MAAM,YAAY,CAAC;AA2BpB;;;;;;;GAOG;AACH,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAL7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAY;gBAG1B,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,qBAAqB,EAC5B,UAAU,EAAE,MAAM;IAmBrC;;;OAGG;IACG,MAAM,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA0C3E;;;;;;;OAOG;IACG,WAAW,CACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,uBAAuB,CAAC;IAkBnC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAgCnD;;;;OAIG;YACW,SAAS;CAsDxB;AAMD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAuB3C;AAED,6EAA6E;AAC7E,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,SAAS,CAElE"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Europe PMC service. Wraps the EPMC REST API with rate-limiting,
|
|
3
|
+
* retries, and JATS XML parsing. Two methods: `search()` for keyword discovery
|
|
4
|
+
* across the EPMC corpus (MED/PMC/PPR/PAT/AGR) and `fullTextXml()` for fetching
|
|
5
|
+
* a record's full-text JATS. The XML parser matches NCBI's ordered config so
|
|
6
|
+
* `parsePmcArticle` consumes the result without modification.
|
|
7
|
+
*
|
|
8
|
+
* Optional service: only constructed when `EUROPEPMC_ENABLED=true` (the
|
|
9
|
+
* default). `getEuropePmcService()` returns `undefined` when disabled so
|
|
10
|
+
* callers can skip the chain step gracefully.
|
|
11
|
+
*
|
|
12
|
+
* @module src/services/europe-pmc/europe-pmc-service
|
|
13
|
+
*/
|
|
14
|
+
import { internalError, JsonRpcErrorCode, McpError, serializationError, } from '@cyanheads/mcp-ts-core/errors';
|
|
15
|
+
import { logger, requestContextService } from '@cyanheads/mcp-ts-core/utils';
|
|
16
|
+
import { XMLParser, XMLValidator } from 'fast-xml-parser';
|
|
17
|
+
import { getServerConfig } from '../../config/server-config.js';
|
|
18
|
+
import { recoveryFor } from '../../services/error-contracts.js';
|
|
19
|
+
import { ensureArray } from '../../services/ncbi/parsing/xml-helpers.js';
|
|
20
|
+
import { EuropePmcApiClient } from './api-client.js';
|
|
21
|
+
import { EuropePmcRequestQueue } from './request-queue.js';
|
|
22
|
+
/** Retryable transient codes — same set NCBI uses. */
|
|
23
|
+
const RETRYABLE_CODES = new Set([
|
|
24
|
+
JsonRpcErrorCode.ServiceUnavailable,
|
|
25
|
+
JsonRpcErrorCode.Timeout,
|
|
26
|
+
JsonRpcErrorCode.RateLimited,
|
|
27
|
+
]);
|
|
28
|
+
const MAX_BACKOFF_MS = 30_000;
|
|
29
|
+
function abortableSleep(ms, signal) {
|
|
30
|
+
if (!signal)
|
|
31
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
32
|
+
if (signal.aborted)
|
|
33
|
+
return Promise.reject(signal.reason);
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const onAbort = () => {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
reject(signal.reason);
|
|
38
|
+
};
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
signal.removeEventListener('abort', onAbort);
|
|
41
|
+
resolve();
|
|
42
|
+
}, ms);
|
|
43
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Facade over the Europe PMC REST API. Two methods:
|
|
48
|
+
* - `search()` — keyword search across MED/PMC/PPR/PAT/AGR.
|
|
49
|
+
* - `fullTextXml()` — JATS full text for an EPMC record.
|
|
50
|
+
*
|
|
51
|
+
* Both honor `ctx.signal` for cancellation and retry transient failures with
|
|
52
|
+
* capped exponential backoff plus jitter.
|
|
53
|
+
*/
|
|
54
|
+
export class EuropePmcService {
|
|
55
|
+
client;
|
|
56
|
+
queue;
|
|
57
|
+
maxRetries;
|
|
58
|
+
orderedXmlParser;
|
|
59
|
+
constructor(client, queue, maxRetries) {
|
|
60
|
+
this.client = client;
|
|
61
|
+
this.queue = queue;
|
|
62
|
+
this.maxRetries = maxRetries;
|
|
63
|
+
/**
|
|
64
|
+
* EPMC's fullTextXML is JATS Z39.96 — same DTD PMC uses — so the parser
|
|
65
|
+
* config mirrors `NcbiResponseHandler.orderedXmlParser`. `preserveOrder`
|
|
66
|
+
* keeps inline mixed content readable; `trimValues: false` retains spaces
|
|
67
|
+
* between text and inline children.
|
|
68
|
+
*/
|
|
69
|
+
this.orderedXmlParser = new XMLParser({
|
|
70
|
+
preserveOrder: true,
|
|
71
|
+
ignoreAttributes: false,
|
|
72
|
+
attributeNamePrefix: '@_',
|
|
73
|
+
parseTagValue: true,
|
|
74
|
+
trimValues: false,
|
|
75
|
+
processEntities: true,
|
|
76
|
+
htmlEntities: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Search Europe PMC. Cursor-based pagination — pass `cursorMark: '*'` (or
|
|
81
|
+
* omit) for the first page; pass the returned `nextCursorMark` for the next.
|
|
82
|
+
*/
|
|
83
|
+
async search(params) {
|
|
84
|
+
const text = await this.queue.enqueue(() => this.withRetry(() => this.client.search(params), 'search', params.signal), 'search', params.signal);
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(text);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw serializationError('Failed to parse Europe PMC search JSON response.', {
|
|
91
|
+
reason: 'europepmc_invalid_response',
|
|
92
|
+
responseSnippet: text.substring(0, 200),
|
|
93
|
+
...recoveryFor('europepmc_invalid_response'),
|
|
94
|
+
}, { cause: error });
|
|
95
|
+
}
|
|
96
|
+
const hits = ensureArray(parsed.resultList?.result);
|
|
97
|
+
const echoed = parsed.request?.queryString ?? params.query;
|
|
98
|
+
// EPMC echoes back the input cursor mark on the final page, so absence of
|
|
99
|
+
// an explicit "next" or equality with the request's cursor marks the end.
|
|
100
|
+
const cursorMark = parsed.request?.cursorMark ?? params.cursorMark ?? '*';
|
|
101
|
+
const nextCursor = parsed.nextCursorMark && parsed.nextCursorMark !== cursorMark
|
|
102
|
+
? parsed.nextCursorMark
|
|
103
|
+
: undefined;
|
|
104
|
+
return {
|
|
105
|
+
hits,
|
|
106
|
+
hitCount: parsed.hitCount ?? hits.length,
|
|
107
|
+
...(nextCursor && { nextCursorMark: nextCursor }),
|
|
108
|
+
cursorMark,
|
|
109
|
+
query: echoed,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Fetch the JATS full text for an EPMC record. Returns:
|
|
114
|
+
* - `{ kind: 'found', xml, epmcId, source }` — JATS XML string usable
|
|
115
|
+
* directly by tool callers that hold their own parser, or via
|
|
116
|
+
* `parseFullTextXml()` for the parsed tree.
|
|
117
|
+
* - `{ kind: 'not-available', reason }` — EPMC has the record but
|
|
118
|
+
* publishes no fullTextXML (404 or empty body).
|
|
119
|
+
*/
|
|
120
|
+
async fullTextXml(epmcId, source, signal) {
|
|
121
|
+
const outcome = await this.queue.enqueue(() => this.withRetry(() => this.client.fullTextXml(epmcId, signal), `fullTextXml(${epmcId})`, signal), `fullTextXml(${epmcId})`, signal);
|
|
122
|
+
if (outcome.kind === 'not-available') {
|
|
123
|
+
return { kind: 'not-available', reason: outcome.reason };
|
|
124
|
+
}
|
|
125
|
+
return { kind: 'found', xml: outcome.xml, epmcId, source };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Parse a JATS XML string into the ordered node tree consumed by
|
|
129
|
+
* `parsePmcArticle`. Returns the `<article>` JatsNode, or `undefined` when
|
|
130
|
+
* the body doesn't contain an article element (malformed / empty).
|
|
131
|
+
*
|
|
132
|
+
* Throws `SerializationError` only for fundamentally invalid XML; an
|
|
133
|
+
* article-free but well-formed body returns `undefined` so callers can
|
|
134
|
+
* surface a `no-epmc-fulltext` outcome without a hard failure.
|
|
135
|
+
*/
|
|
136
|
+
parseFullTextXml(xml) {
|
|
137
|
+
const validationResult = XMLValidator.validate(xml.replace(/<!DOCTYPE[^>]*>/gi, ''));
|
|
138
|
+
if (validationResult !== true) {
|
|
139
|
+
throw serializationError('Received invalid XML from Europe PMC.', {
|
|
140
|
+
reason: 'europepmc_invalid_response',
|
|
141
|
+
responseSnippet: xml.substring(0, 200),
|
|
142
|
+
...recoveryFor('europepmc_invalid_response'),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
let parsed;
|
|
146
|
+
try {
|
|
147
|
+
parsed = this.orderedXmlParser.parse(xml);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const parserError = error instanceof Error ? error.message : String(error);
|
|
151
|
+
throw serializationError(`Failed to parse Europe PMC fullTextXML response: ${parserError}`, {
|
|
152
|
+
reason: 'europepmc_invalid_response',
|
|
153
|
+
parserError,
|
|
154
|
+
responseSnippet: xml.substring(0, 200),
|
|
155
|
+
...recoveryFor('europepmc_invalid_response'),
|
|
156
|
+
}, { cause: error });
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(parsed))
|
|
159
|
+
return;
|
|
160
|
+
const nodes = parsed;
|
|
161
|
+
return nodes.find((n) => 'article' in n);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Retry wrapper for transient errors. Mirrors NCBI's `withRetry` minus the
|
|
165
|
+
* service-level deadline — EPMC requests are cheaper individually and the
|
|
166
|
+
* caller (typically `ctx.signal`) bounds the total chain.
|
|
167
|
+
*/
|
|
168
|
+
async withRetry(execute, label, signal) {
|
|
169
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
170
|
+
if (signal?.aborted)
|
|
171
|
+
throw signal.reason;
|
|
172
|
+
try {
|
|
173
|
+
return await execute();
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (signal?.aborted)
|
|
177
|
+
throw signal.reason;
|
|
178
|
+
if (!(error instanceof McpError))
|
|
179
|
+
throw error;
|
|
180
|
+
if (!RETRYABLE_CODES.has(error.code))
|
|
181
|
+
throw error;
|
|
182
|
+
if (attempt < this.maxRetries) {
|
|
183
|
+
const baseDelay = Math.min(1000 * 2 ** attempt, MAX_BACKOFF_MS);
|
|
184
|
+
const jitter = baseDelay * (0.75 + 0.5 * Math.random());
|
|
185
|
+
const retryDelay = Math.round(jitter);
|
|
186
|
+
logger.warning(`Europe PMC ${label} failed. Retrying (${attempt + 1}/${this.maxRetries}) in ${retryDelay}ms.`, requestContextService.createRequestContext({
|
|
187
|
+
operation: 'EuropePmcRetry',
|
|
188
|
+
label,
|
|
189
|
+
attempt: attempt + 1,
|
|
190
|
+
retryDelay,
|
|
191
|
+
}));
|
|
192
|
+
await abortableSleep(retryDelay, signal);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const attempts = this.maxRetries + 1;
|
|
196
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
197
|
+
throw new McpError(error.code, `${msg} (failed after ${attempts} attempts)`, {
|
|
198
|
+
reason: 'europepmc_unreachable',
|
|
199
|
+
label,
|
|
200
|
+
attempts,
|
|
201
|
+
...recoveryFor('europepmc_unreachable'),
|
|
202
|
+
}, { cause: error });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
throw internalError('Europe PMC request failed after all retries.', {
|
|
206
|
+
reason: 'europepmc_unreachable',
|
|
207
|
+
label,
|
|
208
|
+
...recoveryFor('europepmc_unreachable'),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ─── Init / Accessor ────────────────────────────────────────────────────────
|
|
213
|
+
let _service;
|
|
214
|
+
/**
|
|
215
|
+
* Initialize the Europe PMC service when enabled. Safe to call regardless of
|
|
216
|
+
* config — `EUROPEPMC_ENABLED=false` leaves the service unset so callers see
|
|
217
|
+
* `undefined` and skip the chain step.
|
|
218
|
+
*/
|
|
219
|
+
export function initEuropePmcService() {
|
|
220
|
+
const config = getServerConfig();
|
|
221
|
+
if (!config.europepmcEnabled) {
|
|
222
|
+
logger.info('Europe PMC service disabled (EUROPEPMC_ENABLED=false).');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const client = new EuropePmcApiClient({
|
|
226
|
+
timeoutMs: config.europepmcTimeoutMs,
|
|
227
|
+
...(config.europepmcEmail && { email: config.europepmcEmail }),
|
|
228
|
+
});
|
|
229
|
+
const queue = new EuropePmcRequestQueue(config.europepmcRequestDelayMs);
|
|
230
|
+
_service = new EuropePmcService(client, queue, config.europepmcMaxRetries);
|
|
231
|
+
logger.info('Europe PMC service initialized.', requestContextService.createRequestContext({
|
|
232
|
+
operation: 'EuropePmcInit',
|
|
233
|
+
requestDelayMs: config.europepmcRequestDelayMs,
|
|
234
|
+
maxRetries: config.europepmcMaxRetries,
|
|
235
|
+
timeoutMs: config.europepmcTimeoutMs,
|
|
236
|
+
hasEmail: !!config.europepmcEmail,
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
/** Returns the initialized service, or `undefined` when EPMC is disabled. */
|
|
240
|
+
export function getEuropePmcService() {
|
|
241
|
+
return _service;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=europe-pmc-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"europe-pmc-service.js","sourceRoot":"","sources":["../../../src/services/europe-pmc/europe-pmc-service.ts"],"names":[],"mappings":"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"}
|