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.
@@ -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
@@ -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: Optional[int] = None,
24
- year_max: Optional[int] = None,
22
+ year_min: int | None = None,
23
+ year_max: int | None = None,
25
24
  search_mode: str = "local",
26
- sources: Optional[List[str]] = None,
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: Optional[List[str]] = None,
44
- bibtex_path: Optional[str] = None,
45
- project: Optional[str] = None,
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: Optional[str] = None,
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: Optional[List[str]] = None,
98
- bibtex_path: Optional[str] = None,
99
- project: Optional[str] = None,
100
- output_dir: Optional[str] = None,
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: Optional[str] = None,
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: Optional[str] = None,
142
- pdf_paths: Optional[List[str]] = None,
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: List[str],
156
- resolver_url: Optional[str] = None,
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: Optional[str] = None,
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: Optional[str] = None,
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: Optional[str] = None,
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: Optional[List[str]] = None,
259
- bibtex_path: Optional[str] = None,
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: Optional[str] = None,
274
- doi: Optional[str] = None,
275
- project: Optional[str] = None,
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: Optional[int] = None,
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: Optional[List[str]] = None,
301
- bibtex_path: Optional[str] = None,
302
- project: Optional[str] = None,
303
- workers: Optional[int] = None,
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: Optional[str] = None,
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: Optional[str] = None,
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
- # CrossRef Tools (via crossref-local delegation - 167M+ papers)
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
- result = await crossref_search_handler(
389
- query=query,
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
- @mcp.tool()
399
- async def scholar_crossref_get(
400
- doi: str,
401
- include_citations: bool = False,
402
- include_references: bool = False,
403
- ) -> str:
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
- result = await crossref_get_handler(
408
- doi=doi,
409
- include_citations=include_citations,
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
- @mcp.tool()
415
- async def scholar_crossref_count(query: str) -> str:
416
- """[scholar] Count papers matching a query in CrossRef database."""
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
- result = await crossref_citations_handler(
432
- doi=doi,
433
- direction=direction,
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
@@ -3,8 +3,12 @@
3
3
  # File: src/scitex/canvas/mcp_server.py
4
4
  # ----------------------------------------
5
5
 
6
- """
7
- MCP Server for SciTeX canvas - Multi-panel figure composition.
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
- """Main entry point for the MCP server."""
142
+ """Run the MCP server."""
130
143
  if not MCP_AVAILABLE:
131
144
  import sys
132
145
 
@@ -10,11 +10,25 @@ __FILE__ = "./src/scitex/capture/mcp_server.py"
10
10
  __DIR__ = os.path.dirname(__FILE__)
11
11
  # ----------------------------------------
12
12
 
13
- """
14
- MCP Server for SciTeX Capture - Screen Capture for Python
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
@@ -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=8097, type=int, help="Port for HTTP/SSE (default: 8097)"
131
+ "--port", default=8085, type=int, help="Port for HTTP/SSE (default: 8085)"
128
132
  )
129
133
  def start(transport, host, port):
130
- """
131
- Start the scholar MCP server
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 8097
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.scholar.mcp_server import main as run_server
151
+ from scitex.mcp_server import run_server
142
152
 
143
153
  if transport != "stdio":
144
- click.secho(f"Starting scholar MCP server ({transport})", fg="cyan")
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
- ("scholar_search_papers", "Search for papers by query"),
210
- ("scholar_resolve_dois", "Resolve DOIs to metadata"),
211
- ("scholar_enrich_bibtex", "Enrich BibTeX with abstracts/DOIs"),
212
- ("scholar_download_pdf", "Download PDF for a paper"),
213
- ("scholar_download_pdfs_batch", "Batch download PDFs"),
214
- ("scholar_get_library_status", "Get library status"),
215
- ("scholar_parse_bibtex", "Parse BibTeX file"),
216
- ("scholar_validate_pdfs", "Validate downloaded PDFs"),
217
- ("scholar_authenticate", "Authenticate with institution"),
218
- ("scholar_check_auth_status", "Check authentication status"),
219
- ("scholar_fetch_papers", "Fetch papers by DOIs"),
220
- ("scholar_crossref_search", "Search CrossRef database"),
221
- ("scholar_crossref_get", "Get paper by DOI from CrossRef"),
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)