Spaces:
Paused
Paused
| import os | |
| import uvicorn | |
| from mcp.server.fastmcp import FastMCP | |
| from sqlalchemy import create_engine, text | |
| # 1. Database Connection (Optimized for 2 CPU / 16GB RAM) | |
| DB_URI = os.getenv("DB_URI", "postgresql://postgres:password@localhost:5432/postgres") | |
| # Pool size 5 prevents too many context switches on 2 vCPUs | |
| engine = create_engine(DB_URI, pool_size=5, max_overflow=10) | |
| # 2. Initialize FastMCP | |
| mcp = FastMCP("Shamela4") | |
| def fetch_data(query: str, params: dict = {}): | |
| try: | |
| with engine.connect() as conn: | |
| result = conn.execute(text(query), params) | |
| return [dict(row._mapping) for row in result] | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def fetch_one(query: str, params: dict = {}): | |
| try: | |
| with engine.connect() as conn: | |
| result = conn.execute(text(query), params).fetchone() | |
| if result: | |
| return dict(result._mapping) | |
| return None | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def get_database_stats() -> dict: | |
| """Get database statistics: counts of books, authors, categories, and pages.""" | |
| try: | |
| with engine.connect() as conn: | |
| books_count = conn.execute(text("SELECT COUNT(book_id) FROM books")).scalar() or 0 | |
| authors_count = conn.execute(text("SELECT COUNT(author_id) FROM authors")).scalar() or 0 | |
| categories_count = conn.execute(text("SELECT COUNT(category_id) FROM categories")).scalar() or 0 | |
| pages_count = conn.execute(text("SELECT COUNT(page_id) FROM pages")).scalar() or 0 | |
| return { | |
| "total_categories": categories_count, | |
| "total_authors": authors_count, | |
| "total_books": books_count, | |
| "total_pages": pages_count, | |
| } | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def search_categories(limit: int = 50) -> list: | |
| """Get list of categories in the library.""" | |
| limit = min(limit, 100) # Hard limit | |
| return fetch_data("SELECT category_id, category_name_ar, name_en FROM categories ORDER BY sort_order LIMIT :limit", {"limit": limit}) | |
| def search_authors(search_term: str, limit: int = 10) -> list: | |
| """Search for authors by their Arabic name.""" | |
| limit = min(limit, 20) | |
| query = "SELECT author_id, name_ar, death_hijri FROM authors WHERE name_ar LIKE :search ORDER BY death_hijri LIMIT :limit" | |
| return fetch_data(query, {"search": f"%{search_term}%", "limit": limit}) | |
| def search_books(title_or_author: str, limit: int = 10) -> list: | |
| """Search for books by title or author name.""" | |
| limit = min(limit, 20) | |
| query = """ | |
| SELECT book_id, title_ar, main_author_name_ar, main_author_death_hijri | |
| FROM books | |
| WHERE title_ar LIKE :search OR main_author_name_ar LIKE :search | |
| ORDER BY book_id LIMIT :limit | |
| """ | |
| return fetch_data(query, {"search": f"%{title_or_author}%", "limit": limit}) | |
| def get_book_toc(book_id: int) -> list: | |
| """Get the Table of Contents (Daftar Isi) for a specific book_id.""" | |
| query = "SELECT title_id, title_text, page_id, parent_id FROM toc WHERE book_id = :book_id ORDER BY title_id" | |
| return fetch_data(query, {"book_id": book_id}) | |
| def search_text_in_books(query: str, limit: int = 5) -> list: | |
| """ | |
| Full-text search inside book pages. | |
| Use this to find specific Arabic text/words inside the books. | |
| Returns highly relevant text snippets (highlights). | |
| """ | |
| limit = min(limit, 10) # Strict limit for efficiency on 16GB RAM | |
| # Use websearch_to_tsquery for efficient and safe natural language parsing | |
| sql = """ | |
| SELECT p.book_id, p.page_num, p.sequence_num, b.title_ar as book_title, | |
| ts_headline('arabic', p.body, websearch_to_tsquery('arabic', :query)) as snippet | |
| FROM pages p | |
| JOIN books b ON p.book_id = b.book_id | |
| WHERE to_tsvector('arabic', p.body) @@ websearch_to_tsquery('arabic', :query) | |
| ORDER BY p.book_id, p.sequence_num | |
| LIMIT :limit | |
| """ | |
| return fetch_data(sql, {"query": query, "limit": limit}) | |
| def get_page_content(book_id: int, page_num: int) -> dict: | |
| """Retrieve the exact, full text of a specific page number in a book.""" | |
| query = "SELECT page_num, body, footnotes FROM pages WHERE book_id = :book_id AND page_num = :page_num LIMIT 1" | |
| return fetch_one(query, {"book_id": book_id, "page_num": page_num}) | |
| if __name__ == "__main__": | |
| print("Starting Shamela4 FastMCP Server with SSE on port 8001...") | |
| mcp.run(transport="sse", host="0.0.0.0", port=8001) | |