crossref-local 0.3.1__py3-none-any.whl → 0.4.0__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.
- crossref_local/__init__.py +23 -9
- crossref_local/__main__.py +0 -0
- crossref_local/aio.py +0 -0
- crossref_local/api.py +104 -29
- crossref_local/cache.py +466 -0
- crossref_local/cache_export.py +83 -0
- crossref_local/cache_viz.py +296 -0
- crossref_local/citations.py +0 -0
- crossref_local/cli.py +205 -137
- crossref_local/cli_cache.py +179 -0
- crossref_local/cli_completion.py +245 -0
- crossref_local/cli_main.py +20 -0
- crossref_local/cli_mcp.py +275 -0
- crossref_local/config.py +21 -24
- crossref_local/db.py +0 -0
- crossref_local/fts.py +0 -0
- crossref_local/impact_factor/__init__.py +0 -0
- crossref_local/impact_factor/calculator.py +0 -0
- crossref_local/impact_factor/journal_lookup.py +0 -0
- crossref_local/mcp_server.py +262 -51
- crossref_local/models.py +0 -0
- crossref_local/remote.py +5 -0
- crossref_local/server.py +7 -7
- {crossref_local-0.3.1.dist-info → crossref_local-0.4.0.dist-info}/METADATA +63 -24
- crossref_local-0.4.0.dist-info/RECORD +27 -0
- {crossref_local-0.3.1.dist-info → crossref_local-0.4.0.dist-info}/entry_points.txt +1 -1
- crossref_local-0.3.1.dist-info/RECORD +0 -20
- {crossref_local-0.3.1.dist-info → crossref_local-0.4.0.dist-info}/WHEEL +0 -0
crossref_local/mcp_server.py
CHANGED
|
@@ -10,24 +10,27 @@ Usage:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
from typing import Optional
|
|
14
13
|
|
|
15
14
|
from fastmcp import FastMCP
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
from .
|
|
16
|
+
|
|
17
|
+
from . import (
|
|
18
|
+
get as _get,
|
|
19
|
+
info as _info,
|
|
20
|
+
search as _search,
|
|
21
|
+
)
|
|
19
22
|
|
|
20
23
|
# Initialize MCP server
|
|
21
24
|
mcp = FastMCP(
|
|
22
25
|
name="crossref-local",
|
|
23
26
|
instructions="Local CrossRef database with 167M+ works and full-text search. "
|
|
24
|
-
"Use
|
|
25
|
-
"
|
|
27
|
+
"Use search to find papers, search_by_doi for DOI lookup, enrich_dois to add "
|
|
28
|
+
"citation counts and references, and status for stats.",
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
@mcp.tool()
|
|
30
|
-
def
|
|
33
|
+
def search(
|
|
31
34
|
query: str,
|
|
32
35
|
limit: int = 10,
|
|
33
36
|
offset: int = 0,
|
|
@@ -48,11 +51,11 @@ def search_works(
|
|
|
48
51
|
JSON string with search results including total count and matching works.
|
|
49
52
|
|
|
50
53
|
Examples:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
search("machine learning")
|
|
55
|
+
search("CRISPR", limit=20)
|
|
56
|
+
search("neural network AND memory", with_abstracts=True)
|
|
54
57
|
"""
|
|
55
|
-
results =
|
|
58
|
+
results = _search(query, limit=min(limit, 100), offset=offset)
|
|
56
59
|
|
|
57
60
|
works_data = []
|
|
58
61
|
for work in results.works:
|
|
@@ -80,7 +83,7 @@ def search_works(
|
|
|
80
83
|
|
|
81
84
|
|
|
82
85
|
@mcp.tool()
|
|
83
|
-
def
|
|
86
|
+
def search_by_doi(doi: str, as_citation: bool = False) -> str:
|
|
84
87
|
"""Get detailed information about a work by DOI.
|
|
85
88
|
|
|
86
89
|
Args:
|
|
@@ -91,10 +94,10 @@ def get_work(doi: str, as_citation: bool = False) -> str:
|
|
|
91
94
|
JSON string with work metadata, or formatted citation string.
|
|
92
95
|
|
|
93
96
|
Examples:
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
search_by_doi("10.1038/nature12373")
|
|
98
|
+
search_by_doi("10.1126/science.aax0758", as_citation=True)
|
|
96
99
|
"""
|
|
97
|
-
work =
|
|
100
|
+
work = _get(doi)
|
|
98
101
|
|
|
99
102
|
if work is None:
|
|
100
103
|
return json.dumps({"error": f"DOI not found: {doi}"})
|
|
@@ -106,69 +109,277 @@ def get_work(doi: str, as_citation: bool = False) -> str:
|
|
|
106
109
|
|
|
107
110
|
|
|
108
111
|
@mcp.tool()
|
|
109
|
-
def
|
|
110
|
-
"""
|
|
112
|
+
def status() -> str:
|
|
113
|
+
"""Get database statistics and status.
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
Returns:
|
|
116
|
+
JSON string with database path, work count, FTS index count, and citation count.
|
|
117
|
+
"""
|
|
118
|
+
db_info = _info()
|
|
119
|
+
return json.dumps(db_info, indent=2)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@mcp.tool()
|
|
123
|
+
def enrich_dois(dois: list[str]) -> str:
|
|
124
|
+
"""Enrich DOIs with full metadata including citation counts and references.
|
|
125
|
+
|
|
126
|
+
Use this after search() to get detailed metadata for papers.
|
|
127
|
+
The search() tool returns basic info (title, authors, year, journal).
|
|
128
|
+
This tool adds: citation_count, references, volume, issue, publisher, etc.
|
|
129
|
+
|
|
130
|
+
Typical workflow:
|
|
131
|
+
1. search("epilepsy seizure prediction") -> get DOIs
|
|
132
|
+
2. enrich_dois([doi1, doi2, ...]) -> get full metadata
|
|
113
133
|
|
|
114
134
|
Args:
|
|
115
|
-
|
|
135
|
+
dois: List of DOIs to enrich (e.g., ["10.1038/nature12373", "10.1126/science.aax0758"])
|
|
116
136
|
|
|
117
137
|
Returns:
|
|
118
|
-
JSON string with
|
|
138
|
+
JSON string with enriched works including citation_count and references.
|
|
119
139
|
|
|
120
140
|
Examples:
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
enrich_dois(["10.1038/nature12373"])
|
|
142
|
+
enrich_dois(["10.1038/s41467-017-02577-y", "10.1093/brain/aww019"])
|
|
123
143
|
"""
|
|
124
|
-
|
|
125
|
-
|
|
144
|
+
from . import get_many as _get_many
|
|
145
|
+
|
|
146
|
+
works = _get_many(dois)
|
|
147
|
+
|
|
148
|
+
works_data = []
|
|
149
|
+
for work in works:
|
|
150
|
+
works_data.append(work.to_dict())
|
|
151
|
+
|
|
152
|
+
return json.dumps(
|
|
153
|
+
{
|
|
154
|
+
"requested": len(dois),
|
|
155
|
+
"found": len(works_data),
|
|
156
|
+
"works": works_data,
|
|
157
|
+
},
|
|
158
|
+
indent=2,
|
|
159
|
+
)
|
|
126
160
|
|
|
127
161
|
|
|
128
162
|
@mcp.tool()
|
|
129
|
-
def
|
|
130
|
-
|
|
163
|
+
def cache_create(
|
|
164
|
+
name: str,
|
|
165
|
+
query: str,
|
|
166
|
+
limit: int = 1000,
|
|
167
|
+
) -> str:
|
|
168
|
+
"""Create a paper cache from search query.
|
|
169
|
+
|
|
170
|
+
Fetches full metadata for papers matching query and saves to disk cache.
|
|
171
|
+
Use this to build a reusable paper collection for a research topic.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
name: Cache name (e.g., "epilepsy", "alzheimers")
|
|
175
|
+
query: FTS search query
|
|
176
|
+
limit: Max papers to cache (default: 1000)
|
|
131
177
|
|
|
132
178
|
Returns:
|
|
133
|
-
JSON
|
|
179
|
+
JSON with cache info (path, paper count, size)
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
cache_create("epilepsy", "epilepsy seizure prediction", limit=500)
|
|
134
183
|
"""
|
|
135
|
-
|
|
136
|
-
|
|
184
|
+
from . import cache
|
|
185
|
+
|
|
186
|
+
info = cache.create(name, query=query, limit=limit)
|
|
187
|
+
return json.dumps(info.to_dict(), indent=2)
|
|
137
188
|
|
|
138
189
|
|
|
139
190
|
@mcp.tool()
|
|
140
|
-
def
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
191
|
+
def cache_query(
|
|
192
|
+
name: str,
|
|
193
|
+
fields: list[str] | None = None,
|
|
194
|
+
include_abstract: bool = False,
|
|
195
|
+
include_references: bool = False,
|
|
196
|
+
include_citations: bool = False,
|
|
197
|
+
year_min: int | None = None,
|
|
198
|
+
year_max: int | None = None,
|
|
199
|
+
journal: str | None = None,
|
|
200
|
+
limit: int | None = None,
|
|
144
201
|
) -> str:
|
|
145
|
-
"""
|
|
202
|
+
"""Query cached papers with field filtering.
|
|
146
203
|
|
|
147
|
-
|
|
204
|
+
Returns minimal data to reduce context usage. Specify only fields needed.
|
|
148
205
|
|
|
149
206
|
Args:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
207
|
+
name: Cache name
|
|
208
|
+
fields: Explicit field list (e.g., ["doi", "title", "year"])
|
|
209
|
+
include_abstract: Include abstract (default: False)
|
|
210
|
+
include_references: Include references list (default: False)
|
|
211
|
+
include_citations: Include citation_count (default: False)
|
|
212
|
+
year_min: Filter by minimum year
|
|
213
|
+
year_max: Filter by maximum year
|
|
214
|
+
journal: Filter by journal name (substring match)
|
|
215
|
+
limit: Max results to return
|
|
153
216
|
|
|
154
217
|
Returns:
|
|
155
|
-
JSON
|
|
218
|
+
JSON array of filtered papers
|
|
156
219
|
|
|
157
220
|
Examples:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
221
|
+
cache_query("epilepsy", fields=["doi", "title", "year"])
|
|
222
|
+
cache_query("epilepsy", year_min=2020, include_citations=True, limit=50)
|
|
223
|
+
"""
|
|
224
|
+
from . import cache
|
|
225
|
+
|
|
226
|
+
papers = cache.query(
|
|
227
|
+
name,
|
|
228
|
+
fields=fields,
|
|
229
|
+
include_abstract=include_abstract,
|
|
230
|
+
include_references=include_references,
|
|
231
|
+
include_citations=include_citations,
|
|
232
|
+
year_min=year_min,
|
|
233
|
+
year_max=year_max,
|
|
234
|
+
journal=journal,
|
|
235
|
+
limit=limit,
|
|
236
|
+
)
|
|
237
|
+
return json.dumps({"count": len(papers), "papers": papers}, indent=2)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@mcp.tool()
|
|
241
|
+
def cache_stats(name: str) -> str:
|
|
242
|
+
"""Get cache statistics.
|
|
243
|
+
|
|
244
|
+
Returns year distribution, top journals, citation stats without loading full data.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
name: Cache name
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
JSON with statistics (paper_count, year_range, top_journals, etc.)
|
|
251
|
+
"""
|
|
252
|
+
from . import cache
|
|
253
|
+
|
|
254
|
+
stats = cache.stats(name)
|
|
255
|
+
return json.dumps(stats, indent=2)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@mcp.tool()
|
|
259
|
+
def cache_list() -> str:
|
|
260
|
+
"""List all available caches.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
JSON array of cache info (name, path, paper_count, size)
|
|
264
|
+
"""
|
|
265
|
+
from . import cache
|
|
266
|
+
|
|
267
|
+
caches = cache.list_caches()
|
|
268
|
+
return json.dumps([c.to_dict() for c in caches], indent=2)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@mcp.tool()
|
|
272
|
+
def cache_top_cited(
|
|
273
|
+
name: str,
|
|
274
|
+
n: int = 20,
|
|
275
|
+
year_min: int | None = None,
|
|
276
|
+
year_max: int | None = None,
|
|
277
|
+
) -> str:
|
|
278
|
+
"""Get top cited papers from cache.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
name: Cache name
|
|
282
|
+
n: Number of papers to return
|
|
283
|
+
year_min: Filter by minimum year
|
|
284
|
+
year_max: Filter by maximum year
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
JSON array of top cited papers
|
|
288
|
+
"""
|
|
289
|
+
from .cache_viz import get_top_cited
|
|
290
|
+
|
|
291
|
+
papers = get_top_cited(name, n=n, year_min=year_min, year_max=year_max)
|
|
292
|
+
return json.dumps(papers, indent=2)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@mcp.tool()
|
|
296
|
+
def cache_citation_summary(name: str) -> str:
|
|
297
|
+
"""Get citation statistics for cached papers.
|
|
298
|
+
|
|
299
|
+
Returns mean, median, max citations and counts of highly cited papers.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
name: Cache name
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
JSON with citation statistics
|
|
161
306
|
"""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
307
|
+
from .cache_viz import get_citation_summary
|
|
308
|
+
|
|
309
|
+
summary = get_citation_summary(name)
|
|
310
|
+
return json.dumps(summary, indent=2)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@mcp.tool()
|
|
314
|
+
def cache_plot_scatter(
|
|
315
|
+
name: str,
|
|
316
|
+
output: str,
|
|
317
|
+
top_n: int = 10,
|
|
318
|
+
) -> str:
|
|
319
|
+
"""Generate year vs citations scatter plot.
|
|
320
|
+
|
|
321
|
+
Saves plot to file and returns top cited papers.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
name: Cache name
|
|
325
|
+
output: Output file path (png/pdf/svg)
|
|
326
|
+
top_n: Number of top papers to label on plot
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
JSON with output path and top papers list
|
|
330
|
+
"""
|
|
331
|
+
from .cache_viz import plot_year_citations
|
|
332
|
+
|
|
333
|
+
result = plot_year_citations(name, output=output, top_n=top_n)
|
|
334
|
+
return json.dumps(result, indent=2)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@mcp.tool()
|
|
338
|
+
def cache_plot_network(
|
|
339
|
+
name: str,
|
|
340
|
+
output: str,
|
|
341
|
+
max_nodes: int = 100,
|
|
342
|
+
) -> str:
|
|
343
|
+
"""Generate citation network visualization.
|
|
344
|
+
|
|
345
|
+
Creates interactive HTML graph showing citation relationships.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
name: Cache name
|
|
349
|
+
output: Output HTML file path
|
|
350
|
+
max_nodes: Maximum papers to include
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
JSON with network stats
|
|
354
|
+
"""
|
|
355
|
+
from .cache_viz import plot_citation_network
|
|
356
|
+
|
|
357
|
+
result = plot_citation_network(name, output=output, max_nodes=max_nodes)
|
|
358
|
+
return json.dumps(result, indent=2)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@mcp.tool()
|
|
362
|
+
def cache_export(
|
|
363
|
+
name: str,
|
|
364
|
+
output_path: str,
|
|
365
|
+
format: str = "json",
|
|
366
|
+
fields: list[str] | None = None,
|
|
367
|
+
) -> str:
|
|
368
|
+
"""Export cache to file.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
name: Cache name
|
|
372
|
+
output_path: Output file path
|
|
373
|
+
format: Export format (json, csv, bibtex, dois)
|
|
374
|
+
fields: Fields to include (for json/csv)
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
JSON with output path
|
|
378
|
+
"""
|
|
379
|
+
from . import cache
|
|
380
|
+
|
|
381
|
+
path = cache.export(name, output_path, format=format, fields=fields)
|
|
382
|
+
return json.dumps({"exported": path, "format": format})
|
|
172
383
|
|
|
173
384
|
|
|
174
385
|
def run_server(
|
crossref_local/models.py
CHANGED
|
File without changes
|
crossref_local/remote.py
CHANGED
|
@@ -199,6 +199,11 @@ class RemoteClient:
|
|
|
199
199
|
authors=item.get("authors", []),
|
|
200
200
|
year=item.get("year"),
|
|
201
201
|
journal=item.get("journal"),
|
|
202
|
+
volume=item.get("volume"),
|
|
203
|
+
issue=item.get("issue"),
|
|
204
|
+
page=item.get("page"),
|
|
205
|
+
abstract=item.get("abstract"),
|
|
206
|
+
citation_count=item.get("citation_count"),
|
|
202
207
|
)
|
|
203
208
|
works.append(work)
|
|
204
209
|
return works
|
crossref_local/server.py
CHANGED
|
@@ -4,11 +4,11 @@ This server provides proper full-text search using FTS5 index,
|
|
|
4
4
|
unlike the Django API which only scans a limited subset.
|
|
5
5
|
|
|
6
6
|
Usage:
|
|
7
|
-
crossref-local api # Run on default port
|
|
7
|
+
crossref-local api # Run on default port 8333
|
|
8
8
|
crossref-local api --port 8080 # Custom port
|
|
9
9
|
|
|
10
10
|
# Or directly:
|
|
11
|
-
uvicorn crossref_local.server:app --host 0.0.0.0 --port
|
|
11
|
+
uvicorn crossref_local.server:app --host 0.0.0.0 --port 8333
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import time
|
|
@@ -18,14 +18,14 @@ from fastapi import FastAPI, Query, HTTPException
|
|
|
18
18
|
from fastapi.middleware.cors import CORSMiddleware
|
|
19
19
|
from pydantic import BaseModel
|
|
20
20
|
|
|
21
|
-
from . import fts
|
|
21
|
+
from . import fts, __version__
|
|
22
22
|
from .db import get_db
|
|
23
23
|
from .models import Work
|
|
24
24
|
|
|
25
25
|
app = FastAPI(
|
|
26
26
|
title="CrossRef Local API",
|
|
27
27
|
description="Fast full-text search across 167M+ scholarly works",
|
|
28
|
-
version=
|
|
28
|
+
version=__version__,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
# CORS middleware
|
|
@@ -61,7 +61,7 @@ class SearchResponse(BaseModel):
|
|
|
61
61
|
|
|
62
62
|
class InfoResponse(BaseModel):
|
|
63
63
|
name: str = "CrossRef Local API"
|
|
64
|
-
version: str =
|
|
64
|
+
version: str = __version__
|
|
65
65
|
status: str = "running"
|
|
66
66
|
mode: str = "local"
|
|
67
67
|
total_papers: int
|
|
@@ -75,7 +75,7 @@ def root():
|
|
|
75
75
|
"""API root with endpoint information."""
|
|
76
76
|
return {
|
|
77
77
|
"name": "CrossRef Local API",
|
|
78
|
-
"version":
|
|
78
|
+
"version": __version__,
|
|
79
79
|
"status": "running",
|
|
80
80
|
"endpoints": {
|
|
81
81
|
"health": "/health",
|
|
@@ -341,7 +341,7 @@ def api_stats_compat():
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
|
|
344
|
-
def run_server(host: str = "0.0.0.0", port: int =
|
|
344
|
+
def run_server(host: str = "0.0.0.0", port: int = 8333):
|
|
345
345
|
"""Run the FastAPI server."""
|
|
346
346
|
import uvicorn
|
|
347
347
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crossref-local
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Local CrossRef database with 167M+ works and full-text search
|
|
5
5
|
Project-URL: Homepage, https://github.com/ywatanabe1989/crossref_local
|
|
6
6
|
Project-URL: Repository, https://github.com/ywatanabe1989/crossref_local
|
|
@@ -43,6 +43,12 @@ Requires-Dist: networkx>=3.0; extra == 'viz'
|
|
|
43
43
|
Requires-Dist: pyvis>=0.3; extra == 'viz'
|
|
44
44
|
Description-Content-Type: text/markdown
|
|
45
45
|
|
|
46
|
+
<!-- ---
|
|
47
|
+
!-- Timestamp: 2026-01-16 19:15:51
|
|
48
|
+
!-- Author: ywatanabe
|
|
49
|
+
!-- File: /home/ywatanabe/proj/crossref-local/README.md
|
|
50
|
+
!-- --- -->
|
|
51
|
+
|
|
46
52
|
# CrossRef Local
|
|
47
53
|
|
|
48
54
|
Local CrossRef database with 167M+ scholarly works, full-text search, and impact factor calculation.
|
|
@@ -55,6 +61,22 @@ Local CrossRef database with 167M+ scholarly works, full-text search, and impact
|
|
|
55
61
|
<img src="examples/readme_figure.png" alt="CrossRef Local Demo" width="800"/>
|
|
56
62
|
</p>
|
|
57
63
|
|
|
64
|
+
<details>
|
|
65
|
+
<summary><strong>MCP Demo Video</strong></summary>
|
|
66
|
+
|
|
67
|
+
<p align="center">
|
|
68
|
+
<a href="https://scitex.ai/media/videos/crossref-local-v0.3.1-demo.mp4">
|
|
69
|
+
<img src="examples/demo_mcp_out/crossref-local-v0.3.1-demo-thumbnail_6m55s.png" alt="Demo Video Thumbnail" width="600"/>
|
|
70
|
+
</a>
|
|
71
|
+
</p>
|
|
72
|
+
|
|
73
|
+
Live demonstration of MCP server integration with Claude Code for `epilepsy seizure prediction` literature review:
|
|
74
|
+
- Full-text search on title, abstracts, and keywords across 167M papers (22ms response)
|
|
75
|
+
|
|
76
|
+
📄 [Full demo documentation](examples/demo_mcp.org) | 📊 [Generated diagrams](examples/demo_mcp_out/)
|
|
77
|
+
|
|
78
|
+
</details>
|
|
79
|
+
|
|
58
80
|
<details>
|
|
59
81
|
<summary><strong>Why CrossRef Local?</strong></summary>
|
|
60
82
|
|
|
@@ -135,9 +157,8 @@ async def main():
|
|
|
135
157
|
|
|
136
158
|
```bash
|
|
137
159
|
crossref-local search "CRISPR genome editing" -n 5
|
|
138
|
-
crossref-local
|
|
139
|
-
crossref-local
|
|
140
|
-
crossref-local info # Database stats
|
|
160
|
+
crossref-local search-by-doi 10.1038/nature12373
|
|
161
|
+
crossref-local status # Configuration and database stats
|
|
141
162
|
```
|
|
142
163
|
|
|
143
164
|
With abstracts (`-a` flag):
|
|
@@ -160,53 +181,56 @@ Found 4 matches in 128.4ms
|
|
|
160
181
|
|
|
161
182
|
Start the FastAPI server:
|
|
162
183
|
```bash
|
|
163
|
-
crossref-local
|
|
184
|
+
crossref-local run-server-http --host 0.0.0.0 --port 8333
|
|
164
185
|
```
|
|
165
186
|
|
|
166
187
|
Endpoints:
|
|
167
188
|
```bash
|
|
168
189
|
# Search works (FTS5)
|
|
169
|
-
curl "http://localhost:
|
|
190
|
+
curl "http://localhost:8333/works?q=CRISPR&limit=10"
|
|
170
191
|
|
|
171
192
|
# Get by DOI
|
|
172
|
-
curl "http://localhost:
|
|
193
|
+
curl "http://localhost:8333/works/10.1038/nature12373"
|
|
173
194
|
|
|
174
195
|
# Batch DOI lookup
|
|
175
|
-
curl -X POST "http://localhost:
|
|
196
|
+
curl -X POST "http://localhost:8333/works/batch" \
|
|
176
197
|
-H "Content-Type: application/json" \
|
|
177
198
|
-d '{"dois": ["10.1038/nature12373", "10.1126/science.aax0758"]}'
|
|
178
199
|
|
|
179
200
|
# Database info
|
|
180
|
-
curl "http://localhost:
|
|
201
|
+
curl "http://localhost:8333/info"
|
|
181
202
|
```
|
|
182
203
|
|
|
183
|
-
|
|
204
|
+
HTTP mode (connect to running server):
|
|
184
205
|
```bash
|
|
185
|
-
# On local machine
|
|
186
|
-
ssh -L
|
|
206
|
+
# On local machine (if server is remote)
|
|
207
|
+
ssh -L 8333:127.0.0.1:8333 your-server
|
|
187
208
|
|
|
188
209
|
# Python client
|
|
189
|
-
from crossref_local import
|
|
190
|
-
|
|
210
|
+
from crossref_local import configure_http
|
|
211
|
+
configure_http("http://localhost:8333")
|
|
212
|
+
|
|
213
|
+
# Or via CLI
|
|
214
|
+
crossref-local --http search "CRISPR"
|
|
191
215
|
```
|
|
192
216
|
|
|
193
217
|
</details>
|
|
194
218
|
|
|
195
219
|
<details>
|
|
196
|
-
<summary><strong>MCP Server
|
|
220
|
+
<summary><strong>MCP Server</strong></summary>
|
|
197
221
|
|
|
198
|
-
Run as MCP
|
|
222
|
+
Run as MCP (Model Context Protocol) server:
|
|
199
223
|
```bash
|
|
200
|
-
crossref-local
|
|
224
|
+
crossref-local run-server-mcp
|
|
201
225
|
```
|
|
202
226
|
|
|
203
|
-
|
|
227
|
+
Local MCP client configuration:
|
|
204
228
|
```json
|
|
205
229
|
{
|
|
206
230
|
"mcpServers": {
|
|
207
231
|
"crossref-local": {
|
|
208
232
|
"command": "crossref-local",
|
|
209
|
-
"args": ["
|
|
233
|
+
"args": ["run-server-mcp"],
|
|
210
234
|
"env": {
|
|
211
235
|
"CROSSREF_LOCAL_DB": "/path/to/crossref.db"
|
|
212
236
|
}
|
|
@@ -215,12 +239,27 @@ Add to Claude Desktop config (`~/.config/claude/claude_desktop_config.json`):
|
|
|
215
239
|
}
|
|
216
240
|
```
|
|
217
241
|
|
|
242
|
+
Remote MCP via HTTP (recommended):
|
|
243
|
+
```bash
|
|
244
|
+
# On server: start persistent MCP server
|
|
245
|
+
crossref-local run-server-mcp -t http --host 0.0.0.0 --port 8082
|
|
246
|
+
```
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"mcpServers": {
|
|
250
|
+
"crossref-remote": {
|
|
251
|
+
"url": "http://your-server:8082/mcp"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
See [docs/remote-deployment.md](docs/remote-deployment.md) for systemd and Docker setup.
|
|
258
|
+
|
|
218
259
|
Available tools:
|
|
219
|
-
- `
|
|
220
|
-
- `
|
|
221
|
-
- `
|
|
222
|
-
- `database_info` - Database statistics
|
|
223
|
-
- `calculate_impact_factor` - Journal impact factor
|
|
260
|
+
- `search` - Full-text search across 167M+ papers
|
|
261
|
+
- `search_by_doi` - Get paper by DOI
|
|
262
|
+
- `status` - Database statistics
|
|
224
263
|
|
|
225
264
|
</details>
|
|
226
265
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
crossref_local/__init__.py,sha256=eCvA9q-nZSWCIoMZxINV5xsboXovv4YeqZfjfLzi820,3696
|
|
2
|
+
crossref_local/__main__.py,sha256=N1c1ESGgJkAwsSWXANUgmzxC1OJEIqw-cl9m4pmNP7s,110
|
|
3
|
+
crossref_local/aio.py,sha256=En2btSn3euRbEYav1919gsmdC8iQaMbgGUso-IThCwo,5490
|
|
4
|
+
crossref_local/api.py,sha256=4VGThHU3RZaH5dgjax69iUHfRlh7j91LaM00guDKtKs,7436
|
|
5
|
+
crossref_local/cache.py,sha256=sMrmiUJr8-zRBDXnHg0M_3q3MpgzT7V1bqMKb5kpKVg,12291
|
|
6
|
+
crossref_local/cache_export.py,sha256=DU44Rv894fZz6OnBETbgwYzwVOPg3zJ6sP85yE5EYbc,2519
|
|
7
|
+
crossref_local/cache_viz.py,sha256=0VAHK-i1xR5StEYrJ_IAs0HSk0MQNVFi4EjPtlZEwTY,8498
|
|
8
|
+
crossref_local/citations.py,sha256=QFahv84upNnXP_89A8bHxEbAdz7wHbh5LEniGcAiHas,12402
|
|
9
|
+
crossref_local/cli.py,sha256=OMgrWviTT6Q9wLPWyhb9qDiRlVAJCuRDwDVpddyRPUk,16352
|
|
10
|
+
crossref_local/cli_cache.py,sha256=8QykJ7-H313x7oKRw5fJ9Coay27Y8uBWGx5JR1VQ1_Y,6220
|
|
11
|
+
crossref_local/cli_completion.py,sha256=yzqwMbGbbqXJQHdztuaqji-AdFTd8zDDhHeNGdw4NpU,7341
|
|
12
|
+
crossref_local/cli_main.py,sha256=NgCGB5-ThofTRPoxxwZpsfCQTo5WIw8oP7zBSKhEiDQ,444
|
|
13
|
+
crossref_local/cli_mcp.py,sha256=JBGveR_K7M_EzmHb3G9xr1IZPHAf-vvht-yAn5IzkQM,8524
|
|
14
|
+
crossref_local/config.py,sha256=tBAqnnscLOIF5dobEjOA4b57OVfGeGhpnmRonYKt7ng,4839
|
|
15
|
+
crossref_local/db.py,sha256=x7dXQXjsFN4LavtkNAKTNw1cUBMG-2h53-Z-Xlq6aoQ,3696
|
|
16
|
+
crossref_local/fts.py,sha256=yZMh_vmtFentXKAFGTS4z7ZNNj7p_ItgfFP5i0yQltw,4448
|
|
17
|
+
crossref_local/mcp_server.py,sha256=BN1BByTI54Rh-QoMZA2wUTV7lxdX-fLvoiEaH3YP-W0,10974
|
|
18
|
+
crossref_local/models.py,sha256=b_yYb91O6RwEPpEqe2Wmdz12WIfE5itjEus4-fCLxLI,5476
|
|
19
|
+
crossref_local/remote.py,sha256=Ju4NewXsw_cNX4vq6OeSu8fKtyf9CLwno-4uWkpwAOM,8638
|
|
20
|
+
crossref_local/server.py,sha256=CpCZm5ZVLSPfcke1lGkvEOp1XsH6bUGm5M0i9pj343U,9059
|
|
21
|
+
crossref_local/impact_factor/__init__.py,sha256=pcgVCPogBisANYE5Vp2PHVGPgxoMsSXr-6utqVE97-4,559
|
|
22
|
+
crossref_local/impact_factor/calculator.py,sha256=eZ13URAZzPdRyAQpS8zXe_T33e2lm_gQhtoJCXbfIGM,15977
|
|
23
|
+
crossref_local/impact_factor/journal_lookup.py,sha256=Ztx6ZeWxfmPvA3KfcW5h_yz01XPstIdk91j3nu2Q-qw,8846
|
|
24
|
+
crossref_local-0.4.0.dist-info/METADATA,sha256=l-quaEeSKQwlveyZgKwZfW1SwyNsM9G8yZhGOHtGtMs,9508
|
|
25
|
+
crossref_local-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
26
|
+
crossref_local-0.4.0.dist-info/entry_points.txt,sha256=1LhHFzJ7VWhNmGY6yIGDJ5EhKTYCyU7CbLEooXQWhLQ,116
|
|
27
|
+
crossref_local-0.4.0.dist-info/RECORD,,
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
crossref_local/__init__.py,sha256=AJIkriNQBf61SPoi-cfxNr25pdoQMS3KvLhsNoCfJmQ,3316
|
|
2
|
-
crossref_local/__main__.py,sha256=N1c1ESGgJkAwsSWXANUgmzxC1OJEIqw-cl9m4pmNP7s,110
|
|
3
|
-
crossref_local/aio.py,sha256=En2btSn3euRbEYav1919gsmdC8iQaMbgGUso-IThCwo,5490
|
|
4
|
-
crossref_local/api.py,sha256=h2lpoA7qsCzxBTv0Na3Etgk0hZCWZFKmalreIYsmOhw,5343
|
|
5
|
-
crossref_local/citations.py,sha256=QFahv84upNnXP_89A8bHxEbAdz7wHbh5LEniGcAiHas,12402
|
|
6
|
-
crossref_local/cli.py,sha256=WMk7GxJtTf2ZCH1ldkcUmX-643n5DE11kGu_K7AkOrA,15132
|
|
7
|
-
crossref_local/config.py,sha256=4LGZJ3CmsA9YRv48FkEqVR2xljuSjl0MYiMrT8ljk14,5050
|
|
8
|
-
crossref_local/db.py,sha256=x7dXQXjsFN4LavtkNAKTNw1cUBMG-2h53-Z-Xlq6aoQ,3696
|
|
9
|
-
crossref_local/fts.py,sha256=yZMh_vmtFentXKAFGTS4z7ZNNj7p_ItgfFP5i0yQltw,4448
|
|
10
|
-
crossref_local/mcp_server.py,sha256=KDcBvVMXrhemO7cS4kBMfEvp0Qb-LDsVbnPhnLaaC-4,5796
|
|
11
|
-
crossref_local/models.py,sha256=b_yYb91O6RwEPpEqe2Wmdz12WIfE5itjEus4-fCLxLI,5476
|
|
12
|
-
crossref_local/remote.py,sha256=p8P0zotkNYchqqKGOsqcFiHR10qD4pYmJ26-ltyqO4s,8389
|
|
13
|
-
crossref_local/server.py,sha256=lEc0EA3jVx31q1EEYOaT4cr9l2_fGpoQZmpYdnoGxFQ,9034
|
|
14
|
-
crossref_local/impact_factor/__init__.py,sha256=pcgVCPogBisANYE5Vp2PHVGPgxoMsSXr-6utqVE97-4,559
|
|
15
|
-
crossref_local/impact_factor/calculator.py,sha256=eZ13URAZzPdRyAQpS8zXe_T33e2lm_gQhtoJCXbfIGM,15977
|
|
16
|
-
crossref_local/impact_factor/journal_lookup.py,sha256=Ztx6ZeWxfmPvA3KfcW5h_yz01XPstIdk91j3nu2Q-qw,8846
|
|
17
|
-
crossref_local-0.3.1.dist-info/METADATA,sha256=Wiqa6MGJXMgMFI3qXss4IcLr0wo20Jsbl9UgAfyXrrU,8480
|
|
18
|
-
crossref_local-0.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
19
|
-
crossref_local-0.3.1.dist-info/entry_points.txt,sha256=BZbDvHLHzlKzFc-dqLAFwPrWGmGq5yFuD3vslzbmRnk,111
|
|
20
|
-
crossref_local-0.3.1.dist-info/RECORD,,
|
|
File without changes
|