Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ jobs:
continue-on-error: true
run: |
coverage run -m unittest -v test_module.TestScholarly
- name: Run MCP server unit tests
# Offline tests; the ones that need the MCP SDK (Python 3.10+) skip themselves on 3.8.
run: |
coverage run -a -m unittest -v test_mcp
- name: Generate coverage report (macOS only)
if:
"matrix.os == 'macos-latest'"
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ This means your code that uses an earlier version of `scholarly` is guaranteed t
```
**Note:** Tor option is unavailable with conda installation.

- **MCP server** (Python 3.10+):

To expose `scholarly` to LLM agents and other [Model Context Protocol](https://modelcontextprotocol.io/) clients, install with the `mcp` tag as
```bash
pip3 install scholarly[mcp]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Quote the MCP extra in shell install commands

On macOS's default zsh, the unquoted scholarly[mcp] is parsed as a filename-generation pattern and, with the usual no-match case, the command aborts before pip runs. The Tor instructions immediately above already quote their extra for this reason; quote this command and the other new README/docs install examples so MCP installation works when pasted into zsh.

Useful? React with 👍 / 👎.

```
and see [Running as an MCP server](#running-as-an-mcp-server).

## Tests

To check if your installation is successful, run the tests by executing the `test_module.py` file as:
Expand Down Expand Up @@ -125,6 +133,29 @@ It is therefore recommended to always set up a proxy in the beginning of your ap
The developers use `ScraperAPI` to run the tests in Github Actions.
The developers of `scholarly` are not affiliated with any of the proxy services and do not profit from them. If your favorite service is not supported, please submit an issue or even better, follow it up with a pull request.

## Running as an MCP server

`scholarly` can run as a [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server, so that LLM agents and MCP clients (Claude Desktop, IDE agents, agent gateways, ...) can query Google Scholar as a tool.

```bash
pip3 install scholarly[mcp] # needs Python 3.10+
scholarly-mcp # stdio transport, for clients that launch the server themselves
scholarly-mcp --transport streamable-http --host 127.0.0.1 --port 8000
scholarly-mcp --proxy free # none (default) | free | tor | scraperapi
```

Example client configuration (Claude Desktop style `mcpServers` entry):

```json
{"mcpServers": {"scholarly": {"command": "scholarly-mcp", "args": ["--proxy", "free"]}}}
```

Tools: `search_authors`, `get_author`, `search_publications`, `get_publication`, `get_bibtex`, `get_citing_publications` and `get_related_articles`. All are read-only and take plain names, ids or titles, not Python objects.

Google Scholar blocks automated clients quickly (see the **IMPORTANT** note above), and the server cannot avoid that, so keep `limit` small and use `--proxy`. Credentials are read from the environment, never from flags: `SCRAPER_API_KEY` for `--proxy scraperapi`, and `SCHOLARLY_TOR_SOCKS_PORT`, `SCHOLARLY_TOR_CONTROL_PORT` and `SCHOLARLY_TOR_PASSWORD` for `--proxy tor`.
The network transports have no authentication, so they bind to `127.0.0.1` by default; do not expose them to untrusted networks.
See the [documentation](https://scholarly.readthedocs.io/en/stable/mcp.html) for details.

## Contributing

We welcome contributions from you.
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Welcome to scholarly's documentation!

quickstart
scholarly
mcp


Indices and tables
Expand Down
100 changes: 100 additions & 0 deletions docs/mcp.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Running scholarly as an MCP server
==================================

``scholarly`` can run as a `Model Context Protocol <https://modelcontextprotocol.io/>`_ (MCP)
server, so that LLM agents and other MCP clients can query Google Scholar as a tool.
The MCP layer is optional and needs Python 3.10 or newer::

pip3 install scholarly[mcp]

It works with both the 1.x (``FastMCP``) and 2.x (``MCPServer``) generations of the
`MCP Python SDK <https://github.com/modelcontextprotocol/python-sdk>`_.

Launching the server
--------------------

The ``scholarly-mcp`` command (equivalently ``python -m scholarly.mcp``) starts the server::

