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 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
- server.run(transport=transport)
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}))
@@ -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.1
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=B29AeCWYYk3mj8eZP-it0i_SgbMUzzWQBfZ0DO3HvgQ,4706
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=P9U3RjYzcztE0KxXvJSy5wSBaUg2CM9tpByljYrsrl4,4607
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=oKy8CFwTYyIPD73IOcfrUsokING8jpIyosAQ9WraO9E,2645
26
- academia_mcp/tools/web_search.py,sha256=kj3BrPdTVfyTjZ_9Jl2n3YUGzcRZk8diQs6cVSVmPrQ,6293
27
- academia_mcp-1.10.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
28
- academia_mcp-1.10.1.dist-info/METADATA,sha256=3Vuyr7l05zOUPVWTnWY_2p6cS1XSHt26oZLtkB47tVw,6311
29
- academia_mcp-1.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- academia_mcp-1.10.1.dist-info/entry_points.txt,sha256=gxkiKJ74w2FwJpSECpjA3XtCfI5ZfrM6N8cqnwsq4yY,51
31
- academia_mcp-1.10.1.dist-info/top_level.txt,sha256=CzGpRFsRRJRqWEb1e3SUlcfGqRzOxevZGaJWrtGF8W0,13
32
- academia_mcp-1.10.1.dist-info/RECORD,,
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,,