academia-mcp 1.11.4__py3-none-any.whl → 1.11.5__py3-none-any.whl

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/server.py CHANGED
@@ -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",
academia_mcp/tools/s2.py CHANGED
@@ -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)
academia_mcp/utils.py CHANGED
@@ -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,12 +4,12 @@ academia_mcp/files.py,sha256=J_81COWWryBp2bHVe_9dg9c0rbVIzv7-zWkJ7CS9lr4,485
4
4
  academia_mcp/llm.py,sha256=zpGkuJFf58Ofgys_fi28-47_wJ1a7sIs_yZvI1Si6z0,993
5
5
  academia_mcp/pdf.py,sha256=9PlXzHGhb6ay3ldbTdxCcTWvH4TkET3bnb64mgoh9i0,1273
6
6
  academia_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- academia_mcp/server.py,sha256=sxr_M1JQif29hSuduXvoknPgidwoau8WKszT_P9mEVI,6396
7
+ academia_mcp/server.py,sha256=oKlEjIhqzd2xmK8gtCDMKzqiXMd9K50K-3fyHhcke5Y,6466
8
8
  academia_mcp/settings.py,sha256=c5s4dI8V_cWmMED-jKDmHjfdIaBcxwEK4HdHNQ3WUIg,1096
9
- academia_mcp/utils.py,sha256=ixtYI7qidFJpc8Tzc5sokseCLx4r0yFFgbktKaY-Ixo,4904
9
+ academia_mcp/utils.py,sha256=BVoEkGlW0HPH4bbWIFLzVwjA95ocT6OnoZBUhs87DK0,4904
10
10
  academia_mcp/latex_templates/agents4science_2025/agents4science_2025.sty,sha256=hGcEPCYBJS4vdhWvN_yEaJC4GvT_yDroI94CfY2Oguk,12268
11
11
  academia_mcp/latex_templates/agents4science_2025/agents4science_2025.tex,sha256=Tl1QkHXHRopw9VEfWrD3Layr5JP_0gIzVQjL4KXIWqc,15814
12
- academia_mcp/tools/__init__.py,sha256=cCS073dW2S5T_YWrbwd-H9QRiervZ7bof_2sdvzR8JQ,1561
12
+ academia_mcp/tools/__init__.py,sha256=RpOCL2t-_fJev55rXL1za1MdrY6sESaTCszUCsEW474,1614
13
13
  academia_mcp/tools/anthology_search.py,sha256=rhFpJZqGLABgr0raDuH0CARBiAJNJtEI4dlMrKNHfDQ,7669
14
14
  academia_mcp/tools/arxiv_download.py,sha256=4zl9QkWF7uU2BYz4XH7Fu_51htolSYtO7a2v4_ikhxg,10633
15
15
  academia_mcp/tools/arxiv_search.py,sha256=p4DeCvV3TUpj58etx0QOxm-GYKAWkP9AGjLv4HUUqUc,8857
@@ -20,14 +20,14 @@ academia_mcp/tools/image_processing.py,sha256=BFj5D0lYbe6OCAL9LSHjS1adr8C0BrtWBX
20
20
  academia_mcp/tools/latex.py,sha256=B1Leqt1FHY6H3DlUgeYse4LMFpf4-K1FQViXl5MKk8A,6144
21
21
  academia_mcp/tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  academia_mcp/tools/review.py,sha256=Va0lFJJKuk-NvWhKS3UZ-Dnuk7CyuDQ4S1nd70D-ffE,11117
23
- academia_mcp/tools/s2.py,sha256=ykJOkpHnyZRlfyDnCIL9m-Rnu5dBecoKxg0SjlzdvLk,6457
23
+ academia_mcp/tools/s2.py,sha256=_Ea7IyF5wH7ZWT59mI2b8wsxQp33W3URQV_EUiZuL2s,8221
24
24
  academia_mcp/tools/speech_to_text.py,sha256=YZzMqdvunzXkpcadP_mYhm6cs4qH1Y_42SfY-7eX4O4,1601
25
25
  academia_mcp/tools/visit_webpage.py,sha256=uJZx9vBGS8q-J-VH4Pr7T9lNtDsWU83gJhlotcd1ajg,3788
26
26
  academia_mcp/tools/web_search.py,sha256=CHgco8DufTFwtVecgDOOMylIY99iUmCdb0oZtpGntx0,8646
27
27
  academia_mcp/tools/yt_transcript.py,sha256=ilfOpX14moC1bKHbFmOVvZ8-_NxuQQUoQbV28e9FBaE,1217
28
- academia_mcp-1.11.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
29
- academia_mcp-1.11.4.dist-info/METADATA,sha256=j4_CAs-0UIfhs0ai-XdrSFJcCvid4AkVbcqNnvhrQrg,6517
30
- academia_mcp-1.11.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- academia_mcp-1.11.4.dist-info/entry_points.txt,sha256=gxkiKJ74w2FwJpSECpjA3XtCfI5ZfrM6N8cqnwsq4yY,51
32
- academia_mcp-1.11.4.dist-info/top_level.txt,sha256=CzGpRFsRRJRqWEb1e3SUlcfGqRzOxevZGaJWrtGF8W0,13
33
- academia_mcp-1.11.4.dist-info/RECORD,,
28
+ academia_mcp-1.11.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
29
+ academia_mcp-1.11.5.dist-info/METADATA,sha256=ExVjjzRzBc5eF9kVNZW_kAQdVCY3QZwE-jpZlcZSO6Q,6517
30
+ academia_mcp-1.11.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ academia_mcp-1.11.5.dist-info/entry_points.txt,sha256=gxkiKJ74w2FwJpSECpjA3XtCfI5ZfrM6N8cqnwsq4yY,51
32
+ academia_mcp-1.11.5.dist-info/top_level.txt,sha256=CzGpRFsRRJRqWEb1e3SUlcfGqRzOxevZGaJWrtGF8W0,13
33
+ academia_mcp-1.11.5.dist-info/RECORD,,