all-in-mcp 0.1.4__tar.gz → 0.2.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: all-in-mcp
3
- Version: 0.1.4
3
+ Version: 0.2.2
4
4
  Summary: An MCP (Model Context Protocol) server providing daily-use utility functions and academic paper search capabilities
5
5
  Project-URL: Homepage, https://github.com/jiahaoxiang2000/all-in-mcp
6
6
  Project-URL: Repository, https://github.com/jiahaoxiang2000/all-in-mcp
@@ -52,14 +52,24 @@ An MCP (Model Context Protocol) server that provides daily-use utility functions
52
52
  ### Daily Utilities
53
53
 
54
54
  - **Academic Research**: IACR ePrint Archive paper search, download, and reading
55
+ - **Bibliography Search**: CryptoBib database search for cryptography papers
55
56
 
56
57
  ### Paper Search Capabilities
57
58
 
59
+ #### IACR ePrint Archive
60
+
58
61
  - Search academic papers from IACR ePrint Archive
59
62
  - Download PDF files
60
63
  - Extract and read text content from papers
61
64
  - Metadata extraction (authors, publication dates, abstracts)
62
65
 
66
+ #### CryptoBib Database
67
+
68
+ - Search comprehensive cryptography bibliography database
69
+ - Access to thousands of cryptographic research papers
70
+ - Retrieve structured paper metadata or raw BibTeX entries
71
+ - Support for all major cryptography venues and conferences
72
+
63
73
  ## Quick Start
64
74
 
65
75
  ### Prerequisites
@@ -75,7 +85,7 @@ An MCP (Model Context Protocol) server that provides daily-use utility functions
75
85
  pip install all-in-mcp
76
86
  ```
77
87
 
78
- ### Installation
88
+ ### Option 2: Install from Source
79
89
 
80
90
  1. Clone this repository:
81
91
 
@@ -201,6 +211,7 @@ Complete documentation is available in the [`docs/`](docs/) directory:
201
211
  - **[API Reference](docs/api.md)** - Complete API documentation
202
212
  - **[Installation Guide](docs/installation.md)** - Setup instructions
203
213
  - **[IACR Integration](docs/iacr.md)** - Academic paper search details
214
+ - **[CryptoBib Integration](docs/cryptobib.md)** - Bibliography database search
204
215
  - **[Development Guide](docs/development.md)** - Contributing guidelines
205
216
  - **[PyPI Setup Guide](docs/pypi-setup.md)** - Publishing configuration
206
217
  - **[Examples](docs/examples.md)** - Usage examples
@@ -7,14 +7,24 @@ An MCP (Model Context Protocol) server that provides daily-use utility functions
7
7
  ### Daily Utilities
8
8
 
9
9
  - **Academic Research**: IACR ePrint Archive paper search, download, and reading
10
+ - **Bibliography Search**: CryptoBib database search for cryptography papers
10
11
 
11
12
  ### Paper Search Capabilities
12
13
 
14
+ #### IACR ePrint Archive
15
+
13
16
  - Search academic papers from IACR ePrint Archive
14
17
  - Download PDF files
15
18
  - Extract and read text content from papers
16
19
  - Metadata extraction (authors, publication dates, abstracts)
17
20
 
21
+ #### CryptoBib Database
22
+
23
+ - Search comprehensive cryptography bibliography database
24
+ - Access to thousands of cryptographic research papers
25
+ - Retrieve structured paper metadata or raw BibTeX entries
26
+ - Support for all major cryptography venues and conferences
27
+
18
28
  ## Quick Start
19
29
 
20
30
  ### Prerequisites
@@ -30,7 +40,7 @@ An MCP (Model Context Protocol) server that provides daily-use utility functions
30
40
  pip install all-in-mcp
31
41
  ```
32
42
 
33
- ### Installation
43
+ ### Option 2: Install from Source
34
44
 
35
45
  1. Clone this repository:
36
46
 
@@ -156,6 +166,7 @@ Complete documentation is available in the [`docs/`](docs/) directory:
156
166
  - **[API Reference](docs/api.md)** - Complete API documentation
157
167
  - **[Installation Guide](docs/installation.md)** - Setup instructions
158
168
  - **[IACR Integration](docs/iacr.md)** - Academic paper search details
169
+ - **[CryptoBib Integration](docs/cryptobib.md)** - Bibliography database search
159
170
  - **[Development Guide](docs/development.md)** - Contributing guidelines
160
171
  - **[PyPI Setup Guide](docs/pypi-setup.md)** - Publishing configuration
161
172
  - **[Examples](docs/examples.md)** - Usage examples
@@ -108,6 +108,61 @@ URL: https://eprint.iacr.org/2023/1234
108
108
  [Full extracted text content]
