academia-mcp 1.1.4__py3-none-any.whl → 1.2.1__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/llm.py +38 -0
- academia_mcp/server.py +9 -3
- academia_mcp/tools/__init__.py +3 -2
- academia_mcp/tools/bitflip.py +151 -62
- academia_mcp/tools/document_qa.py +15 -40
- {academia_mcp-1.1.4.dist-info → academia_mcp-1.2.1.dist-info}/METADATA +2 -1
- {academia_mcp-1.1.4.dist-info → academia_mcp-1.2.1.dist-info}/RECORD +11 -10
- {academia_mcp-1.1.4.dist-info → academia_mcp-1.2.1.dist-info}/WHEEL +0 -0
- {academia_mcp-1.1.4.dist-info → academia_mcp-1.2.1.dist-info}/entry_points.txt +0 -0
- {academia_mcp-1.1.4.dist-info → academia_mcp-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {academia_mcp-1.1.4.dist-info → academia_mcp-1.2.1.dist-info}/top_level.txt +0 -0
academia_mcp/llm.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
import os
|
2
|
+
from typing import List, Dict, Any
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
from openai import AsyncOpenAI
|
6
|
+
from openai.types.chat.chat_completion_message import ChatCompletionMessage
|
7
|
+
|
8
|
+
|
9
|
+
class ChatMessage(BaseModel): # type: ignore
|
10
|
+
role: str
|
11
|
+
content: str | List[Dict[str, Any]]
|
12
|
+
|
13
|
+
|
14
|
+
ChatMessages = List[ChatMessage]
|
15
|
+
|
16
|
+
|
17
|
+
async def llm_acall(model_name: str, prompt: str) -> str:
|
18
|
+
key = os.getenv("OPENROUTER_API_KEY", "")
|
19
|
+
assert key, "Please set OPENROUTER_API_KEY in the environment variables"
|
20
|
+
base_url = os.getenv("BASE_URL", "https://openrouter.ai/api/v1")
|
21
|
+
|
22
|
+
messages: ChatMessages = [
|
23
|
+
ChatMessage(role="user", content=prompt),
|
24
|
+
]
|
25
|
+
client = AsyncOpenAI(base_url=base_url, api_key=key)
|
26
|
+
response: ChatCompletionMessage = (
|
27
|
+
(
|
28
|
+
await client.chat.completions.create(
|
29
|
+
model=model_name,
|
30
|
+
messages=messages,
|
31
|
+
temperature=0.0,
|
32
|
+
)
|
33
|
+
)
|
34
|
+
.choices[0]
|
35
|
+
.message
|
36
|
+
)
|
37
|
+
assert response.content, "Response content is None"
|
38
|
+
return response.content
|
academia_mcp/server.py
CHANGED
@@ -15,7 +15,12 @@ from .tools.document_qa import document_qa
|
|
15
15
|
from .tools.md_to_pdf import md_to_pdf
|
16
16
|
from .tools.web_search import web_search, tavily_web_search, exa_web_search, brave_web_search
|
17
17
|
from .tools.visit_webpage import visit_webpage
|
18
|
-
from .tools.bitflip import
|
18
|
+
from .tools.bitflip import (
|
19
|
+
extract_bitflip_info,
|
20
|
+
generate_research_proposal,
|
21
|
+
score_research_proposals,
|
22
|
+
)
|
23
|
+
|
19
24
|
|
20
25
|
load_dotenv()
|
21
26
|
|
@@ -53,6 +58,9 @@ def run(
|
|
53
58
|
server.add_tool(anthology_search)
|
54
59
|
server.add_tool(md_to_pdf)
|
55
60
|
server.add_tool(visit_webpage)
|
61
|
+
server.add_tool(extract_bitflip_info)
|
62
|
+
server.add_tool(generate_research_proposal)
|
63
|
+
server.add_tool(score_research_proposals)
|
56
64
|
|
57
65
|
if os.getenv("TAVILY_API_KEY"):
|
58
66
|
server.add_tool(tavily_web_search)
|
@@ -64,8 +72,6 @@ def run(
|
|
64
72
|
server.add_tool(web_search)
|
65
73
|
if os.getenv("OPENROUTER_API_KEY"):
|
66
74
|
server.add_tool(document_qa)
|
67
|
-
server.add_tool(propose_improvement_idea)
|
68
|
-
server.add_tool(extract_bitflip_info)
|
69
75
|
|
70
76
|
if port is None:
|
71
77
|
port = find_free_port()
|
academia_mcp/tools/__init__.py
CHANGED
@@ -7,7 +7,7 @@ from .document_qa import document_qa
|
|
7
7
|
from .md_to_pdf import md_to_pdf
|
8
8
|
from .web_search import web_search, tavily_web_search, exa_web_search, brave_web_search
|
9
9
|
from .visit_webpage import visit_webpage
|
10
|
-
from .bitflip import
|
10
|
+
from .bitflip import extract_bitflip_info, generate_research_proposal, score_research_proposals
|
11
11
|
|
12
12
|
|
13
13
|
__all__ = [
|
@@ -24,6 +24,7 @@ __all__ = [
|
|
24
24
|
"exa_web_search",
|
25
25
|
"brave_web_search",
|
26
26
|
"visit_webpage",
|
27
|
-
"propose_improvement_idea",
|
28
27
|
"extract_bitflip_info",
|
28
|
+
"generate_research_proposal",
|
29
|
+
"score_research_proposals",
|
29
30
|
]
|
academia_mcp/tools/bitflip.py
CHANGED
@@ -4,15 +4,24 @@
|
|
4
4
|
import json
|
5
5
|
import os
|
6
6
|
import random
|
7
|
-
from typing import List,
|
7
|
+
from typing import List, Optional, Any
|
8
8
|
|
9
|
-
from openai import OpenAI
|
10
9
|
from pydantic import BaseModel
|
11
|
-
from openai.types.chat import ChatCompletionMessage
|
12
10
|
from datasets import load_dataset # type: ignore
|
13
11
|
|
14
12
|
from academia_mcp.tools.arxiv_download import arxiv_download
|
15
13
|
from academia_mcp.utils import extract_json, encode_prompt
|
14
|
+
from academia_mcp.llm import llm_acall
|
15
|
+
|
16
|
+
|
17
|
+
class ProposalDataset:
|
18
|
+
dataset: Optional[List[Any]] = None
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def get_dataset(cls) -> List[Any]:
|
22
|
+
if cls.dataset is None:
|
23
|
+
cls.dataset = list(load_dataset("UniverseTBD/hypogen-dr1")["train"])
|
24
|
+
return cls.dataset
|
16
25
|
|
17
26
|
|
18
27
|
EXTRACT_PROMPT = """
|
@@ -85,24 +94,98 @@ Try to be as specific as possible.
|
|
85
94
|
Now, please propose a chain of reasoning that leads to an improvement idea for this Bit:
|
86
95
|
{{bit}}
|
87
96
|
|
97
|
+
{% if additional_context %}Additional context:
|
98
|
+
{{additional_context}}{% endif %}
|
99
|
+
|
100
|
+
Finalize your idea by providing the idea details:
|
101
|
+
- 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. Ensure these are simple and feasible. Be specific in exactly how you would test the hypothesis, and detail precise algorithmic changes. Include the evaluation metrics you would use.
|
103
|
+
- Risks and limitations: A list of potential risks and limitations of the proposal.
|
104
|
+
|
88
105
|
Return only the JSON object in this exact format (no extra text):
|
89
106
|
{
|
90
107
|
"chain_of_reasoning": "Chain of reasoning that leads to an improvement idea for this Bit. At least 5 sentences.",
|
91
108
|
"flip": "Innovative approach or solution, in at least two sentences",
|
92
|
-
"spark": "4-6 word summary"
|
109
|
+
"spark": "4-6 word summary",
|
110
|
+
"abstract": "An abstract that summarizes the proposal in conference format (approximately 250 words).",
|
111
|
+
"experiments": ["...", "..."],
|
112
|
+
"risks_and_limitations": "A list of potential risks and limitations of the proposal."
|
93
113
|
}
|
94
114
|
"""
|
95
115
|
|
96
116
|
|
97
|
-
|
98
|
-
|
99
|
-
|
117
|
+
SCORE_PROMPT = """
|
118
|
+
You are a highly advanced research assistant.
|
119
|
+
You are given a list of research proposals.
|
120
|
+
Your task is to score the proposals.
|
121
|
+
|
122
|
+
Proposals:
|
123
|
+
{% for proposal in proposals %}
|
124
|
+
- Proposal ID: {{proposal["proposal_id"]}}
|
125
|
+
- Spark: {{proposal["spark"]}}
|
126
|
+
- Abstract: {{proposal["abstract"]}}
|
127
|
+
- Experiments: {{proposal["experiments"]}}
|
128
|
+
- Risks and limitations: {{proposal["risks_and_limitations"]}}
|
129
|
+
{% endfor %}
|
130
|
+
|
131
|
+
Here are the criteria:
|
132
|
+
- "Strengths": A list of strengths of the proposal.
|
133
|
+
- "Weaknesses": A list of weaknesses of the proposal.
|
134
|
+
- "Novelty": Is the proposal novel? A rating from 1 to 4 (low, medium, high, very high).
|
135
|
+
- "Clarity": Is the proposal clear? A rating from 1 to 4 (low, medium, high, very high).
|
136
|
+
- "Significance": Is the proposal significant? A rating from 1 to 4 (low, medium, high, very high).
|
137
|
+
- "Feasibility": Is the proposal feasible and easy to implement? A rating from 1 to 4 (low, medium, high, very high).
|
138
|
+
- "Soundness": Is the proposal sound? A rating from 1 to 4 (poor, fair, good, excellent).
|
139
|
+
- "Overall": A rating from 1 to 10 (very strong reject to award quality).
|
140
|
+
|
141
|
+
Return only scores for all proposals in this exact format (no extra text):
|
142
|
+
[
|
143
|
+
{
|
144
|
+
"proposal_id": 0,
|
145
|
+
"spark": "...",
|
146
|
+
"strengths": ["...", "..."],
|
147
|
+
"weaknesses": ["...", "..."],
|
148
|
+
"novelty": 2,
|
149
|
+
"clarity": 2,
|
150
|
+
"significance": 2,
|
151
|
+
"feasibility": 2,
|
152
|
+
"soundness": 2,
|
153
|
+
"overall": 5
|
154
|
+
},
|
155
|
+
...
|
156
|
+
]
|
157
|
+
"""
|
158
|
+
|
159
|
+
|
160
|
+
class BitFlipInfo(BaseModel): # type: ignore
|
161
|
+
bit: str
|
162
|
+
flip: str
|
163
|
+
spark: str
|
164
|
+
|
100
165
|
|
166
|
+
class Proposal(BaseModel): # type: ignore
|
167
|
+
proposal_id: Optional[int] = None
|
168
|
+
flip: str
|
169
|
+
spark: str
|
170
|
+
abstract: str
|
171
|
+
experiments: List[str]
|
172
|
+
risks_and_limitations: List[str]
|
101
173
|
|
102
|
-
ChatMessages = List[ChatMessage]
|
103
174
|
|
175
|
+
class ProposalScores(BaseModel): # type: ignore
|
176
|
+
proposal_id: int
|
177
|
+
spark: str
|
178
|
+
strengths: List[str]
|
179
|
+
weaknesses: List[str]
|
180
|
+
novelty: int
|
181
|
+
clarity: int
|
182
|
+
significance: int
|
183
|
+
feasibility: int
|
184
|
+
soundness: int
|
185
|
+
overall: int
|
104
186
|
|
105
|
-
|
187
|
+
|
188
|
+
async def extract_bitflip_info(arxiv_id: str) -> str:
|
106
189
|
"""
|
107
190
|
Extracts the Bit-Flip information from the arXiv paper.
|
108
191
|
|
@@ -121,73 +204,79 @@ def extract_bitflip_info(arxiv_id: str) -> str:
|
|
121
204
|
Args:
|
122
205
|
arxiv_id: The arXiv ID of the paper to extract the Bit-Flip information from.
|
123
206
|
"""
|
124
|
-
base_url = os.getenv("BASE_URL", "https://openrouter.ai/api/v1")
|
125
|
-
key = os.getenv("OPENROUTER_API_KEY", "")
|
126
|
-
assert key, "Please set OPENROUTER_API_KEY in the environment variables"
|
127
207
|
model_name = os.getenv("BITFLIP_MODEL_NAME", "deepseek/deepseek-chat-v3-0324")
|
128
|
-
|
129
208
|
paper = arxiv_download(arxiv_id)
|
130
209
|
abstract = json.loads(paper)["abstract"]
|
131
210
|
prompt = encode_prompt(EXTRACT_PROMPT, abstract=abstract)
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
response: ChatCompletionMessage = (
|
137
|
-
client.chat.completions.create(
|
138
|
-
model=model_name,
|
139
|
-
messages=messages,
|
140
|
-
temperature=0.0,
|
141
|
-
)
|
142
|
-
.choices[0]
|
143
|
-
.message
|
144
|
-
)
|
145
|
-
assert response.content, "Response content is None"
|
146
|
-
result = extract_json(response.content)
|
147
|
-
return json.dumps(result, ensure_ascii=False)
|
211
|
+
content = await llm_acall(model_name=model_name, prompt=prompt)
|
212
|
+
result = extract_json(content)
|
213
|
+
bitflip_info: BitFlipInfo = BitFlipInfo.model_validate(result)
|
214
|
+
return str(bitflip_info.model_dump_json())
|
148
215
|
|
149
216
|
|
150
|
-
def
|
217
|
+
async def generate_research_proposal(bit: str, additional_context: str = "") -> str:
|
151
218
|
"""
|
152
|
-
Proposes an improvement idea for the
|
219
|
+
Proposes an improvement idea for the Bit.
|
153
220
|
|
154
|
-
|
221
|
+
Args:
|
222
|
+
bit: The Bit to propose an improvement idea for. The bit is a technical limitation or conventional approach of some paper.
|
223
|
+
additional_context: Additional context to use when proposing the improvement idea.
|
224
|
+
|
225
|
+
Returns a JSON string with a research proposal in this format:
|
155
226
|
{
|
156
|
-
"
|
157
|
-
"flip": "Innovative approach or solution",
|
158
|
-
"spark": "4-6 word summary"
|
227
|
+
"proposal_id": ...,
|
228
|
+
"flip": "Innovative approach or solution, in at least two sentences",
|
229
|
+
"spark": "4-6 word summary",
|
230
|
+
"abstract": "An abstract that summarizes the proposal in conference format (approximately 250 words).",
|
231
|
+
"experiments": ["...", "..."],
|
232
|
+
"risks_and_limitations": "A list of potential risks and limitations of the proposal."
|
159
233
|
}
|
160
234
|
Use `json.loads` to deserialize the result if you want to get specific fields.
|
161
|
-
|
162
|
-
Args:
|
163
|
-
arxiv_id: The arXiv ID of the paper to propose an improvement idea for.
|
164
235
|
"""
|
165
|
-
base_url = os.getenv("BASE_URL", "https://openrouter.ai/api/v1")
|
166
|
-
key = os.getenv("OPENROUTER_API_KEY", "")
|
167
|
-
assert key, "Please set OPENROUTER_API_KEY in the environment variables"
|
168
236
|
model_name = os.getenv("BITFLIP_MODEL_NAME", "deepseek/deepseek-chat-v3-0324")
|
237
|
+
examples = ProposalDataset.get_dataset()[:]
|
238
|
+
examples = random.choices(examples, k=4)
|
169
239
|
|
170
|
-
|
171
|
-
|
240
|
+
prompt = encode_prompt(
|
241
|
+
IMPROVEMENT_PROMPT, bit=bit, examples=examples, additional_context=additional_context
|
242
|
+
)
|
243
|
+
content = await llm_acall(model_name=model_name, prompt=prompt)
|
244
|
+
result = extract_json(content)
|
245
|
+
proposal: Proposal = Proposal.model_validate(result)
|
246
|
+
proposal.proposal_id = random.randint(0, 1000000)
|
247
|
+
return str(proposal.model_dump_json())
|
172
248
|
|
173
|
-
examples = list(load_dataset("UniverseTBD/hypogen-dr1")["train"])
|
174
|
-
random.shuffle(examples)
|
175
|
-
examples = examples[:4]
|
176
249
|
|
177
|
-
|
178
|
-
|
179
|
-
|
250
|
+
async def score_research_proposals(proposals: List[str]) -> str:
|
251
|
+
"""
|
252
|
+
Scores a list of research proposals.
|
253
|
+
Use proposals obtained with the `generate_research_proposal` tool.
|
254
|
+
|
255
|
+
Returns a JSON string with a list of scores in this format:
|
256
|
+
[
|
257
|
+
{
|
258
|
+
"proposal_id": 0,
|
259
|
+
"spark": "...",
|
260
|
+
"strengths": ["...", "..."],
|
261
|
+
"weaknesses": ["...", "..."],
|
262
|
+
"novelty": 2,
|
263
|
+
"clarity": 2,
|
264
|
+
"significance": 2,
|
265
|
+
"feasibility": 2,
|
266
|
+
"soundness": 2,
|
267
|
+
"overall": 5
|
268
|
+
},
|
269
|
+
...
|
180
270
|
]
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
)
|
191
|
-
|
192
|
-
|
193
|
-
return json.dumps(result, ensure_ascii=False)
|
271
|
+
Use `json.loads` to deserialize the result if you want to get specific fields.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
proposals: A list of JSON strings with research proposals.
|
275
|
+
"""
|
276
|
+
model_name = os.getenv("BITFLIP_MODEL_NAME", "deepseek/deepseek-chat-v3-0324")
|
277
|
+
proposals = [Proposal.model_validate_json(proposal) for proposal in proposals]
|
278
|
+
prompt = encode_prompt(SCORE_PROMPT, proposals=proposals)
|
279
|
+
content = await llm_acall(model_name=model_name, prompt=prompt)
|
280
|
+
scores = extract_json(content)
|
281
|
+
final_scores = [ProposalScores.model_validate(score) for score in scores]
|
282
|
+
return json.dumps([s.model_dump() for s in final_scores], ensure_ascii=False)
|
@@ -1,18 +1,16 @@
|
|
1
1
|
import os
|
2
|
-
|
2
|
+
import json
|
3
|
+
from typing import List, Any, Dict
|
3
4
|
from dotenv import load_dotenv
|
4
5
|
|
5
6
|
from pydantic import BaseModel
|
6
|
-
from openai import OpenAI
|
7
|
-
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionMessage
|
8
7
|
|
8
|
+
from academia_mcp.llm import llm_acall
|
9
9
|
|
10
10
|
load_dotenv()
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
)
|
15
|
-
PROMPT = """Please answer the following questions based solely on the provided document.
|
12
|
+
PROMPT = """You are a helpful assistant that answers questions about documents accurately and concisely.
|
13
|
+
Please answer the following questions based solely on the provided document.
|
16
14
|
If there is no answer in the document, output "There is no answer in the provided document".
|
17
15
|
First cite ALL relevant document fragments, then provide a final answer.
|
18
16
|
Answer all given questions one by one.
|
@@ -40,8 +38,8 @@ class ChatMessage(BaseModel): # type: ignore
|
|
40
38
|
ChatMessages = List[ChatMessage]
|
41
39
|
|
42
40
|
|
43
|
-
def document_qa(
|
44
|
-
document: str,
|
41
|
+
async def document_qa(
|
42
|
+
document: str | Dict[str, Any],
|
45
43
|
question: str,
|
46
44
|
) -> str:
|
47
45
|
"""
|
@@ -58,39 +56,16 @@ def document_qa(
|
|
58
56
|
Returns an answer to all questions based on the document content.
|
59
57
|
|
60
58
|
Args:
|
61
|
-
|
62
|
-
|
59
|
+
question: Question (or questions) to be answered about the document.
|
60
|
+
document: The full text of the document to analyze.
|
63
61
|
"""
|
64
62
|
assert question and question.strip(), "Please provide non-empty 'question'"
|
63
|
+
if isinstance(document, dict):
|
64
|
+
document = json.dumps(document)
|
65
|
+
|
65
66
|
assert document and document.strip(), "Please provide non-empty 'document'"
|
66
67
|
|
67
|
-
base_url = os.getenv("BASE_URL", "https://openrouter.ai/api/v1")
|
68
|
-
key = os.getenv("OPENROUTER_API_KEY", "")
|
69
|
-
assert key, "Please set OPENROUTER_API_KEY in the environment variables"
|
70
68
|
model_name = os.getenv("DOCUMENT_QA_MODEL_NAME", "deepseek/deepseek-chat-v3-0324")
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
ChatMessage(
|
75
|
-
role="user",
|
76
|
-
content=PROMPT.format(question=question, document=document),
|
77
|
-
),
|
78
|
-
]
|
79
|
-
|
80
|
-
sdk_messages = [
|
81
|
-
cast(ChatCompletionMessageParam, m.model_dump(exclude_none=True)) for m in messages
|
82
|
-
]
|
83
|
-
client = OpenAI(base_url=base_url, api_key=key)
|
84
|
-
response: ChatCompletionMessage = (
|
85
|
-
client.chat.completions.create(
|
86
|
-
model=model_name,
|
87
|
-
messages=sdk_messages,
|
88
|
-
temperature=0.0,
|
89
|
-
)
|
90
|
-
.choices[0]
|
91
|
-
.message
|
92
|
-
)
|
93
|
-
|
94
|
-
if response.content is None:
|
95
|
-
raise Exception("Response content is None")
|
96
|
-
return response.content.strip()
|
69
|
+
prompt = PROMPT.format(question=question, document=document)
|
70
|
+
content = await llm_acall(model_name=model_name, prompt=prompt)
|
71
|
+
return content.strip()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: academia-mcp
|
3
|
-
Version: 1.1
|
3
|
+
Version: 1.2.1
|
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
|
@@ -31,6 +31,7 @@ Requires-Dist: pytest>=8.4.1
|
|
31
31
|
Requires-Dist: openai>=1.97.1
|
32
32
|
Requires-Dist: jinja2>=3.1.6
|
33
33
|
Requires-Dist: datasets>=4.0.0
|
34
|
+
Requires-Dist: pytest-asyncio>=1.1.0
|
34
35
|
Dynamic: license-file
|
35
36
|
|
36
37
|
# Academia MCP
|
@@ -1,24 +1,25 @@
|
|
1
1
|
academia_mcp/__init__.py,sha256=2Ru2I5u4cE7DrkkAsibDUEF1K6sYtqppb9VyFrRoQKI,94
|
2
2
|
academia_mcp/__main__.py,sha256=rcmsOtJd3SA82exjrcGBuxuptcoxF8AXI7jNjiVq2BY,59
|
3
3
|
academia_mcp/files.py,sha256=hI5dj4h0fX8V3DXKI_C8vs1fte2uc9gsBXC6prLV4o4,745
|
4
|
+
academia_mcp/llm.py,sha256=o84FQNSbjjVSk9DlvFXWsUDiz5IOaavYU6kOqnPEG7E,1071
|
4
5
|
academia_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
academia_mcp/server.py,sha256=
|
6
|
+
academia_mcp/server.py,sha256=q9bhacVm-8uuIMc_xSeymmVaIB8pQCqfTQx5GU8hhpM,2492
|
6
7
|
academia_mcp/utils.py,sha256=tkHBho-NfzAR8rplFaiRYq4sBmQ9V3JPjDJTDrz58Xs,4041
|
7
|
-
academia_mcp/tools/__init__.py,sha256=
|
8
|
+
academia_mcp/tools/__init__.py,sha256=8_8QWPRYmPiUjdiNrQilEEnCRR-UBU7g-56jT52V3VQ,934
|
8
9
|
academia_mcp/tools/anthology_search.py,sha256=_5s8EzdV7NQD_F3bjVH4XlKKHOJlFtWlQVrPbODuc3I,7847
|
9
10
|
academia_mcp/tools/arxiv_download.py,sha256=xanzt77TZBQRngzGbKCRz4Hp-Mwfe_q-46eRW23TpVs,11219
|
10
11
|
academia_mcp/tools/arxiv_search.py,sha256=pzM18qrF3QL03A53w003kE7hQi3s3QKtjgw0m7K88UY,8355
|
11
|
-
academia_mcp/tools/bitflip.py,sha256=
|
12
|
-
academia_mcp/tools/document_qa.py,sha256=
|
12
|
+
academia_mcp/tools/bitflip.py,sha256=u0hSOPWbnCDu2EbA_RkueX496SvTKz9QhZcXugshSfI,10949
|
13
|
+
academia_mcp/tools/document_qa.py,sha256=lWtzRNFKwQpQO8vPNvYYbNnGh3LsBmGl6f9vSRIuqaw,2245
|
13
14
|
academia_mcp/tools/hf_datasets_search.py,sha256=KiBkqT4rXjEN4oc1AWZOPnqN_Go90TQogY5-DUm3LQo,2854
|
14
15
|
academia_mcp/tools/md_to_pdf.py,sha256=Ovc_-8j7gIZNEM1d0ZDH-8qbtfZLSaNmCm5DQjrtM0k,12810
|
15
16
|
academia_mcp/tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
17
|
academia_mcp/tools/s2_citations.py,sha256=dqrBp76RrX1zH2XzcMAoWBbvbtyhxLeF-xnqOKD_JiM,4852
|
17
18
|
academia_mcp/tools/visit_webpage.py,sha256=0zAZYeQxPDu0OjgAAvbMLZh0ttaS5q-_4WhgsEPrbsI,1542
|
18
19
|
academia_mcp/tools/web_search.py,sha256=NAkbXdD9mKxsIXhWN32dRd_EiaB3G6ENy-n-bc7HAaQ,5448
|
19
|
-
academia_mcp-1.1.
|
20
|
-
academia_mcp-1.1.
|
21
|
-
academia_mcp-1.1.
|
22
|
-
academia_mcp-1.1.
|
23
|
-
academia_mcp-1.1.
|
24
|
-
academia_mcp-1.1.
|
20
|
+
academia_mcp-1.2.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
21
|
+
academia_mcp-1.2.1.dist-info/METADATA,sha256=mLcD1By5dg9L8hNMlybFg5IUjS4S7uzXggGshnTESV8,1899
|
22
|
+
academia_mcp-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
+
academia_mcp-1.2.1.dist-info/entry_points.txt,sha256=gxkiKJ74w2FwJpSECpjA3XtCfI5ZfrM6N8cqnwsq4yY,51
|
24
|
+
academia_mcp-1.2.1.dist-info/top_level.txt,sha256=CzGpRFsRRJRqWEb1e3SUlcfGqRzOxevZGaJWrtGF8W0,13
|
25
|
+
academia_mcp-1.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|