@pipeworx/mcp-semanticscholar 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/server.json +1 -1
- package/src/index.ts +19 -5
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Semantic Scholar Academic Graph MCP.
|
|
4
4
|
|
|
5
|
-
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 867+ live data sources.
|
|
6
6
|
|
|
7
7
|
## Tools
|
|
8
8
|
|
|
@@ -23,7 +23,7 @@ Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
|
23
23
|
}
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Or connect to the full Pipeworx gateway for access to all
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 867+ data sources:
|
|
27
27
|
|
|
28
28
|
```json
|
|
29
29
|
{
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.pipeworx-io/semanticscholar",
|
|
4
4
|
"title": "Semanticscholar",
|
|
5
5
|
"description": "Semantic Scholar Academic Graph MCP.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.1",
|
|
7
7
|
"websiteUrl": "https://pipeworx.io/packs/semanticscholar",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/pipeworx-io/mcp-semanticscholar",
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,20 @@ interface McpToolExport {
|
|
|
30
30
|
const BASE = 'https://api.semanticscholar.org/graph/v1';
|
|
31
31
|
const UA = 'pipeworx/1.0 (+https://pipeworx.io)';
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Build request headers. When a Semantic Scholar API key is available — provided
|
|
35
|
+
* by the platform (PLATFORM_SEMANTICSCHOLAR_KEY, injected by the gateway as
|
|
36
|
+
* args._apiKey) or by a caller bringing their own — send it as `x-api-key` for a
|
|
37
|
+
* dedicated rate limit. The keyless public pool 429s aggressively; a key gives a
|
|
38
|
+
* private allowance. Falls back to keyless when no key is present.
|
|
39
|
+
*/
|
|
40
|
+
function s2Headers(args: Record<string, unknown>): Record<string, string> {
|
|
41
|
+
const headers: Record<string, string> = { Accept: 'application/json', 'User-Agent': UA };
|
|
42
|
+
const key = typeof args._apiKey === 'string' ? args._apiKey.trim() : '';
|
|
43
|
+
if (key) headers['x-api-key'] = key;
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
const tools: McpToolExport['tools'] = [
|
|
34
48
|
{
|
|
35
49
|
name: 'search_papers',
|
|
@@ -139,7 +153,7 @@ function truncate(s: unknown, n: number): string | undefined {
|
|
|
139
153
|
function rateLimited(): { error: string } {
|
|
140
154
|
return {
|
|
141
155
|
error:
|
|
142
|
-
'Semantic Scholar rate limit (429). The
|
|
156
|
+
'Semantic Scholar rate limit (429). The API allows ~1 request/second (cumulative across endpoints) — retry in a moment.',
|
|
143
157
|
};
|
|
144
158
|
}
|
|
145
159
|
|
|
@@ -176,7 +190,7 @@ async function searchPapers(args: Record<string, unknown>): Promise<unknown> {
|
|
|
176
190
|
if (typeof args.fields_of_study === 'string' && args.fields_of_study.trim())
|
|
177
191
|
url += `&fieldsOfStudy=${encodeURIComponent(args.fields_of_study.trim())}`;
|
|
178
192
|
|
|
179
|
-
const res = await fetch(url, { headers:
|
|
193
|
+
const res = await fetch(url, { headers: s2Headers(args) });
|
|
180
194
|
if (res.status === 429) return rateLimited();
|
|
181
195
|
if (!res.ok) return { error: `Semantic Scholar: ${res.status} ${(await res.text()).slice(0, 200)}` };
|
|
182
196
|
|
|
@@ -195,7 +209,7 @@ async function getPaper(args: Record<string, unknown>): Promise<unknown> {
|
|
|
195
209
|
'title,abstract,year,authors,citationCount,referenceCount,venue,externalIds,url,openAccessPdf,tldr,fieldsOfStudy,publicationTypes';
|
|
196
210
|
|
|
197
211
|
const url = `${BASE}/paper/${encodeURIComponent(paperId)}?fields=${encodeURIComponent(fields)}`;
|
|
198
|
-
const res = await fetch(url, { headers:
|
|
212
|
+
const res = await fetch(url, { headers: s2Headers(args) });
|
|
199
213
|
if (res.status === 429) return rateLimited();
|
|
200
214
|
if (res.status === 404) return { error: 'paper not found', paper_id: paperId };
|
|
201
215
|
if (!res.ok) return { error: `Semantic Scholar: ${res.status} ${(await res.text()).slice(0, 200)}` };
|
|
@@ -232,7 +246,7 @@ async function getPaperCitations(args: Record<string, unknown>): Promise<unknown
|
|
|
232
246
|
const fields = 'title,year,authors,citationCount';
|
|
233
247
|
|
|
234
248
|
const url = `${BASE}/paper/${encodeURIComponent(paperId)}/citations?fields=${encodeURIComponent(fields)}&limit=${limit}`;
|
|
235
|
-
const res = await fetch(url, { headers:
|
|
249
|
+
const res = await fetch(url, { headers: s2Headers(args) });
|
|
236
250
|
if (res.status === 429) return rateLimited();
|
|
237
251
|
if (res.status === 404) return { error: 'paper not found', paper_id: paperId };
|
|
238
252
|
if (!res.ok) return { error: `Semantic Scholar: ${res.status} ${(await res.text()).slice(0, 200)}` };
|
|
@@ -261,7 +275,7 @@ async function getAuthor(args: Record<string, unknown>): Promise<unknown> {
|
|
|
261
275
|
const fields = 'name,affiliations,paperCount,citationCount,hIndex,url';
|
|
262
276
|
|
|
263
277
|
const url = `${BASE}/author/search?query=${encodeURIComponent(name)}&fields=${encodeURIComponent(fields)}&limit=5`;
|
|
264
|
-
const res = await fetch(url, { headers:
|
|
278
|
+
const res = await fetch(url, { headers: s2Headers(args) });
|
|
265
279
|
if (res.status === 429) return rateLimited();
|
|
266
280
|
if (!res.ok) return { error: `Semantic Scholar: ${res.status} ${(await res.text()).slice(0, 200)}` };
|
|
267
281
|
|