109
109
  ```
110
110
 
111
+ ## CryptoBib Bibliography Search
112
+
113
+ ### search-cryptobib-papers
114
+
115
+ Search CryptoBib bibliography database for cryptography papers. CryptoBib is a comprehensive BibTeX database of cryptography-related academic papers.
116
+
117
+ **Parameters:**
118
+
119
+ - `query` (string, required): Search query string (e.g., 'cryptography', 'lattice', 'homomorphic')
120
+ - `max_results` (integer, optional): Maximum number of results to return (default: 10)
121
+ - `return_bibtex` (boolean, optional): Whether to return raw BibTeX entries (default: false)
122
+ - `force_download` (boolean, optional): Force download the newest crypto.bib file (default: false)
123
+
124
+ **Returns:**
125
+
126
+ - List of papers with metadata or raw BibTeX entries
127
+
128
+ **Example:**
129
+
130
+ ```json
131
+ {
132
+ "name": "search-cryptobib-papers",
133
+ "arguments": {
134
+ "query": "lattice cryptography",
135
+ "max_results": 5,
136
+ "return_bibtex": true,
137
+ "force_download": false
138
+ }
139
+ }
140
+ ```
141
+
142
+ **Response:**
143
+
144
+ ````text
145
+ Found 5 BibTeX entries for query 'lattice cryptography':
146
+
147
+ Entry 1:
148
+ ```bibtex
149
+ @InProceedings{CRYPTO:RegLyr05,
150
+ author = "Oded Regev and Ricky Steinfeld",
151
+ title = "Learning With Errors over Rings",
152
+ booktitle = "CRYPTO 2005",
153
+ year = 2005,
154
+ ...
155
+ }
156
+ ````
157
+
158
+ **Important Notes:**
159
+
160
+ - **First Use**: On first use, the tool will download the crypto.bib file (~50MB) and cache it locally in the `./downloads` directory
161
+ - **Caching**: Subsequent searches use the cached file unless `force_download` is set to true
162
+ - **BibTeX Retrieval**: To get specific BibTeX entries, use the `search-cryptobib-papers` tool with `return_bibtex=true` and a specific search query
163
+ - **Content**: The crypto.bib file contains thousands of cryptography-related papers from major conferences and journals
164
+ - **Performance**: Local file searching is much faster than streaming searches
165
+
111
166
  ## Error Handling
112
167
 
113
168
  All tools return error messages in case of failures:
