内容
# 开放论文MCP
[](./LICENSE)
[](https://www.python.org/downloads/)
[](https://github.com/Kaago/openpapers-mcp/actions/workflows/ci.yml)
[](#开发)
一个用于科学论文研究的本地[模型上下文协议](https://modelcontextprotocol.io)服务器 - 搜索、元数据、摘要和**合法**的开放访问PDF下载。完全在您的机器上通过标准输入输出运行。与Claude Desktop、ZCode、Cursor等任何其他MCP客户端配合使用。
**为什么使用此工具而不是直接访问API?** 它抽象了三个开放的学术API,转化为五个LLM可调用的工具,重建了OpenAlex的倒置索引中的摘要,通过Unpaywall查找合法的OA副本,并安全地下载PDF(SSRF安全,魔术字节验证,原子操作)。无需Sci-Hub,无需付费墙,无需机构许可证。
## 支持三个免费、开放的API
| API | 角色 | 认证 |
|---|---|---|
| [OpenAlex](https://openalex.org) | 搜索、元数据、摘要(倒置索引)、概念、引用 | `mailto`(礼貌池) |
| [CrossRef](https://crossref.org) | DOI查找、参考列表、资助者、出版商 | `mailto` + 用户代理 |
| [Unpaywall](https://unpaywall.org) | 来自存储库和出版商的合法开放访问PDF | `email` |
> **这是什么**:不是Sci-Hub,不是付费墙 bypass,也不是EBSCO(需要机构许可证)。仅通过Unpaywall提供合法的开放访问来源。
---
## 快速入门
```bash
git clone https://github.com/Kaago/openpapers-mcp.git
cd OpenPapers
cp .env.example .env # 然后编辑CONTACT_EMAIL
uv sync # 安装依赖
uv run pytest # 69个离线测试(+ 3个实时测试,需OPENPAPERS_LIVE=1)
uv run openpapers # 启动stdio MCP服务器
```
需求:Python ≥ 3.12 和 [`uv`](https://docs.astral.sh/uv/)。
---
## 工具
| 工具 | 描述 |
|---|---|
| `search_papers(query, num_results=10, year_from?, year_to?)` | OpenAlex相关性搜索。返回DOI、标题、作者、年份、场地、引用、OA状态、概念。 |
| `get_paper(doi)` | 完整的元数据 + 摘要(重建)+ 参考文献(CrossRef增强)+ 作者,含ORCID/ affiliations + 资助者 + 出版商。 |
| `find_oa_pdf(doi)` | Unpaywall查找。返回`is_oa`,最佳OA位置的直接PDF网址,所有OA位置(版本、许可证、主机类型)。 |
| `download_pdf(url, doi?, filename?)` | 将PDF流式传输到 `$PDF_DIR`。**SSRF安全**(私有/环回/元数据IP被拒绝),**魔术字节验证** (`%PDF-`),上限`PDF_MAX_BYTES`,**原子写入**(无部分文件)。 |
| `research_topic(query, max_results=5)` | 便利:搜索 + 同时丰富顶级结果的OA状态。紧凑的概览,用于确定研究领域。 |
### 典型工作流程
```
search_papers("transformer attention") → 候选列表
└─ get_paper("10.48550/arXiv.1706.03762") → 完整记录 + 摘要
└─ find_oa_pdf("10.48550/arXiv.1706.03762") → OA网址
└─ download_pdf("<url>", "10.48550/arXiv.1706.03762") → 本地文件
```
---
## 配置
所有配置都存放在`.env`(git-ignored)中。参见[`.env.example`](./.env.example):
```dotenv
# 您的电子邮件 - 仅作为mailto=/User-Agent发送到三个API。
# 设置POLITE_POOL=0以完全省略(隐私模式)。
CONTACT_EMAIL=your-email@example.com
POLITE_POOL=1
# 下载的PDF存储位置。
PDF_DIR=./pdfs
# HTTP调优
HTTP_TIMEOUT=30
HTTP_MAX_RETRIES=3
PDF_MAX_BYTES=104857600 # 100 MB
# 日志记录
LOG_LEVEL=INFO
```
如果`CONTACT_EMAIL`未设置,则使用中立的占位符 - 服务器仍然有效,只是可能有更严格的速率限制。
---
## 隐私
- **您的电子邮件**仅在`mailto=`查询参数/`User-Agent`头部发送到OpenAlex、CrossRef和Unpaywall - 绝不发送给其他任何人。**不会**在INFO级别记录(仅masked形式,例如`you***@x.com`)。设置`POLITE_POOL=0`以完全省略。
- **搜索查询和DOI**作为正常操作的一部分发送到这三个API。stderr日志在错误时可能包含它们,日志级别为WARNING。stderr流由您的MCP客户端捕获到本地日志文件中。
- **下载的PDF**在`PDF_DIR`下持久存储在磁盘上,并显示您的研究兴趣。该目录被git忽略。
---
## 连接您的MCP客户端
服务器是一个标准的stdio MCP服务器。将它添加到您的客户端配置中,替换`/path/to/OpenPapers`为您的checkout路径。
### Claude Desktop
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
```json
{
"mcpServers": {
"openpapers": {
"command": "uv",
"args": ["run", "--directory", "/path/to/OpenPapers", "openpapers"]
}
}
}
```
### ZCode
`~/.zcode/config.toml` (或工作区 `.zcode/config.toml`):
```toml
[[mcp_servers]]
name = "openpapers"
transport = "stdio"
[mcp_servers.stdio]
command = "uv"
args = ["run", "--directory", "/path/to/OpenPapers", "openpapers"]
```
### Cursor
`.cursor/mcp.json`:
```json
{
"mcpServers": {
"openpapers": {
"command": "uv",
"args": ["run", "--directory", "/path/to/OpenPapers", "openpapers"]
}
}
}
```
编辑后,重启客户端。五个`openpapers`工具应该会出现。
---
## 开发
```bash
# 测试(离线 - 使用respx模拟,无需网络)
uv run pytest
# 校验和格式化
uv run ruff check .
uv run ruff format --check .
# 类型检查
uv run mypy
# 可选:针对真实API的实时烟雾测试
OPENPAPERS_LIVE=1 uv run pytest -m live
```
参见 [CONTRIBUTING.md](./CONTRIBUTING.md) 获取完整的开发者指南,包括 **仅限离线测试** 惯例。
### 项目布局
```
src/openpapers/
├── __main__.py # 入口点:`uv run openpapers`
├── server.py # FastMCP + 5种工具注册
├── config.py # .env加载、路径、常量、版本源
├── http_client.py # httpx客户端,含重试、礼貌头部、安全下载
├── security.py # SSRF和路径穿越防护
├── models.py # Pydantic模型(公共工具合约)
└── services/
├── openalex.py # 搜索、get_by_doi、摘要重建
├── crossref.py # DOI元数据、参考文献、资助者、出版商
├── unpaywall.py # OA查找
├── downloader.py # PDF下载,含原子写入和魔术字节校验
└── util.py # DOI规范化、摘要重建、名称解析
tests/ # 69个离线测试(使用respx)+ 3个实时烟雾测试
```
### 设计笔记
- **默认礼貌。** `mailto`/`User-Agent`头部在每个请求上设置,并带有`POLITE_POOL=0`的退出通道,用于隐私模式。
- **摘要重建。** OpenAlex存储摘要为 `{word: [positions]}` 以避免重新分发全文;原始摘要无损重建。
- **PDF安全。** 内容类型必须是 `application/pdf` **且** 字节必须以 `%PDF-` 开头。网址针对方案和解析主机进行验证(环回、RFC1918、链路本地 `169.254/16`、CGNAT、ULA全部被拒绝)。文件名被净化,路径必须位于 `PDF_DIR` 内。下载写入 `<name>.pdf.part` 并在成功时原子性重命名。
- **DOI规范化。** 接受裸DOI、 `doi:...` 、 `https://doi.org/...` 或嵌入在周围文本中的DOI。正则表达式排除URL重要字符 (`?#&[]`) 以防止API-URL注入。
- **CrossRef增强是尽力而为。** 如果CrossRef宕机或没有记录, `get_paper` 仍将返回OpenAlex记录和空引用。
- **版本的单一真相源。** `__version__` 位于 `config.py` ; `pyproject.toml` 镜像它。
---
## 限制和替代方案
- **无付费全文。** 如果论文不是OA,您将获得元数据 + 摘要但无PDF。对于付费内容,请使用您的机构VPN/图书馆代理或EBSCO Discovery Service集成(需要许可证)。
- **摘要覆盖。** OpenAlex有大多数近期论文的摘要,但较旧的作品(约2000年之前,一些出版商)可能有 `abstract: null`。
- **速率限制。** 三个API都很慷慨但并非无限。当遇到429/5xx时,服务器会使用指数退避重试,遵守`Retry-After`(秒和HTTP日期表单)。对于批量工作,请限制调用或自行托管OpenAlex快照。
---
## 数据许可和归属
此服务器是三个上游API的薄客户端。每个API都有其自己的条款:
- **OpenAlex** - 元数据是CC0;他们要求注明出处。参见 [openalex.org](https://openalex.org)。
- **CrossRef** - 元数据是CC0;REST API有 [使用条款](https://www.crossref.org/documentation/retrieve-metadata/rest-api/)。
- **Unpaywall** - 参见 [他们的条款](https://unpaywall.org/products/api)。
您下载的PDF来自Unpaywall识别的出版商/存储库;每个存储库都有自己的许可证(CC-BY、付费但OA副本等)。尊重个别论文的许可证。
---
## 获取帮助
- 🐛 **错误和功能请求:** [打开问题](https://github.com/Kaago/openpapers-mcp/issues/new/choose)
- 🔒 **安全问题:** 参见 [SECURITY.md](./SECURITY.md) - **不要** 打开公共问题
- 💬 **讨论:** GitHub Discussions(如果启用)或问题跟踪器
---
## 许可证
[MIT](./LICENSE) © Philipp Polte. 参见 [CONTRIBUTING.md](./CONTRIBUTING.md) 获取贡献协议, [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) 获取社区标准。