academia-mcp 1.11.2__py3-none-any.whl → 1.11.4__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/files.py CHANGED
@@ -1,30 +1,15 @@
1
- from typing import Optional
2
1
  from pathlib import Path
3
2
 
4
3
  from academia_mcp.settings import settings
5
4
 
6
5
  DIR_PATH = Path(__file__).parent
7
6
  ROOT_PATH = DIR_PATH.parent
8
- DEFAULT_WORKSPACE_DIR_PATH: Path = DIR_PATH / "workdir"
9
7
  DEFAULT_LATEX_TEMPLATES_DIR_PATH: Path = DIR_PATH / "latex_templates"
10
8
 
11
9
 
12
- class WorkspaceDirectory:
13
- workspace_dir: Optional[Path] = None
14
-
15
- @classmethod
16
- def get_dir(cls) -> Path:
17
- if cls.workspace_dir is None:
18
- return Path(settings.WORKSPACE_DIR)
19
- return cls.workspace_dir
20
-
21
- @classmethod
22
- def set_dir(cls, workspace_dir: Path) -> None:
23
- cls.workspace_dir = workspace_dir
24
-
25
-
26
10
  def get_workspace_dir() -> Path:
27
- directory = WorkspaceDirectory.get_dir()
11
+ assert settings.WORKSPACE_DIR is not None, "Please set the WORKSPACE_DIR environment variable"
12
+ directory = Path(settings.WORKSPACE_DIR)
28
13
  if not directory.exists():
29
14
  directory.mkdir(parents=True, exist_ok=True)
30
15
  return directory
academia_mcp/server.py CHANGED
@@ -40,7 +40,7 @@ from academia_mcp.tools.bitflip import (
40
40
  score_research_proposals,
41
41
  )
42
42
  from academia_mcp.tools.review import review_pdf_paper, download_pdf_paper
43
- from academia_mcp.tools.show_image import show_image, describe_image
43
+ from academia_mcp.tools.image_processing import show_image, describe_image
44
44
  from academia_mcp.tools.speech_to_text import speech_to_text
45
45
  from academia_mcp.tools.yt_transcript import yt_transcript
46
46
 
@@ -14,7 +14,7 @@ from .web_search import web_search, tavily_web_search, exa_web_search, brave_web
14
14
  from .visit_webpage import visit_webpage
15
15
  from .bitflip import extract_bitflip_info, generate_research_proposals, score_research_proposals
16
16
  from .review import review_pdf_paper, download_pdf_paper, review_pdf_paper_by_url
17
- from .show_image import show_image, describe_image
17
+ from .image_processing import show_image, describe_image
18
18
  from .speech_to_text import speech_to_text
19
19
  from .yt_transcript import yt_transcript
20
20
 
@@ -1,16 +1,28 @@
1
+ import asyncio
1
2
  import base64
2
- from pathlib import Path
3
+ import contextlib
4
+ import json
5
+ import logging
6
+ import os
7
+ import threading
3
8
  from io import BytesIO
4
- from typing import Dict, Optional
9
+ from pathlib import Path
5
10
  from textwrap import dedent
11
+ from typing import Dict, List, Optional, Any
6
12
 
7
13
  import httpx
14
+ from paddleocr import PaddleOCR # type: ignore
8
15
  from PIL import Image
16
+ from pydantic import BaseModel
9
17
 
10
18
  from academia_mcp.files import get_workspace_dir
19
+ from academia_mcp.llm import ChatMessage, llm_acall
11
20
  from academia_mcp.settings import settings
12
- from academia_mcp.llm import llm_acall, ChatMessage
13
21
 
22
+ paddlex_logger = logging.getLogger("paddlex")
23
+ paddleocr_logger = logging.getLogger("paddleocr")
24
+ paddlex_logger.setLevel(logging.ERROR)
25
+ paddleocr_logger.setLevel(logging.ERROR)
14
26
 