@@ -0,0 +1,166 @@
1
+ # CryptoBib Integration
2
+
3
+ This document describes the CryptoBib integration in the all-in-mcp server.
4
+
5
+ ## Overview
6
+
7
+ CryptoBib (https://cryptobib.di.ens.fr/) is a comprehensive bibliography database for cryptography research papers. This integration allows searching through the CryptoBib database and retrieving BibTeX entries.
8
+
9
+ ## Features
10
+
11
+ ### Search Functionality
12
+
13
+ - Search through the entire CryptoBib database
14
+ - Case-insensitive search across all BibTeX fields
15
+ - Returns both parsed Paper objects and raw BibTeX entries
16
+ - Configurable result limits
17
+
18
+ ### BibTeX Support
19
+
20
+ - Parse BibTeX entries into structured Paper objects
21
+ - Retrieve raw BibTeX entries for direct use
22
+ - Support for all standard BibTeX fields (author, title, booktitle, journal, year, etc.)
23
+ - Clean LaTeX formatting from titles and authors
24
+
25
+ ## Available Tools
26
+
27
+ ### search-cryptobib-papers
28
+
29
+ Search CryptoBib bibliography database for cryptography papers.
30
+
31
+ **Parameters:**
32
+
33
+ - `query` (required): Search query string (e.g., 'lattice', 'homomorphic encryption')
34
+ - `max_results` (optional, default: 10): Maximum number of results to return
35
+ - `return_bibtex` (optional, default: false): Whether to return raw BibTeX entries
36
+ - `force_download` (optional, default: false): Force download the newest crypto.bib file
37
+
38
+ **Examples:**
39
+
40
+ ```json
41
+ {
42
+ "query": "lattice cryptography",
43
+ "max_results": 5,
44
+ "return_bibtex": false
45
+ }
46
+ ```
47
+
48
+ To get specific BibTeX entries, you can use targeted search queries with `return_bibtex=true`.
49
+
50
+ ## Usage Examples
51
+
52
+ ### Basic Search
53
+
54
+ ```python
55
+ from all_in_mcp.academic_platforms.cryptobib import CryptoBibSearcher
56
+
57
+ searcher = CryptoBibSearcher()
58
+
59
+ # Search for papers
60
+ papers = searcher.search("homomorphic encryption", max_results=5)
61
+ for paper in papers:
62
+ print(f"Title: {paper.title}")
63
+ print(f"Authors: {', '.join(paper.authors)}")
64
+ print(f"BibTeX Key: {paper.paper_id}")
65
+ ```
66
+
67
+ ### Get Raw BibTeX
68
+
69
+ ```python
70
+ # Get raw BibTeX entries
71
+ bibtex_entries = searcher.search_bibtex("zero knowledge", max_results=3)
72
+ for entry in bibtex_entries:
73
+ print(entry)
74
+ ```
75
+
76
+ ### Get Specific BibTeX Entry
77
+
78
+ ```python
79
+ # Search for specific entries using targeted queries
80
+ papers = searcher.search("ACISP:LZXSW24", max_results=1)
81
+ if papers:
82
+ # Access the BibTeX from the paper's extra field
83
+ bibtex = papers[0].extra.get('bibtex', '')
84
+ print(bibtex)
85
+
86
+ # Or get raw BibTeX entries directly
87
+ bibtex_entries = searcher.search_bibtex("ACISP:LZXSW24", max_results=1)
88
+ if bibtex_entries:
89
+ print(bibtex_entries[0])
90
+ ```
91
+
92
+ ## BibTeX Entry Format
93
+
94
+ CryptoBib uses standard BibTeX format. Example entry:
95
+
96
+ ```bibtex
97
+ @InProceedings{ACISP:LZXSW24,
98
+ author = "Fangzhou Liu and
99
+ Xueqi Zhu and
100
+ Ruozhou Xu and
101
+ Danping Shi and
102
+ Peng Wang",
103
+ title = "The Offline Quantum Attack Against Modular Addition Variant of {Even}-{Mansour} Cipher",
104
+ pages = "3--19",
105
+ editor = acisp24ed,
106
+ booktitle = acisp24name1,
107
+ volume = acisp24vol1,
108
+ address = acisp24addr,
109
+ month = acisp24month,
110
+ publisher = acisp24pub,
111
+ series = mylncs,
112
+ year = 2024,
113
+ doi = "10.1007/978-981-97-5025-2_1",
114
+ }
115
+ ```
116
+
117
+ ## Implementation Details
118
+
119
+ ### Search Strategy
120
+
121
+ The implementation uses streaming HTTP requests to search through the large crypto.bib file without downloading it entirely. It:
122
+
123
+ 1. Makes a streaming request to the crypto.bib file
124
+ 2. Parses BibTeX entries on-the-fly
125
+ 3. Performs case-insensitive matching against the search query
126
+ 4. Returns results as they are found up to the specified limit
127
+
128
+ ### Performance Considerations
129
+
130
+ - Uses streaming to avoid memory issues with the large bibliography file
131
+ - Processes entries incrementally
132
+ - Stops searching once the maximum number of results is reached
133
+ - Implements proper brace counting for correct BibTeX entry parsing
134
+
135
+ ### Limitations
136
+
137
+ - No PDF download capability (CryptoBib is a bibliography database only)
138
+ - No full-text search of paper content
139
+ - Search is limited to BibTeX field content
140
+ - Dependent on CryptoBib server availability
141
+
142
+ ## Testing
143
+
144
+ Run the CryptoBib tests:
145
+
146
+ ```bash
147
+ # Run all tests
148
+ pytest tests/test_cryptobib.py
149
+
150
+ # Run with verbose output
151
+ pytest tests/test_cryptobib.py -v
152
+
153
+ # Test specific functionality
154
+ python test_cryptobib.py
155
+ ```
156
+
157
+ ## Error Handling
158
+
159
+ The implementation includes comprehensive error handling for:
160
+
161
+ - Network connectivity issues
162
+ - Malformed BibTeX entries
163
+ - Server response errors
164
+ - Parsing failures
165
+
166
+ All errors are logged and gracefully handled, returning empty results or error messages as appropriate.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "all-in-mcp"
3
- version = "0.1.4"
3
+ version = "0.2.2"
4
4
  description = "An MCP (Model Context Protocol) server providing daily-use utility functions and academic paper search capabilities"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -0,0 +1,6 @@
1
+ # all_in_mcp/academic_platforms/__init__.py
2
+ from .base import PaperSource
3
+ from .cryptobib import CryptoBibSearcher
4
+ from .iacr import IACRSearcher
5
+
6
+ __all__ = ["PaperSource", "CryptoBibSearcher", "IACRSearcher"]
@@ -0,0 +1,461 @@
1
+ # all_in_mcp/academic_platforms/cryptobib.py
2
+ import logging
3
+ import os
4
+ import random
5
+ import re
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import ClassVar
9
+
10
+ import requests
11
+
12
+ from ..paper import Paper
13
+ from .base import PaperSource
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class CryptoBibSearcher(PaperSource):
19
+ """CryptoBib (https://cryptobib.di.ens.fr/) bibliography search implementation"""
20
+
21
+ CRYPTOBIB_BASE_URL = "https://cryptobib.di.ens.fr"
22
+ CRYPTOBIB_BIB_URL = "https://cryptobib.di.ens.fr/cryptobib/static/files/crypto.bib"
23
+ BROWSERS: ClassVar[list[str]] = [
24
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
25
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
26
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
27
+ ]
28
+
29
+ def __init__(self, cache_dir: str = "./downloads"):
30
+ self.cache_dir = Path(cache_dir)
31
+ self.cache_dir.mkdir(exist_ok=True)
32
+ self.bib_file_path = self.cache_dir / "crypto.bib"
33
+ self._setup_session()
34
+
35
+ def _setup_session(self):
36
+ """Initialize session with random user agent"""
37
+ self.session = requests.Session()
38
+ self.session.headers.update(
39
+ {
40
+ "User-Agent": random.choice(self.BROWSERS),
41
+ "Accept": "text/plain,text/html,application/xhtml+xml",
42
+ "Accept-Language": "en-US,en;q=0.9",
43
+ }
44
+ )
45
+
46
+ def _download_bib_file(self, force_download: bool = False) -> bool:
47
+ """
48
+ Download the crypto.bib file if not exists or if force_download is True
49
+
50
+ Args:
51
+ force_download: Force download even if file exists
52
+
53
+ Returns:
54
+ bool: True if file is ready, False if download failed
55
+ """
56
+ try:
57
+ # Check if file exists and force_download is False
58
+ if self.bib_file_path.exists() and not force_download:
59
+ logger.info(f"Using cached crypto.bib file at {self.bib_file_path}")
60
+ return True
61
+
62
+ logger.info("Downloading crypto.bib file from CryptoBib...")
63
+ response = self.session.get(self.CRYPTOBIB_BIB_URL, stream=True)
64
+
65
+ if response.status_code != 200:
66
+ logger.error(
67
+ f"Failed to download crypto.bib: HTTP {response.status_code}"
68
+ )
69
+ return False
70
+
71
+ # Download with progress indication
72
+ total_size = int(response.headers.get("content-length", 0))
73
+ downloaded = 0
74
+
75
+ with open(self.bib_file_path, "wb") as f:
76
+ for chunk in response.iter_content(
77
+ chunk_size=1024 * 1024
78
+ ): # 1MB chunks
79
+ if chunk:
80
+ f.write(chunk)
81
+ downloaded += len(chunk)
82
+ if total_size > 0:
83
+ progress = (downloaded / total_size) * 100
84
+ if downloaded % (1024 * 1024 * 5) == 0: # Log every 5MB
85
+ logger.info(f"Download progress: {progress:.1f}%")
86
+
87
+ logger.info(f"Successfully downloaded crypto.bib to {self.bib_file_path}")
88
+ return True
89
+
90
+ except requests.RequestException as e:
91
+ logger.error(f"Error downloading crypto.bib: {e}")
92
+ return False
93
+ except Exception as e:
94
+ logger.error(f"Unexpected error downloading crypto.bib: {e}")
95
+ return False
96
+
97
+ def _parse_bibtex_entry(self, bibtex_text: str) -> Paper | None:
98
+ """Parse a single BibTeX entry into a Paper object"""
99
+ try:
100
+ # Extract entry type and key
101
+ entry_match = re.match(r"@(\w+){([^,]+),", bibtex_text, re.IGNORECASE)
102
+ if not entry_match:
103
+ return None
104
+
105
+ entry_type = entry_match.group(1).lower()
106
+ entry_key = entry_match.group(2).strip()
107
+
108
+ # Initialize fields
109
+ title = ""
110
+ authors = []
111
+ year = None
112
+ booktitle = ""
113
+ journal = ""
114
+ pages = ""
115
+ doi = ""
116
+ url = ""
117
+ abstract = ""
118
+
119
+ # Extract fields using a more robust approach
120
+ # First, normalize the text by removing extra whitespace
121
+ normalized_text = re.sub(r"\s+", " ", bibtex_text)
122
+
123
+ # Extract fields with better pattern matching
124
+ field_dict = {}
125
+
126
+ # Pattern for quoted fields (handles multi-line)
127
+ quoted_pattern = r'(\w+)\s*=\s*"([^"]*(?:[^"\\]|\\.)*)"'
128
+ for match in re.finditer(quoted_pattern, bibtex_text, re.DOTALL):
129
+ field_name = match.group(1).lower().strip()
130
+ field_value = match.group(2).strip()
131
+ field_dict[field_name] = field_value
132
+
133
+ # Pattern for unquoted fields (like numbers)
134
+ unquoted_pattern = r'(\w+)\s*=\s*([^,}\n"]+)'
135
+ for match in re.finditer(unquoted_pattern, bibtex_text):
136
+ field_name = match.group(1).lower().strip()
137
+ field_value = match.group(2).strip()
138
+ # Only add if not already present from quoted pattern
139
+ if field_name not in field_dict:
140
+ field_dict[field_name] = field_value
141
+
142
+ for field_name, field_value in field_dict.items():
143
+ field_name = field_name.lower().strip()
144
+ field_value = field_value.strip()
145
+
146
+ if field_name == "title":
147
+ # Clean up title (remove LaTeX commands)
148
+ title = re.sub(r"[{}]", "", field_value)
149
+ title = re.sub(r"\\[a-zA-Z]+", "", title)
150
+ title = title.strip()
151
+
152
+ elif field_name == "author":
153
+ # Parse authors - handle "and" separator
154
+ author_text = re.sub(r"[{}]", "", field_value)
155
+ authors = [
156
+ author.strip()
157
+ for author in re.split(r"\s+and\s+", author_text)
158
+ if author.strip()
159
+ ]
160
+
161
+ elif field_name == "year":
162
+ try:
163
+ year = int(field_value)
164
+ except ValueError:
165
+ pass
166
+
167
+ elif field_name == "booktitle":
168
+ booktitle = re.sub(r"[{}]", "", field_value)
169
+
170
+ elif field_name == "journal":
171
+ journal = re.sub(r"[{}]", "", field_value)
172
+
173
+ elif field_name == "pages":
174
+ pages = field_value
175
+
176
+ elif field_name == "doi":
177
+ doi = field_value
178
+
179
+ elif field_name == "url":
180
+ url = field_value
181
+
182
+ elif field_name == "abstract":
183
+ abstract = field_value
184
+
185
+ # Determine venue (journal or conference)
186
+ venue = journal if journal else booktitle
187
+ categories = [entry_type] if entry_type else []
188
+
189
+ # Create published date
190
+ published_date = datetime(year, 1, 1) if year else datetime(1900, 1, 1)
191
+
192
+ return Paper(
193
+ paper_id=entry_key,
194
+ title=title,
195
+ authors=authors,
196
+ abstract=abstract,
197
+ url=url,
198
+ pdf_url="", # CryptoBib doesn't provide PDF URLs
199
+ published_date=published_date,
200
+ updated_date=None,
201
+ source="cryptobib",
202
+ categories=categories,
203
+ keywords=[],
204
+ doi=doi,
205
+ citations=0,
206
+ extra={
207
+ "bibtex": bibtex_text.strip(),
208
+ "venue": venue,
209
+ "pages": pages,
210
+ "entry_type": entry_type,
211
+ },
212
+ )
213
+
214
+ except Exception as e:
215
+ logger.warning(f"Failed to parse BibTeX entry: {e}")
216
+ return None
217
+
218
+ def search_bibtex(
219
+ self,
220
+ query: str,
221
+ max_results: int = 10,
222
+ force_download: bool = False,
223
+ year_min: int | None = None,
224
+ year_max: int | None = None,
225
+ ) -> list[str]:
226
+ """
227
+ Search CryptoBib and return raw BibTeX entries
228
+
229
+ Args:
230
+ query: Search query string
231
+ max_results: Maximum number of results to return
232
+ force_download: Force download the newest crypto.bib file
233
+ year_min: Minimum publication year (inclusive, optional)
234
+ year_max: Maximum publication year (inclusive, optional)
235
+
236
+ Returns:
237
+ List[str]: List of BibTeX entries as strings
238
+
239
+ Example:
240
+ >>> searcher = CryptoBibSearcher()
241
+ >>> # Search for recent implementation papers (2020-2024)
242
+ >>> entries = searcher.search_bibtex("implement", max_results=5,
243
+ ... year_min=2020, year_max=2024)
244
+ >>>
245
+ >>> # Search for older RSA papers (1980-2000)
246
+ >>> entries = searcher.search_bibtex("RSA", max_results=10,
247
+ ... year_min=1980, year_max=2000)
248
+ """
249
+ bibtex_entries = []
250
+
251
+ try:
252
+ # Ensure we have the bib file locally
253
+ if not self._download_bib_file(force_download=force_download):
254
+ logger.error("Failed to download crypto.bib file")
255
+ return bibtex_entries
256
+
257
+ # Search in the local file
258
+ logger.info(f"Searching local crypto.bib file for: {query}")
259
+ current_entry = ""
260
+ in_entry = False
261
+ brace_count = 0
262
+
263
+ # Convert query to lowercase for case-insensitive search
264
+ query_lower = query.lower()
265
+
266
+ with open(self.bib_file_path, "r", encoding="utf-8") as f:
267
+ for line_num, line in enumerate(f, 1):
268
+ # Check if this is the start of a new entry
269
+ if line.strip().startswith("@") and not in_entry:
270
+ current_entry = line
271
+ in_entry = True
272
+ brace_count = line.count("{") - line.count("}")
273
+ elif in_entry:
274
+ current_entry += line
275
+ brace_count += line.count("{") - line.count("}")
276
+
277
+ # Check if entry is complete
278
+ if brace_count <= 0:
279
+ # Entry is complete, check if it matches the query
280
+ if query_lower in current_entry.lower():
281
+ # Check year range if specified
282
+ if self._entry_matches_year_range(
283
+ current_entry, year_min, year_max
284
+ ):
285
+ bibtex_entries.append(current_entry.strip())
286
+ logger.info(
287
+ f"Found matching entry {len(bibtex_entries)} at line {line_num}"
288
+ )
289
+
290
+ if len(bibtex_entries) >= max_results:
291
+ break
292
+
293
+ # Reset for next entry
294
+ current_entry = ""
295
+ in_entry = False
296
+ brace_count = 0
297
+
298
+ except FileNotFoundError:
299
+ logger.error(f"crypto.bib file not found at {self.bib_file_path}")
300
+ except Exception as e:
301
+ logger.error(f"CryptoBib search error: {e}")
302
+
303
+ return bibtex_entries[:max_results]
304
+
305
+ def search(
306
+ self,
307
+ query: str,
308
+ max_results: int = 10,
309
+ return_bibtex: bool = False,
310
+ force_download: bool = False,
311
+ year_min: int | None = None,
312
+ year_max: int | None = None,
313
+ ) -> list[Paper]:
314
+ """
315
+ Search CryptoBib bibliography
316
+
317
+ Args:
318
+ query: Search query string
319
+ max_results: Maximum number of results to return
320
+ return_bibtex: If True, include raw BibTeX in results
321
+ force_download: Force download the newest crypto.bib file
322
+ year_min: Minimum publication year (inclusive, optional)
323
+ year_max: Maximum publication year (inclusive, optional)
324
+
325
+ Returns:
326
+ List[Paper]: List of paper objects
327
+
328
+ Example:
329
+ >>> searcher = CryptoBibSearcher()
330
+ >>> # Search for recent zero-knowledge papers (2020-2024)
331
+ >>> papers = searcher.search("zero knowledge", max_results=5,
332
+ ... year_min=2020, year_max=2024)
333
+ >>>
334
+ >>> # Search for classic RSA papers (1977-1990)
335
+ >>> papers = searcher.search("RSA", max_results=10,
336
+ ... year_min=1977, year_max=1990)
337
+ """
338
+ papers = []
339
+
340
+ try:
341
+ # Get BibTeX entries
342
+ bibtex_entries = self.search_bibtex(
343
+ query,
344
+ max_results,
345
+ force_download=force_download,
346
+ year_min=year_min,
347
+ year_max=year_max,
348
+ )
349
+
350
+ # Parse each entry into Paper objects
351
+ for i, bibtex_text in enumerate(bibtex_entries):
352
+ logger.info(f"Parsing entry {i+1}/{len(bibtex_entries)}")
353
+ paper = self._parse_bibtex_entry(bibtex_text)
354
+ if paper:
355
+ papers.append(paper)
356
+
357
+ except Exception as e:
358
+ logger.error(f"CryptoBib search error: {e}")
359
+
360
+ return papers
361
+
362
+ def download_pdf(self, paper_id: str, save_path: str) -> str:
363
+ """
364
+ CryptoBib doesn't provide PDF downloads
365
+ """
366
+ return "Error: CryptoBib is a bibliography database and doesn't provide PDF downloads"
367
+
368
+ def read_paper(self, paper_id: str, save_path: str = "./downloads") -> str:
369
+ """
370
+ CryptoBib doesn't provide paper content reading
371
+ """
372
+ return "Error: CryptoBib is a bibliography database and doesn't provide paper content"
373
+
374
+ def get_bibtex_by_key(
375
+ self, entry_key: str, force_download: bool = False
376
+ ) -> str | None:
377
+ """
378
+ Get a specific BibTeX entry by its key
379
+
380
+ Args:
381
+ entry_key: The BibTeX entry key (e.g., "ACISP:LZXSW24")
382
+ force_download: Force download the newest crypto.bib file
383
+
384
+ Returns:
385
+ str: The BibTeX entry or None if not found
386
+ """
387
+ try:
388
+ # Ensure we have the bib file locally
389
+ if not self._download_bib_file(force_download=force_download):
390
+ logger.error("Failed to download crypto.bib file")
391
+ return None
392
+
393
+ logger.info(f"Searching for BibTeX entry: {entry_key}")
394
+ current_entry = ""
395
+ in_entry = False
396
+ brace_count = 0
397
+
398
+ with open(self.bib_file_path, "r", encoding="utf-8") as f:
399
+ for line in f:
400
+ # Check if this is the start of the entry we're looking for
401
+ if line.strip().startswith(f"@") and entry_key in line:
402
+ current_entry = line
403
+ in_entry = True
404
+ brace_count = line.count("{") - line.count("}")
405
+ elif in_entry:
406
+ current_entry += line
407
+ brace_count += line.count("{") - line.count("}")
408
+
409
+ # Check if entry is complete
410
+ if brace_count <= 0:
411
+ return current_entry.strip()
412
+
413
+ except FileNotFoundError:
414
+ logger.error(f"crypto.bib file not found at {self.bib_file_path}")
415
+ except Exception as e:
416
+ logger.error(f"Error searching for entry {entry_key}: {e}")
417
+
418
+ return None
419
+
420
+ def _entry_matches_year_range(
421
+ self, bibtex_entry: str, year_min: int | None, year_max: int | None
422
+ ) -> bool:
423
+ """
424
+ Check if a BibTeX entry falls within the specified year range
425
+
426
+ Args:
427
+ bibtex_entry: Raw BibTeX entry text
428
+ year_min: Minimum year (inclusive, None means no minimum)
429
+ year_max: Maximum year (inclusive, None means no maximum)
430
+
431
+ Returns:
432
+ bool: True if entry is within year range, False otherwise
433
+ """
434
+ # If no year constraints specified, all entries match
435
+ if year_min is None and year_max is None:
436
+ return True
437
+
438
+ try:
439
+ # Extract year from the BibTeX entry
440
+ year_match = re.search(
441
+ r'year\s*=\s*(?:["{\s]*)?(\d{4})', bibtex_entry, re.IGNORECASE
442
+ )
443
+ if not year_match:
444
+ # If no year found, exclude from results when year filtering is requested
445
+ return False
446
+
447
+ entry_year = int(year_match.group(1))
448
+
449
+ # Check minimum year constraint
450
+ if year_min is not None and entry_year < year_min:
451
+ return False
452
+
453
+ # Check maximum year constraint
454
+ if year_max is not None and entry_year > year_max:
455
+ return False
456
+
457
+ return True
458
+
459
+ except (ValueError, AttributeError):
460
+ # If year parsing fails, exclude from results when year filtering is requested
461
+ return False
@@ -5,13 +5,15 @@ import mcp.types as types
5
5
  from mcp.server import NotificationOptions, Server
6
6
  from mcp.server.models import InitializationOptions
7
7
 
8
- # Import IACR searcher
8
+ # Import searchers
9
9
  from .academic_platforms.iacr import IACRSearcher
10
+ from .academic_platforms.cryptobib import CryptoBibSearcher
10
11
 
11
12
  server = Server("all-in-mcp")
12
13
 
13
- # Initialize IACR searcher
14
+ # Initialize searchers
14
15
  iacr_searcher = IACRSearcher()
16
+ cryptobib_searcher = CryptoBibSearcher(cache_dir="./downloads")
15
17
 
16
18
 
17
19
  @server.list_tools()
@@ -83,6 +85,43 @@ async def handle_list_tools() -> list[types.Tool]:
83
85
  "required": ["paper_id"],
84
86
  },
