Scopus MCP Server
Provides tools to search Scopus for academic articles and retrieve detailed metadata, abstracts, citation counts, and DOI links for individual articles.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Scopus MCP Serverfind peer-reviewed articles on climate change adaptation"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Scopus MCP Server
An MCP server that wraps the Elsevier Scopus API so an MCP client (Claude Desktop, Claude Code, or any other MCP host) can search for and retrieve published academic articles — useful for citation verification and writing-style analysis grounded in real, peer-reviewed sources.
Tools
Tool | Input | What it returns |
|
| Up to |
|
| Full metadata for one article: everything above plus author keywords, subject areas, open-access flag, aggregation type |
|
| Just the abstract text for one article, plus |
All responses are structured JSON (see Response shape below). Every tool returns a friendly, structured error instead of throwing when the Scopus API is unreachable, rate-limited, or given a bad ID — see Error handling.
Under the hood the server calls two Elsevier APIs:
Scopus Search API (
GET /content/search/scopus) — used bysearch_scopus.Abstract Retrieval API (
GET /content/abstract/scopus_id/{id}) — used byget_article_detailsandget_article_abstract, since the Search API does not reliably return full abstracts, citation counts, or keywords.
Related MCP server: MCP-scopus
Project layout
mcp-server/
├── src/
│ ├── index.ts # stdio entry point (for local MCP clients)
│ ├── httpServer.ts # Streamable HTTP entry point (for remote deployment)
│ ├── registerTools.ts # tool definitions, shared by both entry points
│ ├── scopusClient.ts # Elsevier API client: requests, normalization, error mapping
│ ├── types.ts # TypeScript types for raw Scopus responses + normalized output
│ └── logger.ts # structured logger → stderr + logs/scopus-mcp.log
├── test/
│ └── test-connection.ts # standalone connectivity test (bypasses the MCP protocol)
├── logs/ # log file written here at runtime (gitignored)
├── .env.example
├── package.json
└── tsconfig.jsonPrerequisites
Node.js 18 or later (uses the built-in global
fetch). Check withnode -v.A Scopus API key. Register a free key at the Elsevier Developer Portal. Note that Elsevier gates full-text/abstract access by IP range (institutional subscription) or Institutional Token — a key alone is enough to test connectivity and basic search, but some fields may be limited depending on your entitlements.
Setup
cd mcp-server
npm install
cp .env.example .envEdit .env and set your key:
SCOPUS_API_KEY=your_real_key_hereSCOPUS_API_KEY is read from the environment at startup (src/scopusClient.ts); it is never
hard-coded and .env is gitignored so it can't be committed by accident.
Environment variables
Variable | Required | Default | Purpose |
| ✅ | — | Your Elsevier Scopus API key |
| optional | — | Institutional Token, if your key needs one for off-campus access |
| optional |
| Override for testing against a proxy/mock |
| optional |
| Per-request timeout |
| optional |
|
|
| HTTP mode only |
| Port for |
| HTTP mode only |
| Bind address for |
| HTTP mode, strongly recommended | — | If set, |
| HTTP mode, optional | — | Comma-separated |
Test connectivity first
Before wiring the server into any MCP client, verify the Scopus API key and network path work:
npm run test:connectionThis runs test/test-connection.ts, which calls the same client functions the tools use — but
directly, without speaking the MCP protocol — against the sample query "farmland abandonment
Nepal". You can pass your own query instead:
npm run test:connection -- "AUTH(Smith J) AND TITLE(remote sensing)"It walks through all three tools in sequence (search → details → abstract for the first result)
and prints ✅/❌ per step, plus a full request/response log at logs/scopus-mcp.log (see
Logging). Exit code is 0 only if every step succeeded.
Running locally (stdio, for a local MCP client)
npm run dev # runs src/index.ts directly via tsx, no build step
# or
npm run build && npm start # compiles to dist/ then runs the compiled serverThe server communicates over stdio, so running it directly in a terminal will just sit there waiting for JSON-RPC on stdin — that's expected. It's meant to be launched by an MCP client.
Connect it to Claude Code
claude mcp add scopus --env SCOPUS_API_KEY=your_real_key_here -- node /absolute/path/to/mcp-server/dist/index.js(run npm run build first so dist/index.js exists), or add it to a project's .mcp.json:
{
"mcpServers": {
"scopus": {
"command": "node",
"args": ["/absolute/path/to/mcp-server/dist/index.js"],
"env": { "SCOPUS_API_KEY": "your_real_key_here" }
}
}
}Connect it to Claude Desktop
Add the same block to claude_desktop_config.json
(%APPDATA%\Claude\claude_desktop_config.json on Windows,
~/Library/Application Support/Claude/claude_desktop_config.json on macOS), then restart Claude
Desktop:
{
"mcpServers": {
"scopus": {
"command": "node",
"args": ["/absolute/path/to/mcp-server/dist/index.js"],
"env": { "SCOPUS_API_KEY": "your_real_key_here" }
}
}
}Response shape
search_scopus example (truncated):
{
"query": "farmland abandonment Nepal",
"totalResults": 42,
"returnedResults": 10,
"articles": [
{
"scopusId": "85123456789",
"eid": "2-s2.0-85123456789",
"title": "Drivers of farmland abandonment in the mid-hills of Nepal",
"authors": ["Sharma B.", "Poudel K."],
"publicationYear": 2021,
"sourceTitle": "Land Use Policy",
"doi": "10.1016/j.landusepol.2021.105123",
"doiUrl": "https://doi.org/10.1016/j.landusepol.2021.105123",
"scopusUrl": "https://www.scopus.com/inward/record.uri?...",
"citedByCount": 17,
"abstract": null,
"documentType": "Article"
}
]
}get_article_details adds keywords, subjectAreas, openAccess, and aggregationType on top
of the same fields. get_article_abstract returns { scopusId, title, abstract, hasAbstract }.
Fields Scopus doesn't have for a given record come back as null (or [] for list fields, or
hasAbstract: false) rather than being omitted — check for null/false before assuming a field
is missing due to a bug.
Error handling
Every tool catches errors internally and returns isError: true with a structured JSON body
instead of crashing the MCP connection:
{
"error": true,
"kind": "rate_limited",
"message": "Scopus API rate limit exceeded (HTTP 429) for search_scopus(...). Retry after 30s.",
"status": 429,
"retryAfterSeconds": 30
}kind is one of: unauthorized (bad/missing API key), rate_limited (HTTP 429),
not_found (bad Scopus ID / HTTP 404), bad_request (empty query, malformed input),
network_error (DNS/connection failure), timeout (exceeded SCOPUS_REQUEST_TIMEOUT_MS), or
unknown. A search that succeeds but matches nothing is not an error — it returns
totalResults: 0 and a human-readable message suggesting how to broaden the query.
Logging
All API calls and responses are logged for debugging:
Every request logs its URL (API key redacted) before it's sent.
Every response logs status code, elapsed time, and a 500-character body preview.
Logs go to stderr as single-line JSON (never stdout — stdout is reserved for the MCP protocol on the stdio transport) and are also appended to
logs/scopus-mcp.log.Set
LOG_LEVEL=debugfor more detail, orLOG_LEVEL=errorto quiet things down.
Deploying to a remote/serverless platform (Render, Railway, etc.)
The stdio transport (src/index.ts) only works for MCP clients that can spawn a local process —
it's not reachable over the network. To host this server remotely, use the Streamable HTTP
entry point instead: src/httpServer.ts. It serves the same three tools at POST /mcp and adds a
GET /healthz endpoint for the platform's health checks.
Neither Render nor Railway is truly "serverless" (no scale-to-zero cold starts mid-request) — both run this as a normal persistent Node process, which is what a stateful protocol like MCP needs. Treat "serverless platform" here as "managed Node hosting."
Render
Push this repo (or just the
mcp-server/folder) to GitHub.In the Render dashboard: New → Web Service, connect the repo, set root directory to
mcp-serverif it's a subfolder of a larger repo.Build command:
npm install && npm run buildStart command:
npm run start:httpUnder Environment, add:
SCOPUS_API_KEY= your key (mark it as a secret)MCP_HTTP_AUTH_TOKEN= a long random string you generate (e.g.openssl rand -hex 32)optionally
MCP_ALLOWED_HOSTS= your Render hostname, e.g.scopus-mcp.onrender.com
Render sets
PORTautomatically —httpServer.tsreads it, no action needed.Deploy. Health check path:
/healthz.
Railway
New Project → Deploy from GitHub repo, set the service root to
mcp-serverif needed.Railway auto-detects Node; if it doesn't run the right command, set:
Build command:
npm install && npm run buildStart command:
npm run start:http
In Variables, add
SCOPUS_API_KEYandMCP_HTTP_AUTH_TOKENas above.Railway injects
PORTautomatically.Once deployed, your MCP endpoint is
https://<your-app>.up.railway.app/mcp.
Connecting an MCP client to the hosted server
claude mcp add --transport http scopus https://<your-app>/mcp \
--header "Authorization: Bearer <your MCP_HTTP_AUTH_TOKEN>"Security notes for HTTP deployment
Always set
MCP_HTTP_AUTH_TOKEN. Without it, anyone with the URL can call your tools and consume your Scopus API quota — the server logs a startup warning if it's unset.The server binds DNS-rebinding protection automatically for
localhost/127.0.0.1; for a real0.0.0.0deployment, setMCP_ALLOWED_HOSTSto your platform's hostname.Rotate
SCOPUS_API_KEYandMCP_HTTP_AUTH_TOKENvia your platform's secret manager, never by committing them to the repo.Consider putting the platform's own rate limiting / a reverse-proxy in front for public deployments, on top of Elsevier's own per-key rate limits.
Troubleshooting
Symptom | Likely cause |
|
|
| Invalid key, or key lacks Scopus Search entitlements, or missing |
| Elsevier's per-key rate/quota limit hit — back off and retry after |
| The |
| No internet access from this machine/host, corporate proxy blocking |
Tool calls silently do nothing in a stdio client | Something wrote to stdout — check you haven't added a stray |
License
MIT
Available Tools
3 toolsget_article_abstractA
Retrieve just the abstract text for a single Scopus article by its Scopus ID. Returns hasAbstract:false with a null abstract when Scopus has no abstract on file for the article (this is common for older or non-English-language records).
| Name | Required | Description | Default |
|---|---|---|---|
| scopusId | Yes | The Scopus ID of the article, e.g. "85123456789". |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the burden of behavioral disclosure. It usefully documents an important edge case: missing abstracts return hasAbstract:false with a null abstract, common for older or non-English-language records. It does not cover invalid-ID behavior, but for a simple read operation this is a reasonable level of transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with no filler. The main purpose is front-loaded, and the second sentence adds valuable edge-case behavior without being verbose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter tool with no output schema, the description covers the main purpose and a key edge case. It partially describes the return contract via hasAbstract and abstract, though it does not mention invalid-ID behavior. Overall, the core information needed to call the tool correctly is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter has full schema coverage with a description and example. The tool description adds no parameter-specific meaning beyond what the schema already provides, so the baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource: 'Retrieve just the abstract text for a single Scopus article by its Scopus ID.' The qualifiers 'just' and 'single' clearly distinguish this from the sibling tools search_scopus and get_article_details.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use this tool: when only the abstract is needed and a Scopus ID is already known. However, it does not explicitly mention alternatives or exclusions, such as using search_scopus to find the ID or get_article_details for full metadata.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_article_detailsA
Retrieve full metadata for a single Scopus article by its Scopus ID, including title, authors, publication year, source title, DOI, citation count, author keywords, subject areas, and abstract (when available). Use the scopusId returned by search_scopus.
| Name | Required | Description | Default |
|---|---|---|---|
| scopusId | Yes | The Scopus ID of the article, e.g. "85123456789" (the "SCOPUS_ID:" prefix, if present, is stripped automatically). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden. It describes what the call returns, including the conditional 'abstract (when available),' which clarifies a key edge case. It does not discuss auth, rate limits, or invalid-ID behavior, but the enumerated output is enough for this simple read operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is one sentence with no filler. The core action and ID source are front-loaded, and the field list is an efficient way to convey the return shape.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter lookup with no output schema, the description covers the essential context: what to provide, where to get it, and what metadata will come back. It could add behavior for missing/unknown IDs, but nothing critical is missing for normal invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already describes scopusId and the automatic prefix stripping, so the baseline is high. The description adds provenance semantics by telling the agent to use a scopusId returned by search_scopus, which is useful information not present in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource: 'Retrieve full metadata for a single Scopus article by its Scopus ID.' The enumeration of fields (title, authors, DOI, citation count, etc.) makes the purpose concrete and distinct from a search or abstract-only tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit usage pointer: 'Use the scopusId returned by search_scopus,' which tells the agent where the required ID comes from. It does not explicitly say when to prefer the sibling get_article_abstract over this tool, so it stops short of full exclusion guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_scopusA
Search Scopus for published academic articles by author name, keywords, title, or DOI. Returns up to count (default 10, max 25) results with title, authors, publication year, abstract (when Scopus provides one in search results), source title, DOI, and Scopus ID. Use this first to find candidate articles, then call get_article_details or get_article_abstract with a returned scopusId for full metadata.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Number of results to return (default 10, max 25). | |
| query | Yes | Search query. Supports free text (e.g. "farmland abandonment Nepal") or Scopus field-search syntax (e.g. "AUTH(Smith J)", "TITLE(remote sensing)", "DOI(10.1000/xyz123)"). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden of behavioral disclosure. It covers the return payload, count defaults and limits, and the conditional presence of abstracts. It does not mention rate limits, auth requirements, or explicit read-only status, but for a search tool the core behavior is well disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences with no filler. It front-loads the purpose, then the return contract, then the follow-up workflow—every sentence adds value and is easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Even without an output schema, the description lists the returned fields, count constraints, and a nuanced caveat about abstract availability. The tool has only two simple parameters, and the description gives an agent enough context to invoke it correctly and interpret results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description reiterates the query modes and count behavior already present in the schema without adding significant new parameter meaning beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Search'), a clear resource ('Scopus'), and identifies the search facets: author name, keywords, title, or DOI. It also distinguishes itself from siblings by describing candidate-finding versus get_article_details/get_article_abstract metadata retrieval.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states the intended workflow: 'Use this first to find candidate articles, then call get_article_details or get_article_abstract with a returned scopusId for full metadata.' This tells an agent exactly when to use this tool and how to proceed afterward.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v1.0.0- First observed
get_article_abstract - First observed
get_article_details - First observed
search_scopus
TDQS
Scored across 3 tools
The three tools have clearly distinct primary actions: searching versus retrieving by Scopus ID. However, get_article_details and get_article_abstract overlap since details also includes the abstract when available, which could cause minor confusion about which to call.
All tool names follow a consistent verb_noun pattern: search_scopus, get_article_details, get_article_abstract. The get_article_* prefix for the two retrieval tools reinforces a predictable structure.
Three tools is on the small side but appropriately scoped for a simple search-and-retrieve workflow. The count feels slightly thin for a general-purpose Scopus API wrapper, but each tool serves a necessary step in the primary flow.
The core lifecycle of discovering articles and retrieving full metadata or abstracts is covered, with no dead ends. Minor gaps exist, such as no direct citation-list or author-detail endpoints, but these are reasonable omissions given the stated purpose.
Maintenance
Related MCP Connectors
Academic research MCP server for paper search, citation checks, graphs, and deep research.
Scrape arXiv, OpenAlex and Crossref papers by author, topic, journal or DOI. Pay per row.
Academic paper search, scientific literature, citation analysis, arXiv & semantic related-work.
Scholarly search: OpenAlex, Crossref, arXiv, OpenCitations and PubMed in one endpoint.
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides access to the Elsevier Scopus API, enabling AI assistants to search for academic papers, retrieve detailed abstracts, and look up author profiles. It facilitates bibliometric research and scholarly data analysis through natural language commands.580 PyPI41MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to search and retrieve real academic papers from Scopus, preventing citation hallucination by providing accurate paper metadata, author info, and citation analysis.153MIT
- FlicenseAqualityDmaintenanceEnables searching and retrieving academic papers, authors, citations, and recommendations from Semantic Scholar via MCP.9-
- AlicenseAqualityCmaintenanceEnables AI agents to search and retrieve academic papers, author profiles, and citation data from the Scopus database via MCP tools.7MIT