pdfparsing / mcp_server.py
Hritam-Ai
Added pdf parsing full backend
c86be6a
Raw
History Blame Contribute Delete
7.93 kB
from mcp.server.fastmcp import FastMCP
from typing import Dict, List, Any, Optional
import logging
import json
from pathlib import Path
import os
logger = logging.getLogger(__name__)
# Create FastMCP server instance
mcp = FastMCP("PDF Parser")
# Global document storage
documents: Dict[str, Dict] = {}
# Storage file path - use absolute path to ensure it works from any directory
STORAGE_FILE = Path(__file__).parent / "storage.json"
def load_documents():
"""Load documents from storage.json file"""
global documents
try:
if STORAGE_FILE.exists():
with open(STORAGE_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
documents.clear()
documents.update(data)
logger.info(f"Loaded {len(documents)} documents from storage at {STORAGE_FILE}")
else:
logger.info(f"No storage file found at {STORAGE_FILE}, starting with empty documents")
documents.clear()
except Exception as e:
logger.error(f"Error loading documents from storage: {str(e)}")
documents.clear()
def reload_documents():
"""Reload documents from storage - called by tools to ensure fresh data"""
load_documents()
# Load documents on startup
load_documents()
@mcp.tool()
def list_documents() -> str:
"""List all processed PDF documents"""
# Always reload documents to get latest data
reload_documents()
if not documents:
return "No documents available."
doc_list = []
for file_id, doc in documents.items():
doc_list.append(f"- {file_id}: {doc['filename']} (Status: {doc['status']})")
return "Available documents:\n" + "\n".join(doc_list)
@mcp.tool()
def get_document_summary(file_id: str) -> str:
"""Get AI-generated summary of a specific document"""
# Always reload documents to get latest data
reload_documents()
if file_id not in documents:
return f"Document {file_id} not found."
doc = documents[file_id]
if "summary" not in doc:
return f"No summary available for document {file_id}."
summary = doc["summary"]
if summary["type"] == "single_chunk":
return f"Document: {doc['filename']}\n\nSummary:\n{summary['summary']}"
else:
# Multi-chunk summary
result = f"Document: {doc['filename']}\n\nOverall Summary:\n{summary['overall_summary']}\n\n"
result += "Chunk Summaries:\n"
for i, chunk_summary in enumerate(summary['chunk_summaries']):
result += f"\nChunk {i+1}:\n{chunk_summary}\n"
return result
@mcp.tool()
def get_document_content(file_id: str, chunk_id: Optional[int] = None) -> str:
"""Get full content of a specific document or a specific chunk"""
# Always reload documents to get latest data
reload_documents()
if file_id not in documents:
return f"Document {file_id} not found."
doc = documents[file_id]
if chunk_id is not None:
# Get specific chunk
if "chunks" in doc and chunk_id < len(doc["chunks"]):
chunk = doc["chunks"][chunk_id]
return f"Document: {doc['filename']}\nChunk {chunk_id} (Pages {chunk['page_range']}):\n\n{chunk['text']}"
else:
return f"Chunk {chunk_id} not found in document {file_id}."
else:
# Get full content
if "extracted_text" in doc:
return f"Document: {doc['filename']}\n\nFull Content:\n{doc['extracted_text']}"
else:
return f"Content not available for document {file_id}."
@mcp.tool()
def search_documents(query: str, file_id: Optional[str] = None) -> str:
"""Search for specific content across all documents or within a specific document"""
# Always reload documents to get latest data
reload_documents()
results = []
documents_to_search = {file_id: documents[file_id]} if file_id else documents
for doc_id, doc in documents_to_search.items():
if "extracted_text" not in doc:
continue
text = doc["extracted_text"].lower()
query_lower = query.lower()
if query_lower in text:
# Find context around matches
lines = doc["extracted_text"].split('\n')
matching_lines = []
for i, line in enumerate(lines):
if query_lower in line.lower():
# Include context (previous and next lines)
start = max(0, i-2)
end = min(len(lines), i+3)
context = lines[start:end]
matching_lines.append(f"Line {i+1}: " + "\n".join(context))
if matching_lines:
results.append(f"Document: {doc['filename']} (ID: {doc_id})")
results.extend(matching_lines[:5]) # Limit results
results.append("")
if not results:
return f"No results found for query: {query}"
return f"Search results for '{query}':\n\n" + "\n".join(results)
@mcp.tool()
def get_document_metadata(file_id: str) -> str:
"""Get metadata about a document (pages, size, processing time, etc.)"""
# Always reload documents to get latest data
reload_documents()
if file_id not in documents:
return f"Document {file_id} not found."
doc = documents[file_id]
metadata = {
"File ID": file_id,
"Filename": doc.get("filename", "N/A"),
"Status": doc.get("status", "N/A"),
"Processed At": doc.get("processed_at", "N/A"),
"Chunks": len(doc.get("chunks", [])),
"Total Tokens": sum(chunk.get("tokens", 0) for chunk in doc.get("chunks", [])),
"Has Summary": "summary" in doc
}
if "summary" in doc:
metadata["Summary Type"] = doc["summary"].get("type", "N/A")
text = f"Metadata for {doc.get('filename', file_id)}:\n\n"
text += "\n".join([f"{key}: {value}" for key, value in metadata.items()])
return text
@mcp.tool()
def answer_question(question: str, file_id: str) -> str:
"""Answer a question about a specific document using its content and summary"""
# Always reload documents to get latest data
reload_documents()
if file_id not in documents:
return f"Document {file_id} not found."
doc = documents[file_id]
# Get document summary for context
summary_text = "No summary available"
if "summary" in doc:
summary = doc["summary"]
if summary["type"] == "single_chunk":
summary_text = summary["summary"]
else:
summary_text = summary["overall_summary"]
# Get document content (truncated for context)
content = doc.get("extracted_text", "")
content_preview = content[:5000] + "..." if len(content) > 5000 else content
# Return context for the question
return f"""Question: {question}
Document: {doc['filename']}
Summary: {summary_text}
Content Preview: {content_preview}
To get a detailed AI-powered answer, please use the Anthropic API integration in the main application."""
# Helper functions for document management
def update_document_data(file_id: str, document_data: Dict[str, Any]):
"""Update document data in MCP server"""
documents[file_id] = document_data
logger.info(f"Updated document data for {file_id}")
def remove_document(file_id: str):
"""Remove document from MCP server"""
if file_id in documents:
del documents[file_id]
logger.info(f"Removed document {file_id} from MCP server")
def get_server():
"""Get the FastMCP server instance"""
return mcp