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.
Files changed (54) hide show
  1. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/PKG-INFO +1 -1
  2. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/server.py +2 -0
  3. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/__init__.py +8 -1
  4. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/s2.py +68 -19
  5. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/utils.py +1 -1
  6. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/PKG-INFO +1 -1
  7. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/pyproject.toml +1 -1
  8. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_bitflip.py +0 -15
  9. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_s2.py +29 -0
  10. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/LICENSE +0 -0
  11. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/README.md +0 -0
  12. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/__init__.py +0 -0
  13. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/__main__.py +0 -0
  14. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/files.py +0 -0
  15. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/latex_templates/agents4science_2025/agents4science_2025.sty +0 -0
  16. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/latex_templates/agents4science_2025/agents4science_2025.tex +0 -0
  17. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/llm.py +0 -0
  18. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/pdf.py +0 -0
  19. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/py.typed +0 -0
  20. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/settings.py +0 -0
  21. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/anthology_search.py +0 -0
  22. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/arxiv_download.py +0 -0
  23. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/arxiv_search.py +0 -0
  24. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/bitflip.py +0 -0
  25. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/document_qa.py +0 -0
  26. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/hf_datasets_search.py +0 -0
  27. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/image_processing.py +0 -0
  28. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/latex.py +0 -0
  29. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/py.typed +0 -0
  30. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/review.py +0 -0
  31. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/speech_to_text.py +0 -0
  32. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/visit_webpage.py +0 -0
  33. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/web_search.py +0 -0
  34. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp/tools/yt_transcript.py +0 -0
  35. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/SOURCES.txt +0 -0
  36. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/dependency_links.txt +0 -0
  37. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/entry_points.txt +0 -0
  38. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/requires.txt +0 -0
  39. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/academia_mcp.egg-info/top_level.txt +0 -0
  40. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/setup.cfg +0 -0
  41. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_anthology_search.py +0 -0
  42. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_arxiv_download.py +0 -0
  43. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_arxiv_search.py +0 -0
  44. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_document_qa.py +0 -0
  45. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_extract_json.py +0 -0
  46. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_hf_dataset_search.py +0 -0
  47. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_image_processing.py +0 -0
  48. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_latex.py +0 -0
  49. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_review.py +0 -0
  50. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_server.py +0 -0
  51. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_speech_to_text.py +0 -0
  52. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_visit_webpage.py +0 -0
  53. {academia_mcp-1.11.4 → academia_mcp-1.11.5}/tests/test_web_search.py +0 -0
  54. {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.4
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 s2_get_references, s2_get_citations, s2_corpus_id_from_arxiv_id, s2_get_info
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
- PAPER_URL_TEMPLATE = "https://api.semanticscholar.org/graph/v1/paper/{paper_id}?fields={fields}"
12
- CITATIONS_URL_TEMPLATE = "https://api.semanticscholar.org/graph/v1/paper/{paper_id}/citations?fields={fields}&offset={offset}&limit={limit}"
13
- REFERENCES_URL_TEMPLATE = "https://api.semanticscholar.org/graph/v1/paper/{paper_id}/references?fields={fields}&offset={offset}&limit={limit}"
14
- FIELDS = "title,authors,externalIds,venue,citationCount,publicationDate"
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
- entry = entry["citingPaper"] if "citingPaper" in entry else entry["citedPaper"]
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
- paper_id=paper_id, fields=FIELDS, offset=offset, limit=limit
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(paper_id=paper_id, fields=FIELDS)
101
- paper_response = get_with_retries(paper_url)
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
- paper_id=paper_id, fields=FIELDS, offset=offset, limit=limit
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}", fields="externalIds")
147
- response = get_with_retries(paper_url)
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
- paper_url = PAPER_URL_TEMPLATE.format(paper_id=f"arxiv:{arxiv_id}", fields=FIELDS)
163
- response = get_with_retries(paper_url)
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)
@@ -42,7 +42,7 @@ def post_with_retries(
42
42
  def get_with_retries(
43
43
  url: str,
44
44
  api_key: Optional[str] = None,
45
- timeout: int = 30,
45
+ timeout: int = 60,
46
46
  num_retries: int = 3,
47
47
  backoff_factor: float = 3.0,
48
48
  params: Optional[Dict[str, Any]] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: academia-mcp
3
- Version: 1.11.4
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "academia-mcp"
7
- version = "1.11.4"
7
+ version = "1.11.5"
8
8
  description = "MCP server that provides different tools to search for scientific publications"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -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