academia-mcp 1.11.4__tar.gz → 1.11.5__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.
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/PKG-INFO +1 -1
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/server.py +2 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/__init__.py +8 -1
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/s2.py +68 -19
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/utils.py +1 -1
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/PKG-INFO +1 -1
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/pyproject.toml +1 -1
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_bitflip.py +0 -15
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_s2.py +29 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/LICENSE +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/README.md +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/__init__.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/__main__.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/files.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/latex_templates/agents4science_2025/agents4science_2025.sty +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/latex_templates/agents4science_2025/agents4science_2025.tex +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/llm.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/pdf.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/py.typed +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/settings.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/anthology_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/arxiv_download.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/arxiv_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/bitflip.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/document_qa.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/hf_datasets_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/image_processing.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/latex.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/py.typed +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/review.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/speech_to_text.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/visit_webpage.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/web_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/yt_transcript.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/SOURCES.txt +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/dependency_links.txt +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/entry_points.txt +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/requires.txt +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/top_level.txt +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/setup.cfg +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_anthology_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_arxiv_download.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_arxiv_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_document_qa.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_extract_json.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_hf_dataset_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_image_processing.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_latex.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_review.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_server.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_speech_to_text.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_visit_webpage.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_web_search.py +0 -0
- {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_yt_transcript.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: academia-mcp
|
3
|
-
Version: 1.11.
|
3
|
+
Version: 1.11.5
|
4
4
|
Summary: MCP server that provides different tools to search for scientific publications
|
5
5
|
Author-email: Ilya Gusev <phoenixilya@gmail.com>
|
6
6
|
Project-URL: Homepage, https://github.com/IlyaGusev/academia_mcp
|
@@ -17,6 +17,7 @@ from academia_mcp.tools.s2 import (
|
|
17
17
|
s2_get_references,
|
18
18
|
s2_corpus_id_from_arxiv_id,
|
19
19
|
s2_get_info,
|
20
|
+
s2_search,
|
20
21
|
)
|
21
22
|
from academia_mcp.tools.hf_datasets_search import hf_datasets_search
|
22
23
|
from academia_mcp.tools.anthology_search import anthology_search
|
@@ -86,6 +87,7 @@ def create_server(
|
|
86
87
|
server.add_tool(s2_get_citations, structured_output=True)
|
87
88
|
server.add_tool(s2_get_references, structured_output=True)
|
88
89
|
server.add_tool(s2_get_info, structured_output=True)
|
90
|
+
server.add_tool(s2_search, structured_output=True)
|
89
91
|
server.add_tool(s2_corpus_id_from_arxiv_id)
|
90
92
|
server.add_tool(hf_datasets_search)
|
91
93
|
server.add_tool(anthology_search)
|
@@ -2,7 +2,13 @@ from .arxiv_search import arxiv_search
|
|
2
2
|
from .anthology_search import anthology_search
|
3
3
|
from .arxiv_download import arxiv_download
|
4
4
|
from .hf_datasets_search import hf_datasets_search
|
5
|
-
from .s2 import
|
5
|
+
from .s2 import (
|
6
|
+
s2_get_references,
|
7
|
+
s2_get_citations,
|
8
|
+
s2_corpus_id_from_arxiv_id,
|
9
|
+
s2_get_info,
|
10
|
+
s2_search,
|
11
|
+
)
|
6
12
|
from .document_qa import document_qa
|
7
13
|
from .latex import (
|
8
14
|
compile_latex,
|
@@ -26,6 +32,7 @@ __all__ = [
|
|
26
32
|
"s2_get_citations",
|
27
33
|
"s2_corpus_id_from_arxiv_id",
|
28
34
|
"s2_get_info",
|
35
|
+
"s2_search",
|
29
36
|
"hf_datasets_search",
|
30
37
|
"document_qa",
|
31
38
|
"compile_latex",
|
@@ -8,10 +8,12 @@ from pydantic import BaseModel, Field
|
|
8
8
|
from academia_mcp.utils import get_with_retries
|
9
9
|
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
BASE_URL = "https://api.semanticscholar.org/graph/v1"
|
12
|
+
PAPER_URL_TEMPLATE = "{base_url}/paper/{paper_id}"
|
13
|
+
CITATIONS_URL_TEMPLATE = "{base_url}/paper/{paper_id}/citations"
|
14
|
+
REFERENCES_URL_TEMPLATE = "{base_url}/paper/{paper_id}/references"
|
15
|
+
SEARCH_URL_TEMPLATE = "{base_url}/paper/search"
|
16
|
+
FIELDS = "paperId,title,authors,externalIds,venue,citationCount,publicationDate"
|
15
17
|
|
16
18
|
|
17
19
|
class S2PaperInfo(BaseModel): # type: ignore
|
@@ -38,7 +40,10 @@ def _format_authors(authors: List[Dict[str, Any]]) -> List[str]:
|
|
38
40
|
|
39
41
|
|
40
42
|
def _clean_entry(entry: Dict[str, Any]) -> S2PaperInfo:
|
41
|
-
|
43
|
+
if "citingPaper" in entry:
|
44
|
+
entry = entry["citingPaper"]
|
45
|
+
elif "citedPaper" in entry:
|
46
|
+
entry = entry["citedPaper"]
|
42
47
|
external_ids = entry.get("externalIds")
|
43
48
|
if not external_ids:
|
44
49
|
external_ids = dict()
|
@@ -88,17 +93,17 @@ def s2_get_citations(
|
|
88
93
|
arxiv_id = arxiv_id.split("v")[0]
|
89
94
|
paper_id = f"arxiv:{arxiv_id}"
|
90
95
|
|
91
|
-
url = CITATIONS_URL_TEMPLATE.format(
|
92
|
-
|
93
|
-
)
|
94
|
-
response = get_with_retries(url)
|
96
|
+
url = CITATIONS_URL_TEMPLATE.format(base_url=BASE_URL, paper_id=paper_id)
|
97
|
+
payload = {"fields": FIELDS, "offset": offset, "limit": limit}
|
98
|
+
response = get_with_retries(url, params=payload)
|
95
99
|
result = response.json()
|
96
100
|
entries = result["data"]
|
97
101
|
total_count = len(result["data"]) + result["offset"]
|
98
102
|
|
99
103
|
if "next" in result:
|
100
|
-
paper_url = PAPER_URL_TEMPLATE.format(
|
101
|
-
|
104
|
+
paper_url = PAPER_URL_TEMPLATE.format(base_url=BASE_URL, paper_id=paper_id)
|
105
|
+
payload = {"fields": FIELDS}
|
106
|
+
paper_response = get_with_retries(paper_url, params=payload)
|
102
107
|
paper_result = paper_response.json()
|
103
108
|
total_count = paper_result["citationCount"]
|
104
109
|
|
@@ -123,10 +128,9 @@ def s2_get_references(
|
|
123
128
|
arxiv_id = arxiv_id.split("v")[0]
|
124
129
|
paper_id = f"arxiv:{arxiv_id}"
|
125
130
|
|
126
|
-
url = REFERENCES_URL_TEMPLATE.format(
|
127
|
-
|
128
|
-
)
|
129
|
-
response = get_with_retries(url)
|
131
|
+
url = REFERENCES_URL_TEMPLATE.format(base_url=BASE_URL, paper_id=paper_id)
|
132
|
+
payload = {"fields": FIELDS, "offset": offset, "limit": limit}
|
133
|
+
response = get_with_retries(url, params=payload)
|
130
134
|
result = response.json()
|
131
135
|
entries = result["data"]
|
132
136
|
total_count = len(result["data"]) + result["offset"]
|
@@ -143,8 +147,9 @@ def s2_corpus_id_from_arxiv_id(arxiv_id: str) -> int:
|
|
143
147
|
assert isinstance(arxiv_id, str), "Error: Your arxiv_id must be a string"
|
144
148
|
if "v" in arxiv_id:
|
145
149
|
arxiv_id = arxiv_id.split("v")[0]
|
146
|
-
paper_url = PAPER_URL_TEMPLATE.format(paper_id=f"arxiv:{arxiv_id}"
|
147
|
-
|
150
|
+
paper_url = PAPER_URL_TEMPLATE.format(base_url=BASE_URL, paper_id=f"arxiv:{arxiv_id}")
|
151
|
+
payload = {"fields": "externalIds"}
|
152
|
+
response = get_with_retries(paper_url, params=payload)
|
148
153
|
result = response.json()
|
149
154
|
return int(result["externalIds"]["CorpusId"])
|
150
155
|
|
@@ -159,8 +164,10 @@ def s2_get_info(arxiv_id: str) -> S2PaperInfo:
|
|
159
164
|
assert isinstance(arxiv_id, str), "Error: Your arxiv_id must be a string"
|
160
165
|
if "v" in arxiv_id:
|
161
166
|
arxiv_id = arxiv_id.split("v")[0]
|
162
|
-
|
163
|
-
|
167
|
+
paper_id = f"arxiv:{arxiv_id}"
|
168
|
+
payload = {"fields": FIELDS}
|
169
|
+
paper_url = PAPER_URL_TEMPLATE.format(base_url=BASE_URL, paper_id=paper_id)
|
170
|
+
response = get_with_retries(paper_url, params=payload)
|
164
171
|
json_data = response.json()
|
165
172
|
return S2PaperInfo(
|
166
173
|
arxiv_id=json_data.get("externalIds", {}).get("ArXiv"),
|
@@ -171,3 +178,45 @@ def s2_get_info(arxiv_id: str) -> S2PaperInfo:
|
|
171
178
|
citation_count=int(json_data.get("citationCount", 0)),
|
172
179
|
publication_date=str(json_data.get("publicationDate", "")),
|
173
180
|
)
|
181
|
+
|
182
|
+
|
183
|
+
def s2_search(
|
184
|
+
query: str,
|
185
|
+
offset: int = 0,
|
186
|
+
limit: int = 5,
|
187
|
+
min_citation_count: int = 0,
|
188
|
+
publication_date: Optional[str] = None,
|
189
|
+
) -> S2SearchResponse:
|
190
|
+
"""
|
191
|
+
Search the S2 corpus for a given query.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
query: The query to search for.
|
195
|
+
offset: The offset to scroll through results. 10 items will be skipped if offset=10. 0 by default.
|
196
|
+
limit: The maximum number of items to return. limit=50 by default.
|
197
|
+
min_citation_count: The minimum citation count to return. 0 by default.
|
198
|
+
publication_date: Restricts results to the given range of publication dates or years (inclusive).
|
199
|
+
Accepts the format <startDate>:<endDate> with each date in YYYY-MM-DD format. None by default.
|
200
|
+
"""
|
201
|
+
url = SEARCH_URL_TEMPLATE.format(base_url=BASE_URL)
|
202
|
+
payload = {
|
203
|
+
"query": query,
|
204
|
+
"offset": offset,
|
205
|
+
"limit": limit,
|
206
|
+
"minCitationCount": min_citation_count,
|
207
|
+
"fields": FIELDS,
|
208
|
+
}
|
209
|
+
if publication_date:
|
210
|
+
payload["publicationDateOrYear"] = publication_date
|
211
|
+
response = get_with_retries(url, params=payload, backoff_factor=10.0, num_retries=5)
|
212
|
+
result = response.json()
|
213
|
+
if "data" not in result:
|
214
|
+
return S2SearchResponse(
|
215
|
+
total_count=0,
|
216
|
+
returned_count=0,
|
217
|
+
offset=offset if offset else 0,
|
218
|
+
results=[],
|
219
|
+
)
|
220
|
+
entries = result["data"]
|
221
|
+
total_count = result["total"]
|
222
|
+
return _format_entries(entries, offset if offset else 0, total_count)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: academia-mcp
|
3
|
-
Version: 1.11.
|
3
|
+
Version: 1.11.5
|
4
4
|
Summary: MCP server that provides different tools to search for scientific publications
|
5
5
|
Author-email: Ilya Gusev <phoenixilya@gmail.com>
|
6
6
|
Project-URL: Homepage, https://github.com/IlyaGusev/academia_mcp
|
@@ -35,18 +35,3 @@ async def test_bitflip_score_research_proposals_base() -> None:
|
|
35
35
|
assert scores.proposals[1].strengths is not None
|
36
36
|
assert scores.proposals[0].weaknesses is not None
|
37
37
|
assert scores.proposals[1].weaknesses is not None
|
38
|
-
|
39
|
-
|
40
|
-
async def test_bitflip_score_research_proposals_str() -> None:
|
41
|
-
arxiv_id = "2503.07826"
|
42
|
-
bit = (await extract_bitflip_info(arxiv_id)).bit
|
43
|
-
proposals = await generate_research_proposals(bit=bit, num_proposals=2)
|
44
|
-
scores = await score_research_proposals(proposals)
|
45
|
-
assert scores.proposals
|
46
|
-
assert len(scores.proposals) == 2
|
47
|
-
assert scores.proposals[0].spark is not None
|
48
|
-
assert scores.proposals[1].spark is not None
|
49
|
-
assert scores.proposals[0].strengths is not None
|
50
|
-
assert scores.proposals[1].strengths is not None
|
51
|
-
assert scores.proposals[0].weaknesses is not None
|
52
|
-
assert scores.proposals[1].weaknesses is not None
|
@@ -3,6 +3,7 @@ from academia_mcp.tools import (
|
|
3
3
|
s2_get_references,
|
4
4
|
s2_corpus_id_from_arxiv_id,
|
5
5
|
s2_get_info,
|
6
|
+
s2_search,
|
6
7
|
)
|
7
8
|
|
8
9
|
|
@@ -40,3 +41,31 @@ def test_s2_get_info() -> None:
|
|
40
41
|
assert info.citation_count is not None
|
41
42
|
assert info.publication_date is not None
|
42
43
|
assert info.external_ids["CorpusId"] == 272593138
|
44
|
+
|
45
|
+
|
46
|
+
def test_s2_search_base() -> None:
|
47
|
+
result = s2_search("transformers")
|
48
|
+
assert result.total_count >= 1
|
49
|
+
assert "transformers" in str(result.results).lower()
|
50
|
+
assert result.offset == 0
|
51
|
+
assert result.returned_count == 5
|
52
|
+
|
53
|
+
|
54
|
+
def test_s2_search_offset() -> None:
|
55
|
+
result = s2_search("transformers", offset=10)
|
56
|
+
assert result.total_count >= 1
|
57
|
+
assert "transformers" in str(result.results).lower()
|
58
|
+
assert result.offset == 10
|
59
|
+
assert result.returned_count == 5
|
60
|
+
|
61
|
+
|
62
|
+
def test_s2_search_min_citation_count() -> None:
|
63
|
+
result = s2_search("transformers", min_citation_count=100000)
|
64
|
+
assert result.total_count >= 2 and result.total_count <= 10
|
65
|
+
|
66
|
+
|
67
|
+
def test_s2_search_publication_date() -> None:
|
68
|
+
result = s2_search(
|
69
|
+
"transformers", min_citation_count=100000, publication_date="2017-01-01:2017-12-31"
|
70
|
+
)
|
71
|
+
assert result.total_count == 1
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|