85
87
  ),
88
+ types.Tool(
89
+ name="search-cryptobib-papers",
90
+ description="Search CryptoBib bibliography database for cryptography papers",
91
+ inputSchema={
92
+ "type": "object",
93
+ "properties": {
94
+ "query": {
95
+ "type": "string",
96
+ "description": "Search query string (e.g., 'cryptography', 'lattice', 'homomorphic')",
97
+ },
98
+ "max_results": {
99
+ "type": "integer",
100
+ "description": "Maximum number of papers to return (default: 10)",
101
+ "default": 10,
102
+ },
103
+ "return_bibtex": {
104
+ "type": "boolean",
105
+ "description": "Whether to return raw BibTeX entries (default: False)",
106
+ "default": False,
107
+ },
108
+ "force_download": {
109
+ "type": "boolean",
110
+ "description": "Force download the newest crypto.bib file (default: False)",
111
+ "default": False,
112
+ },
113
+ "year_min": {
114
+ "type": "integer",
115
+ "description": "Minimum publication year (inclusive, optional)",
116
+ },
117
+ "year_max": {
118
+ "type": "integer",
119
+ "description": "Maximum publication year (inclusive, optional)",
120
+ },
121
+ },
122
+ "required": ["query"],
123
+ },
124
+ ),
86
125
  ]
