.codex-plugin — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited .codex-plugin (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/orcid-mcp-server</h1> <p><b>Search and retrieve researcher profiles, works, affiliations, funding, and peer review records from the ORCID registry via MCP. STDIO or Streamable HTTP.</b> <div>9 Tools • 2 Resources</div> </p> </div>
<div align="center">
</div>
<div align="center">
</div>
<div align="center">
Public Hosted Server: https://orcid.caseyjhand.com/mcp
</div>
Researcher identity data from the ORCID registry. Search and disambiguate authors, build a researcher dossier from profile, works, affiliations, funding, and peer review records, and chain external identifiers to Crossref, PubMed, or arXiv.
| Tool | Description |
|---|---|
orcid_search_researchers | Search the ORCID registry using structured field params (name, affiliation, keyword, ROR ID, DOI, PMID, grant number) |
orcid_get_profile | Fetch a researcher's public profile — name, other names, biography, keywords, researcher URLs, external identifiers |
orcid_get_works | Retrieve works (publications, datasets, software, preprints) for a researcher, filtered by work type and publication year, paginated |
orcid_get_work_detail | Accept up to 1,000 put-codes, fetch the first 100 distinct works, and return deferred codes for continuation |
orcid_get_affiliations | Fetch affiliation records: employment, education, memberships, and more |
orcid_get_funding | List grouped funding summaries or select one award's deposited description, amount, and source |
orcid_get_peer_reviews | Fetch peer review activity: convening organizations, reviewer role, review type |
orcid_get_research_resources | List research resources — compute allocations, equipment access, lab facilities |
orcid_resolve_researcher | Disambiguate an ambiguous author name to a ranked list of verified ORCID iD candidates |
| Resource | Description |
|---|---|
orcid://researcher/{orcid_id}/profile | Researcher profile (person section) — name, other names, bio, keywords, external IDs |
orcid://researcher/{orcid_id}/works | Works list for a researcher — the first 25 plus the total count |
All resource data is also reachable via tools. Use resources when injecting stable researcher context into a prompt; use tools when filtering or processing results is needed.
The server also publishes an agent skill, orcid-guide (skill://orcid-guide/SKILL.md, source in skills/orcid-guide/): identity-confirmation gates, identifier chains between the tools, and raw Solr field syntax.
orcid_search_researchers <sub>tool</sub>given_name, family_name, affiliation, keyword, ror_id, doi, pmid, grant_number — AND together automatically; query adds raw Solr syntax — sent as written when it is the only field, otherwise ANDed as one parenthesized group so a top-level OR keeps its alternatives (an exclusion-only query such as -keyword:x is ANDed ungrouped, since ORCID matches nothing for a group of only exclusions)given_name phrase-matches, except that a value of only initials (J., J. A.) matches given names starting with each letterdoi and pmid map to doi-self / pmid-self field queries — finds researchers who linked that specific work to their ORCID record. URL and label forms (https://doi.org/…, doi:…, https://pubmed.ncbi.nlm.nih.gov/…/, PMID:…) are accepted and reduced to the bare identifier, preserving its case. Nonblank values must contain a complete DOI (10. followed by digits, /, and a non-whitespace suffix) or numeric PMID; malformed and prefix-only values fail before a request. Omitted or genuinely blank fields remain unsetror_id takes a ROR ID bare (00f54p054) or as a ror.org URL (https://ror.org/…, ror.org/…, a www. host, a trailing slash), in either letter case, and is reduced to the https://ror.org/<lowercase id> form ORCID indexes; a value that is not a ROR ID, or whose check digits do not match, is rejected before any search runsgrant_number phrase-matches the grant numbers on researchers' funding items, case-insensitively, over the parts between separators such as hyphens and slashes — 5F31MH010500 also matches 5F31MH010500-03, but a number cut mid-part matches nothing — pair with orcid_get_funding to inspect each matchrows: results per page, default 20; a larger value (up to 10,000) is clamped to 1,000, the most ORCID serves per request, and a notice names both sizes on both surfaces. start: 0–10,000 offset pagination (the ORCID Public API's ceiling for unauthenticated requests)rows results, fewer only at the end of the matches; nextStart is start plus the number returned while matches remainorcid_resolve_researcher for ambiguous names needing ranked disambiguationorcid_get_profile <sub>tool</sub>0000-0001-2345-6789) or an orcid.org URI (https://orcid.org/…, orcid.org/…, a www. host, a trailing slash); a lowercase x check digit is canonicalized to X. Every tool that takes orcid_id accepts the same forms; the orcid://researcher/{orcid_id}/… resources take the bare iDorcidId/orcidUri report the primary iD; on the tools, selfAsserted is judged against it and a notice names both iDsnotice names every section with no public data (e.g. "no public email addresses or countries"), since an empty section can be private rather than emptyorcid_get_works <sub>tool</sub>work_types keeps works of the listed types (ORCID's 58-value vocabulary, e.g. journal-article, preprint, data-set; any listed type matches, an empty list filters nothing); year_from and year_to keep works published in that inclusive range, read from the first four digits of publicationDate. Under a year bound, undated works drop out and a notice counts them. The filters run over the whole record before paging, at no extra upstream cost: matchedCount counts the matches, workCount the whole record, and appliedFilters echoes what was appliedlimit is clamped to 1,000 (accepted up to 10,000) with a notice naming both sizes. Page with offset and the returned nextOffset; a page holds limit works, fewer only at the end, and truncated says more remain. An offset past the matches returns a notice saying so; no match returns a notice naming the filters and workCountinclude_external_ids to false to drop DOI/PMID/arXiv/ISBN identifier lists for a lighter payloadputCode to orcid_get_work_detail for abstracts and contributor listssources — the researcher, or member organizations such as Crossref or a university system — with selfAsserted marking what the researcher asserted; an empty list does not mean no publicationsorcid_get_work_detail <sub>tool</sub>put_codes: 1–1,000 raw codes per call (from orcid_get_works), deduplicated in first-occurrence order before fetching the first 100 distinct codes in one request; every record ORCID returns for them comes back, and a notice names the input cap when appliedcontributorCount and contributorsTruncated marking the cut; a citation over 8,192 bytes is left out and flagged citationOmitted, so a large-collaboration paper costs about what its other fields do. Even so, 100 large-collaboration records run past 1 MB: size batches to what the step needsorcid_get_works lists for the work group; sources names that sourceerrors entries — the rest of the batch still resolvesdeferredPutCodes lists, in request order, the distinct codes beyond the input cap and any ORCID's answer left out; pass it as put_codes in another call. Notices distinguish the two causesRateLimited error carrying ORCID's retryAfter when it sent oneorcid_get_affiliations <sub>tool</sub>types filters which sections to return: employment, education, invited-positions, distinctions, memberships, qualifications, services, or all — default is employment + education; an explicit empty list is rejectedsources names who added it — the researcher, or a member organization such as a university research information system; an empty result does not mean no affiliationorcid_get_funding <sub>tool</sub>put_code, returns the complete grouped funding list in one request with view: summary; each preferred summary exposes its optional putCodefundingCount; when the group's versions record distinct award periods, such as renewals under one grant number, periods lists eachputCode as put_code for view: detail: fetches only that deposit, with its own source and optional description, URL, and amount: { value?, currency? }. Decimal text is preserved exactly; missing amounts and currencies stay absentfunding_not_found and denied public access returns funding_inaccessible; each error explains how to recoversources names which; most researchers with real grants have no entries here, and absence does not imply no fundingorcid_get_peer_reviews <sub>tool</sub>offset (default 0) and limit (default 25; a larger value, up to 10,000, is clamped to 1,000 with a notice naming both sizes on both surfaces)reviewCount counts returned reviews, limit of them, fewer only at the end; totalCount reports the full history. Continue with nextOffset when truncated is true. Empty and past-end pages retain the total with no continuation, and an offset past a non-empty history returns a notice saying soreviewer, editor, chair, etc.), review type, completion date, an ISSN-keyed group identifier, and the convening organization ORCID records per recordsources names which; coverage varies widely by researcherorcid_get_research_resources <sub>tool</sub>sources that deposited itview: summary lists proposals; proposalHosts includes every proposal host, while hostOrganization keeps the first host for compatibilityputCode as put_code for view: detail: each item carries its name, type, all operators in hosts, external identifiers, and URL; proposal provenance stays on the resource recorditem_limit items from item_offset: item_offset defaults to 0, item_limit to 25, and a larger item_limit returns at most 100 (accepted maximum 10,000); both require put_code. itemCount, returnedItemCount, itemOffset, itemsTruncated, and nextItemOffset describe the page; top-level truncated mirrors itemsTruncated, and itemLimitNotice discloses a reduced limit on both surfacesnextItemOffset with the same put_code. Empty and past-end pages have no continuationorcid_resolve_researcher <sub>tool</sub>rows default 5; a larger value, up to 10,000, returns at most 20 at every search stage) with transparent disambiguation signals: name match type (exact/partial/other-name/none), institution overlap flag, and anchor type (doi/pmid/none). A notice reports requested and effective sizes when capped, alongside empty-result guidanceJosé Baselga and Jose Baselga classify the same candidate identicallydoi or pmid is provided (bare or as a URL), uses doi-self or pmid-self as an anchor — researchers who have linked that work to their ORCID record are near-deterministic matchesJennifer Doudna), a byline with initials (J. Doudna, Jennifer A. Doudna), or Family, Given (Doudna, Jennifer, read as Jennifer Doudna); an initial counts toward a partial match when the candidate's first given name starts with itorcid://researcher/{orcid_id}/profile <sub>resource</sub>application/json{orcid_id} is the bare iD, with an X or x check digit — a URI segment cannot carry the https://orcid.org/ formdata.reason and a recovery hint, like the tools: invalid_orcid_id for a checksum-invalid iD (rejected locally, before any upstream call), profile_not_found for an iD ORCID does not know or a record with no public nameorcid_get_profile tool when the response needs to flow into conditional logicorcid://researcher/{orcid_id}/works <sub>resource</sub>workCount (the total available) as application/json{orcid_id} is the bare iD, with an X or x check digitinvalid_orcid_id (checksum, before any upstream call) or profile_not_found (unknown iD, data.orcidId included), each with a recovery hintorcid_get_works tool to page the full list or filter resultsBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
ORCID-specific:
https://pub.orcid.org/v3.0) — no API key required for public read endpointsexpanded-search as the primary search backend — returns ORCID iD, name, and institution data inline, eliminating N+1 profile fetches/activities call for affiliation queries, filtered client-side — eliminates up to 7 parallel upstream calls vs. per-section fetching<i>, <sup>, <h4>) is stripped to plain text; the deposited citation is relayed verbatimAgent-friendly output:
orcid_resolve_researcher returns raw disambiguation signals (name match type, institution overlap, anchor type) instead of a synthetic confidence scoresources: the person or member organization that added it (a university system, funder, Crossref, Web of Science) and whether the researcher asserted it (selfAsserted); grouped works and funding list every source in the grouporcid_search_researchers reports numFound and a truncated flag against the ORCID Public API's 10,000-offset ceiling, with nextStart to continue; orcid_get_works reports workCount, matchedCount, and truncated against the requested page, and orcid_get_peer_reviews totalCount and truncated, each with nextOffset to continue; orcid_get_research_resources detail reports truncated with nextItemOffsetorcid_get_work_detail returns per-put-code errors alongside successfully resolved works instead of failing the whole batch, and names any put-codes past the 100-code fetch cap or missing from ORCID's answerorcid_get_works, orcid_get_affiliations, orcid_get_funding, orcid_get_peer_reviews, and orcid_get_research_resources return a notice when a result is empty, explaining that this may mean neither the researcher nor a member organization added entries, or that visibility settings hide them, rather than confirmed absence; orcid_get_profile names each section with no public datastructuredContent carries every value verbatimConnect to the hosted instance, no install:
claude mcp add --transport http orcid-mcp-server https://orcid.caseyjhand.com/mcpOr run it locally:
claude mcp add orcid-mcp-server -- npx -y @cyanheads/orcid-mcp-serverFor other clients, add this to the MCP config file:
{
"mcpServers": {
"orcid-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/orcid-mcp-server"]
}
}
}No API key is required: the ORCID Public API is open for public read access, for non-commercial use under ORCID Public API ToS §2. For Docker or a source build, see Running the server.
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
ORCID_API_BASE_URL | Override the ORCID API base URL. Useful for pointing at the sandbox (https://pub.sandbox.orcid.org/v3.0/). | https://pub.orcid.org/v3.0 |
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path | /mcp |
MCP_SESSION_MODE | HTTP session mode: auto, stateful, or stateless. This server declares stateless in createApp() — no tool asks the caller for input mid-handler — and a set value overrides it. | stateless |
MCP_PUBLIC_URL | Public origin for TLS-terminating reverse-proxy deployments | none |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.) | info |
MCP_GC_PRESSURE_INTERVAL_MS | Opt-in Bun-only forced-GC pressure loop (ms). Try 60000 if heap growth is observed under sustained HTTP load. | 0 (disabled) |
LOGS_DIR | Directory for log files (Node.js only) | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 | in-memory |
OTEL_ENABLED | Enable OpenTelemetry | false |
See .env.example for the full list of optional overrides.
Requires Bun v1.4.0 or higher (or Node.js v24+).
git clone https://github.com/cyanheads/orcid-mcp-server.git
cd orcid-mcp-server && bun install
cp .env.example .env # no required variables # One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against specdocker run --rm -p 3010:3010 ghcr.io/cyanheads/orcid-mcp-server:latestThe image defaults to HTTP transport, stateless session mode, and logs to /var/log/orcid-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/orcid-mcp-server:latest"]. To build locally, docker build -t orcid-mcp-server .; OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and resources, inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Nine tools across search, disambiguation, profile, works, work detail, affiliations, funding, peer reviews, and research resources. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Profile and works resources. |
src/services/orcid | ORCID Public API v3.0 service layer — search, record section fetchers, retry/backoff. |
skills/ | Agent skill published over MCP and in the npm package, Docker image, and .mcpb bundle (orcid-guide). |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagecreateApp() arraysIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testApache-2.0 — see LICENSE for details.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.