{
  "markdown": "# scholar-mcp\n\n<!-- mcp-name: io.github.pvliesdonk/scholar-mcp -->\n\n[![CI](https://github.com/pvliesdonk/scholar-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/pvliesdonk/scholar-mcp/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/pvliesdonk/scholar-mcp/graph/badge.svg)](https://codecov.io/gh/pvliesdonk/scholar-mcp) [![PyPI](https://img.shields.io/pypi/v/pvliesdonk-scholar-mcp)](https://pypi.org/project/pvliesdonk-scholar-mcp/) [![Python](https://img.shields.io/pypi/pyversions/pvliesdonk-scholar-mcp)](https://pypi.org/project/pvliesdonk-scholar-mcp/) [![License](https://img.shields.io/github/license/pvliesdonk/scholar-mcp)](LICENSE) [![Docker](https://img.shields.io/github/v/release/pvliesdonk/scholar-mcp?label=ghcr.io&logo=docker)](https://github.com/pvliesdonk/scholar-mcp/pkgs/container/scholar-mcp) [![Docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://pvliesdonk.github.io/scholar-mcp/) [![llms.txt](https://img.shields.io/badge/llms.txt-available-brightgreen)](https://pvliesdonk.github.io/scholar-mcp/latest/llms.txt) [![Template](https://img.shields.io/badge/dynamic/yaml?url=https://raw.githubusercontent.com/pvliesdonk/scholar-mcp/main/.copier-answers.yml&query=%24._commit&label=template)](https://github.com/pvliesdonk/fastmcp-server-template)\n\nA [FastMCP](https://github.com/jlowin/fastmcp) server for the scholarly citation landscape (**papers**, **patents**, **books**, and **standards**), giving LLMs a unified way to search, cross-reference, and retrieve prior art across all four source types via [Semantic Scholar](https://www.semanticscholar.org/), [EPO Open Patent Services](https://www.epo.org/en/searching-for-patents/data/web-services/ops), [Open Library](https://openlibrary.org/), and standards bodies (NIST, IETF, W3C, ETSI), with [OpenAlex](https://openalex.org/) enrichment and optional [docling-serve](https://github.com/DS4SD/docling-serve) PDF/full-text conversion.\n\n**[Documentation](https://pvliesdonk.github.io/scholar-mcp/)** | **[Config wizard](https://pvliesdonk.github.io/scholar-mcp/latest/configuration-generator/)** | **[PyPI](https://pypi.org/project/pvliesdonk-scholar-mcp/)** | **[Docker](https://github.com/pvliesdonk/scholar-mcp/pkgs/container/scholar-mcp)**\n\n## Features\n\n<!-- DOMAIN-START -->\n\n### Source domains\n\n- **Papers**: full-text search with year/venue/field/citation filters; single-paper lookup by DOI, S2 ID, arXiv ID, ACM ID, or PubMed ID; author profile and name search; forward citations, backward references, BFS graph traversal, shortest-path bridge discovery; recommendations from positive/negative examples; BibTeX/CSL-JSON/RIS citation generation with OpenAlex venue enrichment.\n- **Patents**: search across 100+ patent offices via EPO OPS with CPC/applicant/inventor/jurisdiction filters; bibliographic, claims, description, family, legal, and citations sections; NPL-to-paper resolution via Semantic Scholar and paper-to-patent citation discovery. EPO credentials are optional; other domains work without them.\n- **Books**: Open Library search by title/author/keywords, no API key required; lookup by ISBN-10/13 or by Open Library work/edition ID; subject-based recommendations sorted by popularity; Google Books excerpts and preview links; WorldCat permalinks for library discovery; cover image caching. Papers with an ISBN in `externalIds` are automatically enriched with publisher, edition, cover URL, and subject data from Open Library.\n- **Standards**: identifier resolution, search, and metadata retrieval for NIST, IETF, W3C, and ETSI standards, with optional full-text fetch and Markdown conversion via docling. Tier 2 ISO, IEC, IEEE, Common Criteria (CC), and CEN/CENELEC metadata (including ISO/IEC/IEEE joint standards and the CC ↔ ISO/IEC 15408 cross-link) is synced locally via `sync-standards`. ISO, IEC, IEEE have a live-fetch fallback for unsynced identifiers; CC and CEN have no live API and require a sync first. Citations matching standards patterns (RFC, ISO, NIST SP, IEEE, EN, CC) are automatically enriched with structured `standard_metadata` including identifier, title, body, status, and full-text URL when available (see [docs/guides/standards.md](docs/guides/standards.md)).\n\n### Cross-cutting\n\n- **Enrichment pipeline**: phased enrichment from multiple sources: OpenAlex (OA status, affiliations, funders, concepts), CrossRef (publisher, page ranges, container titles), Google Books (preview links, excerpts), and Open Library (book metadata). Runs automatically on paper and book results.\n- **PDF conversion**: download open-access PDFs and convert to Markdown via [docling-serve](https://github.com/DS4SD/docling-serve), with optional VLM enrichment for formulas and figures; automatic fallback to ArXiv, PubMed Central, and Unpaywall when Semantic Scholar has no OA link; direct URL download for PDFs found elsewhere.\n- **Intelligent caching**: SQLite-backed cache with per-table TTLs (30 days for papers/authors, 7 days for citations/references) and identifier aliasing.\n- **Authentication**: bearer token, OIDC (OAuth 2.1), or both simultaneously (multi-auth).\n- **Multi-transport**: stdio (Claude Desktop), HTTP (streamable-http), and SSE transports.\n- **Linux packages**: `.deb` and `.rpm` packages with systemd service and security hardening.\n\n### Coverage by domain\n\nPer-domain depth is uneven. Papers currently have the richest tool surface (citation graph, recommendations, cross-referencing to all three other domains); standards are the leanest. That reflects public data availability, not a value hierarchy: writing a paper typically needs all four source types for citations and prior art. Parity work is tracked in [GitHub issues](https://github.com/pvliesdonk/scholar-mcp/issues) and [milestones](https://github.com/pvliesdonk/scholar-mcp/milestones); the roadmap shows intent, not a completeness commitment.\n<!-- DOMAIN-END -->\n\n## What you can do with it\n\n<!-- DOMAIN-START -->\n\nWith this server mounted in an MCP client (Claude, etc.), you can:\n\n- **Survey a field**: \"Find the 20 most-cited papers on graph neural networks from 2020 to 2024 and draft a literature review outline.\" Composes `search_papers` + `get_citations` + `enrich_paper`.\n- **Trace a citation path**: \"What's the shortest citation path from 'Attention is All You Need' to 'RLHF for dialogue agents'?\" Uses `find_bridge_papers` + `get_citation_graph`.\n- **Cross-reference prior art**: \"For this patent family, list academic papers it cites and any books or standards that show up in the description.\" Composes `get_patent` + `batch_resolve` + standards/book enrichment.\n- **Generate a bibliography**: \"Emit BibTeX for these 30 DOIs with OpenAlex venue data.\" Uses `generate_citations`.\n- **Look up a standard**: \"What's the latest status of RFC 9000, and fetch the Markdown full text.\" Uses `resolve_standard_identifier` + `get_standard`.\n<!-- DOMAIN-END -->\n\n<!-- ===== TEMPLATE-OWNED SECTIONS BELOW — DO NOT EDIT; CHANGES WILL BE OVERWRITTEN ON COPIER UPDATE ===== -->\n\n## Installation\n\n### From PyPI\n\n```bash\npip install pvliesdonk-scholar-mcp\n```\n\nIf you add optional extras via the `PROJECT-EXTRAS-START` / `PROJECT-EXTRAS-END` sentinels in `pyproject.toml`, document them below:\n\n<!-- DOMAIN-START -->\n\nScholar-mcp ships two optional-dependency groups:\n\n- **`[mcp]`**: installs FastMCP; required to run `scholar-mcp serve` and expose tools over stdio/HTTP.\n- **`[all]`**: currently identical to `[mcp]`; reserved for future optional backends.\n\nFor MCP-server usage:\n\n```bash\npip install 'pvliesdonk-scholar-mcp[mcp]'\n# or, without installing into the environment:\nuvx --from pvliesdonk-scholar-mcp scholar-mcp serve\n```\n\nInstalling the bare `pvliesdonk-scholar-mcp` package is enough for library use (`from scholar_mcp import ...`) but the `scholar-mcp serve` CLI requires `[mcp]`.\n<!-- DOMAIN-END -->\n\n### From source\n\n```bash\ngit clone https://github.com/pvliesdonk/scholar-mcp.git\ncd scholar-mcp\nuv sync --all-extras --all-groups\n```\n\n### Docker\n\n```bash\ndocker pull ghcr.io/pvliesdonk/scholar-mcp:latest\n```\n\nTo run the newest merged code instead of the newest release, use the rolling `edge` tag. It is rebuilt on every merge to `main` and carries no version identity. See [Image tags](docs/deployment/docker.md#image-tags) for the full tag list.\n\n```bash\ndocker pull ghcr.io/pvliesdonk/scholar-mcp:edge\n```\n\nA `compose.yml` ships at the repo root as a starting point. Copy `.env.example` to `.env`, edit, and `docker compose up -d`.\n\nTo attach a remote Python debugger (development only; the protocol is unauthenticated), see [Remote debugging](docs/deployment/docker.md#remote-debugging).\n\n### Linux packages (.deb / .rpm)\n\nDownload `.deb` or `.rpm` packages from the [GitHub Releases](https://github.com/pvliesdonk/scholar-mcp/releases) page. Both install a hardened systemd unit; env configuration is sourced from `/etc/scholar-mcp/env` (copy from the shipped `/etc/scholar-mcp/env.example`).\n\n### Claude Desktop (.mcpb bundle)\n\nDownload the `.mcpb` bundle from the [GitHub Releases](https://github.com/pvliesdonk/scholar-mcp/releases) page and double-click to install, or run:\n\n```bash\nmcpb install scholar-mcp-<version>.mcpb\n```\n\nClaude Desktop prompts for required env vars via a GUI wizard, with no manual JSON editing needed.\n\nFor manual Claude Desktop configuration and setup options, see [Claude Desktop deployment](docs/deployment/claude-desktop.md).\n\n## Release channels\n\nArtifacts ship on three channels. Each row lists exactly what that channel publishes.\n\n| Channel | Version identity | Artifacts |\n|---|---|---|\n| `edge` (rolling) | None; the commit is the identity | Docker image `:edge` rebuilt on every merge to `main`; `.mcpb` bundle as the `mcpb-bundle-edge` workflow artifact; Claude Code plugin `.zip` as the `plugin-zip-edge` artifact; rolling `unstable` docs version. It leaves no git tag, GitHub release, or PyPI entry behind. |\n| Pre-release | `vX.Y.Z-rc.N`, computed and reviewed in its release pull request | PyPI (as the pre-release `X.Y.ZrcN`); GitHub release with wheels, `sdist`, `.deb`/`.rpm` packages, `.mcpb` bundle, plugin `.zip`, and SBOM attached; Docker image under its immutable `vX.Y.Z-rc.N` tag plus the ordering-aware rolling `rc` tag. Skips the plugin marketplace, the MCP registry, and the docs deploy. |\n| Stable | `vX.Y.Z` | Everything: PyPI, Docker (version tag plus ordering-aware `latest` / `vX` / `vX.Y`), `.deb`/`.rpm`, GitHub release assets (wheels, `sdist`, `.mcpb` bundle, plugin `.zip`, SBOM), plugin marketplace and MCP registry entries (when the release is the newest stable), versioned docs with an ordering-aware `latest` alias. |\n\nPre-releases reach PyPI so that a candidate's `.mcpb` bundle installs: the bundle points at PyPI rather than carrying the code. Ordinary installers never see them, because a PEP 440 resolver skips pre-releases unless the requirement pins one or you pass `--pre`. Ask for a candidate by name with `pip install pvliesdonk-scholar-mcp==X.Y.ZrcN`. PyPI spells it in the PEP 440 canonical form, while tags use SemVer. Rolling pointers are ordering-aware, so a patch release cut from an old `release/X.Y` branch never moves `latest`-style tags back to older content, and a candidate for an already-released version never moves `rc`. See [Release process](docs/deployment/release-process.md) for the full model.\n\n## Quick start\n\n```bash\nscholar-mcp serve                                # stdio transport\nscholar-mcp serve --transport http --port 8000   # streamable HTTP\n```\n\nFor library usage (embedding the domain logic without the MCP transport), import from the `scholar_mcp` package directly. Backend clients live under `src/scholar_mcp/_s2_client.py`, `_epo_client.py`, `_openlibrary_client.py`, and `_standards_client.py`.\n\n### Server info\n\nThe server registers a built-in `get_server_info` tool (via `fastmcp_pvl_core.register_server_info_tool`) so operators can confirm the deployed version with a single MCP call. The default response carries `server_name`, `server_version`, and `core_version`. Servers that talk to a remote upstream wire upstream version reporting inside the `DOMAIN-UPSTREAM-START` / `DOMAIN-UPSTREAM-END` sentinel in `src/scholar_mcp/server.py`; see [`CLAUDE.md`](CLAUDE.md#server-info-tool-get_server_info) for the wiring pattern.\n\n## Configuration\n\nCore environment variables shared across all `fastmcp-pvl-core`-based services:\n\n<!-- GENERATED-ENV-TABLE-CORE-START — generated by scripts/gen_config_surface.py; do not edit -->\n| Variable | Default | Description |\n|---|---|---|\n| `SCHOLAR_MCP_KV_STORE_URL` | `file:///data/state` | Persistent-state backend URL shared by every pvl-core subsystem that needs state. `memory://` is in-process and lost on restart; `file:///path` persists on one server; `redis://`, `dynamodb://` and `mongodb://` each need their matching extra. When unset, defaults to `file:///data/state` (the volume family Docker images mount), or to `memory://`; with a warning; on a host where that directory is not usable. |\n| `FASTMCP_LOG_LEVEL` | `INFO` | Log level for FastMCP internals and app loggers (DEBUG / INFO / WARNING / ERROR / CRITICAL). The -v CLI flag overrides to DEBUG. |\n| `FASTMCP_ENABLE_RICH_LOGGING` | `true` | Set false for plain or structured JSON log output. |\n<!-- GENERATED-ENV-TABLE-CORE-END -->\n\nDomain-specific variables go below under [Domain configuration](#domain-configuration).\n\n## Authorization (opt-in)\n\nThis server inherits opt-in per-subject authorization from `fastmcp-pvl-core`. The default posture is **off**: every authenticated caller can use every tool, resource, and prompt. Turn it on by pointing `SCHOLAR_MCP_ACL_PATH` at a TOML ACL file; the middleware is installed only when the path is set, and individual tools opt in by declaring `meta={\"required_scope\": \"<scope>\"}` at registration. A tool without `required_scope` is unrestricted regardless of caller.\n\nWire it in by uncommenting the `acl_path` field in `src/scholar_mcp/config.py` and the `AuthorizationMiddleware` stanza in `src/scholar_mcp/server.py`; both ship as commented stubs in the scaffold.\n\n### ACL TOML schema\n\n```toml\n[subjects]\n\"user:alice@example.com\" = [\"read\", \"write\"]\n\"user:admin@example.com\" = [\"*\"]              # wildcard — any required scope passes\n\"service:ci-bot\"         = [\"read\"]\n\"local\"                  = [\"*\"]              # auth-disabled subject (no bearer / OIDC vars set)\n```\n\n- **Subject strings are opaque.** The `<kind>:<id>` convention is documentation only; the library treats each subject as a literal string.\n- **`*` is the only library-treated special scope**: it grants every required scope. Subject-side wildcards (`*` as an ACL key) are rejected at load time.\n- **Scope vocabulary is domain-defined.** Per-project or per-folder gating is encoded into the scope string itself, such as `read:project-foo` or `write:vault/personal`; `fastmcp-pvl-core` treats every scope except `*` as opaque.\n\n### Subject ↔ bearer-token alignment\n\nThe subject string used as a *value* in the bearer-tokens TOML (`SCHOLAR_MCP_BEARER_TOKENS_FILE`) is the same string used as a *key* in the ACL TOML. Same string, opposite roles, so keep the two files consistent when adding or removing a principal. See [Mapped bearer tokens](docs/guides/authentication.md#mapped-bearer-tokens-multi-subject) in the authentication guide for the bearer-tokens TOML schema.\n\nIn single-token mode (`SCHOLAR_MCP_BEARER_TOKEN`) every authenticated caller shares one subject, the library's default (currently `\"bearer-anon\"`); override it with `SCHOLAR_MCP_BEARER_DEFAULT_SUBJECT`; reference *that* string as the ACL key. When no auth is configured (no `SCHOLAR_MCP_BEARER_TOKEN`, `SCHOLAR_MCP_BEARER_TOKENS_FILE`, or OIDC env vars set, which is common in stdio dev rigs but also possible on HTTP), every request resolves to the literal subject `\"local\"`. Reference that string as the ACL key for un-authenticated local sessions.\n\n## Authentication\n\nCallers authenticate via a bearer token or OIDC (mutually exclusive). See the [Authentication guide](docs/guides/authentication.md) for setup, mapped multi-subject tokens, OIDC, and troubleshooting.\n\n## Post-scaffold checklist\n\nAfter `copier copy` and `gh repo create --push`:\n\n1. **Fill in the DOMAIN blocks** (every section marked with a `DOMAIN` sentinel comment) in this README and in `CLAUDE.md`. The `GENERATED-ENV-TABLE-*` regions are not DOMAIN blocks; the config generator owns them and rewrites them on every run.\n2. Configure GitHub secrets (see below).\n3. Install dev + docs tooling: `uv sync --all-extras --all-groups`.\n4. Install pre-commit hooks: `uv run pre-commit install`.\n5. Run the gate locally: `uv run pytest -x -q && uv run ruff check --fix . && uv run ruff format . && uv run mypy src/ tests/`.\n6. Push the first commit. CI should be green.\n\n## GitHub secrets\n\nCI workflows reference three repository secrets. Configure them via **Settings → Secrets and variables → Actions** or with `gh secret set`:\n\n| Secret | Used by | How to generate |\n|---|---|---|\n| `RELEASE_TOKEN` | `release-prepare.yml`, `release.yml`, `release-notes.yml`, `copier-update.yml`, `renovate.yml`, `bootstrap.yml` | Fine-grained PAT at <https://github.com/settings/personal-access-tokens/new> with `contents: write`, `pull_requests: write`, and `administration: write` (bootstrap applies the repository rulesets + auto-merge). Must belong to a repository admin: the shipped rulesets grant bypass to the admin role, and the release tag + GitHub release that knope creates after a release pull request merges rely on it (pull requests the token opens also need it so their CI runs). Scoped to this repo. |\n| `CODECOV_TOKEN` | `ci.yml` | <https://codecov.io>: sign in with GitHub and add the repo. The upload token is on its settings page. |\n| `CLAUDE_CODE_OAUTH_TOKEN` | `claude.yml`, `claude-code-review.yml`, `release-notes.yml` | Run `claude setup-token` locally and paste the result. |\n\n```bash\ngh secret set RELEASE_TOKEN\ngh secret set CODECOV_TOKEN\ngh secret set CLAUDE_CODE_OAUTH_TOKEN\n```\n\n> Dependency updates are handled by **Renovate** (`renovate.yml`), which reuses\n> `RELEASE_TOKEN`. It maintains `uv.lock` and auto-merges patch/minor bumps once\n> the `CI Success` check is green; `bootstrap.yml` enables auto-merge and applies\n> the repository rulesets (`.github/rulesets/`) on first push. See\n> [Repository Protection](docs/deployment/repository-protection.md) for the\n> per-branch posture and bypass model. GitHub Actions are updated in the copier\n> template and arrive via `copier update`, not per-repo.\n\n`GITHUB_TOKEN` is auto-provided; no action needed.\n\n## Local development\n\nThe PR gate (matches CI):\n\n```bash\nuv run pytest -x -q                                  # tests\nuv run ruff check --fix . && uv run ruff format .    # lint + format\nuv run mypy src/ tests/                              # type-check\n```\n\nPre-commit runs a subset of the gate on each commit; see `.pre-commit-config.yaml` for details, or [`CLAUDE.md`](CLAUDE.md) for the full Hard PR Acceptance Gates.\n\n## Troubleshooting\n\n### Moving a scaffolded project\n\n`uv sync` creates `.venv/bin/*` scripts with absolute shebangs pointing at the venv Python. If you move the repo after scaffolding (`mv /old/path /new/path`), `uv run pytest` fails with `ModuleNotFoundError: No module named 'fastmcp'` because the stale shebang resolves to a different interpreter than the venv's site-packages.\n\n**Fix:**\n\n```bash\nrm -rf .venv\nuv sync --all-extras --all-groups\n```\n\n`uv run python -m pytest` also works as a one-shot workaround (bypasses the stale entry-script shim).\n\n### `uv.lock` refresh after `copier update`\n\nWhen `copier update` introduces new dependencies (such as a new extra added to `pyproject.toml.jinja`), the CI install step runs `uv sync --locked`, which fails against a stale lockfile. Run `uv lock` locally and commit the refreshed `uv.lock` alongside accepting the copier-update PR.\n\nCI installs with `--locked` (and the review workflow with `--frozen`) so no job ever rewrites `uv.lock` in its own workspace: a job that re-locks hides the drift it just repaired, and a dirty workspace breaks any later `git checkout` in the same job. Lockfile drift then shows up as a red install step with a clear message, not as a silent mutation.\n\n## Links\n\n- [Documentation](https://pvliesdonk.github.io/scholar-mcp/)\n- [llms.txt](https://pvliesdonk.github.io/scholar-mcp/latest/llms.txt)\n- [FastMCP](https://gofastmcp.com)\n- [fastmcp-pvl-core](https://pypi.org/project/fastmcp-pvl-core/)\n\n<!-- ===== TEMPLATE-OWNED SECTIONS END ===== -->\n\n## Domain configuration\n\nDomain environment variables use the `SCHOLAR_MCP_` prefix:\n\n<!-- GENERATED-ENV-TABLE-DOMAIN-START — generated by scripts/gen_config_surface.py; do not edit -->\n| Variable | Default | Required | Description |\n|---|---|---|---|\n| `SCHOLAR_GITHUB_TOKEN` | (none) | No | GitHub token used to raise rate limits when fetching standards documents from GitHub. Optional; unauthenticated requests work at reduced limits. |\n| `SCHOLAR_MCP_READ_ONLY` | `true` | No | When true, write-tagged tools (PDF download and conversion cache writes) are hidden. Set false to enable them. |\n| `SCHOLAR_MCP_S2_API_KEY` | (none) | No | Semantic Scholar API key. Optional but strongly recommended: unauthenticated requests are limited to ~1 req/s. Request one at https://www.semanticscholar.org/product/api#api-key-form. |\n| `SCHOLAR_MCP_DOCLING_URL` | (none) | No | Base URL of a running docling-serve instance for PDF conversion (such as http://localhost:5001). When unset, PDF conversion tools return an error. |\n| `SCHOLAR_MCP_VLM_API_URL` | (none) | No | OpenAI-compatible VLM endpoint for formula and figure enrichment during PDF conversion. |\n| `SCHOLAR_MCP_VLM_API_KEY` | (none) | No | API key for the VLM endpoint. |\n| `SCHOLAR_MCP_VLM_MODEL` | `gpt-4o` | No | Model name to use with the VLM endpoint. |\n| `SCHOLAR_MCP_CACHE_DIR` | `/data/scholar-mcp` | No | Directory for the SQLite cache database (cache.db) and downloaded PDFs (pdfs/, md/). |\n| `SCHOLAR_MCP_CONTACT_EMAIL` | (none) | No | Contact email for the OpenAlex polite pool (improves rate limits). Also enables Unpaywall lookups as a PDF fallback source. |\n| `SCHOLAR_MCP_EPO_CONSUMER_KEY` | (none) | No | EPO Open Patent Services consumer key. Optional; patent tools are hidden when unset. Register at https://developers.epo.org/user/register. |\n| `SCHOLAR_MCP_EPO_CONSUMER_SECRET` | (none) | No | EPO Open Patent Services consumer secret. Optional; patent tools are hidden when unset. |\n| `SCHOLAR_MCP_GOOGLE_BOOKS_API_KEY` | (none) | No | Google Books API key. Optional; book tools work unauthenticated at reduced rate limits. |\n| `SCHOLAR_MCP_JOBS_SOFT_DEADLINE_S` | `25.0` | No | Seconds a long-running tool call may run in the foreground before it is promoted to a background job and a job handle is returned instead. |\n| `SCHOLAR_MCP_JOBS_RESULT_TTL_S` | `3600.0` | No | Seconds a background-job record (working or finished) is retained for polling before it expires from the store. |\n| `SCHOLAR_MCP_JOBS_MAX_PER_SUBJECT` | `256` | No | Maximum live background jobs per calling subject; further promotions are rejected until older records expire. |\n<!-- GENERATED-ENV-TABLE-DOMAIN-END -->\n\nDomain-config fields are composed inside `src/scholar_mcp/config.py` between the `CONFIG-FIELDS-START` / `CONFIG-FIELDS-END` sentinels; env reads go through `fastmcp_pvl_core.env(_ENV_PREFIX, \"SUFFIX\", default)` so naming stays consistent, and field invariants go in `__post_init__` between the `CONFIG-VALIDATE-START` / `CONFIG-VALIDATE-END` sentinels. Each field's `metadata` `help` and `tags` generate the table above directly, so keep them accurate and complete.\n\nScholar-mcp pings Semantic Scholar once on startup and every 7 days\nthereafter to keep the configured key from being removed for inactivity\n(Semantic Scholar may remove keys unused for 60+ days). If S2 starts\nrejecting the key with `403 Forbidden`, this shows up in the server logs\nas `s2_key_forbidden` (on real tool calls) or `s2_keepalive_key_forbidden`\n(from the background keepalive); grep for either to confirm a dead key\nversus a transient upstream issue.\n\n## Key design decisions\n\n<!-- DOMAIN-START -->\n\n- **Library-first, MCP-optional.** The core domain logic (S2/EPO/Open Library/standards clients, enrichment pipeline, cache) is importable without FastMCP; the MCP server is a thin async wrapper. Enables reuse in scripts, notebooks, and other servers.\n- **Sync domain code, async MCP layer.** Backend clients are synchronous; MCP tools call them via `asyncio.to_thread()`. Simpler client code, explicit offloading at the transport boundary.\n- **SQLite cache with per-table TTLs and identifier aliases.** Papers / authors last 30 days, citations / references 7 days. DOI ↔ S2 ID ↔ arXiv ID aliasing survives across cache clears so repeated enrichment hits the same row.\n- **Read-only by default.** Write-tagged tools (PDF download/convert, patent PDF) are hidden unless `SCHOLAR_MCP_READ_ONLY=false`. Safer default for first-run.\n- **Slow work becomes a background job.** Every tool whose work can run long runs through the `fastmcp-pvl-core` jobs layer, whether the slow part is a docling conversion, an EPO throttle being waited out, or a graph walk making one request per node. A call that beats `SCHOLAR_MCP_JOBS_SOFT_DEADLINE_S` returns its result directly; a slower one returns a handle to poll with `get_job_result`. No tool decides in advance whether to go background, so a cache hit needs no special case.\n- **EPO throttling is waited out, not queued.** The traffic light is consulted before every request and cached for a minute, so a retry sooner than that would re-read the cache rather than ask again. Each backoff outlasts the cache; an exhausted daily quota is reported immediately instead, since it will not clear today.\n- **Tier 2 standards sync out-of-band.** ISO/IEC/IEEE/CC/CEN catalogues come from community Relaton dumps via `scholar-mcp sync-standards`, not live at runtime, which avoids paywalled-HTML scraping and keeps tool calls fast.\n<!-- DOMAIN-END -->\n\n## Quick Start details\n\n### stdio transport (Claude Desktop / MCP clients)\n\n```bash\nuvx --from pvliesdonk-scholar-mcp scholar-mcp serve\n```\n\n> **API key optional but recommended:** The server works without a Semantic Scholar API key, but unauthenticated requests are limited to ~1 req/s and will hit 429 throttles quickly during multi-step operations like citation graph traversal. [Request a free key](https://www.semanticscholar.org/product/api#api-key-form) to get ~10 req/s.\n\nClaude Desktop configuration (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"scholar\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"pvliesdonk-scholar-mcp\", \"scholar-mcp\", \"serve\"],\n      \"env\": {\n        \"SCHOLAR_MCP_S2_API_KEY\": \"your-key\"\n      }\n    }\n  }\n}\n```\n\n### HTTP transport\n\n```bash\nuvx --from pvliesdonk-scholar-mcp scholar-mcp serve --transport http --port 8000\n```\n\n### Claude Code plugin\n\n```bash\n/plugin marketplace add pvliesdonk/claude-plugins\n/plugin install scholar-mcp@pvliesdonk\n```\n\n### Syncing Tier 2 standards catalogues\n\nTier 2 bodies (ISO, IEC, IEEE, CC, CEN) are populated from community-curated bulk dumps rather than live-scraped at MCP-server runtime. Run the sync on first install and periodically thereafter:\n\n```bash\nscholar-mcp sync-standards            # all registered bodies\nscholar-mcp sync-standards --body ISO # only ISO\nscholar-mcp sync-standards --body IEEE # only IEEE\nscholar-mcp sync-standards --body CC   # only Common Criteria\nscholar-mcp sync-standards --body CEN # only CEN/CENELEC\nscholar-mcp sync-standards --force    # re-sync even if upstream SHA is unchanged\n```\n\nSchedule via cron, launchd, or a systemd timer. Weekly is sufficient; standards change slowly. First sync can take several minutes; subsequent runs that find no upstream changes exit within seconds.\n\n## MCP Tools\n\n29 tools, organised by scholarly source type.\n\n### Papers\n\n#### Search & retrieval\n\n| Tool | Description |\n|---|---|\n| `search_papers` | Full-text search with year, venue, field-of-study, and citation-count filters. Returns up to 100 results with pagination. |\n| `get_paper` | Fetch full metadata for a single paper by DOI, S2 ID, arXiv ID, ACM ID, or PubMed ID. |\n| `get_author` | Fetch author profile with publications, or search by name. |\n\n#### Citation graph\n\n| Tool | Description |\n|---|---|\n| `get_citations` | Forward citations (papers that cite a given paper) with optional filters. |\n| `get_references` | Backward references (papers cited by a given paper). |\n| `get_citation_graph` | BFS traversal from seed papers, returning nodes + edges up to configurable depth. |\n| `find_bridge_papers` | Shortest citation path between two papers. |\n\n#### Recommendations & citation generation\n\n| Tool | Description |\n|---|---|\n| `recommend_papers` | Paper recommendations from 1 to 5 positive examples and optional negative examples. |\n| `generate_citations` | Generate BibTeX, CSL-JSON, or RIS citations for up to 100 papers, with automatic entry type inference and optional OpenAlex venue enrichment. |\n| `enrich_paper` | Augment Semantic Scholar metadata with OpenAlex fields (affiliations, funders, OA status, concepts). |\n\n### Patents\n\n| Tool | Description |\n|---|---|\n| `search_patents` | Search patents across 100+ patent offices via EPO OPS with CPC / applicant / inventor / jurisdiction / date filters. |\n| `get_patent` | Fetch bibliographic / claims / description / family / legal / citations sections for a single patent by publication number. Citations include NPL-to-paper resolution via Semantic Scholar. |\n| `get_citing_patents` | Find patents that cite a given academic paper (best-effort; EPO OPS citation search coverage is incomplete). |\n| `fetch_patent_pdf` | Download a patent PDF via authenticated EPO OPS and optionally convert to Markdown. |\n\n> Patent tools are hidden when `SCHOLAR_MCP_EPO_CONSUMER_KEY` and `SCHOLAR_MCP_EPO_CONSUMER_SECRET` are not set. `fetch_patent_pdf` is also write-tagged and hidden when `SCHOLAR_MCP_READ_ONLY=true`.\n\n### Books\n\n| Tool | Description |\n|---|---|\n| `search_books` | Search for books by title, author, ISBN, or keywords via Open Library. Returns up to 50 results. |\n| `get_book` | Fetch book metadata by ISBN-10, ISBN-13, Open Library work ID, or edition ID. Optionally download and cache the cover image locally. |\n| `get_book_excerpt` | Fetch a book excerpt and description from Google Books by ISBN. Shows preview availability and link. |\n| `recommend_books` | Recommend books for a subject via Open Library, sorted by popularity. |\n\n> Papers with an ISBN in their `externalIds` are automatically enriched with `book_metadata` (publisher, edition, cover URL, subjects, and more) from Open Library when fetched via `get_paper`, `get_citations`, `get_references`, or `get_citation_graph`. Book records also include `worldcat_url` (when ISBN-13 is present), `google_books_url`, and `snippet` from Google Books enrichment. Cover images can be downloaded and cached locally via `get_book`.\n\n### Standards\n\n| Tool | Description |\n|---|---|\n| `resolve_standard_identifier` | Normalise a messy citation string such as `\"rfc9000\"` or `\"nist 800-53\"` to canonical form and body. |\n| `search_standards` | Search standards by identifier, title, or free text, optionally filtered to one body (`NIST`, `IETF`, `W3C`, `ETSI`). |\n| `get_standard` | Retrieve a standard by canonical or fuzzy identifier, optionally fetching and converting the full text via docling. |\n\n> Tier-1 bodies (NIST, IETF, W3C, ETSI) are supported with full metadata and optional full-text conversion. Tier-2 bodies (ISO, IEC, IEEE, CC, CEN/CENELEC) are populated locally via `scholar-mcp sync-standards`.\n\n### Cross-source Utility\n\n| Tool | Description |\n|---|---|\n| `batch_resolve` | Resolve up to 100 mixed identifiers (paper DOIs, patent numbers, ISBNs) to full metadata in one call, routing each to the right backend with OpenAlex fallback. |\n\n### PDF Conversion (requires docling-serve)\n\n| Tool | Description |\n|---|---|\n| `fetch_paper_pdf` | Download PDF for a paper (S2 open-access, then ArXiv/PMC/Unpaywall fallback). |\n| `convert_pdf_to_markdown` | Convert a local PDF to Markdown via docling-serve. |\n| `fetch_and_convert` | Full pipeline: fetches the PDF with fallback sources, then converts it to Markdown and returns both. |\n| `fetch_pdf_by_url` | Download a PDF from any URL and optionally convert to Markdown. |\n\n> PDF tools are write-tagged and hidden when `SCHOLAR_MCP_READ_ONLY=true` (the default). `fetch_patent_pdf` (above) and the `get_standard` full-text mode cover the patent and standards equivalents.\n\n### Job Polling\n\n| Tool | Description |\n|---|---|\n| `get_job_result` | Retrieve the outcome of a background job by ID. |\n\n> Tools answer directly when the work is quick, including on a cache hit. A slower call returns `{\"status\": \"working\", \"job_id\": \"...\", \"poll_with\": \"get_job_result\"}`; poll with the tool the handle names until the status is terminal.\n\n## Docker Compose\n\n```yaml\nservices:\n  scholar-mcp:\n    image: ghcr.io/pvliesdonk/scholar-mcp:latest\n    restart: unless-stopped\n    environment:\n      SCHOLAR_MCP_S2_API_KEY: \"${SCHOLAR_MCP_S2_API_KEY}\"\n      SCHOLAR_MCP_DOCLING_URL: \"http://docling-serve:5001\"\n      SCHOLAR_MCP_VLM_API_URL: \"${VLM_API_URL:-}\"\n      SCHOLAR_MCP_VLM_API_KEY: \"${VLM_API_KEY:-}\"\n      SCHOLAR_MCP_CACHE_DIR: \"/data/scholar-mcp\"\n      SCHOLAR_MCP_READ_ONLY: \"false\"\n    volumes:\n      - scholar-mcp-data:/data/scholar-mcp\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.scholar-mcp.rule=Host(`scholar-mcp.yourdomain.com`)\"\n\n  docling-serve:\n    image: ghcr.io/ds4sd/docling-serve:latest\n    restart: unless-stopped\n\nvolumes:\n  scholar-mcp-data:\n```\n\n## Cache Management\n\n```bash\n# Show cache statistics (row counts, database size)\nscholar-mcp cache stats\n\n# Clear all cached data (preserves identifier aliases)\nscholar-mcp cache clear\n\n# Remove entries older than 30 days\nscholar-mcp cache clear --older-than 30\n\n# Override cache directory\nscholar-mcp cache stats --cache-dir /path/to/cache\n```\n",
  "bytes": 34151,
  "sha": "0309052faf233e3885d4ddadbd708668285416419fd0bd414ad374f25b7ee294",
  "repo_slug": "pvliesdonk/scholar-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_pvliesdonk_scholar_mcp_7a1008a9/readme"
}