87
126
 
88
127
 
@@ -186,6 +225,104 @@ async def handle_call_tool(
186
225
  else:
187
226
  return [types.TextContent(type="text", text=result)]
188
227
 
228
+ elif name == "search-cryptobib-papers":
229
+ query = arguments.get("query", "")
230
+ max_results = arguments.get("max_results", 10)
231
+ return_bibtex = arguments.get("return_bibtex", False)
232
+ force_download = arguments.get("force_download", False)
233
+ year_min = arguments.get("year_min")
234
+ year_max = arguments.get("year_max")
235
+
236
+ if not query:
237
+ return [
238
+ types.TextContent(
239
+ type="text", text="Error: Query parameter is required"
240
+ )
241
+ ]
242
+
243
+ if return_bibtex:
244
+ # Return raw BibTeX entries
245
+ bibtex_entries = cryptobib_searcher.search_bibtex(
246
+ query,
247
+ max_results,
248
+ force_download=force_download,
249
+ year_min=year_min,
250
+ year_max=year_max,
251
+ )
252
+
253
+ if not bibtex_entries:
254
+ year_filter_msg = ""
255
+ if year_min or year_max:
256
+ year_range = (
257
+ f" ({year_min or 'earliest'}-{year_max or 'latest'})"
258
+ )
259
+ year_filter_msg = f" in year range{year_range}"
260
+ return [
261
+ types.TextContent(
262
+ type="text",
263
+ text=f"No BibTeX entries found for query: {query}{year_filter_msg}",
264
+ )
265
+ ]
266
+
267
+ year_filter_msg = ""
268
+ if year_min or year_max:
269
+ year_range = f" ({year_min or 'earliest'}-{year_max or 'latest'})"
270
+ year_filter_msg = f" in year range{year_range}"
271
+ result_text = f"Found {len(bibtex_entries)} BibTeX entries for query '{query}'{year_filter_msg}:\n\n"
272
+ for i, entry in enumerate(bibtex_entries, 1):
273
+ result_text += f"Entry {i}:\n```bibtex\n{entry}\n```\n\n"
274
+
275
+ return [types.TextContent(type="text", text=result_text)]
276
+ else:
277
+ # Return parsed Paper objects
278
+ papers = cryptobib_searcher.search(
279
+ query,
280
+ max_results,
281
+ force_download=force_download,
282
+ year_min=year_min,
283
+ year_max=year_max,
284
+ )
285
+
286
+ if not papers:
287
+ year_filter_msg = ""
288
+ if year_min or year_max:
289
+ year_range = (
290
+ f" ({year_min or 'earliest'}-{year_max or 'latest'})"
291
+ )
292
+ year_filter_msg = f" in year range{year_range}"
293
+ return [
294
+ types.TextContent(
295
+ type="text",
296
+ text=f"No papers found for query: {query}{year_filter_msg}",
297
+ )
298
+ ]
299
+
300
+ year_filter_msg = ""
301
+ if year_min or year_max:
302
+ year_range = f" ({year_min or 'earliest'}-{year_max or 'latest'})"
303
+ year_filter_msg = f" in year range{year_range}"
304
+ result_text = f"Found {len(papers)} CryptoBib papers for query '{query}'{year_filter_msg}:\n\n"
305
+ for i, paper in enumerate(papers, 1):
306
+ result_text += f"{i}. **{paper.title}**\n"
307
+ result_text += f" - Entry Key: {paper.paper_id}\n"
308
+ result_text += f" - Authors: {', '.join(paper.authors)}\n"
309
+ if paper.extra and "venue" in paper.extra:
310
+ result_text += f" - Venue: {paper.extra['venue']}\n"
311
+ if paper.published_date and paper.published_date.year > 1900:
312
+ result_text += f" - Year: {paper.published_date.year}\n"
313
+ if paper.doi:
314
+ result_text += f" - DOI: {paper.doi}\n"
315
+ if paper.extra and "pages" in paper.extra:
316
+ result_text += f" - Pages: {paper.extra['pages']}\n"
317
+ # Only include BibTeX when explicitly requested
318
+ if return_bibtex and paper.extra and "bibtex" in paper.extra:
319
+ result_text += (
320
+ f" - BibTeX:\n```bibtex\n{paper.extra['bibtex']}\n```\n"
321
+ )
322
+ result_text += "\n"
323
+
324
+ return [types.TextContent(type="text", text=result_text)]
325
+
189
326
  else:
190
327
  raise ValueError(f"Unknown tool: {name}")
191
328
 
@@ -1 +0,0 @@
1
- # all_in_mcp/academic_platforms/__init__.py
File without changes
File without changes
File without changes