scholarly-mcp # stdio transport (default)
scholarly-mcp --transport sse --port 8000
scholarly-mcp --transport streamable-http --host 127.0.0.1 --port 8000
scholarly-mcp --proxy free --log-level INFO

``--transport``
``stdio`` (default) for clients that start the server themselves, or the network
transports ``sse`` and ``streamable-http``.
``--host`` / ``--port``
Interface and port of the network transports (default ``127.0.0.1:8000``). The network
transports have **no authentication**, so only bind a non-loopback address on a trusted
network; the server logs a warning when you do.
``--proxy``
``none`` (default), ``free``, ``tor`` or ``scraperapi``. This configures the same

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Tell Tor users to install the Tor extra

In a clean environment following the documented pip install scholarly[mcp], selecting this advertised --proxy tor mode cannot work: the mcp extra in setup.py installs only the MCP SDK, while ProxyGenerator.Tor_External() raises RuntimeError unless the separate stem/tor extra is installed. Document scholarly[mcp,tor] for this mode (and preferably translate missing support to ProxyConfigError) so the listed option is usable.

Useful? React with 👍 / 👎.

:class:`ProxyGenerator <scholarly.ProxyGenerator>` that the Python API uses, and the server
refuses to start if the proxy cannot be set up rather than silently running unproxied.
``--log-level``
Logging threshold. Logs are written to ``stderr`` because ``stdout`` carries the protocol
on the ``stdio`` transport.

Credentials are read from the environment, never from command line flags, so that they do not
appear in process listings:

``SCRAPER_API_KEY``
API key for ``--proxy scraperapi``.
``SCHOLARLY_TOR_SOCKS_PORT``, ``SCHOLARLY_TOR_CONTROL_PORT``, ``SCHOLARLY_TOR_PASSWORD``
Settings of an already running Tor instance for ``--proxy tor`` (defaults ``9050``, ``9051``
and an empty password).

A ``.env`` file is honoured, as for the Python API.

Client configuration
--------------------

For clients that launch the server (for example Claude Desktop)::

{
"mcpServers": {
"scholarly": {
"command": "scholarly-mcp",
"args": ["--proxy", "free"]
}
}
}

Tools
-----

All tools are read-only and take plain names, identifiers or titles, because an MCP client
cannot hold on to ``Author`` or ``Publication`` objects between calls. Results are plain JSON.
Tools that return a list accept a ``limit`` (at most 50 per call).

``search_authors(name, limit=5)``
Search authors by name, optionally with an affiliation, e.g. ``"Marty Banks, Berkeley"``.
``get_author(scholar_id, sections=None, sortby="citedby", publication_limit=10)``
Fetch an author profile by Google Scholar id. ``sections`` is any of ``basics``, ``indices``,
``counts``, ``coauthors``, ``publications`` and ``public_access`` (default: all but
``public_access``). ``publication_limit=0`` fetches every publication.
``search_publications(query, limit=5, year_low=None, year_high=None, sort_by="relevance", patents=True)``
Search publications with a free-text query.
``get_publication(title)``
Full details of the best match for a title.
``get_bibtex(title)``
BibTeX entry of the best match for a title.
``get_citing_publications(title, limit=10)``
Publications that cite the best match for a title.
``get_related_articles(title, limit=10)``
Articles related to the best match for a title.

Calls are executed one at a time. ``scholarly`` keeps a single stateful session and Google
Scholar penalises bursts, so concurrent requests would only make blocking more likely.

Errors and limitations
----------------------

Invalid arguments, "no such publication" and Google Scholar refusing to serve the request are
reported to the client as tool errors with an explanatory message. Unexpected internal errors are
reported as errors too; SDK 2.x deliberately withholds their details from the client.

Google Scholar sometimes answers a blocked client with a sign-in or CAPTCHA page rather than an
error, which ``scholarly`` parses as "no results". Empty listings therefore carry a ``note``
explaining that an empty result can mean "blocked". Keep ``limit`` small, avoid repeating
identical queries, and use ``--proxy`` (see "Using proxies" in the :doc:`quickstart`).
Loading