cyanheads avatar

Openalex Mcp Server

cyanheads/openalex-mcp-server

Connects Claude to OpenAlex's catalog of 270 million academic publications, authors, institutions, and topics. Exposes four tools: entity search with filters and pagination, trend aggregation for distribution analysis, name-to-ID resolution via autocomplete, and citation graph traversal in both directions. Also includes two structured prompts for literature reviews and research landscape analysis. Built on the author's mcp-ts-core framework with typed API clients, automatic ID normalization across DOI/ORCID/ROR/PMID formats, and abstract reconstruction from OpenAlex's inverted indices. Available as a public hosted instance at openalex.caseyjhand.com or self-hosted via npx. Useful for systematic literature searches, citation network analysis, tracking research trends, or resolving ambiguous author and institution names before filtering.

GitHub stars
17
tools
5
transport
STDIO, HTTP
env vars
13
last updated
Nov 11, 2026

Install

From registry metadata
$ npx add-mcp https://openalex.caseyjhand.com/mcp --name openalex

Run in your terminal. add-mcp asks which agents to add it to (Claude Code, Codex, Cursor and more). Add -g to install it for every project.

Review the command, arguments and environment values before installing. MCP servers run with your local permissions.

Other ways

Claude Code

$ claude mcp add --transport http openalex https://openalex.caseyjhand.com/mcp

Run in your terminal. Add --scope user to make it available in every project.

Tools

Verified live · 5 tools

Verified live against the running server on Nov 13, 2026.

openalex_resolve_nameResolve a name or an identifier to an OpenAlex ID. ALWAYS use this before filtering by entity — names are ambiguous, IDs are not. A name returns up to 10 autocomplete matches with disambiguation hints. An identifier — OpenAlex ID, DOI, ORCID, ROR, PMID, PMCID, ISSN, or arXiv I...3 params

Resolve a name or an identifier to an OpenAlex ID. ALWAYS use this before filtering by entity — names are ambiguous, IDs are not. A name returns up to 10 autocomplete matches with disambiguation hints. An identifier — OpenAlex ID, DOI, ORCID, ROR, PMID, PMCID, ISSN, or arXiv I...

