academia-mcp 1.10.1__py3-none-any.whl → 1.10.2__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 +24 -1
- academia_mcp/tools/visit_webpage.py +4 -3
- academia_mcp/tools/web_search.py +5 -4
- academia_mcp/utils.py +9 -0
- {academia_mcp-1.10.1.dist-info → academia_mcp-1.10.2.dist-info}/METADATA +1 -1
- {academia_mcp-1.10.1.dist-info → academia_mcp-1.10.2.dist-info}/RECORD +10 -10
- {academia_mcp-1.10.1.dist-info → academia_mcp-1.10.2.dist-info}/WHEEL +0 -0
- {academia_mcp-1.10.1.dist-info → academia_mcp-1.10.2.dist-info}/entry_points.txt +0 -0
- {academia_mcp-1.10.1.dist-info → academia_mcp-1.10.2.dist-info}/licenses/LICENSE +0 -0
- {academia_mcp-1.10.1.dist-info → academia_mcp-1.10.2.dist-info}/top_level.txt +0 -0
academia_mcp/server.py
CHANGED
@@ -5,7 +5,9 @@ from typing import Optional, Literal
|
|
5
5
|
|
6
6
|
import fire # type: ignore
|
7
7
|
from mcp.server.fastmcp import FastMCP
|
8
|
+
import uvicorn
|
8
9
|
from uvicorn.config import LOGGING_CONFIG as UVICORN_LOGGING_CONFIG
|
10
|
+
from starlette.middleware.cors import CORSMiddleware
|
9
11
|
|
10
12
|
from academia_mcp.settings import settings
|
11
13
|
from academia_mcp.tools.arxiv_search import arxiv_search
|
@@ -133,9 +135,30 @@ def run(
|
|
133
135
|
port = int(settings.PORT)
|
134
136
|
else:
|
135
137
|
port = find_free_port()
|
138
|
+
|
136
139
|
server.settings.port = port
|
137
140
|
server.settings.host = host
|
138
|
-
|
141
|
+
|
142
|
+
if transport == "streamable-http":
|
143
|
+
# Enable CORS for browser-based clients
|
144
|
+
app = server.streamable_http_app()
|
145
|
+
app.add_middleware(
|
146
|
+
CORSMiddleware,
|
147
|
+
allow_origins=["*"],
|
148
|
+
allow_credentials=True,
|
149
|
+
allow_methods=["GET", "POST", "OPTIONS"],
|
150
|
+
allow_headers=["*"],
|
151
|
+
expose_headers=["mcp-session-id", "mcp-protocol-version"],
|
152
|
+
max_age=86400,
|
153
|
+
)
|
154
|
+
uvicorn.run(
|
155
|
+
app,
|
156
|
+
host=server.settings.host,
|
157
|
+
port=server.settings.port,
|
158
|
+
log_level=server.settings.log_level.lower(),
|
159
|
+
)
|
160
|
+
else:
|
161
|
+
server.run(transport=transport)
|
139
162
|
|
140
163
|
|
141
164
|
if __name__ == "__main__":
|
@@ -6,6 +6,7 @@ from markdownify import markdownify # type: ignore
|
|
6
6
|
|
7
7
|
from academia_mcp.utils import get_with_retries, post_with_retries
|
8
8
|
from academia_mcp.settings import settings
|
9
|
+
from academia_mcp.utils import sanitize_output
|
9
10
|
|
10
11
|
EXA_CONTENTS_URL = "https://api.exa.ai/contents"
|
11
12
|
TAVILY_EXTRACT_URL = "https://api.tavily.com/extract"
|
@@ -20,7 +21,7 @@ def _exa_visit_webpage(url: str) -> str:
|
|
20
21
|
"text": True,
|
21
22
|
}
|
22
23
|
response = post_with_retries(EXA_CONTENTS_URL, payload=payload, api_key=key)
|
23
|
-
return json.dumps(response.json()["results"][0])
|
24
|
+
return sanitize_output(json.dumps(response.json()["results"][0]))
|
24
25
|
|
25
26
|
|
26
27
|
def _tavily_visit_webpage(url: str) -> str:
|
@@ -30,7 +31,7 @@ def _tavily_visit_webpage(url: str) -> str:
|
|
30
31
|
"urls": [url],
|
31
32
|
}
|
32
33
|
response = post_with_retries(TAVILY_EXTRACT_URL, payload=payload, api_key=key)
|
33
|
-
return json.dumps(response.json()["results"][0]["raw_content"])
|
34
|
+
return sanitize_output(json.dumps(response.json()["results"][0]["raw_content"]))
|
34
35
|
|
35
36
|
|
36
37
|
def visit_webpage(url: str, provider: Optional[str] = "tavily") -> str:
|
@@ -67,4 +68,4 @@ def visit_webpage(url: str, provider: Optional[str] = "tavily") -> str:
|
|
67
68
|
)
|
68
69
|
markdown_content = markdownify(response.text).strip()
|
69
70
|
markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content)
|
70
|
-
return json.dumps({"id": url, "text": markdown_content})
|
71
|
+
return sanitize_output(json.dumps({"id": url, "text": markdown_content}))
|
academia_mcp/tools/web_search.py
CHANGED
@@ -3,6 +3,7 @@ from typing import Optional
|
|
3
3
|
|
4
4
|
from academia_mcp.utils import post_with_retries, get_with_retries
|
5
5
|
from academia_mcp.settings import settings
|
6
|
+
from academia_mcp.utils import sanitize_output
|
6
7
|
|
7
8
|
|
8
9
|
EXA_SEARCH_URL = "https://api.exa.ai/search"
|
@@ -56,7 +57,7 @@ def web_search(
|
|
56
57
|
elif provider == "tavily":
|
57
58
|
result = json.loads(tavily_web_search(query, limit))
|
58
59
|
result["search_provider"] = provider
|
59
|
-
return json.dumps(result, ensure_ascii=False)
|
60
|
+
return sanitize_output(json.dumps(result, ensure_ascii=False))
|
60
61
|
|
61
62
|
|
62
63
|
def tavily_web_search(query: str, limit: Optional[int] = 20) -> str:
|
@@ -92,7 +93,7 @@ def tavily_web_search(query: str, limit: Optional[int] = 20) -> str:
|
|
92
93
|
result["content"] = content
|
93
94
|
result.pop("raw_content", None)
|
94
95
|
result.pop("score", None)
|
95
|
-
return json.dumps({"results": results}, ensure_ascii=False)
|
96
|
+
return sanitize_output(json.dumps({"results": results}, ensure_ascii=False))
|
96
97
|
|
97
98
|
|
98
99
|
def exa_web_search(query: str, limit: Optional[int] = 20) -> str:
|
@@ -131,7 +132,7 @@ def exa_web_search(query: str, limit: Optional[int] = 20) -> str:
|
|
131
132
|
|
132
133
|
response = post_with_retries(EXA_SEARCH_URL, payload, key)
|
133
134
|
results = response.json()["results"]
|
134
|
-
return json.dumps({"results": results}, ensure_ascii=False)
|
135
|
+
return sanitize_output(json.dumps({"results": results}, ensure_ascii=False))
|
135
136
|
|
136
137
|
|
137
138
|
def brave_web_search(query: str, limit: Optional[int] = 20) -> str:
|
@@ -159,4 +160,4 @@ def brave_web_search(query: str, limit: Optional[int] = 20) -> str:
|
|
159
160
|
}
|
160
161
|
response = get_with_retries(BRAVE_SEARCH_URL, key, params=payload)
|
161
162
|
results = response.json()["web"]["results"]
|
162
|
-
return json.dumps({"results": results}, ensure_ascii=False)
|
163
|
+
return sanitize_output(json.dumps({"results": results}, ensure_ascii=False))
|
academia_mcp/utils.py
CHANGED
@@ -165,3 +165,12 @@ def truncate_content(
|
|
165
165
|
prefix = content[:half_length]
|
166
166
|
suffix = content[-half_length:]
|
167
167
|
return prefix + disclaimer + suffix
|
168
|
+
|
169
|
+
|
170
|
+
def sanitize_output(output: str) -> str:
|
171
|
+
"""
|
172
|
+
See https://github.com/modelcontextprotocol/python-sdk/issues/1144#issuecomment-3076506124
|
173
|
+
"""
|
174
|
+
output = output.replace("\x85", " ")
|
175
|
+
output = output.replace("\u0085", " ")
|
176
|
+
return output
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: academia-mcp
|
3
|
-
Version: 1.10.
|
3
|
+
Version: 1.10.2
|
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,9 +4,9 @@ academia_mcp/files.py,sha256=ynIt0XbU1Z7EPWkv_hVX0pGKsLlmjYv-MVJLOfi6yzs,817
|
|
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=
|
7
|
+
academia_mcp/server.py,sha256=hNfQSFwgbVMNyB7ElGpJ0RCTGeRP7jb5--7KZmG8cW8,5423
|
8
8
|
academia_mcp/settings.py,sha256=MSQYjmhZ3NDalTzu4z3ey1Aw60TrhkDcPWUTE4-iOaU,995
|
9
|
-
academia_mcp/utils.py,sha256=
|
9
|
+
academia_mcp/utils.py,sha256=lRlb615JJ_0d4gcFpMoBjB6w0xXcde9dFDw0LwYpSPQ,4863
|
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
12
|
academia_mcp/tools/__init__.py,sha256=lGUy5C4IymplHOXqOiwDD7CT4Z8aPHJqSxXo2g9qkks,1493
|
@@ -22,11 +22,11 @@ academia_mcp/tools/review.py,sha256=Va0lFJJKuk-NvWhKS3UZ-Dnuk7CyuDQ4S1nd70D-ffE,
|
|
22
22
|
academia_mcp/tools/s2.py,sha256=QX7-pbetab3Xt_1tvVPU6o5D_NAe9y6jcTGRBK1vwtY,6200
|
23
23
|
academia_mcp/tools/show_image.py,sha256=jiJlQ53dbZ0T61OBhCT3IKVvBl9NHc6jHgWLfg5BxiE,3856
|
24
24
|
academia_mcp/tools/speech_to_text.py,sha256=YZzMqdvunzXkpcadP_mYhm6cs4qH1Y_42SfY-7eX4O4,1601
|
25
|
-
academia_mcp/tools/visit_webpage.py,sha256=
|
26
|
-
academia_mcp/tools/web_search.py,sha256=
|
27
|
-
academia_mcp-1.10.
|
28
|
-
academia_mcp-1.10.
|
29
|
-
academia_mcp-1.10.
|
30
|
-
academia_mcp-1.10.
|
31
|
-
academia_mcp-1.10.
|
32
|
-
academia_mcp-1.10.
|
25
|
+
academia_mcp/tools/visit_webpage.py,sha256=15JPmh43n3Zx_RM22MD8eD3ZIRuXWlHQFyCnOfWlD7k,2743
|
26
|
+
academia_mcp/tools/web_search.py,sha256=0gKE3gtLBhdQ6G1eSgYLs1LIuo__PHwsYx5I5mTn254,6408
|
27
|
+
academia_mcp-1.10.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
28
|
+
academia_mcp-1.10.2.dist-info/METADATA,sha256=kYgvc7ICQ9pAcvBc3pz4Ul_hP-QEUa3VP_o2SYZoQ_U,6311
|
29
|
+
academia_mcp-1.10.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
30
|
+
academia_mcp-1.10.2.dist-info/entry_points.txt,sha256=gxkiKJ74w2FwJpSECpjA3XtCfI5ZfrM6N8cqnwsq4yY,51
|
31
|
+
academia_mcp-1.10.2.dist-info/top_level.txt,sha256=CzGpRFsRRJRqWEb1e3SUlcfGqRzOxevZGaJWrtGF8W0,13
|
32
|
+
academia_mcp-1.10.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|