@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 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 842+ live data sources.
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 842+ data sources:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipeworx/mcp-semanticscholar",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Semantic Scholar Academic Graph MCP.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
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.0",
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 keyless public API throttles aggressively wait a few seconds and retry.',
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: { Accept: 'application/json', 'User-Agent': UA } });
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: { Accept: 'application/json', 'User-Agent': UA } });
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: { Accept: 'application/json', 'User-Agent': UA } });
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: { Accept: 'application/json', 'User-Agent': UA } });
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