semantic-scholar-mcp 1.0.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/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Semantic Scholar MCP
2
+
3
+ Search academic papers and read citation counts from the public Semantic Scholar API. No key required.
4
+
5
+ This file is self contained. It reads public data only and never writes to the machine. All output is bounded and honest about what could not be fetched.
6
+
7
+ ## Tools
8
+
9
+
10
+ * `search_papers` Search papers.
11
+ * `paper_info` Details and citations for a paper.
12
+
13
+ ## Usage
14
+
15
+ ```bash
16
+ npm install
17
+ npm run build
18
+ node dist/index.js
19
+ ```
20
+
21
+ Semantic Scholar rate limits anonymous access. The server retries and reports an honest error when the limit is hit.
package/dist/api.js ADDED
@@ -0,0 +1,40 @@
1
+ const BASE = "https://api.semanticscholar.org/graph/v1";
2
+ const UA = "mrfentmen-semantic-scholar-mcp/1.0 (https://github.com/mrfentmen)";
3
+ export class ScholarError extends Error {
4
+ }
5
+ async function get(url, retries = 3) {
6
+ let last = null;
7
+ for (let attempt = 0; attempt <= retries; attempt++) {
8
+ const res = await fetch(url, { headers: { "User-Agent": UA, Accept: "application/json" }, signal: AbortSignal.timeout(25000) });
9
+ if (res.status === 429) {
10
+ last = new ScholarError("Semantic Scholar rate limit hit, wait and retry");
11
+ await new Promise((r) => setTimeout(r, 3000 * (attempt + 1)));
12
+ continue;
13
+ }
14
+ if (!res.ok)
15
+ throw new ScholarError(`Semantic Scholar error ${res.status}`);
16
+ return (await res.json());
17
+ }
18
+ throw last instanceof Error ? last : new ScholarError("Semantic Scholar request failed");
19
+ }
20
+ export async function searchPapers(args) {
21
+ const q = (args.query ?? "").trim();
22
+ if (!q)
23
+ throw new ScholarError("Provide search terms");
24
+ const limit = Math.min(args.limit ?? 5, 15);
25
+ const d = await get(`${BASE}/paper/search?query=${encodeURIComponent(q)}&limit=${limit}&fields=title,year,authors,citationCount,externalIds`);
26
+ const items = d?.data ?? [];
27
+ if (!items.length)
28
+ return "No papers found";
29
+ return items.map((p, i) => {
30
+ const author = (p?.authors ?? []).slice(0, 3).map((a) => a.name).join(", ");
31
+ return `${i + 1}. ${p.title ?? "Untitled"} (${p.year ?? "year n/a"})\n ${author || "n/a"} | ${p.citationCount ?? 0} citations${p?.externalIds?.DOI ? ` | doi ${p.externalIds.DOI}` : ""}`;
32
+ }).join("\n\n");
33
+ }
34
+ export async function paperInfo(args) {
35
+ const id = (args.paperId ?? "").trim();
36
+ if (!id)
37
+ throw new ScholarError("Provide a paper ID");
38
+ const d = await get(`${BASE}/paper/${encodeURIComponent(id)}?fields=title,year,abstract,authors,citationCount,referenceCount,venue,externalIds`);
39
+ return `Title: ${d.title ?? "n/a"} (${d.year ?? "year n/a"})\nVenue: ${d.venue ?? "n/a"}\nAuthors: ${(d.authors ?? []).slice(0, 5).map((a) => a.name).join(", ") || "n/a"}\nCitations: ${d.citationCount ?? 0} | References: ${d.referenceCount ?? 0}${d.externalIds?.DOI ? ` | doi ${d.externalIds.DOI}` : ""}\n\n${(d.abstract ?? "no abstract").slice(0, 500)}`;
40
+ }
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createServer } from "./server.js";
3
+ const main = async () => { const server = createServer(); await server.connect(new StdioServerTransport()); };
4
+ main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });
package/dist/server.js ADDED
@@ -0,0 +1,38 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { paperInfo } from "./api.js";
4
+ import { searchPapers } from "./api.js";
5
+ const text = (value) => ({ content: [{ type: "text", text: value }] });
6
+ const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
7
+ const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
8
+ const error = (e) => `Error: ${e instanceof Error ? e.message : String(e)}`;
9
+ export function createServer() {
10
+ const server = new McpServer({ name: "semantic-scholar-mcp", version: "1.0.0" });
11
+ server.registerTool("search_papers", {
12
+ title: "Search papers",
13
+ description: "Search academic papers.",
14
+ inputSchema: z.object({ query: z.string().describe("Search terms."), limit: z.number().describe("Max results.").optional() }),
15
+ annotations: READ_ONLY,
16
+ }, async (args) => {
17
+ try {
18
+ return text(await searchPapers(args));
19
+ }
20
+ catch (e) {
21
+ return textError(error(e));
22
+ }
23
+ });
24
+ server.registerTool("paper_info", {
25
+ title: "Paper info",
26
+ description: "Get details and citation count for a paper.",
27
+ inputSchema: z.object({ paperId: z.string().describe("Semantic Scholar paper ID.") }),
28
+ annotations: READ_ONLY,
29
+ }, async (args) => {
30
+ try {
31
+ return text(await paperInfo(args));
32
+ }
33
+ catch (e) {
34
+ return textError(error(e));
35
+ }
36
+ });
37
+ return server;
38
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "type": "module",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/mrfentmen/semantic-scholar-mcp.git"
7
+ },
8
+ "bin": {
9
+ "semantic-scholar-mcp": "./dist/index.js"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "files": [
13
+ "dist",
14
+ "server.json",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "start": "node dist/index.js",
20
+ "dev": "npm run build && node dist/index.js"
21
+ },
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.0.4",
25
+ "zod": "^3.23.8"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.0.0",
29
+ "typescript": "^5.6.0"
30
+ },
31
+ "name": "semantic-scholar-mcp",
32
+ "description": "Search academic papers and read citations from Semantic Scholar. No key required.",
33
+ "mcpName": "io.github.mrfentmen/semantic-scholar-mcp",
34
+ "keywords": [
35
+ "mcp",
36
+ "papers",
37
+ "academic",
38
+ "citations",
39
+ "research"
40
+ ],
41
+ "engines": {
42
+ "node": ">=20"
43
+ }
44
+ }
package/server.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.mrfentmen/semantic-scholar-mcp",
4
+ "description": "Search academic papers and read citations from Semantic Scholar. No key required.",
5
+ "repository": {
6
+ "url": "https://github.com/mrfentmen/semantic-scholar-mcp",
7
+ "source": "github"
8
+ },
9
+ "version": "1.0.0",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "semantic-scholar-mcp",
14
+ "version": "1.0.0",
15
+ "transport": {
16
+ "type": "stdio"
17
+ }
18
+ }
19
+ ]
20
+ }