apaper-mcp 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apaper_mcp-0.3.0/.github/workflows/release-publish.yml +137 -0
- apaper_mcp-0.3.0/.gitignore +10 -0
- apaper_mcp-0.3.0/CHANGELOG.md +85 -0
- apaper_mcp-0.3.0/LICENSE +21 -0
- apaper_mcp-0.3.0/PKG-INFO +176 -0
- apaper_mcp-0.3.0/README.md +154 -0
- apaper_mcp-0.3.0/docs/introducing-apaper-mcp.md +83 -0
- apaper_mcp-0.3.0/prompt/release.md +43 -0
- apaper_mcp-0.3.0/pyproject.toml +43 -0
- apaper_mcp-0.3.0/src/apaper_mcp/__init__.py +3 -0
- apaper_mcp-0.3.0/src/apaper_mcp/__main__.py +7 -0
- apaper_mcp-0.3.0/src/apaper_mcp/formatters.py +176 -0
- apaper_mcp-0.3.0/src/apaper_mcp/models.py +42 -0
- apaper_mcp-0.3.0/src/apaper_mcp/platforms/__init__.py +1 -0
- apaper_mcp-0.3.0/src/apaper_mcp/platforms/arxiv.py +257 -0
- apaper_mcp-0.3.0/src/apaper_mcp/platforms/cnki.py +103 -0
- apaper_mcp-0.3.0/src/apaper_mcp/platforms/dblp.py +57 -0
- apaper_mcp-0.3.0/src/apaper_mcp/platforms/iacr.py +171 -0
- apaper_mcp-0.3.0/src/apaper_mcp/platforms/scholar.py +62 -0
- apaper_mcp-0.3.0/src/apaper_mcp/proxy.py +92 -0
- apaper_mcp-0.3.0/src/apaper_mcp/server.py +388 -0
- apaper_mcp-0.3.0/tests/test_migration.py +203 -0
- apaper_mcp-0.3.0/uv.lock +759 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
name: Release and Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
tag:
|
|
10
|
+
description: "Tag to publish (e.g. v0.2.1)"
|
|
11
|
+
required: true
|
|
12
|
+
type: string
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v6
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Set up Python
|
|
32
|
+
uses: actions/setup-python@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.12"
|
|
35
|
+
|
|
36
|
+
- name: Resolve tag name
|
|
37
|
+
id: tag
|
|
38
|
+
run: |
|
|
39
|
+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
40
|
+
echo "tag_name=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
|
|
41
|
+
else
|
|
42
|
+
echo "tag_name=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
- name: Verify project version matches tag
|
|
46
|
+
run: |
|
|
47
|
+
PROJECT_VERSION=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
|
|
48
|
+
TAG_NAME="${{ steps.tag.outputs.tag_name }}"
|
|
49
|
+
EXPECTED_TAG="v${PROJECT_VERSION}"
|
|
50
|
+
|
|
51
|
+
if [ "$TAG_NAME" != "$EXPECTED_TAG" ]; then
|
|
52
|
+
echo "Tag ${TAG_NAME} does not match project version ${PROJECT_VERSION}"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
- name: Run tests
|
|
57
|
+
run: uv run pytest
|
|
58
|
+
|
|
59
|
+
- name: Build package
|
|
60
|
+
run: uv build
|
|
61
|
+
|
|
62
|
+
- name: Upload package artifacts
|
|
63
|
+
uses: actions/upload-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: python-package
|
|
66
|
+
path: dist/*
|
|
67
|
+
|
|
68
|
+
publish:
|
|
69
|
+
needs: build
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
permissions:
|
|
72
|
+
contents: read
|
|
73
|
+
id-token: write
|
|
74
|
+
|
|
75
|
+
steps:
|
|
76
|
+
- name: Download package artifacts
|
|
77
|
+
uses: actions/download-artifact@v4
|
|
78
|
+
with:
|
|
79
|
+
name: python-package
|
|
80
|
+
path: dist/
|
|
81
|
+
|
|
82
|
+
- name: Publish package to PyPI
|
|
83
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
84
|
+
with:
|
|
85
|
+
packages-dir: dist/
|
|
86
|
+
|
|
87
|
+
create-release:
|
|
88
|
+
needs: publish
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
permissions:
|
|
91
|
+
contents: write
|
|
92
|
+
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
with:
|
|
96
|
+
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
|
|
97
|
+
|
|
98
|
+
- name: Download package artifacts
|
|
99
|
+
uses: actions/download-artifact@v4
|
|
100
|
+
with:
|
|
101
|
+
name: python-package
|
|
102
|
+
path: dist/
|
|
103
|
+
|
|
104
|
+
- name: Resolve tag name
|
|
105
|
+
id: tag
|
|
106
|
+
run: |
|
|
107
|
+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
108
|
+
echo "tag_name=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
|
|
109
|
+
else
|
|
110
|
+
echo "tag_name=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
- name: Extract release notes from changelog
|
|
114
|
+
run: |
|
|
115
|
+
TAG_NAME="${{ steps.tag.outputs.tag_name }}"
|
|
116
|
+
VERSION="${TAG_NAME#v}"
|
|
117
|
+
|
|
118
|
+
awk -v version="$VERSION" '
|
|
119
|
+
$0 ~ "^## \\[" version "\\]" { in_section = 1; next }
|
|
120
|
+
in_section && /^## \[/ { exit }
|
|
121
|
+
in_section { print }
|
|
122
|
+
' CHANGELOG.md > RELEASE_NOTES.md
|
|
123
|
+
|
|
124
|
+
if [ ! -s RELEASE_NOTES.md ]; then
|
|
125
|
+
echo "Release notes for ${TAG_NAME} were not found in CHANGELOG.md" > RELEASE_NOTES.md
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
- name: Create GitHub Release
|
|
129
|
+
uses: softprops/action-gh-release@v2
|
|
130
|
+
with:
|
|
131
|
+
tag_name: ${{ steps.tag.outputs.tag_name }}
|
|
132
|
+
name: Release ${{ steps.tag.outputs.tag_name }}
|
|
133
|
+
draft: false
|
|
134
|
+
prerelease: false
|
|
135
|
+
body_path: RELEASE_NOTES.md
|
|
136
|
+
files: |
|
|
137
|
+
dist/*
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on Keep a Changelog, and this project follows Semantic Versioning.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2026-07-28
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Python 3.12+ package with `uv` project management and an `apaper-mcp` console entry point. (@isomoes)
|
|
14
|
+
- PyPI publishing workflow with GitHub Actions Trusted Publishing support. (@isomoes)
|
|
15
|
+
- MIT license and complete package metadata for distribution on PyPI. (@isomoes)
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- Legacy TypeScript, Bun, and npm implementation and release artifacts. (@isomoes)
|
|
20
|
+
|
|
21
|
+
## [0.2.0] - 2026-06-26
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `SPIDER_PROXY` env var routes outbound requests (arXiv, IACR, DBLP, Google Scholar, and PDF downloads) through a proxy. CNKI is excluded because it authenticates by institutional IP. Accepts `socks5`/`socks4`/`http`/`https` URLs in both the standard `scheme://user:pass@host:port` form and the colon-delimited `scheme://host:port:user:pass` form (e.g. Kookeey), on both the Node and Bun runtimes (under Bun, SOCKS is tunnelled via a local HTTP→SOCKS bridge). Idempotent requests are retried on transient proxy connection failures, and if the proxy is set but invalid or can't be initialised, requests are blocked rather than sent direct so the real IP never leaks. (#2, @isomoes)
|
|
26
|
+
- IACR per-request timeouts are configurable via `IACR_TIMEOUT_MS` (default `30000`) and `IACR_DOWNLOAD_TIMEOUT_MS` (default `60000`), raised from the previous fixed 15s/30s to tolerate slow proxies. (#2, @isomoes)
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- IACR requests now detect Cloudflare's bot challenge (which intermittently blocks downloads/searches) and report it with an actionable message and the URL to open in a browser, instead of an opaque `HTTP 403` — and never save a challenge HTML page as a `.pdf`. Requests also send browser-like headers (full User-Agent, `Accept`, `Referer`). (#2, @isomoes)
|
|
31
|
+
|
|
32
|
+
## [0.1.5] - 2026-06-08
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- arXiv requests are throttled and retried on HTTP 429 / 5xx with exponential backoff honouring `Retry-After`; a persistent block reports the throttled IP and wait time. Tunable via `ARXIV_MIN_INTERVAL_MS`, `ARXIV_MAX_RETRIES`, `ARXIV_BACKOFF_BASE_MS`, `ARXIV_BACKOFF_MAX_MS`, `ARXIV_BACKOFF_JITTER_MS`, and `ARXIV_IP_ECHO_URL`. (#1, @isomoes)
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- arXiv tools no longer fail on the first HTTP 429, and the search tool description no longer claims rate-limiting that was not implemented. (#1, @isomoes)
|
|
41
|
+
|
|
42
|
+
## [0.1.4] - 2026-05-17
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- arXiv search support via the `search_arxiv_papers` tool, with category, date-range, and sort options. (@isomoes)
|
|
47
|
+
- arXiv PDF download support via the `download_arxiv_paper` tool. (@isomoes)
|
|
48
|
+
- `ARXIV_SEARCH_URL`, `ARXIV_ADVANCED_URL`, and `ARXIV_PDF_BASE` env overrides for routing arXiv traffic through a reachable mirror. (@isomoes)
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- arXiv search scrapes the `arxiv.org/search/` HTML page instead of `export.arxiv.org/api/query` so it works on networks where the Atom API endpoint is blocked or aggressively rate-limited. (@isomoes)
|
|
53
|
+
|
|
54
|
+
## [0.1.3] - 2026-05-16
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- CNKI (中国知网) search support via the `search_cnki_papers` tool. (@isomoes)
|
|
59
|
+
- CNKI PDF download support via the `download_cnki_paper` tool. (@isomoes)
|
|
60
|
+
- Automatic IP-based CNKI session login on whitelisted institutional networks; no manual cookie management required. (@isomoes)
|
|
61
|
+
|
|
62
|
+
## [0.1.2] - 2026-03-26
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- Test release to verify tag-triggered GitHub Actions publishing now that the workflow is active. (@isomoes)
|
|
67
|
+
|
|
68
|
+
## [0.1.1] - 2026-03-26
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- npm packaging for `@ai4paper/apaper-mcp`, including the `apaper-mcp` CLI entrypoint. (@isomoes)
|
|
73
|
+
- Tag-driven GitHub Actions release publishing and changelog-backed release notes. (@isomoes)
|
|
74
|
+
- README updates covering npm installation and local release verification workflow. (@isomoes)
|
|
75
|
+
|
|
76
|
+
## [0.1.0] - 2026-03-26
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
- Initial Bun + TypeScript MCP server for academic paper research. (@isomoes)
|
|
81
|
+
- IACR ePrint search support with optional detail fetching and year filters. (@isomoes)
|
|
82
|
+
- IACR PDF download support. (@isomoes)
|
|
83
|
+
- DBLP search support with year filtering, venue filtering, and optional BibTeX output. (@isomoes)
|
|
84
|
+
- Google Scholar search support with year filtering. (@isomoes)
|
|
85
|
+
- Text formatters for MCP tool responses and stdio server startup entrypoint. (@isomoes)
|
apaper_mcp-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 isomoes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apaper-mcp
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Academic paper research MCP server
|
|
5
|
+
Project-URL: Homepage, https://github.com/ai4paper/apaper-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/ai4paper/apaper-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/ai4paper/apaper-mcp/issues
|
|
8
|
+
Author: isomoes
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: academic research,mcp,papers
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
19
|
+
Requires-Dist: httpx[socks]>=0.27
|
|
20
|
+
Requires-Dist: mcp>=1.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# apaper-mcp
|
|
24
|
+
|
|
25
|
+
An MCP (Model Context Protocol) server that gives AI assistants direct access to academic paper databases. It exposes a unified set of tools for searching and downloading papers across arXiv, IACR ePrint, DBLP, Google Scholar, and CNKI (中国知网), so an MCP-compatible client (Claude Code, Claude Desktop, or any other) can run literature searches, pull BibTeX entries, and fetch PDFs without leaving the chat.
|
|
26
|
+
|
|
27
|
+
## Tools
|
|
28
|
+
|
|
29
|
+
| Tool | Source | Description |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `search_arxiv_papers` | arXiv | Search with category, date-range, and sort options |
|
|
32
|
+
| `download_arxiv_paper` | arXiv | Download a PDF |
|
|
33
|
+
| `search_iacr_papers` | IACR ePrint | Search the ePrint archive |
|
|
34
|
+
| `download_iacr_paper` | IACR ePrint | Download a PDF |
|
|
35
|
+
| `search_dblp_papers` | DBLP | Search, optionally with BibTeX |
|
|
36
|
+
| `search_google_scholar_papers` | Google Scholar | Search |
|
|
37
|
+
| `search_cnki_papers` | CNKI (中国知网) | Search |
|
|
38
|
+
| `download_cnki_paper` | CNKI (中国知网) | Download a PDF |
|
|
39
|
+
|
|
40
|
+
arXiv tools scrape the public `arxiv.org/search/` HTML page, which works on
|
|
41
|
+
networks where the `export.arxiv.org` Atom API is blocked or rate-limited. If
|
|
42
|
+
you need to route through a mirror, set `ARXIV_SEARCH_URL`,
|
|
43
|
+
`ARXIV_ADVANCED_URL`, and/or `ARXIV_PDF_BASE`.
|
|
44
|
+
|
|
45
|
+
The arXiv tools throttle themselves and retry HTTP 429 / 5xx with exponential
|
|
46
|
+
backoff, honouring `Retry-After`; on a persistent block the error names the
|
|
47
|
+
throttled IP and wait time. Tune with `ARXIV_MIN_INTERVAL_MS` (`3000`),
|
|
48
|
+
`ARXIV_MAX_RETRIES` (`3`), `ARXIV_BACKOFF_BASE_MS` (`3000`),
|
|
49
|
+
`ARXIV_BACKOFF_MAX_MS` (`60000`), `ARXIV_BACKOFF_JITTER_MS` (`500`), and
|
|
50
|
+
`ARXIV_IP_ECHO_URL` (`""` to disable the IP lookup).
|
|
51
|
+
|
|
52
|
+
CNKI tools require institutional access. On IP-based networks the session
|
|
53
|
+
cookie is obtained automatically on first use — no manual login needed.
|
|
54
|
+
|
|
55
|
+
IACR ePrint sits behind Cloudflare, which intermittently serves a JS bot
|
|
56
|
+
challenge that automated HTTP clients can't solve — most often on PDF downloads.
|
|
57
|
+
Search keeps working; when a download is challenged the tool reports it clearly
|
|
58
|
+
(rather than saving the challenge page) and gives you the URL to fetch in a
|
|
59
|
+
browser. This is server-side and unrelated to any proxy.
|
|
60
|
+
|
|
61
|
+
## Proxy
|
|
62
|
+
|
|
63
|
+
Set `SPIDER_PROXY` to route outbound requests — arXiv, IACR, DBLP, Google
|
|
64
|
+
Scholar, and their PDF downloads — through a proxy. CNKI is excluded on purpose:
|
|
65
|
+
it authenticates by institutional IP and always uses this host's real address.
|
|
66
|
+
Both a standard URL and the colon-delimited form some mobile-proxy providers
|
|
67
|
+
hand out are accepted:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
SPIDER_PROXY="socks5://user:pass@host:1086" # standard
|
|
71
|
+
SPIDER_PROXY="socks5://host:1086:user:pass" # host:port:user:pass (e.g. Kookeey)
|
|
72
|
+
SPIDER_PROXY="http://user:pass@host:8080" # HTTP CONNECT proxy
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`socks5`, `socks4`, `http`, and `https` schemes are supported (the scheme
|
|
76
|
+
defaults to `http` when omitted) by the Python runtime. The scheme must match
|
|
77
|
+
the proxy's port — a SOCKS port won't accept `http://` and vice versa. If
|
|
78
|
+
`SPIDER_PROXY` is set but invalid or can't be initialised, requests are blocked
|
|
79
|
+
rather than sent direct, so a misconfigured proxy never leaks this host's real
|
|
80
|
+
IP.
|
|
81
|
+
|
|
82
|
+
Flaky residential/mobile proxies routinely drop or refuse connections;
|
|
83
|
+
idempotent (GET) requests through the proxy are retried automatically on such
|
|
84
|
+
transient failures. IACR fires the most requests per search (a detail fetch per
|
|
85
|
+
result), so its per-request timeouts default high for slow proxies — tune with
|
|
86
|
+
`IACR_TIMEOUT_MS` (`30000`) and `IACR_DOWNLOAD_TIMEOUT_MS` (`60000`).
|
|
87
|
+
|
|
88
|
+
## Install from PyPI
|
|
89
|
+
|
|
90
|
+
The maintained runtime requires Python 3.12+. Run the published package
|
|
91
|
+
directly with `uvx`:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uvx apaper-mcp
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
To install the command for repeated use:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv tool install apaper-mcp
|
|
101
|
+
apaper-mcp
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For an MCP client, use the published package as the local stdio command:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcp": {
|
|
109
|
+
"apaper-mcp": {
|
|
110
|
+
"type": "local",
|
|
111
|
+
"command": ["uvx", "apaper-mcp"],
|
|
112
|
+
"enabled": true
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
Clone the repository and install its development dependencies with `uv`:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
uv sync
|
|
124
|
+
uv run apaper-mcp
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Run tests with `uv run pytest`.
|
|
128
|
+
|
|
129
|
+
The Python source uses a standard `src` package layout. Platform-specific
|
|
130
|
+
clients live under `src/apaper_mcp/platforms/`, while shared server, proxy,
|
|
131
|
+
formatter, and model code stays in `src/apaper_mcp/`.
|
|
132
|
+
|
|
133
|
+
### Local MCP testing
|
|
134
|
+
|
|
135
|
+
Start the MCP Inspector against the Python stdio server:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npx @modelcontextprotocol/inspector uv run --directory . apaper-mcp
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The equivalent module command is:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx @modelcontextprotocol/inspector \
|
|
145
|
+
uv run --directory . python -m apaper_mcp
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
To pass the proxy to the inspected server:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npx @modelcontextprotocol/inspector \
|
|
152
|
+
-e SPIDER_PROXY="$SPIDER_PROXY" \
|
|
153
|
+
uv run --directory . apaper-mcp
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The Inspector opens a local web UI for listing tools, inspecting schemas, and
|
|
157
|
+
calling tools.
|
|
158
|
+
|
|
159
|
+
### Tool schemas
|
|
160
|
+
|
|
161
|
+
- `search_arxiv_papers`
|
|
162
|
+
- input: `{ "query": string, "max_results"?: number, "date_from"?: string, "date_to"?: string, "categories"?: string[], "sort_by"?: "relevance" | "date" }`
|
|
163
|
+
- `download_arxiv_paper`
|
|
164
|
+
- input: `{ "paper_id": string, "save_path"?: string }` (paper_id like `2103.12345` or `2103.12345v2`)
|
|
165
|
+
- `search_iacr_papers`
|
|
166
|
+
- input: `{ "query": string, "max_results"?: number, "fetch_details"?: boolean, "year_min"?: number | string, "year_max"?: number | string }`
|
|
167
|
+
- `download_iacr_paper`
|
|
168
|
+
- input: `{ "paper_id": string, "save_path"?: string }`
|
|
169
|
+
- `search_dblp_papers`
|
|
170
|
+
- input: `{ "query": string, "max_results"?: number, "year_from"?: number | string, "year_to"?: number | string, "venue_filter"?: string, "include_bibtex"?: boolean }`
|
|
171
|
+
- `search_google_scholar_papers`
|
|
172
|
+
- input: `{ "query": string, "max_results"?: number, "year_low"?: number | string, "year_high"?: number | string }`
|
|
173
|
+
- `search_cnki_papers`
|
|
174
|
+
- input: `{ "query": string, "page_num"?: number, "page_size"?: number }`
|
|
175
|
+
- `download_cnki_paper`
|
|
176
|
+
- input: `{ "href": string, "save_path"?: string }` (use an `href` from `search_cnki_papers`)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# apaper-mcp
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that gives AI assistants direct access to academic paper databases. It exposes a unified set of tools for searching and downloading papers across arXiv, IACR ePrint, DBLP, Google Scholar, and CNKI (中国知网), so an MCP-compatible client (Claude Code, Claude Desktop, or any other) can run literature searches, pull BibTeX entries, and fetch PDFs without leaving the chat.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Source | Description |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `search_arxiv_papers` | arXiv | Search with category, date-range, and sort options |
|
|
10
|
+
| `download_arxiv_paper` | arXiv | Download a PDF |
|
|
11
|
+
| `search_iacr_papers` | IACR ePrint | Search the ePrint archive |
|
|
12
|
+
| `download_iacr_paper` | IACR ePrint | Download a PDF |
|
|
13
|
+
| `search_dblp_papers` | DBLP | Search, optionally with BibTeX |
|
|
14
|
+
| `search_google_scholar_papers` | Google Scholar | Search |
|
|
15
|
+
| `search_cnki_papers` | CNKI (中国知网) | Search |
|
|
16
|
+
| `download_cnki_paper` | CNKI (中国知网) | Download a PDF |
|
|
17
|
+
|
|
18
|
+
arXiv tools scrape the public `arxiv.org/search/` HTML page, which works on
|
|
19
|
+
networks where the `export.arxiv.org` Atom API is blocked or rate-limited. If
|
|
20
|
+
you need to route through a mirror, set `ARXIV_SEARCH_URL`,
|
|
21
|
+
`ARXIV_ADVANCED_URL`, and/or `ARXIV_PDF_BASE`.
|
|
22
|
+
|
|
23
|
+
The arXiv tools throttle themselves and retry HTTP 429 / 5xx with exponential
|
|
24
|
+
backoff, honouring `Retry-After`; on a persistent block the error names the
|
|
25
|
+
throttled IP and wait time. Tune with `ARXIV_MIN_INTERVAL_MS` (`3000`),
|
|
26
|
+
`ARXIV_MAX_RETRIES` (`3`), `ARXIV_BACKOFF_BASE_MS` (`3000`),
|
|
27
|
+
`ARXIV_BACKOFF_MAX_MS` (`60000`), `ARXIV_BACKOFF_JITTER_MS` (`500`), and
|
|
28
|
+
`ARXIV_IP_ECHO_URL` (`""` to disable the IP lookup).
|
|
29
|
+
|
|
30
|
+
CNKI tools require institutional access. On IP-based networks the session
|
|
31
|
+
cookie is obtained automatically on first use — no manual login needed.
|
|
32
|
+
|
|
33
|
+
IACR ePrint sits behind Cloudflare, which intermittently serves a JS bot
|
|
34
|
+
challenge that automated HTTP clients can't solve — most often on PDF downloads.
|
|
35
|
+
Search keeps working; when a download is challenged the tool reports it clearly
|
|
36
|
+
(rather than saving the challenge page) and gives you the URL to fetch in a
|
|
37
|
+
browser. This is server-side and unrelated to any proxy.
|
|
38
|
+
|
|
39
|
+
## Proxy
|
|
40
|
+
|
|
41
|
+
Set `SPIDER_PROXY` to route outbound requests — arXiv, IACR, DBLP, Google
|
|
42
|
+
Scholar, and their PDF downloads — through a proxy. CNKI is excluded on purpose:
|
|
43
|
+
it authenticates by institutional IP and always uses this host's real address.
|
|
44
|
+
Both a standard URL and the colon-delimited form some mobile-proxy providers
|
|
45
|
+
hand out are accepted:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
SPIDER_PROXY="socks5://user:pass@host:1086" # standard
|
|
49
|
+
SPIDER_PROXY="socks5://host:1086:user:pass" # host:port:user:pass (e.g. Kookeey)
|
|
50
|
+
SPIDER_PROXY="http://user:pass@host:8080" # HTTP CONNECT proxy
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`socks5`, `socks4`, `http`, and `https` schemes are supported (the scheme
|
|
54
|
+
defaults to `http` when omitted) by the Python runtime. The scheme must match
|
|
55
|
+
the proxy's port — a SOCKS port won't accept `http://` and vice versa. If
|
|
56
|
+
`SPIDER_PROXY` is set but invalid or can't be initialised, requests are blocked
|
|
57
|
+
rather than sent direct, so a misconfigured proxy never leaks this host's real
|
|
58
|
+
IP.
|
|
59
|
+
|
|
60
|
+
Flaky residential/mobile proxies routinely drop or refuse connections;
|
|
61
|
+
idempotent (GET) requests through the proxy are retried automatically on such
|
|
62
|
+
transient failures. IACR fires the most requests per search (a detail fetch per
|
|
63
|
+
result), so its per-request timeouts default high for slow proxies — tune with
|
|
64
|
+
`IACR_TIMEOUT_MS` (`30000`) and `IACR_DOWNLOAD_TIMEOUT_MS` (`60000`).
|
|
65
|
+
|
|
66
|
+
## Install from PyPI
|
|
67
|
+
|
|
68
|
+
The maintained runtime requires Python 3.12+. Run the published package
|
|
69
|
+
directly with `uvx`:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uvx apaper-mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
To install the command for repeated use:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uv tool install apaper-mcp
|
|
79
|
+
apaper-mcp
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For an MCP client, use the published package as the local stdio command:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcp": {
|
|
87
|
+
"apaper-mcp": {
|
|
88
|
+
"type": "local",
|
|
89
|
+
"command": ["uvx", "apaper-mcp"],
|
|
90
|
+
"enabled": true
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
Clone the repository and install its development dependencies with `uv`:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv sync
|
|
102
|
+
uv run apaper-mcp
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Run tests with `uv run pytest`.
|
|
106
|
+
|
|
107
|
+
The Python source uses a standard `src` package layout. Platform-specific
|
|
108
|
+
clients live under `src/apaper_mcp/platforms/`, while shared server, proxy,
|
|
109
|
+
formatter, and model code stays in `src/apaper_mcp/`.
|
|
110
|
+
|
|
111
|
+
### Local MCP testing
|
|
112
|
+
|
|
113
|
+
Start the MCP Inspector against the Python stdio server:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npx @modelcontextprotocol/inspector uv run --directory . apaper-mcp
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The equivalent module command is:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx @modelcontextprotocol/inspector \
|
|
123
|
+
uv run --directory . python -m apaper_mcp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
To pass the proxy to the inspected server:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx @modelcontextprotocol/inspector \
|
|
130
|
+
-e SPIDER_PROXY="$SPIDER_PROXY" \
|
|
131
|
+
uv run --directory . apaper-mcp
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The Inspector opens a local web UI for listing tools, inspecting schemas, and
|
|
135
|
+
calling tools.
|
|
136
|
+
|
|
137
|
+
### Tool schemas
|
|
138
|
+
|
|
139
|
+
- `search_arxiv_papers`
|
|
140
|
+
- input: `{ "query": string, "max_results"?: number, "date_from"?: string, "date_to"?: string, "categories"?: string[], "sort_by"?: "relevance" | "date" }`
|
|
141
|
+
- `download_arxiv_paper`
|
|
142
|
+
- input: `{ "paper_id": string, "save_path"?: string }` (paper_id like `2103.12345` or `2103.12345v2`)
|
|
143
|
+
- `search_iacr_papers`
|
|
144
|
+
- input: `{ "query": string, "max_results"?: number, "fetch_details"?: boolean, "year_min"?: number | string, "year_max"?: number | string }`
|
|
145
|
+
- `download_iacr_paper`
|
|
146
|
+
- input: `{ "paper_id": string, "save_path"?: string }`
|
|
147
|
+
- `search_dblp_papers`
|
|
148
|
+
- input: `{ "query": string, "max_results"?: number, "year_from"?: number | string, "year_to"?: number | string, "venue_filter"?: string, "include_bibtex"?: boolean }`
|
|
149
|
+
- `search_google_scholar_papers`
|
|
150
|
+
- input: `{ "query": string, "max_results"?: number, "year_low"?: number | string, "year_high"?: number | string }`
|
|
151
|
+
- `search_cnki_papers`
|
|
152
|
+
- input: `{ "query": string, "page_num"?: number, "page_size"?: number }`
|
|
153
|
+
- `download_cnki_paper`
|
|
154
|
+
- input: `{ "href": string, "save_path"?: string }` (use an `href` from `search_cnki_papers`)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Introducing apaper-mcp
|
|
2
|
+
|
|
3
|
+
`apaper-mcp` is an MCP server for one very practical job: supporting paper work inside the conversation. Today that starts with paper discovery, but the larger direction is a more complete academic writing server where an AI client can search literature, collect references, organize source material, and assist with drafting around the same workflow. Instead of wiring separate scripts to different research sites, this project packages focused research tools behind a single Model Context Protocol interface.
|
|
4
|
+
|
|
5
|
+
The project is built with **Python** and the official **Model Context Protocol SDK**. It runs over stdio, which makes it easy to plug into MCP-compatible clients and local inspection tools. The current implementation supports **arXiv**, **IACR ePrint**, **DBLP**, **Google Scholar**, and **CNKI**.
|
|
6
|
+
|
|
7
|
+
## Why this project exists
|
|
8
|
+
|
|
9
|
+
Research and writing workflows often break into too many manual steps. You search one index for cryptography preprints, another for publication metadata, and another for broad citation discovery, then move that material into notes, citations, and draft sections. `apaper-mcp` narrows that gap by turning the research side of that process into MCP tools that an assistant can call directly.
|
|
10
|
+
|
|
11
|
+
That design is especially useful when the goal is not just to find one paper, but to support iterative paper-writing tasks such as:
|
|
12
|
+
|
|
13
|
+
- narrowing a topic by year range
|
|
14
|
+
- comparing results across multiple sources
|
|
15
|
+
- pulling BibTeX-ready metadata from DBLP
|
|
16
|
+
- downloading an IACR PDF for closer reading
|
|
17
|
+
- building a cleaner literature base before outlining or drafting
|
|
18
|
+
|
|
19
|
+
## What the server exposes
|
|
20
|
+
|
|
21
|
+
At the moment, the server registers four tools:
|
|
22
|
+
|
|
23
|
+
- `search_iacr_papers` for querying the IACR ePrint archive, with optional detail fetching and year filters
|
|
24
|
+
- `download_iacr_paper` for saving an IACR paper PDF locally
|
|
25
|
+
- `search_dblp_papers` for publication lookup in DBLP, including venue filtering and optional BibTeX output
|
|
26
|
+
- `search_google_scholar_papers` for broader paper discovery through Google Scholar with year bounds
|
|
27
|
+
|
|
28
|
+
Each tool returns formatted text responses so MCP clients can display results cleanly without extra response shaping. That keeps the server simple while still making it useful in real workflows.
|
|
29
|
+
|
|
30
|
+
## Using it in an MCP client
|
|
31
|
+
|
|
32
|
+
One simple local configuration looks like this:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcp": {
|
|
37
|
+
"apaper-mcp": {
|
|
38
|
+
"type": "local",
|
|
39
|
+
"command": ["uv", "run", "--directory", "/path/to/apaper-mcp", "apaper-mcp"],
|
|
40
|
+
"enabled": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
With that setup, an MCP-compatible client can start the server locally and call its tools during a research session.
|
|
47
|
+
|
|
48
|
+
## Example use cases
|
|
49
|
+
|
|
50
|
+
In practice, that means an assistant can use `apaper-mcp` to:
|
|
51
|
+
|
|
52
|
+
- search papers on a topic across multiple sources
|
|
53
|
+
- collect metadata and BibTeX for references
|
|
54
|
+
- download an IACR paper for closer reading
|
|
55
|
+
- prepare source material before outlining or drafting a paper
|
|
56
|
+
|
|
57
|
+
## How it is put together
|
|
58
|
+
|
|
59
|
+
The codebase keeps the implementation intentionally direct. The MCP entrypoint lives in `src/apaper_mcp/server.py`, where each tool is registered with the Python MCP SDK. Platform-specific fetching and parsing live in separate modules under `src/apaper_mcp/`, which keeps the transport layer and the scraping/API logic from bleeding into each other.
|
|
60
|
+
|
|
61
|
+
That split also reflects the nature of the supported sources:
|
|
62
|
+
|
|
63
|
+
- **IACR** and **Google Scholar** are handled by fetching and parsing HTML
|
|
64
|
+
- **DBLP** uses its JSON API and can optionally fetch BibTeX records
|
|
65
|
+
|
|
66
|
+
This is not trying to be a full academic knowledge graph or a large orchestration framework. It is a compact MCP server with a clear surface area, aimed at making paper search easier to automate and easier to integrate into AI-assisted research and writing sessions.
|
|
67
|
+
|
|
68
|
+
## Future directions
|
|
69
|
+
|
|
70
|
+
Today, `apaper-mcp` focuses on search and retrieval. The next step is to extend it into a more complete paper-writing server.
|
|
71
|
+
|
|
72
|
+
- reference management tools for collecting, deduplicating, and exporting citations across sources
|
|
73
|
+
- PDF processing tools for extracting metadata, section text, figures, or notes from downloaded papers
|
|
74
|
+
- literature review helpers for clustering papers by topic, method, or publication venue
|
|
75
|
+
- outlining and drafting support for turning research notes into section plans, related-work summaries, or first-pass prose
|
|
76
|
+
- revision tools for checking consistency between claims, citations, and bibliography entries
|
|
77
|
+
- submission-oriented utilities for format checks, metadata cleanup, and venue-specific packaging
|
|
78
|
+
|
|
79
|
+
Those additions would move the project from paper search into a broader research-writing workspace.
|
|
80
|
+
|
|
81
|
+
## Closing thought
|
|
82
|
+
|
|
83
|
+
`apaper-mcp` is a focused example of what MCP is good at: exposing useful capabilities in a form that AI clients can call reliably. Right now, that means less context switching, less glue code, and a cleaner path from a research question to a usable set of papers. With the right extensions, it can grow into a practical MCP server for the full paper-writing workflow rather than only the search stage.
|