scitex 2.15.3__py3-none-any.whl → 2.15.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.
- scitex/_mcp_resources/__init__.py +2 -0
- scitex/_mcp_resources/_scholar.py +148 -0
- scitex/_mcp_tools/scholar.py +50 -99
- scitex/canvas/mcp_server.py +16 -3
- scitex/capture/mcp_server.py +16 -2
- scitex/cli/scholar/__init__.py +55 -28
- scitex/cli/scholar/_crossref_scitex.py +25 -266
- scitex/cli/scholar/_openalex_scitex.py +55 -0
- scitex/scholar/__init__.py +14 -9
- scitex/scholar/_mcp/crossref_tool_schemas.py +133 -0
- scitex/scholar/_mcp/openalex_handlers.py +212 -0
- scitex/scholar/_mcp/openalex_tool_schemas.py +96 -0
- scitex/scholar/_mcp/tool_schemas.py +16 -1
- scitex/scholar/local_dbs/__init__.py +31 -0
- scitex/scholar/local_dbs/crossref_scitex.py +30 -0
- scitex/scholar/local_dbs/openalex_scitex.py +30 -0
- scitex/scholar/mcp_server.py +59 -4
- scitex/stats/mcp_server.py +16 -3
- scitex/template/mcp_server.py +16 -3
- scitex/ui/mcp_server.py +16 -3
- {scitex-2.15.3.dist-info → scitex-2.15.4.dist-info}/METADATA +1 -1
- {scitex-2.15.3.dist-info → scitex-2.15.4.dist-info}/RECORD +25 -18
- scitex/scholar/crossref_scitex.py +0 -367
- {scitex-2.15.3.dist-info → scitex-2.15.4.dist-info}/WHEEL +0 -0
- {scitex-2.15.3.dist-info → scitex-2.15.4.dist-info}/entry_points.txt +0 -0
- {scitex-2.15.3.dist-info → scitex-2.15.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -20,6 +20,7 @@ from ._cheatsheet import register_cheatsheet_resources
|
|
|
20
20
|
from ._figrecipe import register_figrecipe_resources
|
|
21
21
|
from ._formats import register_format_resources
|
|
22
22
|
from ._modules import register_module_resources
|
|
23
|
+
from ._scholar import register_scholar_resources
|
|
23
24
|
from ._session import register_session_resources
|
|
24
25
|
|
|
25
26
|
__all__ = ["register_resources"]
|
|
@@ -32,6 +33,7 @@ def register_resources(mcp) -> None:
|
|
|
32
33
|
register_module_resources(mcp)
|
|
33
34
|
register_format_resources(mcp)
|
|
34
35
|
register_figrecipe_resources(mcp)
|
|
36
|
+
register_scholar_resources(mcp)
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
# EOF
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-29
|
|
3
|
+
# File: src/scitex/_mcp_resources/_scholar.py
|
|
4
|
+
"""Scholar library resources for FastMCP unified server.
|
|
5
|
+
|
|
6
|
+
Provides dynamic resources for:
|
|
7
|
+
- scholar://library/{project} - Project paper listings
|
|
8
|
+
- scholar://bibtex/{filename} - BibTeX file contents
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
__all__ = ["register_scholar_resources"]
|
|
19
|
+
|
|
20
|
+
# Directory configuration
|
|
21
|
+
SCITEX_BASE_DIR = Path(os.getenv("SCITEX_DIR", Path.home() / ".scitex"))
|
|
22
|
+
SCITEX_SCHOLAR_DIR = SCITEX_BASE_DIR / "scholar"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_scholar_dir() -> Path:
|
|
26
|
+
"""Get the scholar data directory."""
|
|
27
|
+
SCITEX_SCHOLAR_DIR.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
return SCITEX_SCHOLAR_DIR
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register_scholar_resources(mcp) -> None:
|
|
32
|
+
"""Register scholar library resources with FastMCP server."""
|
|
33
|
+
|
|
34
|
+
@mcp.resource("scholar://library")
|
|
35
|
+
def list_library_projects() -> str:
|
|
36
|
+
"""List all scholar library projects with paper counts."""
|
|
37
|
+
scholar_dir = _get_scholar_dir()
|
|
38
|
+
library_dir = scholar_dir / "library"
|
|
39
|
+
|
|
40
|
+
if not library_dir.exists():
|
|
41
|
+
return json.dumps({"projects": [], "total": 0}, indent=2)
|
|
42
|
+
|
|
43
|
+
projects = []
|
|
44
|
+
for project_dir in library_dir.iterdir():
|
|
45
|
+
if project_dir.is_dir() and not project_dir.name.startswith("."):
|
|
46
|
+
pdf_count = len(list(project_dir.rglob("*.pdf")))
|
|
47
|
+
metadata_count = len(list(project_dir.rglob("metadata.json")))
|
|
48
|
+
projects.append(
|
|
49
|
+
{
|
|
50
|
+
"name": project_dir.name,
|
|
51
|
+
"pdf_count": pdf_count,
|
|
52
|
+
"paper_count": metadata_count,
|
|
53
|
+
"uri": f"scholar://library/{project_dir.name}",
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return json.dumps(
|
|
58
|
+
{
|
|
59
|
+
"projects": projects,
|
|
60
|
+
"total": len(projects),
|
|
61
|
+
"library_path": str(library_dir),
|
|
62
|
+
},
|
|
63
|
+
indent=2,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@mcp.resource("scholar://library/{project}")
|
|
67
|
+
def get_library_project(project: str) -> str:
|
|
68
|
+
"""Get papers in a specific library project."""
|
|
69
|
+
library_dir = _get_scholar_dir() / "library" / project
|
|
70
|
+
|
|
71
|
+
if not library_dir.exists():
|
|
72
|
+
return json.dumps({"error": f"Project not found: {project}"}, indent=2)
|
|
73
|
+
|
|
74
|
+
metadata_files = list(library_dir.rglob("metadata.json"))
|
|
75
|
+
papers = []
|
|
76
|
+
|
|
77
|
+
for meta_file in metadata_files[:100]:
|
|
78
|
+
try:
|
|
79
|
+
with open(meta_file) as f:
|
|
80
|
+
meta = json.load(f)
|
|
81
|
+
pdf_exists = any(meta_file.parent.glob("*.pdf"))
|
|
82
|
+
papers.append(
|
|
83
|
+
{
|
|
84
|
+
"id": meta_file.parent.name,
|
|
85
|
+
"title": meta.get("title"),
|
|
86
|
+
"doi": meta.get("doi"),
|
|
87
|
+
"authors": meta.get("authors", [])[:3],
|
|
88
|
+
"year": meta.get("year"),
|
|
89
|
+
"has_pdf": pdf_exists,
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
return json.dumps(
|
|
96
|
+
{
|
|
97
|
+
"project": project,
|
|
98
|
+
"paper_count": len(papers),
|
|
99
|
+
"papers": papers,
|
|
100
|
+
},
|
|
101
|
+
indent=2,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@mcp.resource("scholar://bibtex")
|
|
105
|
+
def list_bibtex_files() -> str:
|
|
106
|
+
"""List recent BibTeX files in scholar directory."""
|
|
107
|
+
scholar_dir = _get_scholar_dir()
|
|
108
|
+
bib_files = []
|
|
109
|
+
|
|
110
|
+
for bib_file in sorted(
|
|
111
|
+
scholar_dir.rglob("*.bib"),
|
|
112
|
+
key=lambda p: p.stat().st_mtime,
|
|
113
|
+
reverse=True,
|
|
114
|
+
)[:20]:
|
|
115
|
+
mtime = datetime.fromtimestamp(bib_file.stat().st_mtime)
|
|
116
|
+
bib_files.append(
|
|
117
|
+
{
|
|
118
|
+
"name": bib_file.name,
|
|
119
|
+
"path": str(bib_file),
|
|
120
|
+
"modified": mtime.isoformat(),
|
|
121
|
+
"uri": f"scholar://bibtex/{bib_file.name}",
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return json.dumps(
|
|
126
|
+
{
|
|
127
|
+
"bibtex_files": bib_files,
|
|
128
|
+
"total": len(bib_files),
|
|
129
|
+
},
|
|
130
|
+
indent=2,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@mcp.resource("scholar://bibtex/{filename}")
|
|
134
|
+
def get_bibtex_file(filename: str) -> str:
|
|
135
|
+
"""Read a BibTeX file by name."""
|
|
136
|
+
scholar_dir = _get_scholar_dir()
|
|
137
|
+
bib_files = list(scholar_dir.rglob(filename))
|
|
138
|
+
|
|
139
|
+
if not bib_files:
|
|
140
|
+
return json.dumps({"error": f"BibTeX file not found: {filename}"}, indent=2)
|
|
141
|
+
|
|
142
|
+
with open(bib_files[0]) as f:
|
|
143
|
+
content = f.read()
|
|
144
|
+
|
|
145
|
+
return content
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# EOF
|
scitex/_mcp_tools/scholar.py
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
-
from typing import List, Optional
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def _json(data: dict) -> str:
|
|
@@ -20,10 +19,10 @@ def register_scholar_tools(mcp) -> None:
|
|
|
20
19
|
async def scholar_search_papers(
|
|
21
20
|
query: str,
|
|
22
21
|
limit: int = 20,
|
|
23
|
-
year_min:
|
|
24
|
-
year_max:
|
|
22
|
+
year_min: int | None = None,
|
|
23
|
+
year_max: int | None = None,
|
|
25
24
|
search_mode: str = "local",
|
|
26
|
-
sources:
|
|
25
|
+
sources: list[str] | None = None,
|
|
27
26
|
) -> str:
|
|
28
27
|
"""[scholar] Search for scientific papers. Supports local library search and external databases (CrossRef, Semantic Scholar, PubMed, arXiv, OpenAlex)."""
|
|
29
28
|
from scitex.scholar._mcp.handlers import search_papers_handler
|
|
@@ -40,9 +39,9 @@ def register_scholar_tools(mcp) -> None:
|
|
|
40
39
|
|
|
41
40
|
@mcp.tool()
|
|
42
41
|
async def scholar_resolve_dois(
|
|
43
|
-
titles:
|
|
44
|
-
bibtex_path:
|
|
45
|
-
project:
|
|
42
|
+
titles: list[str] | None = None,
|
|
43
|
+
bibtex_path: str | None = None,
|
|
44
|
+
project: str | None = None,
|
|
46
45
|
resume: bool = True,
|
|
47
46
|
) -> str:
|
|
48
47
|
"""[scholar] Resolve DOIs from paper titles using Crossref API. Supports resumable operation for large batches."""
|
|
@@ -59,7 +58,7 @@ def register_scholar_tools(mcp) -> None:
|
|
|
59
58
|
@mcp.tool()
|
|
60
59
|
async def scholar_enrich_bibtex(
|
|
61
60
|
bibtex_path: str,
|
|
62
|
-
output_path:
|
|
61
|
+
output_path: str | None = None,
|
|
63
62
|
add_abstracts: bool = True,
|
|
64
63
|
add_citations: bool = True,
|
|
65
64
|
add_impact_factors: bool = True,
|
|
@@ -94,10 +93,10 @@ def register_scholar_tools(mcp) -> None:
|
|
|
94
93
|
|
|
95
94
|
@mcp.tool()
|
|
96
95
|
async def scholar_download_pdfs_batch(
|
|
97
|
-
dois:
|
|
98
|
-
bibtex_path:
|
|
99
|
-
project:
|
|
100
|
-
output_dir:
|
|
96
|
+
dois: list[str] | None = None,
|
|
97
|
+
bibtex_path: str | None = None,
|
|
98
|
+
project: str | None = None,
|
|
99
|
+
output_dir: str | None = None,
|
|
101
100
|
max_concurrent: int = 3,
|
|
102
101
|
resume: bool = True,
|
|
103
102
|
) -> str:
|
|
@@ -116,7 +115,7 @@ def register_scholar_tools(mcp) -> None:
|
|
|
116
115
|
|
|
117
116
|
@mcp.tool()
|
|
118
117
|
async def scholar_get_library_status(
|
|
119
|
-
project:
|
|
118
|
+
project: str | None = None,
|
|
120
119
|
include_details: bool = False,
|
|
121
120
|
) -> str:
|
|
122
121
|
"""[scholar] Get status of the paper library: download progress, missing PDFs, validation status."""
|
|
@@ -138,8 +137,8 @@ def register_scholar_tools(mcp) -> None:
|
|
|
138
137
|
|
|
139
138
|
@mcp.tool()
|
|
140
139
|
async def scholar_validate_pdfs(
|
|
141
|
-
project:
|
|
142
|
-
pdf_paths:
|
|
140
|
+
project: str | None = None,
|
|
141
|
+
pdf_paths: list[str] | None = None,
|
|
143
142
|
) -> str:
|
|
144
143
|
"""[scholar] Validate PDF files in library for completeness and readability."""
|
|
145
144
|
from scitex.scholar._mcp.handlers import validate_pdfs_handler
|
|
@@ -152,8 +151,8 @@ def register_scholar_tools(mcp) -> None:
|
|
|
152
151
|
|
|
153
152
|
@mcp.tool()
|
|
154
153
|
async def scholar_resolve_openurls(
|
|
155
|
-
dois:
|
|
156
|
-
resolver_url:
|
|
154
|
+
dois: list[str],
|
|
155
|
+
resolver_url: str | None = None,
|
|
157
156
|
resume: bool = True,
|
|
158
157
|
) -> str:
|
|
159
158
|
"""[scholar] Resolve publisher URLs via OpenURL resolver for institutional access."""
|
|
@@ -169,7 +168,7 @@ def register_scholar_tools(mcp) -> None:
|
|
|
169
168
|
@mcp.tool()
|
|
170
169
|
async def scholar_authenticate(
|
|
171
170
|
method: str,
|
|
172
|
-
institution:
|
|
171
|
+
institution: str | None = None,
|
|
173
172
|
force: bool = False,
|
|
174
173
|
confirm: bool = False,
|
|
175
174
|
) -> str:
|
|
@@ -215,7 +214,7 @@ def register_scholar_tools(mcp) -> None:
|
|
|
215
214
|
@mcp.tool()
|
|
216
215
|
async def scholar_export_papers(
|
|
217
216
|
output_path: str,
|
|
218
|
-
project:
|
|
217
|
+
project: str | None = None,
|
|
219
218
|
format: str = "bibtex",
|
|
220
219
|
filter_has_pdf: bool = False,
|
|
221
220
|
) -> str:
|
|
@@ -233,7 +232,7 @@ def register_scholar_tools(mcp) -> None:
|
|
|
233
232
|
@mcp.tool()
|
|
234
233
|
async def scholar_create_project(
|
|
235
234
|
project_name: str,
|
|
236
|
-
description:
|
|
235
|
+
description: str | None = None,
|
|
237
236
|
) -> str:
|
|
238
237
|
"""[scholar] Create a new scholar project for organizing papers."""
|
|
239
238
|
from scitex.scholar._mcp.handlers import create_project_handler
|
|
@@ -255,8 +254,8 @@ def register_scholar_tools(mcp) -> None:
|
|
|
255
254
|
@mcp.tool()
|
|
256
255
|
async def scholar_add_papers_to_project(
|
|
257
256
|
project: str,
|
|
258
|
-
dois:
|
|
259
|
-
bibtex_path:
|
|
257
|
+
dois: list[str] | None = None,
|
|
258
|
+
bibtex_path: str | None = None,
|
|
260
259
|
) -> str:
|
|
261
260
|
"""[scholar] Add papers to a project by DOI or from BibTeX file."""
|
|
262
261
|
from scitex.scholar._mcp.handlers import add_papers_to_project_handler
|
|
@@ -270,14 +269,14 @@ def register_scholar_tools(mcp) -> None:
|
|
|
270
269
|
|
|
271
270
|
@mcp.tool()
|
|
272
271
|
async def scholar_parse_pdf_content(
|
|
273
|
-
pdf_path:
|
|
274
|
-
doi:
|
|
275
|
-
project:
|
|
272
|
+
pdf_path: str | None = None,
|
|
273
|
+
doi: str | None = None,
|
|
274
|
+
project: str | None = None,
|
|
276
275
|
mode: str = "scientific",
|
|
277
276
|
extract_sections: bool = True,
|
|
278
277
|
extract_tables: bool = False,
|
|
279
278
|
extract_images: bool = False,
|
|
280
|
-
max_pages:
|
|
279
|
+
max_pages: int | None = None,
|
|
281
280
|
) -> str:
|
|
282
281
|
"""[scholar] Parse PDF content to extract text, sections (IMRaD), tables, images, and metadata."""
|
|
283
282
|
from scitex.scholar._mcp.handlers import parse_pdf_content_handler
|
|
@@ -297,14 +296,14 @@ def register_scholar_tools(mcp) -> None:
|
|
|
297
296
|
# Job management tools (from job_handlers.py)
|
|
298
297
|
@mcp.tool()
|
|
299
298
|
async def scholar_fetch_papers(
|
|
300
|
-
papers:
|
|
301
|
-
bibtex_path:
|
|
302
|
-
project:
|
|
303
|
-
workers:
|
|
299
|
+
papers: list[str] | None = None,
|
|
300
|
+
bibtex_path: str | None = None,
|
|
301
|
+
project: str | None = None,
|
|
302
|
+
workers: int | None = None,
|
|
304
303
|
browser_mode: str = "stealth",
|
|
305
304
|
chrome_profile: str = "system",
|
|
306
305
|
force: bool = False,
|
|
307
|
-
output:
|
|
306
|
+
output: str | None = None,
|
|
308
307
|
async_mode: bool = True,
|
|
309
308
|
) -> str:
|
|
310
309
|
"""[scholar] Fetch papers to your library. Supports async mode (default) which returns immediately with a job_id for tracking."""
|
|
@@ -325,7 +324,7 @@ def register_scholar_tools(mcp) -> None:
|
|
|
325
324
|
|
|
326
325
|
@mcp.tool()
|
|
327
326
|
async def scholar_list_jobs(
|
|
328
|
-
status:
|
|
327
|
+
status: str | None = None,
|
|
329
328
|
limit: int = 20,
|
|
330
329
|
) -> str:
|
|
331
330
|
"""[scholar] List all background jobs with their status."""
|
|
@@ -369,79 +368,31 @@ def register_scholar_tools(mcp) -> None:
|
|
|
369
368
|
result = await get_job_result_handler(job_id=job_id)
|
|
370
369
|
return _json(result)
|
|
371
370
|
|
|
372
|
-
#
|
|
373
|
-
|
|
374
|
-
# =========================================================================
|
|
371
|
+
# Import crossref-local and openalex-local MCP servers
|
|
372
|
+
_import_local_db_servers(mcp)
|
|
375
373
|
|
|
376
|
-
@mcp.tool()
|
|
377
|
-
async def scholar_crossref_search(
|
|
378
|
-
query: str,
|
|
379
|
-
limit: int = 20,
|
|
380
|
-
offset: int = 0,
|
|
381
|
-
year_min: Optional[int] = None,
|
|
382
|
-
year_max: Optional[int] = None,
|
|
383
|
-
enrich: bool = False,
|
|
384
|
-
) -> str:
|
|
385
|
-
"""[scholar] Search CrossRef database (167M+ papers) via crossref-local. Fast full-text search with optional citation enrichment."""
|
|
386
|
-
from scitex.scholar._mcp.crossref_handlers import crossref_search_handler
|
|
387
374
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
limit=limit,
|
|
391
|
-
offset=offset,
|
|
392
|
-
year_min=year_min,
|
|
393
|
-
year_max=year_max,
|
|
394
|
-
enrich=enrich,
|
|
395
|
-
)
|
|
396
|
-
return _json(result)
|
|
375
|
+
def _import_local_db_servers(mcp) -> None:
|
|
376
|
+
"""Mount crossref-local and openalex-local MCP servers if available.
|
|
397
377
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
"""[scholar] Get paper metadata by DOI from CrossRef database."""
|
|
405
|
-
from scitex.scholar._mcp.crossref_handlers import crossref_get_handler
|
|
378
|
+
Uses fastmcp's mount() for automatic tool delegation.
|
|
379
|
+
Tools are prefixed: crossref_search, openalex_search, etc.
|
|
380
|
+
"""
|
|
381
|
+
# Mount crossref-local MCP server (167M+ papers)
|
|
382
|
+
try:
|
|
383
|
+
from crossref_local.mcp_server import mcp as crossref_mcp
|
|
406
384
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
include_references=include_references,
|
|
411
|
-
)
|
|
412
|
-
return _json(result)
|
|
385
|
+
mcp.mount(crossref_mcp, prefix="crossref")
|
|
386
|
+
except ImportError:
|
|
387
|
+
pass # crossref-local not installed
|
|
413
388
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
from scitex.scholar._mcp.crossref_handlers import crossref_count_handler
|
|
418
|
-
|
|
419
|
-
result = await crossref_count_handler(query=query)
|
|
420
|
-
return _json(result)
|
|
421
|
-
|
|
422
|
-
@mcp.tool()
|
|
423
|
-
async def scholar_crossref_citations(
|
|
424
|
-
doi: str,
|
|
425
|
-
direction: str = "citing",
|
|
426
|
-
limit: int = 100,
|
|
427
|
-
) -> str:
|
|
428
|
-
"""[scholar] Get citation relationships (citing/cited papers) for a DOI."""
|
|
429
|
-
from scitex.scholar._mcp.crossref_handlers import crossref_citations_handler
|
|
389
|
+
# Mount openalex-local MCP server (250M+ papers)
|
|
390
|
+
try:
|
|
391
|
+
from openalex_local._cli.mcp_server import mcp as openalex_mcp
|
|
430
392
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
limit=limit,
|
|
435
|
-
)
|
|
436
|
-
return _json(result)
|
|
437
|
-
|
|
438
|
-
@mcp.tool()
|
|
439
|
-
async def scholar_crossref_info() -> str:
|
|
440
|
-
"""[scholar] Get CrossRef database configuration and status info."""
|
|
441
|
-
from scitex.scholar._mcp.crossref_handlers import crossref_info_handler
|
|
442
|
-
|
|
443
|
-
result = await crossref_info_handler()
|
|
444
|
-
return _json(result)
|
|
393
|
+
mcp.mount(openalex_mcp, prefix="openalex")
|
|
394
|
+
except ImportError:
|
|
395
|
+
pass # openalex-local not installed
|
|
445
396
|
|
|
446
397
|
|
|
447
398
|
# EOF
|
scitex/canvas/mcp_server.py
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
# File: src/scitex/canvas/mcp_server.py
|
|
4
4
|
# ----------------------------------------
|
|
5
5
|
|
|
6
|
-
"""
|
|
7
|
-
|
|
6
|
+
"""MCP Server for SciTeX canvas - Multi-panel figure composition.
|
|
7
|
+
|
|
8
|
+
.. deprecated::
|
|
9
|
+
This standalone server is deprecated. Use the unified scitex MCP server:
|
|
10
|
+
CLI: scitex serve
|
|
11
|
+
Python: from scitex.mcp_server import run_server
|
|
8
12
|
|
|
9
13
|
Provides tools for:
|
|
10
14
|
- Creating canvas workspaces
|
|
@@ -15,6 +19,15 @@ Provides tools for:
|
|
|
15
19
|
|
|
16
20
|
from __future__ import annotations
|
|
17
21
|
|
|
22
|
+
import warnings
|
|
23
|
+
|
|
24
|
+
warnings.warn(
|
|
25
|
+
"scitex.canvas.mcp_server is deprecated. Use 'scitex serve' or "
|
|
26
|
+
"'from scitex.mcp_server import run_server' for the unified MCP server.",
|
|
27
|
+
DeprecationWarning,
|
|
28
|
+
stacklevel=2,
|
|
29
|
+
)
|
|
30
|
+
|
|
18
31
|
import asyncio
|
|
19
32
|
|
|
20
33
|
# Graceful MCP dependency handling
|
|
@@ -126,7 +139,7 @@ async def _run_server():
|
|
|
126
139
|
|
|
127
140
|
|
|
128
141
|
def main():
|
|
129
|
-
"""
|
|
142
|
+
"""Run the MCP server."""
|
|
130
143
|
if not MCP_AVAILABLE:
|
|
131
144
|
import sys
|
|
132
145
|
|
scitex/capture/mcp_server.py
CHANGED
|
@@ -10,11 +10,25 @@ __FILE__ = "./src/scitex/capture/mcp_server.py"
|
|
|
10
10
|
__DIR__ = os.path.dirname(__FILE__)
|
|
11
11
|
# ----------------------------------------
|
|
12
12
|
|
|
13
|
-
"""
|
|
14
|
-
|
|
13
|
+
"""MCP Server for SciTeX Capture - Screen Capture for Python.
|
|
14
|
+
|
|
15
|
+
.. deprecated::
|
|
16
|
+
This standalone server is deprecated. Use the unified scitex MCP server:
|
|
17
|
+
CLI: scitex serve
|
|
18
|
+
Python: from scitex.mcp_server import run_server
|
|
19
|
+
|
|
15
20
|
Provides screenshot capture capabilities via Model Context Protocol.
|
|
16
21
|
"""
|
|
17
22
|
|
|
23
|
+
import warnings
|
|
24
|
+
|
|
25
|
+
warnings.warn(
|
|
26
|
+
"scitex.capture.mcp_server is deprecated. Use 'scitex serve' or "
|
|
27
|
+
"'from scitex.mcp_server import run_server' for the unified MCP server.",
|
|
28
|
+
DeprecationWarning,
|
|
29
|
+
stacklevel=2,
|
|
30
|
+
)
|
|
31
|
+
|
|
18
32
|
import asyncio
|
|
19
33
|
import base64
|
|
20
34
|
from datetime import datetime
|
scitex/cli/scholar/__init__.py
CHANGED
|
@@ -21,6 +21,11 @@ CrossRef database (167M+ papers via crossref-local):
|
|
|
21
21
|
scitex scholar crossref-scitex get 10.1038/nature12373
|
|
22
22
|
scitex scholar crossref-scitex count "epilepsy seizure"
|
|
23
23
|
scitex scholar crossref-scitex info
|
|
24
|
+
|
|
25
|
+
OpenAlex database (284M+ works via openalex-local):
|
|
26
|
+
scitex scholar openalex-scitex search "neural networks"
|
|
27
|
+
scitex scholar openalex-scitex search-by-doi 10.1038/nature12373
|
|
28
|
+
scitex scholar openalex-scitex status
|
|
24
29
|
"""
|
|
25
30
|
|
|
26
31
|
from __future__ import annotations
|
|
@@ -31,6 +36,7 @@ from ._crossref_scitex import crossref_scitex
|
|
|
31
36
|
from ._fetch import fetch
|
|
32
37
|
from ._jobs import jobs
|
|
33
38
|
from ._library import config, library
|
|
39
|
+
from ._openalex_scitex import openalex_scitex
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
@click.group(
|
|
@@ -40,8 +46,7 @@ from ._library import config, library
|
|
|
40
46
|
@click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
|
|
41
47
|
@click.pass_context
|
|
42
48
|
def scholar(ctx, help_recursive):
|
|
43
|
-
"""
|
|
44
|
-
Scientific paper management
|
|
49
|
+
r"""Scientific paper management.
|
|
45
50
|
|
|
46
51
|
\b
|
|
47
52
|
Fetch papers, manage your library, and track background jobs.
|
|
@@ -96,8 +101,7 @@ def _print_help_recursive(ctx):
|
|
|
96
101
|
@scholar.group(invoke_without_command=True)
|
|
97
102
|
@click.pass_context
|
|
98
103
|
def mcp(ctx):
|
|
99
|
-
"""
|
|
100
|
-
MCP (Model Context Protocol) server operations
|
|
104
|
+
r"""MCP (Model Context Protocol) server operations.
|
|
101
105
|
|
|
102
106
|
\b
|
|
103
107
|
Commands:
|
|
@@ -124,28 +128,35 @@ def mcp(ctx):
|
|
|
124
128
|
)
|
|
125
129
|
@click.option("--host", default="0.0.0.0", help="Host for HTTP/SSE (default: 0.0.0.0)")
|
|
126
130
|
@click.option(
|
|
127
|
-
"--port", default=
|
|
131
|
+
"--port", default=8085, type=int, help="Port for HTTP/SSE (default: 8085)"
|
|
128
132
|
)
|
|
129
133
|
def start(transport, host, port):
|
|
130
|
-
"""
|
|
131
|
-
|
|
134
|
+
r"""Start the MCP server with scholar tools.
|
|
135
|
+
|
|
136
|
+
\b
|
|
137
|
+
NOTE: This now uses the unified scitex MCP server which includes
|
|
138
|
+
all scholar tools plus other scitex tools (plt, stats, etc.)
|
|
132
139
|
|
|
133
140
|
\b
|
|
134
141
|
Examples:
|
|
135
142
|
scitex scholar mcp start
|
|
136
|
-
scitex scholar mcp start -t http --port
|
|
143
|
+
scitex scholar mcp start -t http --port 8085
|
|
144
|
+
|
|
145
|
+
\b
|
|
146
|
+
Equivalent to: scitex serve -t <transport>
|
|
137
147
|
"""
|
|
138
148
|
import sys
|
|
139
149
|
|
|
140
150
|
try:
|
|
141
|
-
from scitex.
|
|
151
|
+
from scitex.mcp_server import run_server
|
|
142
152
|
|
|
143
153
|
if transport != "stdio":
|
|
144
|
-
click.secho(f"Starting
|
|
154
|
+
click.secho(f"Starting unified scitex MCP server ({transport})", fg="cyan")
|
|
145
155
|
click.echo(f" Host: {host}")
|
|
146
156
|
click.echo(f" Port: {port}")
|
|
157
|
+
click.echo(" Includes: scholar, plt, stats, audio, and more")
|
|
147
158
|
|
|
148
|
-
run_server()
|
|
159
|
+
run_server(transport=transport, host=host, port=port)
|
|
149
160
|
|
|
150
161
|
except ImportError as e:
|
|
151
162
|
click.secho(f"Error: {e}", fg="red", err=True)
|
|
@@ -158,8 +169,7 @@ def start(transport, host, port):
|
|
|
158
169
|
|
|
159
170
|
@mcp.command()
|
|
160
171
|
def doctor():
|
|
161
|
-
"""
|
|
162
|
-
Check MCP server health and dependencies
|
|
172
|
+
r"""Check MCP server health and dependencies.
|
|
163
173
|
|
|
164
174
|
\b
|
|
165
175
|
Example:
|
|
@@ -193,11 +203,18 @@ def doctor():
|
|
|
193
203
|
except ImportError:
|
|
194
204
|
click.secho("NOT INSTALLED (optional)", fg="yellow")
|
|
195
205
|
|
|
206
|
+
click.echo("Checking openalex-local... ", nl=False)
|
|
207
|
+
try:
|
|
208
|
+
import openalex_local # noqa: F401
|
|
209
|
+
|
|
210
|
+
click.secho("OK", fg="green")
|
|
211
|
+
except ImportError:
|
|
212
|
+
click.secho("NOT INSTALLED (optional)", fg="yellow")
|
|
213
|
+
|
|
196
214
|
|
|
197
215
|
@mcp.command("list-tools")
|
|
198
216
|
def list_tools():
|
|
199
|
-
"""
|
|
200
|
-
List available MCP tools
|
|
217
|
+
r"""List available MCP tools.
|
|
201
218
|
|
|
202
219
|
\b
|
|
203
220
|
Example:
|
|
@@ -206,25 +223,35 @@ def list_tools():
|
|
|
206
223
|
click.secho("Scholar MCP Tools", fg="cyan", bold=True)
|
|
207
224
|
click.echo()
|
|
208
225
|
tools = [
|
|
209
|
-
("
|
|
210
|
-
("
|
|
211
|
-
("
|
|
212
|
-
("
|
|
213
|
-
("
|
|
214
|
-
("
|
|
215
|
-
("
|
|
216
|
-
("
|
|
217
|
-
("
|
|
218
|
-
("
|
|
219
|
-
("
|
|
220
|
-
|
|
221
|
-
("
|
|
226
|
+
("search_papers", "Search for papers by query"),
|
|
227
|
+
("resolve_dois", "Resolve DOIs to metadata"),
|
|
228
|
+
("enrich_bibtex", "Enrich BibTeX with abstracts/DOIs"),
|
|
229
|
+
("download_pdf", "Download PDF for a paper"),
|
|
230
|
+
("download_pdfs_batch", "Batch download PDFs"),
|
|
231
|
+
("get_library_status", "Get library status"),
|
|
232
|
+
("parse_bibtex", "Parse BibTeX file"),
|
|
233
|
+
("validate_pdfs", "Validate downloaded PDFs"),
|
|
234
|
+
("authenticate", "Authenticate with institution"),
|
|
235
|
+
("check_auth_status", "Check authentication status"),
|
|
236
|
+
("fetch_papers", "Fetch papers by DOIs (async)"),
|
|
237
|
+
# CrossRef-Local (167M+ papers)
|
|
238
|
+
("crossref_search", "Search CrossRef database (167M+ papers)"),
|
|
239
|
+
("crossref_get", "Get paper by DOI from CrossRef"),
|
|
240
|
+
("crossref_count", "Count papers matching query"),
|
|
241
|
+
("crossref_citations", "Get citation relationships"),
|
|
242
|
+
("crossref_info", "Get CrossRef database status"),
|
|
243
|
+
# OpenAlex-Local (284M+ works)
|
|
244
|
+
("openalex_search", "Search OpenAlex database (284M+ works)"),
|
|
245
|
+
("openalex_get", "Get paper by DOI/ID from OpenAlex"),
|
|
246
|
+
("openalex_count", "Count papers matching query"),
|
|
247
|
+
("openalex_info", "Get OpenAlex database status"),
|
|
222
248
|
]
|
|
223
249
|
for name, desc in tools:
|
|
224
250
|
click.echo(f" {name}: {desc}")
|
|
225
251
|
|
|
226
252
|
|
|
227
253
|
scholar.add_command(crossref_scitex)
|
|
254
|
+
scholar.add_command(openalex_scitex)
|
|
228
255
|
scholar.add_command(fetch)
|
|
229
256
|
scholar.add_command(library)
|
|
230
257
|
scholar.add_command(config)
|