"""Ezer Mishpati - MCP Server entry point. Run with: python -m legal_mcp.server """ from __future__ import annotations import logging import sys from collections.abc import AsyncIterator from contextlib import asynccontextmanager from mcp.server.fastmcp import FastMCP # Configure logging to stderr (stdout is reserved for JSON-RPC) logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", stream=sys.stderr, ) logger = logging.getLogger("legal_mcp") @asynccontextmanager async def lifespan(server: FastMCP) -> AsyncIterator[None]: """Initialize DB schema on startup, close pool on shutdown.""" from legal_mcp.services.db import close_pool, init_schema logger.info("Initializing database schema...") await init_schema() logger.info("Ezer Mishpati MCP server ready") try: yield finally: await close_pool() logger.info("Ezer Mishpati MCP server stopped") # Create MCP server mcp = FastMCP( "Ezer Mishpati - עוזר משפטי", instructions="מערכת AI לסיוע בניסוח החלטות משפטיות בסגנון דפנה תמיר", lifespan=lifespan, ) # ── Import and register tools ─────────────────────────────────────── from legal_mcp.tools import cases, documents, search, drafting, workflow # noqa: E402 # Case management @mcp.tool() async def case_create( case_number: str, title: str, appellants: list[str] | None = None, respondents: list[str] | None = None, subject: str = "", property_address: str = "", permit_number: str = "", committee_type: str = "ועדה מקומית", hearing_date: str = "", notes: str = "", expected_outcome: str = "", ) -> str: """יצירת תיק ערר חדש. expected_outcome: rejection/partial_acceptance/full_acceptance/betterment_levy.""" return await cases.case_create( case_number, title, appellants, respondents, subject, property_address, permit_number, committee_type, hearing_date, notes, expected_outcome, ) @mcp.tool() async def case_list(status: str = "", limit: int = 50) -> str: """רשימת תיקי ערר. סינון אופציונלי לפי סטטוס (new/in_progress/drafted/reviewed/final).""" return await cases.case_list(status, limit) @mcp.tool() async def case_get(case_number: str) -> str: """פרטי תיק מלאים כולל רשימת מסמכים.""" return await cases.case_get(case_number) @mcp.tool() async def case_update( case_number: str, status: str = "", title: str = "", subject: str = "", notes: str = "", hearing_date: str = "", decision_date: str = "", tags: list[str] | None = None, expected_outcome: str = "", ) -> str: """עדכון פרטי תיק. expected_outcome: rejection/partial_acceptance/full_acceptance/betterment_levy.""" return await cases.case_update( case_number, status, title, subject, notes, hearing_date, decision_date, tags, expected_outcome, ) # Documents @mcp.tool() async def document_upload( case_number: str, file_path: str, doc_type: str = "appeal", title: str = "", ) -> str: """העלאה ועיבוד מסמך לתיק ערר (PDF/DOCX/RTF/TXT). מחלץ טקסט ויוצר embeddings.""" return await documents.document_upload(case_number, file_path, doc_type, title) @mcp.tool() async def document_upload_training( file_path: str, decision_number: str = "", decision_date: str = "", subject_categories: list[str] | None = None, title: str = "", ) -> str: """העלאת החלטה קודמת של דפנה לקורפוס הסגנון. קטגוריות: בנייה, שימוש חורג, תכנית, היתר, הקלה, חלוקה, תמ"א 38, היטל השבחה, פיצויים 197.""" return await documents.document_upload_training( file_path, decision_number, decision_date, subject_categories, title, ) @mcp.tool() async def document_get_text(case_number: str, doc_title: str = "") -> str: """קבלת טקסט מלא של מסמך מתוך תיק.""" return await documents.document_get_text(case_number, doc_title) @mcp.tool() async def document_list(case_number: str) -> str: """רשימת מסמכים בתיק.""" return await documents.document_list(case_number) # Claims extraction @mcp.tool() async def extract_claims( case_number: str, doc_title: str = "", party_hint: str = "", ) -> str: """חילוץ טענות מכתב טענות בתיק. מחלץ טענות לפי צד ושומר ב-DB.""" return await documents.extract_claims(case_number, doc_title, party_hint) @mcp.tool() async def get_claims( case_number: str, party_role: str = "", ) -> str: """שליפת טענות שחולצו לתיק. party_role: appellant/respondent/committee/permit_applicant (ריק=הכל).""" return await documents.get_claims(case_number, party_role) # References @mcp.tool() async def extract_references( case_number: str, doc_title: str = "", ) -> str: """זיהוי תכניות, פסיקה וחקיקה מתוך מסמכי תיק. מזהה ומקשר להפניות קיימות ב-DB.""" return await documents.extract_references(case_number, doc_title) # Search @mcp.tool() async def search_decisions( query: str, limit: int = 10, section_type: str = "", ) -> str: """חיפוש סמנטי בהחלטות קודמות ובמסמכים.""" return await search.search_decisions(query, limit, section_type) @mcp.tool() async def search_case_documents( case_number: str, query: str, limit: int = 10, ) -> str: """חיפוש סמנטי בתוך מסמכי תיק ספציפי.""" return await search.search_case_documents(case_number, query, limit) @mcp.tool() async def find_similar_cases( description: str, limit: int = 5, ) -> str: """מציאת תיקים דומים על בסיס תיאור.""" return await search.find_similar_cases(description, limit) # Drafting @mcp.tool() async def get_style_guide() -> str: """שליפת דפוסי הסגנון של דפנה - נוסחאות, ביטויים אופייניים ומבנה.""" return await drafting.get_style_guide() @mcp.tool() async def draft_section( case_number: str, section: str, instructions: str = "", ) -> str: """הרכבת הקשר מלא לניסוח סעיף (עובדות + תקדימים + סגנון).""" return await drafting.draft_section(case_number, section, instructions) @mcp.tool() async def get_chair_directions(case_number: str) -> str: """שליפת עמדות יו"ר הוועדה (דפנה) על סוגיות הערר כ-direction_doc לכותב. קורא מ-analysis-and-research.md שמולא ע"י דפנה דרך ה-UI. מחזיר סטטוס (missing/empty/partial/complete) + עמדות מובנות. """ return await drafting.get_chair_directions(case_number) @mcp.tool() async def get_decision_template(case_number: str) -> str: """תבנית מבנית להחלטה מלאה עם פרטי התיק.""" return await drafting.get_decision_template(case_number) @mcp.tool() async def get_block_context( case_number: str, block_id: str, instructions: str = "", ) -> str: """קבלת הקשר מלא לכתיבת בלוק — ללא API. Claude Code כותב ושומר.""" return await drafting.get_block_context(case_number, block_id, instructions) @mcp.tool() async def save_block_content( case_number: str, block_id: str, content: str, ) -> str: """שמירת בלוק שנכתב ע"י Claude Code ב-DB.""" return await drafting.save_block_content(case_number, block_id, content) @mcp.tool() async def validate_decision(case_number: str) -> str: """בדיקת QA — 6 בדיקות איכות על ההחלטה. אם בדיקה קריטית נכשלת — ייצוא חסום.""" return await drafting.validate_decision(case_number) @mcp.tool() async def export_docx(case_number: str, output_path: str = "") -> str: """ייצוא החלטה לקובץ DOCX מעוצב — גופן David, RTL, כותרות, מספור סעיפים.""" return await drafting.export_docx(case_number, output_path) @mcp.tool() async def analyze_style() -> str: """ניתוח סגנון על קורפוס ההחלטות של דפנה. מחלץ ושומר דפוסי כתיבה.""" return await drafting.analyze_style() @mcp.tool() async def write_block( case_number: str, block_id: str, instructions: str = "", ) -> str: """כתיבת בלוק יחיד בהחלטה: block-alef עד block-yod-bet. שומר ב-DB.""" return await drafting.write_block(case_number, block_id, instructions) @mcp.tool() async def write_all_blocks( case_number: str, start_from: str = "block-alef", instructions: str = "", ) -> str: """כתיבת כל הבלוקים בהחלטה, בלוק אחרי בלוק. שומר כל בלוק מיד.""" return await drafting.write_all_blocks(case_number, start_from, instructions) # Workflow @mcp.tool() async def workflow_status(case_number: str) -> str: """סטטוס תהליך עבודה מלא לתיק.""" return await workflow.workflow_status(case_number) @mcp.tool() async def get_metrics(case_number: str = "") -> str: """מדדי הצלחה — KPIs לתיק ספציפי או דשבורד כולל. ריק = דשבורד.""" return await workflow.get_metrics(case_number) @mcp.tool() async def processing_status() -> str: """סטטוס כללי - מספר תיקים, מסמכים, chunks.""" return await workflow.processing_status() # Outcome & Brainstorming @mcp.tool() async def set_outcome( case_number: str, outcome: str, reasoning: str = "", ) -> str: """הזנת תוצאה לתיק: rejected (דחייה), accepted (קבלה), partial (קבלה חלקית). אם אין נימוק — מפעיל סיעור מוחות.""" return await workflow.set_outcome(case_number, outcome, reasoning) @mcp.tool() async def brainstorm_directions(case_number: str) -> str: """סיעור מוחות — הצגת טענות מרכזיות והצעת 2-3 כיוונים אפשריים לנימוק ההחלטה.""" return await workflow.brainstorm_directions(case_number) @mcp.tool() async def approve_direction( case_number: str, direction_index: int = 0, additional_notes: str = "", ) -> str: """אישור כיוון — יוצר מסמך כיוון מאושר. חובה לפני כתיבת דיון (בלוק י).""" return await workflow.approve_direction(case_number, direction_index, additional_notes) @mcp.tool() async def ingest_final_version( case_number: str, file_path: str = "", final_text: str = "", ) -> str: """קליטת גרסה סופית שדפנה חתמה — השוואה לטיוטה וחילוץ לקחים לשיפור עתידי.""" return await workflow.ingest_final_version(case_number, file_path, final_text) def main(): mcp.run(transport="stdio") if __name__ == "__main__": main()