Skip to main content
Glama
server.py18.5 kB
# apaper/server.py """FastMCP-based academic paper research server.""" import sys from pathlib import Path # Add the parent directory to path for absolute imports current_dir = Path(__file__).parent parent_dir = current_dir.parent sys.path.insert(0, str(parent_dir)) from fastmcp import FastMCP from apaper.platforms import ( IACRSearcher, CryptoBibSearcher, CrossrefSearcher, GoogleScholarSearcher ) from apaper.utils.pdf_reader import read_pdf # Initialize FastMCP server mcp = FastMCP("apaper") # Initialize searchers iacr_searcher = IACRSearcher() cryptobib_searcher = CryptoBibSearcher(cache_dir="./downloads") crossref_searcher = CrossrefSearcher() google_scholar_searcher = GoogleScholarSearcher() @mcp.tool() def search_iacr_papers( query: str, max_results: int = 10, fetch_details: bool = True, year_min: int | str | None = None, year_max: int | str | None = None, ) -> str: """ Search academic papers from IACR ePrint Archive Args: query: Search query string (e.g., 'cryptography', 'secret sharing') max_results: Maximum number of papers to return (default: 10) fetch_details: Whether to fetch detailed information for each paper (default: True) year_min: Minimum publication year (revised after) year_max: Maximum publication year (revised before) """ try: # Convert string parameters to integers if needed year_min_int = None year_max_int = None if year_min is not None: year_min_int = int(year_min) if year_max is not None: year_max_int = int(year_max) papers = iacr_searcher.search( query, max_results=max_results, fetch_details=fetch_details, year_min=year_min_int, year_max=year_max_int, ) if not papers: year_filter_msg = "" if year_min or year_max: year_range = f" ({year_min or 'earliest'}-{year_max or 'latest'})" year_filter_msg = f" in year range{year_range}" return f"No papers found for query: {query}{year_filter_msg}" # Format the results year_filter_msg = "" if year_min or year_max: year_range = f" ({year_min or 'earliest'}-{year_max or 'latest'})" year_filter_msg = f" in year range{year_range}" result_text = f"Found {len(papers)} IACR papers for query '{query}'{year_filter_msg}:\n\n" for i, paper in enumerate(papers, 1): result_text += f"{i}. **{paper.title}**\n" result_text += f" - Paper ID: {paper.paper_id}\n" result_text += f" - Authors: {', '.join(paper.authors)}\n" result_text += f" - URL: {paper.url}\n" result_text += f" - PDF: {paper.pdf_url}\n" if paper.categories: result_text += f" - Categories: {', '.join(paper.categories)}\n" if paper.keywords: result_text += f" - Keywords: {', '.join(paper.keywords)}\n" if paper.abstract: result_text += f" - Abstract: {paper.abstract}\n" result_text += "\n" return result_text except ValueError as e: return f"Error: Invalid year format. Please provide valid integers for year_min and year_max." except Exception as e: return f"Error searching IACR papers: {str(e)}" @mcp.tool() def download_iacr_paper(paper_id: str, save_path: str = "./downloads") -> str: """ Download PDF of an IACR ePrint paper Args: paper_id: IACR paper ID (e.g., '2009/101') save_path: Directory to save the PDF (default: './downloads') """ try: result = iacr_searcher.download_pdf(paper_id, save_path) if result.startswith(("Error", "Failed")): return f"Download failed: {result}" else: return f"PDF downloaded successfully to: {result}" except Exception as e: return f"Error downloading IACR paper: {str(e)}" @mcp.tool() def read_iacr_paper( paper_id: str, save_path: str = "./downloads", start_page: int | str | None = None, end_page: int | str | None = None, ) -> str: """ Read and extract text content from an IACR ePrint paper PDF Args: paper_id: IACR paper ID (e.g., '2009/101') save_path: Directory where the PDF is/will be saved (default: './downloads') start_page: Starting page number (1-indexed, inclusive). Defaults to 1. end_page: Ending page number (1-indexed, inclusive). Defaults to last page. """ try: # Convert string parameters to integers if needed start_page_int = None end_page_int = None if start_page is not None: start_page_int = int(start_page) if end_page is not None: end_page_int = int(end_page) result = iacr_searcher.read_paper( paper_id, save_path, start_page=start_page_int, end_page=end_page_int ) if result.startswith("Error"): return result else: # Truncate very long text for display if len(result) > 5000: truncated_result = ( result[:5000] + f"\n\n... [Text truncated. Full text is {len(result)} characters long]" ) return truncated_result else: return result except ValueError as e: return f"Error: Invalid page number format. Please provide valid integers for start_page and end_page." except Exception as e: return f"Error reading IACR paper: {str(e)}" @mcp.tool() def search_cryptobib_papers( query: str, max_results: int = 10, return_bibtex: bool = False, force_download: bool = False, year_min: int | str | None = None, year_max: int | str | None = None, conferences: list[str] | None = None, ) -> str: """ Search CryptoBib bibliography database for cryptography papers Args: query: Search query string (e.g., 'cryptography', 'lattice', 'homomorphic') max_results: Maximum number of papers to return (default: 10) return_bibtex: Whether to return raw BibTeX entries (default: False) force_download: Force download the newest crypto.bib file (default: False) year_min: Minimum publication year (inclusive, optional) year_max: Maximum publication year (inclusive, optional) conferences: List of conference labels to filter by (e.g., ['CRYPTO', 'EUROCRYPT'] or ['C', 'EC']) """ try: # Convert string parameters to integers if needed year_min_int = None year_max_int = None if year_min is not None: year_min_int = int(year_min) if year_max is not None: year_max_int = int(year_max) if return_bibtex: # Return raw BibTeX entries bibtex_entries = cryptobib_searcher.search_bibtex( query, max_results, force_download=force_download, year_min=year_min_int, year_max=year_max_int, conferences=conferences, ) if not bibtex_entries: filter_msg = "" filters = [] if year_min or year_max: year_range = f"({year_min or 'earliest'}-{year_max or 'latest'})" filters.append(f"year range {year_range}") if conferences: filters.append(f"conferences {conferences}") if filters: filter_msg = f" with filters: {', '.join(filters)}" return f"No BibTeX entries found for query: {query}{filter_msg}" filter_msg = "" filters = [] if year_min or year_max: year_range = f"({year_min or 'earliest'}-{year_max or 'latest'})" filters.append(f"year range {year_range}") if conferences: filters.append(f"conferences {conferences}") if filters: filter_msg = f" with filters: {', '.join(filters)}" result_text = f"Found {len(bibtex_entries)} BibTeX entries for query '{query}'{filter_msg}:\n\n" for i, entry in enumerate(bibtex_entries, 1): result_text += f"Entry {i}:\n```bibtex\n{entry}\n```\n\n" return result_text else: # Return parsed Paper objects papers = cryptobib_searcher.search( query, max_results, force_download=force_download, year_min=year_min_int, year_max=year_max_int, conferences=conferences, ) if not papers: filter_msg = "" filters = [] if year_min or year_max: year_range = f"({year_min or 'earliest'}-{year_max or 'latest'})" filters.append(f"year range {year_range}") if conferences: filters.append(f"conferences {conferences}") if filters: filter_msg = f" with filters: {', '.join(filters)}" return f"No papers found for query: {query}{filter_msg}" filter_msg = "" filters = [] if year_min or year_max: year_range = f"({year_min or 'earliest'}-{year_max or 'latest'})" filters.append(f"year range {year_range}") if conferences: filters.append(f"conferences {conferences}") if filters: filter_msg = f" with filters: {', '.join(filters)}" result_text = f"Found {len(papers)} CryptoBib papers for query '{query}'{filter_msg}:\n\n" for i, paper in enumerate(papers, 1): result_text += f"{i}. **{paper.title}**\n" result_text += f" - Entry Key: {paper.paper_id}\n" result_text += f" - Authors: {', '.join(paper.authors)}\n" if paper.extra and "venue" in paper.extra: result_text += f" - Venue: {paper.extra['venue']}\n" if paper.published_date and paper.published_date.year > 1900: result_text += f" - Year: {paper.published_date.year}\n" if paper.doi: result_text += f" - DOI: {paper.doi}\n" if paper.extra and "pages" in paper.extra: result_text += f" - Pages: {paper.extra['pages']}\n" result_text += "\n" return result_text except ValueError as e: return f"Error: Invalid year format. Please provide valid integers for year_min and year_max." except Exception as e: return f"Error searching CryptoBib papers: {str(e)}" @mcp.tool() def search_google_scholar_papers( query: str, max_results: int = 10, year_low: int | str | None = None, year_high: int | str | None = None, ) -> str: """ Search academic papers from Google Scholar Args: query: Search query string (e.g., 'machine learning', 'neural networks') max_results: Maximum number of papers to return (default: 10) year_low: Minimum publication year (optional) year_high: Maximum publication year (optional) """ try: # Convert string parameters to integers if needed year_low_int = None year_high_int = None if year_low is not None: year_low_int = int(year_low) if year_high is not None: year_high_int = int(year_high) papers = google_scholar_searcher.search( query, max_results=max_results, year_low=year_low_int, year_high=year_high_int, ) if not papers: year_filter_msg = "" if year_low or year_high: year_range = f" ({year_low or 'earliest'}-{year_high or 'latest'})" year_filter_msg = f" in year range{year_range}" return f"No papers found for query: {query}{year_filter_msg}" year_filter_msg = "" if year_low or year_high: year_range = f" ({year_low or 'earliest'}-{year_high or 'latest'})" year_filter_msg = f" in year range{year_range}" result_text = f"Found {len(papers)} Google Scholar papers for query '{query}'{year_filter_msg}:\n\n" for i, paper in enumerate(papers, 1): result_text += f"{i}. **{paper.title}**\n" result_text += f" - Authors: {', '.join(paper.authors)}\n" if paper.citations > 0: result_text += f" - Citations: {paper.citations}\n" if paper.published_date and paper.published_date.year > 1900: result_text += f" - Year: {paper.published_date.year}\n" if paper.url: result_text += f" - URL: {paper.url}\n" if paper.abstract: # Truncate abstract for readability abstract_preview = ( paper.abstract[:300] + "..." if len(paper.abstract) > 300 else paper.abstract ) result_text += f" - Abstract: {abstract_preview}\n" result_text += "\n" return result_text except ValueError as e: return f"Error: Invalid year format. Please provide valid integers for year_low and year_high." except Exception as e: return f"Error searching Google Scholar: {str(e)}" @mcp.tool() def search_crossref_papers( query: str, max_results: int = 10, year_min: int | str | None = None, year_max: int | str | None = None, sort_by: str = "relevance", ) -> str: """ Search academic papers from Crossref database Args: query: Search query string (e.g., 'quantum computing', 'machine learning') max_results: Maximum number of papers to return (default: 10) year_min: Minimum publication year (optional) year_max: Maximum publication year (optional) sort_by: Sort order: relevance, published, indexed, updated (default: relevance) """ try: # Convert string parameters to integers if needed year_min_int = None year_max_int = None if year_min is not None: year_min_int = int(year_min) if year_max is not None: year_max_int = int(year_max) papers = crossref_searcher.search( query, max_results=max_results, year_min=year_min_int, year_max=year_max_int, sort_by=sort_by, ) if not papers: year_filter_msg = "" if year_min or year_max: year_range = f" ({year_min or 'earliest'}-{year_max or 'latest'})" year_filter_msg = f" in year range{year_range}" return f"No papers found for query: {query}{year_filter_msg}" year_filter_msg = "" if year_min or year_max: year_range = f" ({year_min or 'earliest'}-{year_max or 'latest'})" year_filter_msg = f" in year range{year_range}" result_text = f"Found {len(papers)} Crossref papers for query '{query}'{year_filter_msg}:\n\n" for i, paper in enumerate(papers, 1): result_text += f"{i}. **{paper.title}**\n" result_text += f" - Authors: {', '.join(paper.authors)}\n" if paper.doi: result_text += f" - DOI: {paper.doi}\n" if paper.citations > 0: result_text += f" - Citations: {paper.citations}\n" if paper.published_date and paper.published_date.year > 1900: result_text += f" - Year: {paper.published_date.year}\n" if paper.extra and paper.extra.get("journal"): result_text += f" - Journal: {paper.extra['journal']}\n" if paper.extra and paper.extra.get("volume"): result_text += f" - Volume: {paper.extra['volume']}\n" if paper.extra and paper.extra.get("pages"): result_text += f" - Pages: {paper.extra['pages']}\n" if paper.url: result_text += f" - URL: {paper.url}\n" if paper.abstract: # Truncate abstract for readability abstract_preview = ( paper.abstract[:300] + "..." if len(paper.abstract) > 300 else paper.abstract ) result_text += f" - Abstract: {abstract_preview}\n" result_text += "\n" return result_text except ValueError as e: return f"Error: Invalid year format. Please provide valid integers for year_min and year_max." except Exception as e: return f"Error searching Crossref: {str(e)}" @mcp.tool() def read_pdf_file( pdf_source: str, start_page: int | str | None = None, end_page: int | str | None = None, ) -> str: """ Read and extract text content from a PDF file (local or online) Args: pdf_source: Path to local PDF file or URL to online PDF start_page: Starting page number (1-indexed, inclusive). Defaults to 1. end_page: Ending page number (1-indexed, inclusive). Defaults to last page. """ try: # Convert string parameters to integers if needed start_page_int = None end_page_int = None if start_page is not None: start_page_int = int(start_page) if end_page is not None: end_page_int = int(end_page) result = read_pdf(pdf_source, start_page=start_page_int, end_page=end_page_int) return result except ValueError as e: return f"Error: Invalid page number format. Please provide valid integers for start_page and end_page." except Exception as e: return f"Error reading PDF from {pdf_source}: {str(e)}" def main(): """Main entry point for the APaper MCP server.""" mcp.run() if __name__ == "__main__": main()

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jiahaoxiang2000/all-in-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server