scitex 2.15.3__py3-none-any.whl → 2.15.5__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.
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/_mcp/crossref_tool_schemas.py
4
+ """CrossRef-Local tool schemas for MCP server.
5
+
6
+ Provides access to 167M+ papers via crossref-local database.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import mcp.types as types
12
+
13
+ __all__ = ["get_crossref_tool_schemas"]
14
+
15
+
16
+ def get_crossref_tool_schemas() -> list[types.Tool]:
17
+ """Return CrossRef-Local tool schemas."""
18
+ return [
19
+ types.Tool(
20
+ name="crossref_search",
21
+ description=(
22
+ "Search CrossRef database (167M+ papers) via crossref-local. "
23
+ "Fast full-text search with year filtering and citation enrichment."
24
+ ),
25
+ inputSchema={
26
+ "type": "object",
27
+ "properties": {
28
+ "query": {
29
+ "type": "string",
30
+ "description": "Search query string (full-text search)",
31
+ },
32
+ "limit": {
33
+ "type": "integer",
34
+ "description": "Maximum number of results",
35
+ "default": 20,
36
+ },
37
+ "offset": {
38
+ "type": "integer",
39
+ "description": "Number of results to skip for pagination",
40
+ "default": 0,
41
+ },
42
+ "year_min": {
43
+ "type": "integer",
44
+ "description": "Minimum publication year filter",
45
+ },
46
+ "year_max": {
47
+ "type": "integer",
48
+ "description": "Maximum publication year filter",
49
+ },
50
+ "enrich": {
51
+ "type": "boolean",
52
+ "description": "Add citation counts and references",
53
+ "default": False,
54
+ },
55
+ },
56
+ "required": ["query"],
57
+ },
58
+ ),
59
+ types.Tool(
60
+ name="crossref_get",
61
+ description="Get a paper by DOI from CrossRef database with optional citation data.",
62
+ inputSchema={
63
+ "type": "object",
64
+ "properties": {
65
+ "doi": {
66
+ "type": "string",
67
+ "description": "DOI of the paper (e.g., '10.1038/nature12373')",
68
+ },
69
+ "include_citations": {
70
+ "type": "boolean",
71
+ "description": "Include list of DOIs that cite this paper",
72
+ "default": False,
73
+ },
74
+ "include_references": {
75
+ "type": "boolean",
76
+ "description": "Include list of DOIs this paper references",
77
+ "default": False,
78
+ },
79
+ },
80
+ "required": ["doi"],
81
+ },
82
+ ),
83
+ types.Tool(
84
+ name="crossref_count",
85
+ description="Count papers matching a search query in CrossRef database.",
86
+ inputSchema={
87
+ "type": "object",
88
+ "properties": {
89
+ "query": {
90
+ "type": "string",
91
+ "description": "Search query string",
92
+ },
93
+ },
94
+ "required": ["query"],
95
+ },
96
+ ),
97
+ types.Tool(
98
+ name="crossref_citations",
99
+ description="Get citation relationships for a paper (citing papers and/or references).",
100
+ inputSchema={
101
+ "type": "object",
102
+ "properties": {
103
+ "doi": {
104
+ "type": "string",
105
+ "description": "DOI of the paper",
106
+ },
107
+ "direction": {
108
+ "type": "string",
109
+ "description": "Citation direction: 'citing', 'cited', or 'both'",
110
+ "enum": ["citing", "cited", "both"],
111
+ "default": "citing",
112
+ },
113
+ "limit": {
114
+ "type": "integer",
115
+ "description": "Maximum results per direction",
116
+ "default": 100,
117
+ },
118
+ },
119
+ "required": ["doi"],
120
+ },
121
+ ),
122
+ types.Tool(
123
+ name="crossref_info",
124
+ description="Get CrossRef database configuration and status.",
125
+ inputSchema={
126
+ "type": "object",
127
+ "properties": {},
128
+ },
129
+ ),
130
+ ]
131
+
132
+
133
+ # EOF
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/_mcp/openalex_handlers.py
4
+ """OpenAlex-SciTeX handler implementations via openalex-local delegation.
5
+
6
+ These handlers delegate to openalex-local for fast access to 250M+ papers.
7
+ Branded as openalex-scitex to distinguish from official OpenAlex API.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ from datetime import datetime
14
+
15
+ __all__ = [
16
+ "openalex_search_handler",
17
+ "openalex_get_handler",
18
+ "openalex_count_handler",
19
+ "openalex_info_handler",
20
+ ]
21
+
22
+
23
+ def _ensure_openalex():
24
+ """Ensure openalex_scitex module is available."""
25
+ try:
26
+ from scitex.scholar import openalex_scitex
27
+
28
+ return openalex_scitex
29
+ except ImportError as e:
30
+ raise RuntimeError(
31
+ "openalex-local not installed. Install with: pip install openalex-local"
32
+ ) from e
33
+
34
+
35
+ async def openalex_search_handler(
36
+ query: str,
37
+ limit: int = 20,
38
+ offset: int = 0,
39
+ year_min: int | None = None,
40
+ year_max: int | None = None,
41
+ ) -> dict:
42
+ """Search OpenAlex database (250M+ papers) via openalex-local.
43
+
44
+ Args:
45
+ query: Search query string (full-text search)
46
+ limit: Maximum number of results (default: 20)
47
+ offset: Number of results to skip for pagination
48
+ year_min: Minimum publication year filter
49
+ year_max: Maximum publication year filter
50
+ """
51
+ try:
52
+ openalex = _ensure_openalex()
53
+ loop = asyncio.get_running_loop()
54
+
55
+ def do_search():
56
+ # Fetch more results for filtering
57
+ fetch_limit = limit * 2 if (year_min or year_max) else limit
58
+ results = openalex.search(query, limit=fetch_limit, offset=offset)
59
+
60
+ papers = []
61
+ for work in results:
62
+ # Apply year filters
63
+ if year_min and work.year and work.year < year_min:
64
+ continue
65
+ if year_max and work.year and work.year > year_max:
66
+ continue
67
+
68
+ papers.append(
69
+ {
70
+ "doi": work.doi,
71
+ "title": work.title,
72
+ "authors": work.authors[:10] if work.authors else [],
73
+ "year": work.year,
74
+ "journal": work.journal,
75
+ "abstract": (
76
+ work.abstract[:500] + "..."
77
+ if work.abstract and len(work.abstract) > 500
78
+ else work.abstract
79
+ ),
80
+ "citation_count": getattr(work, "citation_count", None),
81
+ "reference_count": getattr(work, "reference_count", None),
82
+ "type": getattr(work, "type", None),
83
+ "openalex_id": getattr(work, "openalex_id", None),
84
+ }
85
+ )
86
+ if len(papers) >= limit:
87
+ break
88
+
89
+ return papers, getattr(results, "total", len(papers))
90
+
91
+ papers, total = await loop.run_in_executor(None, do_search)
92
+
93
+ return {
94
+ "success": True,
95
+ "query": query,
96
+ "total": total,
97
+ "count": len(papers),
98
+ "offset": offset,
99
+ "limit": limit,
100
+ "papers": papers,
101
+ "source": "openalex_local",
102
+ "timestamp": datetime.now().isoformat(),
103
+ }
104
+
105
+ except Exception as e:
106
+ return {"success": False, "error": str(e)}
107
+
108
+
109
+ async def openalex_get_handler(
110
+ doi: str = None,
111
+ openalex_id: str = None,
112
+ ) -> dict:
113
+ """Get a paper by DOI or OpenAlex ID from OpenAlex database.
114
+
115
+ Args:
116
+ doi: DOI of the paper (e.g., '10.1038/nature12373')
117
+ openalex_id: OpenAlex ID (e.g., 'W2100837269')
118
+ """
119
+ if not doi and not openalex_id:
120
+ return {"success": False, "error": "Must provide either doi or openalex_id"}
121
+
122
+ try:
123
+ openalex = _ensure_openalex()
124
+ loop = asyncio.get_running_loop()
125
+
126
+ def do_get():
127
+ identifier = doi or openalex_id
128
+ work = openalex.get(identifier)
129
+ if not work:
130
+ return None
131
+
132
+ result = {
133
+ "doi": work.doi,
134
+ "title": work.title,
135
+ "authors": work.authors,
136
+ "year": work.year,
137
+ "journal": work.journal,
138
+ "abstract": work.abstract,
139
+ "citation_count": getattr(work, "citation_count", None),
140
+ "reference_count": getattr(work, "reference_count", None),
141
+ "type": getattr(work, "type", None),
142
+ "openalex_id": getattr(work, "openalex_id", None),
143
+ "publisher": getattr(work, "publisher", None),
144
+ "url": getattr(work, "url", None),
145
+ }
146
+
147
+ return result
148
+
149
+ result = await loop.run_in_executor(None, do_get)
150
+
151
+ if result is None:
152
+ identifier = doi or openalex_id
153
+ return {
154
+ "success": False,
155
+ "error": f"Paper not found: {identifier}",
156
+ "identifier": identifier,
157
+ }
158
+
159
+ return {
160
+ "success": True,
161
+ "paper": result,
162
+ "source": "openalex_local",
163
+ "timestamp": datetime.now().isoformat(),
164
+ }
165
+
166
+ except Exception as e:
167
+ return {"success": False, "error": str(e)}
168
+
169
+
170
+ async def openalex_count_handler(query: str) -> dict:
171
+ """Count papers matching a search query.
172
+
173
+ Args:
174
+ query: Search query string
175
+ """
176
+ try:
177
+ openalex = _ensure_openalex()
178
+ loop = asyncio.get_running_loop()
179
+
180
+ count = await loop.run_in_executor(None, openalex.count, query)
181
+
182
+ return {
183
+ "success": True,
184
+ "query": query,
185
+ "count": count,
186
+ "source": "openalex_local",
187
+ "timestamp": datetime.now().isoformat(),
188
+ }
189
+
190
+ except Exception as e:
191
+ return {"success": False, "error": str(e)}
192
+
193
+
194
+ async def openalex_info_handler() -> dict:
195
+ """Get information about OpenAlex database configuration and status."""
196
+ try:
197
+ openalex = _ensure_openalex()
198
+ loop = asyncio.get_running_loop()
199
+
200
+ info = await loop.run_in_executor(None, openalex.info)
201
+
202
+ return {
203
+ "success": True,
204
+ "info": info,
205
+ "timestamp": datetime.now().isoformat(),
206
+ }
207
+
208
+ except Exception as e:
209
+ return {"success": False, "error": str(e)}
210
+
211
+
212
+ # EOF
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/_mcp/openalex_tool_schemas.py
4
+ """OpenAlex-Local tool schemas for MCP server.
5
+
6
+ Provides access to 250M+ papers via openalex-local database.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import mcp.types as types
12
+
13
+ __all__ = ["get_openalex_tool_schemas"]
14
+
15
+
16
+ def get_openalex_tool_schemas() -> list[types.Tool]:
17
+ """Return OpenAlex-Local tool schemas."""
18
+ return [
19
+ types.Tool(
20
+ name="openalex_search",
21
+ description=(
22
+ "Search OpenAlex database (250M+ papers) via openalex-local. "
23
+ "Fast full-text search with year filtering. Includes citation data."
24
+ ),
25
+ inputSchema={
26
+ "type": "object",
27
+ "properties": {
28
+ "query": {
29
+ "type": "string",
30
+ "description": "Search query string (full-text search)",
31
+ },
32
+ "limit": {
33
+ "type": "integer",
34
+ "description": "Maximum number of results",
35
+ "default": 20,
36
+ },
37
+ "offset": {
38
+ "type": "integer",
39
+ "description": "Number of results to skip for pagination",
40
+ "default": 0,
41
+ },
42
+ "year_min": {
43
+ "type": "integer",
44
+ "description": "Minimum publication year filter",
45
+ },
46
+ "year_max": {
47
+ "type": "integer",
48
+ "description": "Maximum publication year filter",
49
+ },
50
+ },
51
+ "required": ["query"],
52
+ },
53
+ ),
54
+ types.Tool(
55
+ name="openalex_get",
56
+ description="Get a paper by DOI or OpenAlex ID from OpenAlex database.",
57
+ inputSchema={
58
+ "type": "object",
59
+ "properties": {
60
+ "doi": {
61
+ "type": "string",
62
+ "description": "DOI of the paper (e.g., '10.1038/nature12373')",
63
+ },
64
+ "openalex_id": {
65
+ "type": "string",
66
+ "description": "OpenAlex ID (e.g., 'W2100837269')",
67
+ },
68
+ },
69
+ },
70
+ ),
71
+ types.Tool(
72
+ name="openalex_count",
73
+ description="Count papers matching a search query in OpenAlex database.",
74
+ inputSchema={
75
+ "type": "object",
76
+ "properties": {
77
+ "query": {
78
+ "type": "string",
79
+ "description": "Search query string",
80
+ },
81
+ },
82
+ "required": ["query"],
83
+ },
84
+ ),
85
+ types.Tool(
86
+ name="openalex_info",
87
+ description="Get OpenAlex database configuration and status.",
88
+ inputSchema={
89
+ "type": "object",
90
+ "properties": {},
91
+ },
92
+ ),
93
+ ]
94
+
95
+
96
+ # EOF
@@ -3,9 +3,14 @@
3
3
  # File: src/scitex/scholar/_mcp.tool_schemas.py
4
4
  # ----------------------------------------
5
5
  """Tool schemas for the scitex-scholar MCP server."""
6
+
6
7
  from __future__ import annotations
8
+
7
9
  import mcp.types as types
10
+
8
11
  __all__ = ["get_tool_schemas"]
12
+
13
+
9
14
  def get_tool_schemas() -> list[types.Tool]:
10
15
  """Return all tool schemas for the Scholar MCP server."""
11
16
  base_schemas = [
@@ -500,6 +505,16 @@ def get_tool_schemas() -> list[types.Tool]:
500
505
  },
501
506
  ),
502
507
  ]
508
+ from .crossref_tool_schemas import get_crossref_tool_schemas
503
509
  from .job_tool_schemas import get_job_tool_schemas
504
- return base_schemas + get_job_tool_schemas()
510
+ from .openalex_tool_schemas import get_openalex_tool_schemas
511
+
512
+ return (
513
+ base_schemas
514
+ + get_job_tool_schemas()
515
+ + get_crossref_tool_schemas()
516
+ + get_openalex_tool_schemas()
517
+ )
518
+
519
+
505
520
  # EOF
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/local_dbs/__init__.py
4
+ """Local database integrations for scitex.scholar.
5
+
6
+ This package provides access to local scholarly databases:
7
+ - crossref_scitex: CrossRef database (167M+ papers via crossref-local)
8
+ - openalex_scitex: OpenAlex database (284M+ works via openalex-local)
9
+
10
+ Both modules delegate directly to their respective external packages
11
+ without any additional logic.
12
+
13
+ Usage:
14
+ >>> from scitex.scholar.local_dbs import crossref_scitex
15
+ >>> results = crossref_scitex.search("machine learning")
16
+
17
+ >>> from scitex.scholar.local_dbs import openalex_scitex
18
+ >>> results = openalex_scitex.search("neural networks")
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from . import crossref_scitex, openalex_scitex
24
+
25
+ __all__ = [
26
+ "crossref_scitex",
27
+ "openalex_scitex",
28
+ ]
29
+
30
+
31
+ # EOF
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/local_dbs/crossref_scitex.py
4
+ """CrossRef-SciTeX: Minimal API for crossref-local.
5
+
6
+ Usage:
7
+ >>> from scitex.scholar.local_dbs import crossref_scitex
8
+ >>> results = crossref_scitex.search("machine learning")
9
+ >>> work = crossref_scitex.get("10.1038/nature12373")
10
+ """
11
+
12
+ try:
13
+ from crossref_local import (
14
+ SearchResult,
15
+ # Classes
16
+ Work,
17
+ count,
18
+ get,
19
+ info,
20
+ # Core functions
21
+ search,
22
+ )
23
+ except ImportError as e:
24
+ raise ImportError(
25
+ "crossref-local not installed. Install with: pip install crossref-local"
26
+ ) from e
27
+
28
+ __all__ = ["search", "get", "count", "info", "Work", "SearchResult"]
29
+
30
+ # EOF
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-29
3
+ # File: src/scitex/scholar/local_dbs/openalex_scitex.py
4
+ """OpenAlex-SciTeX: Minimal API for openalex-local.
5
+
6
+ Usage:
7
+ >>> from scitex.scholar.local_dbs import openalex_scitex
8
+ >>> results = openalex_scitex.search("machine learning")
9
+ >>> work = openalex_scitex.get("10.1038/nature12373")
10
+ """
11
+
12
+ try:
13
+ from openalex_local import (
14
+ SearchResult,
15
+ # Classes
16
+ Work,
17
+ count,
18
+ get,
19
+ info,
20
+ # Core functions
21
+ search,
22
+ )
23
+ except ImportError as e:
24
+ raise ImportError(
25
+ "openalex-local not installed. Install with: pip install openalex-local"
26
+ ) from e
27
+
28
+ __all__ = ["search", "get", "count", "info", "Work", "SearchResult"]
29
+
30
+ # EOF
@@ -3,8 +3,17 @@
3
3
  # File: src/scitex/scholar/mcp_server.py