Parameters* required
entity_typestring
Entity type to search. Awards are not among them: OpenAlex has no award autocomplete, so search awards with openalex_search_entities. Omit for cross-entity search (useful when entity type is unknow...one of works · authors · sources · institutions · topics · keywords
filtersobject
Narrow autocomplete results with a filter record object, using the openalex_search_entities value syntax; native string input is unavailable here. On numeric fields, `>=N`, `<=N`, `a..b`, `a..`, an...
query*string
Name or partial name to resolve. Also accepts an identifier, bare or in URL form — OpenAlex ID ("W2741809807", "F4320332161", or an OpenAlex record-page or API URL such as "https://openalex.org/wor...
openalex_search_entitiesSearch, filter, sort, or retrieve by ID. Covers all OpenAlex entity types (works, authors, sources, institutions, topics, keywords, publishers, funders, awards). Pass `id` to retrieve a single entity; on works it also takes an arXiv ID, found by its arXiv landing page when Ope...13 params

Search, filter, sort, or retrieve by ID. Covers all OpenAlex entity types (works, authors, sources, institutions, topics, keywords, publishers, funders, awards). Pass `id` to retrieve a single entity; on works it also takes an arXiv ID, found by its arXiv landing page when Ope...

Parameters* required
cursorstring
Pagination cursor from a previous response. Pass to get the next page. Omit it on the first call — an empty string is rejected, since a supplied-but-blank cursor is a caller mistake rather than a r...
entity_type*string
Type of scholarly entity to search.one of works · authors · sources · institutions · topics · keywords
filtersobject
Filter criteria as field:value pairs. AND across fields (multiple keys). OR within field: pipe-separate ("us|gb"). NOT: prefix "!" ("!us"). Range: "2020-2024". Comparison: ">100", "<50". On numeric...
idstring
Retrieve a single entity by ID. Supports: OpenAlex ID ("W2741809807", or its OpenAlex record-page or API URL such as "https://openalex.org/works/W2741809807" or "https://api.openalex.org/works/W274...
pageinteger
Page number (1-based) for semantic search and for a seeded `sample`, the two searches that paginate with `page` instead of `cursor`. Semantic search ranks a query-dependent candidate set whose size...
per_pageinteger
Results per page. Default 25, or under `sample` the sample size up to 100, so a sample of 100 or fewer returns whole in one call. A larger value returns at most 100 and says so — at most 50 under s...
querystring
Text search query. Supports boolean operators (AND, OR, NOT), quoted phrases ("exact match"), wildcards (machin*), fuzzy matching (machin~1), and proximity ("climate change"~5). On works, wildcards...
sampleinteger
Return a random sample of this many entities matching the filters, up to 10,000; a larger value draws 10,000 and says so (rejected instead on a server set to OPENALEX_CLAMP_PER_PAGE=false). The sam...
search_modestring
Search strategy. "keyword": stemmed full-text (default). "exact": no stemming, matches individual words (use quoted phrases for multi-word exact match), and the mode that takes wildcards on works;...one of keyword · exact · semanticdefault: keyword
seedstring
Deterministic seed for `sample`. Same seed + same filters = the same draw, page for page — required to walk a sample larger than one page with `page`, and worth passing whenever reproducibility mat...
selectarray
OpenAlex top-level field names to return. Always returned: `id`, `display_name` — additional fields you list are appended. A curated default per entity type applies to both searches and single-enti...
sliceobject
Page one array of the record at `id`: returns `id`, `display_name`, and `limit` elements of `field` from `offset` — fewer only at the array end — with the window in `windows`, whose `next` call rep...
sortstring
Sort field. Prefix with "-" for descending. Comma-separate for a multi-key sort, applied left to right, with the "-" prefix set per key ("-publication_year,cited_by_count" sorts by year descending,...
openalex_analyze_trendsAggregate OpenAlex entities into groups and count them. Use for trend analysis (group works by publication_year), distribution analysis (group by oa_status, type, country), and comparative analysis (group by institution or topic). Combine with filters to scope the analysis. Re...7 params

Aggregate OpenAlex entities into groups and count them. Use for trend analysis (group works by publication_year), distribution analysis (group by oa_status, type, country), and comparative analysis (group by institution or topic). Combine with filters to scope the analysis. Re...

Parameters* required
cursorstring
Pagination cursor from a previous response. A supplied cursor selects key order even if order is omitted or "count". Pass the next_cursor from the previous response to advance, subject to the stop...
entity_type*string
Entity type to aggregate.one of works · authors · sources · institutions · topics · keywords
filtersobject
Filter record object, using the same value syntax as openalex_search_entities; native string input is unavailable here. Narrows the population before aggregation. On numeric fields, `>=N`, `<=N`, `...
group_by*string
Field to group by. Works examples: "publication_year", "type", "oa_status", "primary_topic.field.id", "authorships.institutions.country_code", "is_retracted". The native ":include_unknown" suffix a...
include_unknownboolean
Add a group for entities with no value for the grouped field. Hidden by default unless group_by has the native ":include_unknown" suffix, which enables this setting even when the boolean is false;...default: false
orderstring
Sort order for groups. Without a cursor, omit or pass "count" (default) to return the top-N groups by count descending — no further pages. Pass "key" to enumerate distinct values in key-ascending o...one of count · key
per_pageinteger
Maximum groups per page. Default 200 (the upstream cap); positive integers above 200 clamp to 200 with a notice unless OPENALEX_CLAMP_PER_PAGE=false restores rejection. A real top-N knob in count o...default: 200
openalex_get_citation_graphWalk the citation graph one hop from a seed work. Direction picks the edge: incoming citations (`cites`), the seed's own references (`cited_by`), or OpenAlex's algorithmically-related works (`related_to`). Note: `direction` follows OpenAlex's filter convention, which inverts t...7 params

Walk the citation graph one hop from a seed work. Direction picks the edge: incoming citations (`cites`), the seed's own references (`cited_by`), or OpenAlex's algorithmically-related works (`related_to`). Note: `direction` follows OpenAlex's filter convention, which inverts t...

Parameters* required
cursorstring
Pagination cursor from a previous response. Pass to get the next page. Omit it on the first call — an empty string is rejected, since a supplied-but-blank cursor is a caller mistake rather than a r...
direction*string
"cites": works that cite seed_id (incoming citations). "cited_by": works that seed_id cites (its reference list). "related_to": OpenAlex algorithmically-related works (~8-30 typical, may be empty f...one of cites · cited_by · related_to
filtersobject
Additional filter record object to narrow the graph, using the same value syntax as openalex_search_entities; native string input is unavailable here. Example: publication_year=">2020", is_oa="true...
per_pageinteger
Results per page. Default 25; a larger value returns at most 100 and says so (rejected instead on a server set to OPENALEX_CLAMP_PER_PAGE=false).default: 25
seed_id*string
Seed work identifier. Accepts OpenAlex work ID ("W2741809807", or its OpenAlex record-page or API URL such as "https://openalex.org/works/W2741809807"), DOI ("10.1038/nature12373" or full URL), PMI...
selectarray
OpenAlex work field names to return. Always returned: id, display_name. Defaults to the curated works select if omitted. Only top-level fields project. A nested name returns the top-level object ho...
sortstring
Sort field. Prefix with "-" for descending. Comma-separate for a multi-key sort, applied left to right, with the "-" prefix set per key ("-publication_year,cited_by_count" sorts by year descending,...
openalex_describe_fieldsList valid field names for an OpenAlex entity type and context (filter, group_by, or select). Use proactively before constructing a filter or group_by to avoid invalid-field 400 errors. Pass `query` to rank the list by name similarity — useful when you have a partial or guesse...3 params

List valid field names for an OpenAlex entity type and context (filter, group_by, or select). Use proactively before constructing a filter or group_by to avoid invalid-field 400 errors. Pass `query` to rank the list by name similarity — useful when you have a partial or guesse...

Parameters* required
context*string
Field usage context. "filter": fields accepted in the filter param; created_date, updated_date, and their from_/to_ forms, where listed, are ones OpenAlex serves only on a paid plan, and a filter o...one of filter · group_by · select
entity_type*string
OpenAlex entity type to list fields for.one of works · authors · sources · institutions · topics · keywords
querystring
Optional partial or guessed field name to sort results by similarity. Pass the field you tried (e.g. "funder") to get the closest matches first. The complete field list is returned either way — a q...

Sponsors

Configuration

OPENALEX_CLAMP_PER_PAGEdefault: true

Clamp oversized count inputs (per_page, semantic-search per_page, sample, slice.limit) to their caps with a notice. Set false to reject them instead.

OPENALEX_FILTER_STRING_INPUTdefault: true

Accept unambiguous native filter strings on search_entities. Set false to require record objects.

OPENALEX_FILTER_RECORD_REPAIRdefault: true

Accept boolean and number values, nested objects, and quoted keys in the filters record, with a notice. Set false to require string values under dotted keys.

OPENALEX_SELECT_STRING_INPUTdefault: true

Accept a comma-separated select string on search_entities and get_citation_graph, with a notice. Set false to require an array.

OPENALEX_API_KEY

OpenAlex account API key, sent as api_key= - optional (free from https://openalex.org/settings/api). Without it, anonymous rate limits apply.

OPENALEX_MAILTO

Optional email sent as mailto= to identify yourself to OpenAlex (the polite pool). Separate from the API key.

OPENALEX_BASE_URLdefault: https://api.openalex.org

OpenAlex API base URL.

MCP_LOG_LEVELdefault: info

Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').

MCP_TRANSPORT_TYPEdefault: http

Selects the HTTP transport.

MCP_HTTP_HOSTdefault: 127.0.0.1

The hostname for the HTTP server.

MCP_HTTP_PORTdefault: 3010

The port to run the HTTP server on.

MCP_HTTP_ENDPOINT_PATHdefault: /mcp

The endpoint path for the MCP server.

MCP_AUTH_MODEdefault: none

Authentication mode to use: 'none', 'jwt', or 'oauth'.

Readme

@cyanheads/openalex-mcp-server

Access the OpenAlex academic research catalog — 270M+ publications — via MCP. STDIO or Streamable HTTP.

5 Tools • 2 Prompts

Version License Docker MCP SDK npm TypeScript Bun


View the full README on GitHub

Details

Repository
github.com/cyanheads/openalex-mcp-server
Package
@cyanheads/openalex-mcp-server
Transport
STDIO, HTTP
Registry
MCP registry, active
Resources
1
Prompts
2
Tools verified
Nov 13, 2026

More from cyanheads