mcp-scholar 0.1.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.

Potentially problematic release.


This version of mcp-scholar might be problematic. Click here for more details.

@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp_scholar
3
+ Version: 0.1.0
4
+ Summary: MCP Scholar for Google Scholar search
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: beautifulsoup4>=4.13.3
7
+ Requires-Dist: httpx>=0.28.1
8
+ Requires-Dist: lxml>=4.9.3
9
+ Requires-Dist: mcp[cli]>=1.4.1
10
+ Requires-Dist: python-dotenv>=1.0.1
11
+ Requires-Dist: scholarly>=1.7.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # MCP Scholar
15
+
16
+ 基于MCP协议的谷歌学术搜索和分析服务。
17
+
18
+ ## 功能特点
19
+
20
+ - 谷歌学术论文搜索:根据关键词搜索相关论文,并按引用量排序
21
+ - 学者主页分析:分析谷歌学术个人主页,提取引用量最高的论文
22
+ - 支持与Cherry Studio集成:可以作为插件在Cherry Studio中使用
23
+
24
+ ## 安装方法
25
+
26
+ ### 使用uv安装
27
+
28
+ ```bash
29
+ # 克隆仓库
30
+ git clone https://github.com/yourusername/mcp-scholar.git
31
+ cd mcp-scholar
32
+
33
+ # 使用uv安装依赖
34
+ uv pip install -e .
35
+ ```
36
+
37
+ ### 使用pip安装
38
+
39
+ ```bash
40
+ pip install mcp-scholar
41
+ ```
42
+
43
+ ## 使用方法
44
+
45
+ ### 启动服务器
46
+
47
+ ```bash
48
+ # 使用uvx启动
49
+ uvx mcp-scholar
50
+
51
+ # 或者直接使用Python模块
52
+ python -m mcp_scholar
53
+ ```
54
+
55
+ ### 在Cherry Studio中使用
56
+
57
+ 1. 在Cherry Studio中,点击「设置」>「插件」
58
+ 2. 添加新插件,输入MCP服务器地址:`http://localhost:8080`
59
+ 3. 保存设置后,即可在对话中使用学术搜索功能
60
+
61
+ ## 示例用法
62
+
63
+ 在Cherry Studio中,可以使用以下提示:
64
+
65
+ - 「总结5篇关于人工智能的论文」
66
+ - 「分析学者主页 https://scholar.google.com/citations?user=SJdlvnQAAAAJ&hl=en 的前10篇高引论文」
67
+
68
+ ## 开发说明
69
+
70
+ 本项目使用MCP协议开发,基于Python SDK实现。详细信息请参考[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)。
71
+
72
+ ## 许可证
73
+
74
+ MIT
@@ -0,0 +1,61 @@
1
+ # MCP Scholar
2
+
3
+ 基于MCP协议的谷歌学术搜索和分析服务。
4
+
5
+ ## 功能特点
6
+
7
+ - 谷歌学术论文搜索:根据关键词搜索相关论文,并按引用量排序
8
+ - 学者主页分析:分析谷歌学术个人主页,提取引用量最高的论文
9
+ - 支持与Cherry Studio集成:可以作为插件在Cherry Studio中使用
10
+
11
+ ## 安装方法
12
+
13
+ ### 使用uv安装
14
+
15
+ ```bash
16
+ # 克隆仓库
17
+ git clone https://github.com/yourusername/mcp-scholar.git
18
+ cd mcp-scholar
19
+
20
+ # 使用uv安装依赖
21
+ uv pip install -e .
22
+ ```
23
+
24
+ ### 使用pip安装
25
+
26
+ ```bash
27
+ pip install mcp-scholar
28
+ ```
29
+
30
+ ## 使用方法
31
+
32
+ ### 启动服务器
33
+
34
+ ```bash
35
+ # 使用uvx启动
36
+ uvx mcp-scholar
37
+
38
+ # 或者直接使用Python模块
39
+ python -m mcp_scholar
40
+ ```
41
+
42
+ ### 在Cherry Studio中使用
43
+
44
+ 1. 在Cherry Studio中,点击「设置」>「插件」
45
+ 2. 添加新插件,输入MCP服务器地址:`http://localhost:8080`
46
+ 3. 保存设置后,即可在对话中使用学术搜索功能
47
+
48
+ ## 示例用法
49
+
50
+ 在Cherry Studio中,可以使用以下提示:
51
+
52
+ - 「总结5篇关于人工智能的论文」
53
+ - 「分析学者主页 https://scholar.google.com/citations?user=SJdlvnQAAAAJ&hl=en 的前10篇高引论文」
54
+
55
+ ## 开发说明
56
+
57
+ 本项目使用MCP协议开发,基于Python SDK实现。详细信息请参考[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)。
58
+
59
+ ## 许可证
60
+
61
+ MIT
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mcp_scholar"
7
+ version = "0.1.0"
8
+ description = "MCP Scholar for Google Scholar search"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "beautifulsoup4>=4.13.3",
13
+ "httpx>=0.28.1",
14
+ "mcp[cli]>=1.4.1",
15
+ "python-dotenv>=1.0.1",
16
+ "lxml>=4.9.3",
17
+ "scholarly>=1.7.0",
18
+ ]
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "black>=25.1.0",
23
+ "mypy>=1.15.0",
24
+ ]
25
+
26
+ [project.scripts]
27
+ mcp-scholar = "mcp_scholar:cli_main"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["src/mcp_scholar"]
@@ -0,0 +1,4 @@
1
+ """MCP Scholar package."""
2
+ from mcp_scholar.server import cli_main
3
+
4
+ __all__ = ["cli_main"]
@@ -0,0 +1,263 @@
1
+ """
2
+ 谷歌学术搜索和解析功能
3
+ 使用 scholarly 库与谷歌学术交互
4
+ """
5
+
6
+ import re
7
+ import httpx
8
+ import asyncio
9
+ from scholarly import scholarly
10
+ from typing import List, Dict, Any, Optional
11
+
12
+ # 配置 scholarly 的代理,如果需要的话
13
+ # scholarly.use_proxy(http="http://127.0.0.1:7890", https="http://127.0.0.1:7890")
14
+
15
+
16
+ async def search_scholar(query: str, count: int = 5) -> List[Dict[str, Any]]:
17
+ """
18
+ 搜索谷歌学术论文
19
+
20
+ Args:
21
+ query: 搜索关键词
22
+ count: 返回结果数量
23
+
24
+ Returns:
25
+ List[Dict]: 论文信息列表,按引用量排序
26
+ """
27
+ results = []
28
+ try:
29
+ # 使用scholarly库进行搜索
30
+ search_query = scholarly.search_pubs(query)
31
+
32
+ for _ in range(count):
33
+ try:
34
+ pub = next(search_query)
35
+
36
+ # 提取论文信息
37
+ paper = {
38
+ "title": pub.get("bib", {}).get("title", "未知标题"),
39
+ "authors": ", ".join(pub.get("bib", {}).get("author", [])),
40
+ "abstract": pub.get("bib", {}).get("abstract", "无摘要"),
41
+ "citations": pub.get("num_citations", 0),
42
+ "year": pub.get("bib", {}).get("pub_year", "未知年份"),
43
+ "venue": pub.get("bib", {}).get("venue", ""),
44
+ }
45
+
46
+ # 提取论文ID
47
+ pub_url = pub.get("pub_url", "")
48
+ if "citation_for_view=" in pub_url:
49
+ paper["paper_id"] = pub_url.split("citation_for_view=")[-1]
50
+ elif "cluster=" in pub_url:
51
+ paper["paper_id"] = pub_url.split("cluster=")[-1].split("&")[0]
52
+ else:
53
+ paper["paper_id"] = None
54
+
55
+ results.append(paper)
56
+
57
+ # 添加延迟以避免被谷歌限制
58
+ await asyncio.sleep(1)
59
+
60
+ except StopIteration:
61
+ break
62
+ except Exception as e:
63
+ print(f"处理单篇论文时出错: {str(e)}")
64
+ continue
65
+
66
+ return sorted(results, key=lambda x: -x["citations"])
67
+ except Exception as e:
68
+ print(f"搜索谷歌学术时出错: {str(e)}")
69
+ return []
70
+
71
+
72
+ async def get_paper_detail(paper_id: str) -> Optional[Dict[str, Any]]:
73
+ """
74
+ 获取论文详情
75
+
76
+ Args:
77
+ paper_id: 论文ID
78
+
79
+ Returns:
80
+ Dict: 论文详细信息
81
+ """
82
+ try:
83
+ # 尝试通过ID获取论文详情
84
+ if ":" in paper_id: # 可能是DOI或其他标识符
85
+ # 使用scholarly的方式搜索特定论文
86
+ query = f"source:{paper_id}"
87
+ search_query = scholarly.search_pubs(query)
88
+ try:
89
+ pub = next(search_query)
90
+ pub = scholarly.fill(pub)
91
+ except StopIteration:
92
+ return None
93
+ else:
94
+ # 尝试通过聚类ID查找
95
+ url = f"https://scholar.google.com/scholar?cluster={paper_id}"
96
+ async with httpx.AsyncClient() as client:
97
+ response = await client.get(url)
98
+ response.raise_for_status()
99
+
100
+ # 这里需要解析返回的HTML页面,比较复杂
101
+ # 建议使用scholarly的工具
102
+ return None
103
+
104
+ # 提取详细信息
105
+ return {
106
+ "title": pub.get("bib", {}).get("title", "未知标题"),
107
+ "authors": ", ".join(pub.get("bib", {}).get("author", [])),
108
+ "abstract": pub.get("bib", {}).get("abstract", "无摘要"),
109
+ "citations": pub.get("num_citations", 0),
110
+ "year": pub.get("bib", {}).get("pub_year", "未知年份"),
111
+ "venue": pub.get("bib", {}).get("venue", ""),
112
+ "paper_id": paper_id,
113
+ "url": pub.get("pub_url", ""),
114
+ "citation_url": (
115
+ pub.get("cites_id", {}).get("link", "") if pub.get("cites_id") else ""
116
+ ),
117
+ }
118
+ except Exception as e:
119
+ print(f"获取论文详情时出错: {str(e)}")
120
+ return None
121
+
122
+
123
+ async def get_paper_references(paper_id: str, count: int = 5) -> List[Dict[str, Any]]:
124
+ """
125
+ 获取引用指定论文的文献
126
+
127
+ Args:
128
+ paper_id: 论文ID
129
+ count: 返回结果数量
130
+
131
+ Returns:
132
+ List[Dict]: 引用论文信息列表
133
+ """
134
+ results = []
135
+ try:
136
+ # 通过ID获取论文的引用
137
+ if ":" in paper_id: # 可能是DOI或其他标识符
138
+ query = f"cite:{paper_id}"
139
+ else:
140
+ query = f"cites={paper_id}"
141
+
142
+ search_query = scholarly.search_pubs(query)
143
+
144
+ for _ in range(count):
145
+ try:
146
+ pub = next(search_query)
147
+
148
+ paper = {
149
+ "title": pub.get("bib", {}).get("title", "未知标题"),
150
+ "authors": ", ".join(pub.get("bib", {}).get("author", [])),
151
+ "abstract": pub.get("bib", {}).get("abstract", "无摘要"),
152
+ "citations": pub.get("num_citations", 0),
153
+ "year": pub.get("bib", {}).get("pub_year", "未知年份"),
154
+ "venue": pub.get("bib", {}).get("venue", ""),
155
+ }
156
+
157
+ # 提取论文ID
158
+ pub_url = pub.get("pub_url", "")
159
+ if "citation_for_view=" in pub_url:
160
+ paper["paper_id"] = pub_url.split("citation_for_view=")[-1]
161
+ elif "cluster=" in pub_url:
162
+ paper["paper_id"] = pub_url.split("cluster=")[-1].split("&")[0]
163
+ else:
164
+ paper["paper_id"] = None
165
+
166
+ results.append(paper)
167
+
168
+ # 添加延迟以避免被谷歌限制
169
+ await asyncio.sleep(1)
170
+
171
+ except StopIteration:
172
+ break
173
+ except Exception as e:
174
+ print(f"处理引用论文时出错: {str(e)}")
175
+ continue
176
+
177
+ return sorted(results, key=lambda x: -x["citations"])
178
+ except Exception as e:
179
+ print(f"获取论文引用时出错: {str(e)}")
180
+ return []
181
+
182
+
183
+ async def parse_profile(profile_id: str, top_n: int = 5) -> List[Dict[str, Any]]:
184
+ """
185
+ 解析谷歌学术个人主页
186
+
187
+ Args:
188
+ profile_id: 学者ID
189
+ top_n: 返回结果数量
190
+
191
+ Returns:
192
+ List[Dict]: 论文信息列表
193
+ """
194
+ try:
195
+ # 通过ID查找作者
196
+ author = scholarly.search_author_id(profile_id)
197
+ if not author:
198
+ print(f"未找到ID为{profile_id}的学者")
199
+ return []
200
+
201
+ # 获取完整信息
202
+ author = scholarly.fill(author)
203
+
204
+ # 获取发表的论文
205
+ publications = author.get("publications", [])
206
+ papers = []
207
+
208
+ for pub in publications[
209
+ : min(len(publications), top_n * 2)
210
+ ]: # 获取更多论文,以防有些无法填充
211
+ try:
212
+ # 获取详细信息
213
+ filled_pub = scholarly.fill(pub)
214
+
215
+ paper = {
216
+ "title": filled_pub.get("bib", {}).get("title", "未知标题"),
217
+ "authors": ", ".join(filled_pub.get("bib", {}).get("author", [])),
218
+ "citations": filled_pub.get("num_citations", 0),
219
+ "year": filled_pub.get("bib", {}).get("pub_year", "未知年份"),
220
+ "venue": filled_pub.get("bib", {}).get("venue", ""),
221
+ "abstract": filled_pub.get("bib", {}).get("abstract", "无摘要"),
222
+ }
223
+
224
+ # 提取论文ID
225
+ pub_url = filled_pub.get("pub_url", "")
226
+ if "citation_for_view=" in pub_url:
227
+ paper["paper_id"] = pub_url.split("citation_for_view=")[-1]
228
+ elif "cluster=" in pub_url:
229
+ paper["paper_id"] = pub_url.split("cluster=")[-1].split("&")[0]
230
+ else:
231
+ paper["paper_id"] = None
232
+
233
+ papers.append(paper)
234
+
235
+ # 添加延迟以避免被谷歌限制
236
+ await asyncio.sleep(1)
237
+
238
+ except Exception as e:
239
+ print(f"处理作者论文时出错: {str(e)}")
240
+ continue
241
+
242
+ # 按引用数排序
243
+ papers = sorted(papers, key=lambda x: -x["citations"])
244
+ return papers[:top_n]
245
+ except Exception as e:
246
+ print(f"解析学者档案时出错: {str(e)}")
247
+ return []
248
+
249
+
250
+ def extract_profile_id_from_url(url: str) -> str:
251
+ """
252
+ 从谷歌学术个人主页URL中提取学者ID
253
+
254
+ Args:
255
+ url: 谷歌学术个人主页URL
256
+
257
+ Returns:
258
+ str: 学者ID
259
+ """
260
+ match = re.search(r"user=([^&]+)", url)
261
+ if match:
262
+ return match.group(1)
263
+ return ""