4
4
  # ----------------------------------------
5
5
 
6
- """
7
- MCP Server for SciTeX Scholar - Scientific Literature Management
6
+ """MCP Server for SciTeX Scholar - Scientific Literature Management.
7
+
8
+ .. deprecated::
9
+ This standalone server is deprecated. Use the unified scitex MCP server instead:
10
+
11
+ CLI: scitex serve
12
+ Python: from scitex.mcp_server import run_server
13
+
14
+ The unified server includes all scholar tools plus other scitex tools.
15
+ Scholar tools are prefixed with 'scholar_' (e.g., scholar_search_papers).
16
+ Scholar resources are available at scholar://library and scholar://bibtex.
8
17
 
9
18
  Provides tools for:
10
19
  - Searching papers across multiple databases
@@ -16,6 +25,15 @@ Provides tools for:
16
25
 
17
26
  from __future__ import annotations
18
27
 
28
+ import warnings
29
+
30
+ warnings.warn(
31
+ "scitex.scholar.mcp_server is deprecated. Use 'scitex serve' or "
32
+ "'from scitex.mcp_server import run_server' for the unified MCP server.",
33
+ DeprecationWarning,
34
+ stacklevel=2,
35
+ )
36
+
19
37
  import asyncio
20
38
  import os
21
39
  from datetime import datetime
