@cyanheads/pubmed-mcp-server 2.5.5 → 2.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.
- package/CLAUDE.md +1 -1
- package/README.md +11 -8
- package/dist/config/server-config.d.ts +2 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +11 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts +45 -13
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js +434 -135
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/lookup-citation.tool.d.ts +13 -1
- package/dist/mcp-server/tools/definitions/lookup-citation.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/lookup-citation.tool.js +142 -12
- package/dist/mcp-server/tools/definitions/lookup-citation.tool.js.map +1 -1
- package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
- package/dist/services/ncbi/ncbi-service.js +8 -0
- package/dist/services/ncbi/ncbi-service.js.map +1 -1
- package/dist/services/ncbi/types.d.ts +5 -0
- package/dist/services/ncbi/types.d.ts.map +1 -1
- package/dist/services/unpaywall/types.d.ts +55 -0
- package/dist/services/unpaywall/types.d.ts.map +1 -0
- package/dist/services/unpaywall/types.js +7 -0
- package/dist/services/unpaywall/types.js.map +1 -0
- package/dist/services/unpaywall/unpaywall-service.d.ts +42 -0
- package/dist/services/unpaywall/unpaywall-service.d.ts.map +1 -0
- package/dist/services/unpaywall/unpaywall-service.js +165 -0
- package/dist/services/unpaywall/unpaywall-service.js.map +1 -0
- package/package.json +5 -2
- package/server.json +15 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unpaywall service. Resolves a DOI to an open-access location
|
|
3
|
+
* and fetches the raw content (HTML or PDF) for downstream extraction. Enabled
|
|
4
|
+
* only when `UNPAYWALL_EMAIL` is set — absence leaves the fallback disabled.
|
|
5
|
+
*
|
|
6
|
+
* Philosophy: best-effort. Upstream 404s and non-OA DOIs return a `no-oa`
|
|
7
|
+
* resolution; only genuine service failures (5xx, network, timeout) throw.
|
|
8
|
+
* @module src/services/unpaywall/unpaywall-service
|
|
9
|
+
*/
|
|
10
|
+
import { type UnpaywallContent, type UnpaywallLocation, type UnpaywallResolution } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Resolves a DOI to an open-access copy via Unpaywall and fetches its bytes.
|
|
13
|
+
* Constructed only when `UNPAYWALL_EMAIL` is present; absent config → no service.
|
|
14
|
+
*/
|
|
15
|
+
export declare class UnpaywallService {
|
|
16
|
+
private readonly email;
|
|
17
|
+
private readonly timeoutMs;
|
|
18
|
+
constructor(email: string, timeoutMs: number);
|
|
19
|
+
/**
|
|
20
|
+
* Look up a DOI in Unpaywall. Returns a structured outcome — never throws
|
|
21
|
+
* for a DOI that simply has no OA copy or that Unpaywall doesn't know.
|
|
22
|
+
* Throws `McpError(ServiceUnavailable)` only for network/server failures.
|
|
23
|
+
*/
|
|
24
|
+
resolve(doi: string, signal?: AbortSignal): Promise<UnpaywallResolution>;
|
|
25
|
+
/**
|
|
26
|
+
* Fetch the content at an Unpaywall location. Prefers `url_for_pdf` when
|
|
27
|
+
* present (direct PDF bytes) and falls back to `url` (HTML landing page).
|
|
28
|
+
* Throws `McpError(ServiceUnavailable)` on network/server failures or
|
|
29
|
+
* unreadable responses — caller handles partial failures.
|
|
30
|
+
*/
|
|
31
|
+
fetchContent(location: UnpaywallLocation, signal?: AbortSignal): Promise<UnpaywallContent>;
|
|
32
|
+
private fetchAs;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the Unpaywall service if `UNPAYWALL_EMAIL` is configured.
|
|
36
|
+
* Safe to call regardless — absence of the env var leaves the service unset,
|
|
37
|
+
* and `getUnpaywallService()` returns `undefined`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function initUnpaywallService(): void;
|
|
40
|
+
/** Returns the initialized service, or `undefined` when the fallback is disabled. */
|
|
41
|
+
export declare function getUnpaywallService(): UnpaywallService | undefined;
|
|
42
|
+
//# sourceMappingURL=unpaywall-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unpaywall-service.d.ts","sourceRoot":"","sources":["../../../src/services/unpaywall/unpaywall-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAEzB,MAAM,YAAY,CAAC;AAIpB;;;GAGG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM;IAGpC;;;;OAIG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAkD9E;;;;;OAKG;IACG,YAAY,CAAC,QAAQ,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAkBlF,OAAO;CAqDtB;AAiBD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAU3C;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,SAAS,CAElE"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unpaywall service. Resolves a DOI to an open-access location
|
|
3
|
+
* and fetches the raw content (HTML or PDF) for downstream extraction. Enabled
|
|
4
|
+
* only when `UNPAYWALL_EMAIL` is set — absence leaves the fallback disabled.
|
|
5
|
+
*
|
|
6
|
+
* Philosophy: best-effort. Upstream 404s and non-OA DOIs return a `no-oa`
|
|
7
|
+
* resolution; only genuine service failures (5xx, network, timeout) throw.
|
|
8
|
+
* @module src/services/unpaywall/unpaywall-service
|
|
9
|
+
*/
|
|
10
|
+
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
11
|
+
import { fetchWithTimeout, logger, requestContextService } from '@cyanheads/mcp-ts-core/utils';
|
|
12
|
+
import { getServerConfig } from '../../config/server-config.js';
|
|
13
|
+
import { UNPAYWALL_API_BASE, } from './types.js';
|
|
14
|
+
const USER_AGENT = 'pubmed-mcp-server (+https://github.com/cyanheads/pubmed-mcp-server)';
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a DOI to an open-access copy via Unpaywall and fetches its bytes.
|
|
17
|
+
* Constructed only when `UNPAYWALL_EMAIL` is present; absent config → no service.
|
|
18
|
+
*/
|
|
19
|
+
export class UnpaywallService {
|
|
20
|
+
email;
|
|
21
|
+
timeoutMs;
|
|
22
|
+
constructor(email, timeoutMs) {
|
|
23
|
+
this.email = email;
|
|
24
|
+
this.timeoutMs = timeoutMs;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Look up a DOI in Unpaywall. Returns a structured outcome — never throws
|
|
28
|
+
* for a DOI that simply has no OA copy or that Unpaywall doesn't know.
|
|
29
|
+
* Throws `McpError(ServiceUnavailable)` only for network/server failures.
|
|
30
|
+
*/
|
|
31
|
+
async resolve(doi, signal) {
|
|
32
|
+
const normalized = normalizeDoi(doi);
|
|
33
|
+
if (!normalized)
|
|
34
|
+
return { kind: 'no-oa', reason: 'Invalid DOI' };
|
|
35
|
+
const url = `${UNPAYWALL_API_BASE}/${encodeURIComponent(normalized)}?email=${encodeURIComponent(this.email)}`;
|
|
36
|
+
const ctx = requestContextService.createRequestContext({ operation: 'UnpaywallResolve', doi });
|
|
37
|
+
let response;
|
|
38
|
+
try {
|
|
39
|
+
response = await fetchWithTimeout(url, this.timeoutMs, ctx, {
|
|
40
|
+
headers: { Accept: 'application/json', 'User-Agent': USER_AGENT },
|
|
41
|
+
...(signal && { signal }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
46
|
+
throw new McpError(JsonRpcErrorCode.ServiceUnavailable, `Unpaywall request failed: ${msg}`, {
|
|
47
|
+
doi: normalized,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (response.status === 404)
|
|
51
|
+
return { kind: 'no-oa', reason: 'DOI unknown to Unpaywall' };
|
|
52
|
+
if (response.status === 422)
|
|
53
|
+
return { kind: 'no-oa', reason: 'Invalid DOI format' };
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const body = await response.text().catch(() => '');
|
|
56
|
+
throw new McpError(response.status >= 500
|
|
57
|
+
? JsonRpcErrorCode.ServiceUnavailable
|
|
58
|
+
: JsonRpcErrorCode.InvalidRequest, `Unpaywall returned HTTP ${response.status}.`, { doi: normalized, status: response.status, body: body.substring(0, 300) });
|
|
59
|
+
}
|
|
60
|
+
const data = (await response.json());
|
|
61
|
+
if (!data.is_oa)
|
|
62
|
+
return { kind: 'no-oa', reason: 'No open-access copy indexed' };
|
|
63
|
+
const location = data.best_oa_location ?? data.oa_locations?.[0];
|
|
64
|
+
if (!location?.url)
|
|
65
|
+
return { kind: 'no-oa', reason: 'OA flagged but no usable location URL' };
|
|
66
|
+
logger.debug('Unpaywall resolved DOI', {
|
|
67
|
+
doi: normalized,
|
|
68
|
+
hostType: location.host_type ?? null,
|
|
69
|
+
license: location.license ?? null,
|
|
70
|
+
version: location.version ?? null,
|
|
71
|
+
});
|
|
72
|
+
return { kind: 'found', location };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fetch the content at an Unpaywall location. Prefers `url_for_pdf` when
|
|
76
|
+
* present (direct PDF bytes) and falls back to `url` (HTML landing page).
|
|
77
|
+
* Throws `McpError(ServiceUnavailable)` on network/server failures or
|
|
78
|
+
* unreadable responses — caller handles partial failures.
|
|
79
|
+
*/
|
|
80
|
+
async fetchContent(location, signal) {
|
|
81
|
+
const pdfUrl = location.url_for_pdf ?? undefined;
|
|
82
|
+
const htmlUrl = location.url;
|
|
83
|
+
if (pdfUrl) {
|
|
84
|
+
try {
|
|
85
|
+
return await this.fetchAs(pdfUrl, 'pdf', signal);
|
|
86
|
+
}
|
|
87
|
+
catch (pdfErr) {
|
|
88
|
+
logger.debug('Unpaywall PDF fetch failed; falling back to HTML URL', {
|
|
89
|
+
url: pdfUrl,
|
|
90
|
+
error: pdfErr instanceof Error ? pdfErr.message : String(pdfErr),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return this.fetchAs(htmlUrl, 'auto', signal);
|
|
95
|
+
}
|
|
96
|
+
async fetchAs(url, expected, signal) {
|
|
97
|
+
const ctx = requestContextService.createRequestContext({
|
|
98
|
+
operation: 'UnpaywallFetch',
|
|
99
|
+
url,
|
|
100
|
+
expected,
|
|
101
|
+
});
|
|
102
|
+
const accept = expected === 'pdf'
|
|
103
|
+
? 'application/pdf,*/*;q=0.5'
|
|
104
|
+
: 'text/html,application/pdf;q=0.9,*/*;q=0.5';
|
|
105
|
+
let response;
|
|
106
|
+
try {
|
|
107
|
+
response = await fetchWithTimeout(url, this.timeoutMs, ctx, {
|
|
108
|
+
headers: { Accept: accept, 'User-Agent': USER_AGENT },
|
|
109
|
+
redirect: 'follow',
|
|
110
|
+
...(signal && { signal }),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
115
|
+
throw new McpError(JsonRpcErrorCode.ServiceUnavailable, `Unpaywall content fetch failed: ${msg}`, { url });
|
|
116
|
+
}
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
throw new McpError(response.status >= 500
|
|
119
|
+
? JsonRpcErrorCode.ServiceUnavailable
|
|
120
|
+
: JsonRpcErrorCode.InvalidRequest, `Unpaywall content fetch returned HTTP ${response.status}.`, { url, status: response.status });
|
|
121
|
+
}
|
|
122
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
|
123
|
+
const fetchedUrl = response.url || url;
|
|
124
|
+
if (contentType.includes('pdf') || expected === 'pdf') {
|
|
125
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
126
|
+
return { kind: 'pdf', fetchedUrl, body: bytes };
|
|
127
|
+
}
|
|
128
|
+
const text = await response.text();
|
|
129
|
+
return { kind: 'html', fetchedUrl, body: text };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Strip a leading `doi:` prefix or URL wrapping and return a clean DOI, or
|
|
134
|
+
* undefined when the input can't be coerced into one.
|
|
135
|
+
*/
|
|
136
|
+
function normalizeDoi(input) {
|
|
137
|
+
const trimmed = input.trim();
|
|
138
|
+
if (!trimmed)
|
|
139
|
+
return;
|
|
140
|
+
const withoutScheme = trimmed.replace(/^doi:/i, '').replace(/^https?:\/\/(dx\.)?doi\.org\//i, '');
|
|
141
|
+
return withoutScheme.startsWith('10.') ? withoutScheme : undefined;
|
|
142
|
+
}
|
|
143
|
+
// ─── Init / Accessor ────────────────────────────────────────────────────────
|
|
144
|
+
let _service;
|
|
145
|
+
/**
|
|
146
|
+
* Initialize the Unpaywall service if `UNPAYWALL_EMAIL` is configured.
|
|
147
|
+
* Safe to call regardless — absence of the env var leaves the service unset,
|
|
148
|
+
* and `getUnpaywallService()` returns `undefined`.
|
|
149
|
+
*/
|
|
150
|
+
export function initUnpaywallService() {
|
|
151
|
+
const config = getServerConfig();
|
|
152
|
+
if (!config.unpaywallEmail) {
|
|
153
|
+
logger.info('Unpaywall fallback disabled (UNPAYWALL_EMAIL not set).');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
_service = new UnpaywallService(config.unpaywallEmail, config.unpaywallTimeoutMs);
|
|
157
|
+
logger.info('Unpaywall service initialized.', {
|
|
158
|
+
timeoutMs: config.unpaywallTimeoutMs,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** Returns the initialized service, or `undefined` when the fallback is disabled. */
|
|
162
|
+
export function getUnpaywallService() {
|
|
163
|
+
return _service;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=unpaywall-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unpaywall-service.js","sourceRoot":"","sources":["../../../src/services/unpaywall/unpaywall-service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/pubmed-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"mcpName": "io.github.cyanheads/pubmed-mcp-server",
|
|
5
5
|
"description": "MCP server for PubMed/NCBI E-utilities integration. Search articles, fetch metadata, generate citations, explore MeSH terms, and discover related research.",
|
|
6
6
|
"type": "module",
|
|
@@ -69,9 +69,12 @@
|
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@cyanheads/mcp-ts-core": "^0.7.0",
|
|
72
|
+
"defuddle": "^0.18.1",
|
|
72
73
|
"fast-xml-parser": "^5.7.1",
|
|
74
|
+
"linkedom": "^0.18.12",
|
|
73
75
|
"pino-pretty": "^13.1.3",
|
|
74
|
-
"sanitize-html": "^2.17.3"
|
|
76
|
+
"sanitize-html": "^2.17.3",
|
|
77
|
+
"unpdf": "^1.6.0"
|
|
75
78
|
},
|
|
76
79
|
"devDependencies": {
|
|
77
80
|
"@biomejs/biome": "^2.4.13",
|
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.
|
|
9
|
+
"version": "2.6.0",
|
|
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.
|
|
22
|
+
"version": "2.6.0",
|
|
23
23
|
"packageArguments": [
|
|
24
24
|
{
|
|
25
25
|
"type": "positional",
|
|
@@ -49,6 +49,12 @@
|
|
|
49
49
|
"description": "Contact email sent with NCBI requests (recommended by NCBI).",
|
|
50
50
|
"format": "string",
|
|
51
51
|
"isRequired": false
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "UNPAYWALL_EMAIL",
|
|
55
|
+
"description": "Contact email for Unpaywall. When set, pubmed_fetch_fulltext falls back to Unpaywall open-access copies for DOIs not in PMC.",
|
|
56
|
+
"format": "string",
|
|
57
|
+
"isRequired": false
|
|
52
58
|
}
|
|
53
59
|
],
|
|
54
60
|
"transport": {
|
|
@@ -60,7 +66,7 @@
|
|
|
60
66
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
61
67
|
"identifier": "@cyanheads/pubmed-mcp-server",
|
|
62
68
|
"runtimeHint": "bun",
|
|
63
|
-
"version": "2.
|
|
69
|
+
"version": "2.6.0",
|
|
64
70
|
"packageArguments": [
|
|
65
71
|
{
|
|
66
72
|
"type": "positional",
|
|
@@ -124,6 +130,12 @@
|
|
|
124
130
|
"description": "Contact email sent with NCBI requests (recommended by NCBI).",
|
|
125
131
|
"format": "string",
|
|
126
132
|
"isRequired": false
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "UNPAYWALL_EMAIL",
|
|
136
|
+
"description": "Contact email for Unpaywall. When set, pubmed_fetch_fulltext falls back to Unpaywall open-access copies for DOIs not in PMC.",
|
|
137
|
+
"format": "string",
|
|
138
|
+
"isRequired": false
|
|
127
139
|
}
|
|
128
140
|
],
|
|
129
141
|
"transport": {
|