15
27
  DESCRIBE_PROMPTS = {
16
28
  "general": "Provide a general description of this image. Focus on the main subjects, colors, and overall scene.",
@@ -37,16 +49,64 @@ DESCRIBE_PROMPTS = {
37
49
  - If layout is multi-column or tabular, reconstruct lines top-to-bottom, left-to-right; use line breaks between blocks.
38
50
  - For any uncertain or low-confidence characters, mark with a '?' and include a note.
39
51
  - After the raw extraction, provide a clean, normalized version (fixing obvious OCR artifacts) as a separate section.
40
- Return two sections:
52
+ Return three sections:
53
+ [GENERAL IMAGE DESCRIPTION]
54
+ ...
41
55
  [RAW TRANSCRIPTION]
42
56
  ...
43
- [NORMALIZED]
57
+ [NORMALIZED TRANSCRIPTION]
44
58
  ...
45
59
  """
46
60
  ),
47
61
  }
48
62
 
49
63
 
64
+ class OCRBox(BaseModel): # type: ignore
65
+ poly: List[List[float]]
66
+ text: str
67
+ score: float
68
+
69
+
70
+ class OCRSingleton:
71
+ instance: Optional[PaddleOCR] = None
72
+ lock: threading.Lock = threading.Lock()
73
+
74
+ @classmethod
75
+ def get(cls) -> PaddleOCR:
76
+ if cls.instance is not None:
77
+ return cls.instance
78
+ with cls.lock:
79
+ if cls.instance is None:
80
+ with open(os.devnull, "w") as devnull:
81
+ with contextlib.redirect_stderr(devnull):
82
+ cls.instance = PaddleOCR(
83
+ use_doc_orientation_classify=False,
84
+ use_doc_unwarping=False,
85
+ use_textline_orientation=False,
86
+ )
87
+ return cls.instance
88
+
89
+
90
+ async def _run_ocr(path: str) -> Dict[str, Any]:
91
+ def _sync_ocr(path: str) -> Dict[str, Any]:
92
+ try:
93
+ ocr = OCRSingleton.get()
94
+ with open(os.devnull, "w") as devnull:
95
+ with contextlib.redirect_stderr(devnull):
96
+ result = ocr.predict(input=path)[0]
97
+ rec_texts = result["rec_texts"]
98
+ rec_scores = result["rec_scores"]
99
+ rec_polys = result["rec_polys"]
100
+ except Exception as e:
101
+ return {"error": str(e)}
102
+ items = []
103
+ for poly, text, score in zip(rec_polys, rec_texts, rec_scores):
104
+ items.append(OCRBox(poly=poly, text=text, score=score).model_dump())
105
+ return {"boxes": items}
106
+
107
+ return await asyncio.to_thread(_sync_ocr, path)
108
+
109
+
50
110
  def show_image(path: str) -> Dict[str, str]:
51
111
  """
52
112
  Reads an image from the specified URL or from the current work directory.
@@ -91,7 +151,7 @@ async def describe_image(
91
151
  - "general": General description of the image
92
152
  - "detailed": Detailed analysis of the image
93
153
  - "chess": Analysis of a chess position
94
- - "text": Extract and describe text or numbers from the image
154
+ - "text": Extract and describe text or numbers with an OCR pipeline.
95
155
  - "custom": Custom description based on user prompt
96
156
  """
97
157
  image_base64 = show_image(path)["image_base64"]
@@ -116,4 +176,12 @@ async def describe_image(
116
176
  messages=[ChatMessage(role="user", content=content)],
117
177
  **llm_kwargs,
118
178
  )
179
+ if description_type == "text":
180
+ ocr_response = await _run_ocr(path)
181
+ response = json.dumps(
182
+ {
183
+ "vlm_response": response,
184
+ "ocr_response": ocr_response if ocr_response else [],
185
+ }
186
+ )
119
187
  return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: academia-mcp
3
- Version: 1.11.2
3
+ Version: 1.11.4
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
@@ -12,16 +12,14 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: mcp>=1.10.1
14
14
  Requires-Dist: xmltodict>=0.14.0
15
- Requires-Dist: types-xmltodict>=0.14.0
16
15
  Requires-Dist: requests>=2.32.0
17
- Requires-Dist: types-requests>=2.32.0
18
16
  Requires-Dist: pypdf>=5.1.0
19
17
  Requires-Dist: beautifulsoup4>=4.12.0
20
- Requires-Dist: types-beautifulsoup4>=4.12.0
21
18
  Requires-Dist: markdownify==0.14.1
19
+ Requires-Dist: types-xmltodict>=0.14.0
20
+ Requires-Dist: types-requests>=2.32.0
21
+ Requires-Dist: types-beautifulsoup4>=4.12.0
22
22
  Requires-Dist: acl-anthology==0.5.2
23
- Requires-Dist: markdown==3.7.0
24
- Requires-Dist: types-markdown==3.7.0.20250322
25
23
  Requires-Dist: huggingface-hub>=0.32.4
26
24
  Requires-Dist: fire>=0.7.0
27
25
  Requires-Dist: openai>=1.97.1
@@ -31,6 +29,8 @@ Requires-Dist: pymupdf>=1.26.4
31
29
  Requires-Dist: pillow>=11.3.0
32
30
  Requires-Dist: pydantic-settings>=2.6.0
33
31
  Requires-Dist: youtube-transcript-api>=1.2.2
32
+ Requires-Dist: paddlepaddle>=3.2.0
33
+ Requires-Dist: paddleocr>=3.2.0
34
34
  Dynamic: license-file
35
35
 
36
36
  # Academia MCP
@@ -70,12 +70,16 @@ make install
70
70
  ### Quickstart
71
71
  - Run over HTTP (default transport):
72
72
  ```bash
73
+ python -m academia_mcp --transport streamable-http
74
+ # OR
73
75
  uv run -m academia_mcp --transport streamable-http
74
76
  ```
75
77
 
76
78
  - Run over stdio (for local MCP clients like Claude Desktop):
77
79
  ```bash
78
80
  python -m academia_mcp --transport stdio
81
+ # OR
82
+ uv run -m academia_mcp --transport stdio
79
83
  ```
80
84
 
81
85
  Notes:
@@ -122,7 +126,7 @@ Availability notes:
122
126
  - Set one or more of `EXA_API_KEY`, `BRAVE_API_KEY`, `TAVILY_API_KEY` to enable `web_search` and provider tools.
123
127
 
124
128
  ### Environment variables
125
- Set as needed depending on which tools you use:
129
+ Set as needed, depending on which tools you use:
126
130
 
127
131
  - `OPENROUTER_API_KEY`: required for LLM-related tools.
128
132
  - `BASE_URL`: override OpenRouter base URL.
@@ -152,7 +156,7 @@ docker run --rm -p 5056:5056 \
152
156
  academia_mcp
153
157
  ```
154
158
 
155
- Or use existing image: `phoenix120/academia_mcp`
159
+ Or use existing image: [`phoenix120/academia_mcp`](https://hub.docker.com/repository/docker/phoenix120/academia_mcp)
156
160
 
157
161
  ### Examples
158
162
  - [Comprehensive report screencast (YouTube)](https://www.youtube.com/watch?v=4bweqQcN6w8)
@@ -1,33 +1,33 @@
1
1
  academia_mcp/__init__.py,sha256=2Ru2I5u4cE7DrkkAsibDUEF1K6sYtqppb9VyFrRoQKI,94
2
2
  academia_mcp/__main__.py,sha256=rcmsOtJd3SA82exjrcGBuxuptcoxF8AXI7jNjiVq2BY,59
3
- academia_mcp/files.py,sha256=ynIt0XbU1Z7EPWkv_hVX0pGKsLlmjYv-MVJLOfi6yzs,817
3
+ 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=uJNV5YStMJ3sEbx4waOclSw_hN0Gkz5PJ-q8VjX1kfA,6390
7
+ academia_mcp/server.py,sha256=sxr_M1JQif29hSuduXvoknPgidwoau8WKszT_P9mEVI,6396
8
8
  academia_mcp/settings.py,sha256=c5s4dI8V_cWmMED-jKDmHjfdIaBcxwEK4HdHNQ3WUIg,1096
9
9
  academia_mcp/utils.py,sha256=ixtYI7qidFJpc8Tzc5sokseCLx4r0yFFgbktKaY-Ixo,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=Z30vULZwUeUX5nDz5wcv0znhAeBtZRa0dvz7vD8SUYE,1555
12
+ academia_mcp/tools/__init__.py,sha256=cCS073dW2S5T_YWrbwd-H9QRiervZ7bof_2sdvzR8JQ,1561
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
16
16
  academia_mcp/tools/bitflip.py,sha256=0OPxQRU5VLWrcIlj4Ubjeq4gIq0pR5FDhXDHuLR0ppw,12759
17
17
  academia_mcp/tools/document_qa.py,sha256=Wb2nEEVu9UyPp8ktHWeT9wS2JBle8fb9zRjTNVIDdBE,2463
18
18
  academia_mcp/tools/hf_datasets_search.py,sha256=KiBkqT4rXjEN4oc1AWZOPnqN_Go90TQogY5-DUm3LQo,2854
19
+ academia_mcp/tools/image_processing.py,sha256=BFj5D0lYbe6OCAL9LSHjS1adr8C0BrtWBXD4xozkC7I,6776
19
20
  academia_mcp/tools/latex.py,sha256=B1Leqt1FHY6H3DlUgeYse4LMFpf4-K1FQViXl5MKk8A,6144
20
21
  academia_mcp/tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
22
  academia_mcp/tools/review.py,sha256=Va0lFJJKuk-NvWhKS3UZ-Dnuk7CyuDQ4S1nd70D-ffE,11117
22
23
  academia_mcp/tools/s2.py,sha256=ykJOkpHnyZRlfyDnCIL9m-Rnu5dBecoKxg0SjlzdvLk,6457
23
- academia_mcp/tools/show_image.py,sha256=DWSnYMTn_dJpGTLL1r_sbX5XsB6p9z-vClApDANz84s,4534
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.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
29
- academia_mcp-1.11.2.dist-info/METADATA,sha256=-Nbs00tnl2H1LhBJrVriuLsvbwANaqAHwQsbAn9u79k,6356
30
- academia_mcp-1.11.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- academia_mcp-1.11.2.dist-info/entry_points.txt,sha256=gxkiKJ74w2FwJpSECpjA3XtCfI5ZfrM6N8cqnwsq4yY,51
32
- academia_mcp-1.11.2.dist-info/top_level.txt,sha256=CzGpRFsRRJRqWEb1e3SUlcfGqRzOxevZGaJWrtGF8W0,13
33
- academia_mcp-1.11.2.dist-info/RECORD,,
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,,