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.
- scitex/_mcp_resources/__init__.py +2 -0
- scitex/_mcp_resources/_scholar.py +148 -0
- scitex/_mcp_tools/canvas.py +49 -15
- scitex/_mcp_tools/scholar.py +50 -99
- scitex/canvas/__init__.py +169 -287
- scitex/canvas/_legacy.py +171 -0
- 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.5.dist-info}/METADATA +1 -1
- {scitex-2.15.3.dist-info → scitex-2.15.5.dist-info}/RECORD +28 -20
- scitex/scholar/crossref_scitex.py +0 -367
- {scitex-2.15.3.dist-info → scitex-2.15.5.dist-info}/WHEEL +0 -0
- {scitex-2.15.3.dist-info → scitex-2.15.5.dist-info}/entry_points.txt +0 -0
- {scitex-2.15.3.dist-info → scitex-2.15.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
|
|
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
|
scitex/scholar/mcp_server.py
CHANGED
|
@@ -3,8 +3,17 @@
|
|
|
3
3
|
# File: src/scitex/scholar/mcp_server.py
|
|
4
4
|
# ----------------------------------------
|
|
5
5
|
|
|
6
|
-
"""
|
|
7
|
-
|
|
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
|
-
"""
|
|
398
|
+
"""Run the MCP server."""
|
|
344
399
|
if not MCP_AVAILABLE:
|
|
345
400
|
import sys
|
|
346
401
|
|