@@ -67,11 +85,18 @@ class ScholarServer:
67
85
 
68
86
  self._scholar_instance = Scholar()
69
87
  except ImportError as e:
70
- raise RuntimeError(f"Scholar module not available: {e}")
88
+ raise RuntimeError(f"Scholar module not available: {e}") from e
71
89
  return self._scholar_instance
72
90
 
73
91
  def setup_handlers(self):
74
92
  """Set up MCP server handlers."""
93
+ from ._mcp.crossref_handlers import (
94
+ crossref_citations_handler,
95
+ crossref_count_handler,
96
+ crossref_get_handler,
97
+ crossref_info_handler,
98
+ crossref_search_handler,
99
+ )
75
100
  from ._mcp.handlers import (
76
101
  add_papers_to_project_handler,
77
102
  authenticate_handler,
@@ -186,6 +211,36 @@ class ScholarServer:
186
211
  elif name == "get_job_result":
187
212
  return await self._wrap_result(get_job_result_handler(**arguments))
188
213
 
214
+ # CrossRef-Local Tools
215
+ elif name == "crossref_search":
216
+ return await self._wrap_result(crossref_search_handler(**arguments))
217
+ elif name == "crossref_get":
218
+ return await self._wrap_result(crossref_get_handler(**arguments))
219
+ elif name == "crossref_count":
220
+ return await self._wrap_result(crossref_count_handler(**arguments))
221
+ elif name == "crossref_citations":
222
+ return await self._wrap_result(crossref_citations_handler(**arguments))
223
+ elif name == "crossref_info":
224
+ return await self._wrap_result(crossref_info_handler(**arguments))
225
+
226
+ # OpenAlex-Local Tools
227
+ elif name == "openalex_search":
228
+ from ._mcp.openalex_handlers import openalex_search_handler
229
+
230
+ return await self._wrap_result(openalex_search_handler(**arguments))
231
+ elif name == "openalex_get":
232
+ from ._mcp.openalex_handlers import openalex_get_handler
233
+
234
+ return await self._wrap_result(openalex_get_handler(**arguments))
235
+ elif name == "openalex_count":
236
+ from ._mcp.openalex_handlers import openalex_count_handler
237
+
238
+ return await self._wrap_result(openalex_count_handler(**arguments))
239
+ elif name == "openalex_info":
240
+ from ._mcp.openalex_handlers import openalex_info_handler
241
+
242
+ return await self._wrap_result(openalex_info_handler(**arguments))
243
+
189
244
  else:
190
245
  raise ValueError(f"Unknown tool: {name}")
191
246
 
@@ -340,7 +395,7 @@ async def _run_server():
340
395
 
341
396
 
342
397
  def main():
343
- """Main entry point for the MCP server."""
398
+ """Run the MCP server."""
344
399
  if not MCP_AVAILABLE:
345
400
  import sys
346
401