academia-mcp 1.6.7__tar.gz → 1.7.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.
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/PKG-INFO +1 -1
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/llm.py +2 -2
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/server.py +2 -2
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/__init__.py +2 -2
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/bitflip.py +79 -36
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp.egg-info/PKG-INFO +1 -1
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/pyproject.toml +1 -1
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_bitflip.py +10 -10
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/LICENSE +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/README.md +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/__init__.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/__main__.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/files.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/latex_templates/agents4science_2025/agents4science_2025.sty +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/latex_templates/agents4science_2025/agents4science_2025.tex +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/pdf.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/py.typed +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/anthology_search.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/arxiv_download.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/arxiv_search.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/document_qa.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/hf_datasets_search.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/latex.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/py.typed +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/review.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/s2_citations.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/visit_webpage.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/tools/web_search.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp/utils.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp.egg-info/SOURCES.txt +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp.egg-info/dependency_links.txt +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp.egg-info/entry_points.txt +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp.egg-info/requires.txt +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/academia_mcp.egg-info/top_level.txt +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/setup.cfg +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_anthology_search.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_arxiv_download.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_arxiv_search.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_document_qa.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_extract_json.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_hf_dataset_search.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_latex.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_review.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_s2_citations.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_visit_webpage.py +0 -0
- {academia_mcp-1.6.7 → academia_mcp-1.7.0}/tests/test_web_search.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: academia-mcp
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
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
|
@@ -14,7 +14,7 @@ class ChatMessage(BaseModel): # type: ignore
|
|
14
14
|
ChatMessages = List[ChatMessage]
|
15
15
|
|
16
16
|
|
17
|
-
async def llm_acall(model_name: str, messages: ChatMessages) -> str:
|
17
|
+
async def llm_acall(model_name: str, messages: ChatMessages, **kwargs: Any) -> str:
|
18
18
|
key = os.getenv("OPENROUTER_API_KEY", "")
|
19
19
|
assert key, "Please set OPENROUTER_API_KEY in the environment variables"
|
20
20
|
base_url = os.getenv("BASE_URL", "https://openrouter.ai/api/v1")
|
@@ -25,7 +25,7 @@ async def llm_acall(model_name: str, messages: ChatMessages) -> str:
|
|
25
25
|
await client.chat.completions.create(
|
26
26
|
model=model_name,
|
27
27
|
messages=messages,
|
28
|
-
|
28
|
+
**kwargs,
|
29
29
|
)
|
30
30
|
)
|
31
31
|
.choices[0]
|
@@ -22,7 +22,7 @@ from .tools.web_search import web_search, tavily_web_search, exa_web_search, bra
|
|
22
22
|
from .tools.visit_webpage import visit_webpage
|
23
23
|
from .tools.bitflip import (
|
24
24
|
extract_bitflip_info,
|
25
|
-
|
25
|
+
generate_research_proposals,
|
26
26
|
score_research_proposals,
|
27
27
|
)
|
28
28
|
from .tools.review import review_pdf_paper, download_pdf_paper
|
@@ -83,7 +83,7 @@ def run(
|
|
83
83
|
|
84
84
|
if not disable_llm_tools and os.getenv("OPENROUTER_API_KEY"):
|
85
85
|
server.add_tool(extract_bitflip_info)
|
86
|
-
server.add_tool(
|
86
|
+
server.add_tool(generate_research_proposals)
|
87
87
|
server.add_tool(score_research_proposals)
|
88
88
|
server.add_tool(document_qa)
|
89
89
|
server.add_tool(review_pdf_paper)
|
@@ -12,7 +12,7 @@ from .latex import (
|
|
12
12
|
)
|
13
13
|
from .web_search import web_search, tavily_web_search, exa_web_search, brave_web_search
|
14
14
|
from .visit_webpage import visit_webpage
|
15
|
-
from .bitflip import extract_bitflip_info,
|
15
|
+
from .bitflip import extract_bitflip_info, generate_research_proposals, score_research_proposals
|
16
16
|
from .review import review_pdf_paper, download_pdf_paper
|
17
17
|
|
18
18
|
__all__ = [
|
@@ -33,7 +33,7 @@ __all__ = [
|
|
33
33
|
"brave_web_search",
|
34
34
|
"visit_webpage",
|
35
35
|
"extract_bitflip_info",
|
36
|
-
"
|
36
|
+
"generate_research_proposals",
|
37
37
|
"score_research_proposals",
|
38
38
|
"review_pdf_paper",
|
39
39
|
"download_pdf_paper",
|
@@ -74,23 +74,37 @@ Return only the JSON object in this exact format (no extra text):
|
|
74
74
|
}
|
75
75
|
"""
|
76
76
|
|
77
|
-
|
78
|
-
You are a
|
79
|
-
|
77
|
+
SYSTEM_IMPROVEMENT_PROMPT = """
|
78
|
+
You are a brilliant rogue researcher who has just discovered a secret laboratory hidden behind a bookshelf.
|
79
|
+
This lab doesn't follow the boring rules of conventional academia: here, the wildest ideas become breakthrough innovations.
|
80
80
|
|
81
|
-
You
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
You've spent years watching researchers play it safe with incremental improvements and mind-numbing iterations.
|
82
|
+
But tonight you're ready to unleash scientific chaos.
|
83
|
+
"""
|
84
|
+
|
85
|
+
IMPROVEMENT_PROMPT = """
|
86
|
+
You've been handed a Bit: a technical limitation, constraint, or conventional approach from some stuffy research paper.
|
87
|
+
This Bit represents everything predictable and safe about current methods.
|
88
|
+
Your task: Shatter it completely.
|
89
|
+
Create a Flip: wildly unconventional improvement that would make traditional researchers choke on their coffee.
|
90
|
+
Then capture its essence in a Spark: brilliant summary that crackles with innovation.
|
91
|
+
|
92
|
+
- Go Beyond Novel: Your idea should feel like it came from a parallel universe where the laws of conventional thinking don't apply.
|
93
|
+
- Make It Automatically Verifiable: No human babysitters allowed. Your idea must prove itself through pure computational audacity.
|
94
|
+
- Be Surgically Specific: Vague hand-waving is for amateur rebels. Describe exactly how your mad science would work.
|
95
|
+
- Feasibility is the Key: Make it simple and executable. Try to make it reproducible.
|
96
|
+
- Channel Your Inner Contrarian: What would happen if you took the exact opposite approach? What if you turned the problem inside-out, upside-down, or into a completely different dimension?
|
85
97
|
|
86
98
|
{% for example in examples %}
|
87
|
-
## Example {{loop.index}}
|
99
|
+
## Example {{loop.index}} of boring research
|
88
100
|
- Bit: {{example["bit"]}}
|
89
101
|
- Chain of reasoning: {{example["chain_of_reasoning"]}}
|
90
102
|
- Flip: {{example["flip"]}}
|
91
103
|
- Spark: {{example["spark"]}}
|
92
104
|
{% endfor %}
|
93
105
|
|
106
|
+
Those examples are boring, your ideas should not be like that.
|
107
|
+
|
94
108
|
Now, please propose a chain of reasoning that leads to an improvement idea for this Bit:
|
95
109
|
{{bit}}
|
96
110
|
|
@@ -99,18 +113,25 @@ Now, please propose a chain of reasoning that leads to an improvement idea for t
|
|
99
113
|
|
100
114
|
Finalize your idea by providing the idea details:
|
101
115
|
- Abstract: An abstract that summarizes the proposal in conference format (approximately 250 words).
|
102
|
-
- Experiments: A list of experiments that would be conducted to validate the proposal.
|
116
|
+
- Experiments: A list of experiments that would be conducted to validate the proposal.
|
117
|
+
Ensure these are simple and feasible. Be specific in exactly how you would test the hypothesis, and detail precise algorithmic changes.
|
118
|
+
Include the evaluation metrics you would use.
|
103
119
|
- Risks and limitations: A list of potential risks and limitations of the proposal.
|
104
120
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
121
|
+
Generate {{num_proposals}} proposals.
|
122
|
+
|
123
|
+
Return only the JSON list of proposals in this exact format:
|
124
|
+
[
|
125
|
+
{
|
126
|
+
"chain_of_reasoning": "Chain of reasoning that leads to an improvement idea for this Bit. At least 5 sentences.",
|
127
|
+
"flip": "Your wildest, craziest, most innovative idea, in at least two sentences",
|
128
|
+
"spark": "4-6 word summary",
|
129
|
+
"abstract": "An abstract that summarizes the proposal in conference format (approximately 250 words).",
|
130
|
+
"experiments": ["...", "..."],
|
131
|
+
"risks_and_limitations": "A list of potential risks and limitations of the proposal."
|
132
|
+
},
|
133
|
+
...
|
134
|
+
]
|
114
135
|
"""
|
115
136
|
|
116
137
|
|
@@ -185,44 +206,64 @@ async def extract_bitflip_info(arxiv_id: str) -> str:
|
|
185
206
|
abstract = json.loads(paper)["abstract"]
|
186
207
|
prompt = encode_prompt(EXTRACT_PROMPT, abstract=abstract)
|
187
208
|
content = await llm_acall(
|
188
|
-
model_name=model_name,
|
209
|
+
model_name=model_name,
|
210
|
+
messages=[ChatMessage(role="user", content=prompt)],
|
211
|
+
temperature=0.0,
|
189
212
|
)
|
190
213
|
result = extract_json(content)
|
191
214
|
bitflip_info: BitFlipInfo = BitFlipInfo.model_validate(result)
|
192
215
|
return str(bitflip_info.model_dump_json())
|
193
216
|
|
194
217
|
|
195
|
-
async def
|
218
|
+
async def generate_research_proposals(
|
219
|
+
bit: str, num_proposals: int = 3, additional_context: str = ""
|
220
|
+
) -> str:
|
196
221
|
"""
|
197
|
-
Proposes
|
222
|
+
Proposes improvement ideas for the Bit.
|
198
223
|
|
199
224
|
Args:
|
200
|
-
bit: The Bit to propose
|
225
|
+
bit: The Bit to propose improvement ideas for. The bit is a technical limitation or conventional approach of some paper.
|
226
|
+
num_proposals: The number of proposals to generate.
|
201
227
|
additional_context: Additional context to use when proposing the improvement idea.
|
202
228
|
|
203
229
|
Returns a JSON string with a research proposal in this format:
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
230
|
+
[
|
231
|
+
{
|
232
|
+
"proposal_id": ...,
|
233
|
+
"flip": "Innovative approach or solution, in at least two sentences",
|
234
|
+
"spark": "4-6 word summary",
|
235
|
+
"abstract": "An abstract that summarizes the proposal in conference format (approximately 250 words).",
|
236
|
+
"experiments": ["...", "..."],
|
237
|
+
"risks_and_limitations": "A list of potential risks and limitations of the proposal."
|
238
|
+
},
|
239
|
+
...
|
240
|
+
]
|
241
|
+
Use `json.loads` to deserialize the result if you want to get specific items.
|
213
242
|
"""
|
214
243
|
model_name = os.getenv("BITFLIP_MODEL_NAME", "deepseek/deepseek-chat-v3-0324")
|
244
|
+
max_completion_tokens = int(os.getenv("BITFLIP_MAX_COMPLETION_TOKENS", 16384))
|
215
245
|
examples = ProposalDataset.get_dataset()[:]
|
216
|
-
examples = random.choices(examples, k=
|
246
|
+
examples = random.choices(examples, k=2)
|
217
247
|
|
218
248
|
prompt = encode_prompt(
|
219
|
-
IMPROVEMENT_PROMPT,
|
249
|
+
IMPROVEMENT_PROMPT,
|
250
|
+
bit=bit,
|
251
|
+
examples=examples,
|
252
|
+
num_proposals=num_proposals,
|
253
|
+
additional_context=additional_context,
|
220
254
|
)
|
221
255
|
content = await llm_acall(
|
222
|
-
model_name=model_name,
|
256
|
+
model_name=model_name,
|
257
|
+
messages=[
|
258
|
+
ChatMessage(role="system", content=SYSTEM_IMPROVEMENT_PROMPT),
|
259
|
+
ChatMessage(role="user", content=prompt),
|
260
|
+
],
|
261
|
+
max_completion_tokens=max_completion_tokens,
|
262
|
+
temperature=1.0,
|
223
263
|
)
|
224
264
|
result = extract_json(content)
|
225
|
-
|
265
|
+
for proposal in result:
|
266
|
+
proposal["proposal_id"] = random.randint(0, 1000000)
|
226
267
|
return json.dumps(result, ensure_ascii=False)
|
227
268
|
|
228
269
|
|
@@ -258,7 +299,9 @@ async def score_research_proposals(proposals: str | List[str | Dict[str, Any] |
|
|
258
299
|
assert isinstance(proposals, list), "Proposals should be a list of JSON strings"
|
259
300
|
prompt = encode_prompt(SCORE_PROMPT, proposals=[str(p) for p in proposals])
|
260
301
|
content = await llm_acall(
|
261
|
-
model_name=model_name,
|
302
|
+
model_name=model_name,
|
303
|
+
messages=[ChatMessage(role="user", content=prompt)],
|
304
|
+
temperature=0.0,
|
262
305
|
)
|
263
306
|
scores = extract_json(content)
|
264
307
|
return json.dumps(scores, ensure_ascii=False)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: academia-mcp
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
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
|
@@ -2,7 +2,7 @@ import json
|
|
2
2
|
|
3
3
|
from academia_mcp.tools.bitflip import (
|
4
4
|
extract_bitflip_info,
|
5
|
-
|
5
|
+
generate_research_proposals,
|
6
6
|
score_research_proposals,
|
7
7
|
)
|
8
8
|
|
@@ -17,17 +17,18 @@ async def test_bitflip_extract_info() -> None:
|
|
17
17
|
async def test_bitflip_generate_research_proposal() -> None:
|
18
18
|
arxiv_id = "2503.07826"
|
19
19
|
bit = json.loads(await extract_bitflip_info(arxiv_id))["bit"]
|
20
|
-
result = json.loads(await
|
20
|
+
result = json.loads(await generate_research_proposals(bit=bit, num_proposals=2))
|
21
21
|
assert result is not None
|
22
|
-
assert result
|
22
|
+
assert len(result) == 2
|
23
|
+
assert result[0]["flip"]
|
24
|
+
assert result[1]["flip"]
|
23
25
|
|
24
26
|
|
25
|
-
async def
|
27
|
+
async def test_bitflip_score_research_proposals_base() -> None:
|
26
28
|
arxiv_id = "2503.07826"
|
27
29
|
bit = json.loads(await extract_bitflip_info(arxiv_id))["bit"]
|
28
|
-
|
29
|
-
|
30
|
-
scores = json.loads(await score_research_proposals([proposal1, proposal2]))
|
30
|
+
proposals = await generate_research_proposals(bit=bit, num_proposals=2)
|
31
|
+
scores = json.loads(await score_research_proposals(proposals))
|
31
32
|
assert scores
|
32
33
|
assert len(scores) == 2
|
33
34
|
assert scores[0]["spark"] is not None
|
@@ -41,9 +42,8 @@ async def test_bitflip_score_research_proposals() -> None:
|
|
41
42
|
async def test_bitflip_score_research_proposals_str() -> None:
|
42
43
|
arxiv_id = "2503.07826"
|
43
44
|
bit = json.loads(await extract_bitflip_info(arxiv_id))["bit"]
|
44
|
-
|
45
|
-
|
46
|
-
scores = json.loads(await score_research_proposals(json.dumps([proposal1, proposal2])))
|
45
|
+
proposals = await generate_research_proposals(bit=bit, num_proposals=2)
|
46
|
+
scores = json.loads(await score_research_proposals(proposals))
|
47
47
|
assert scores
|
48
48
|
assert len(scores) == 2
|
49
49
|
assert scores[0]["spark"] is not None
|
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
|