Initial commit: MCP server + web upload interface
Ezer Mishpati - AI legal decision drafting system with: - MCP server (FastMCP) with document processing pipeline - Web upload interface (FastAPI) for file upload and classification - pgvector-based semantic search - Hebrew legal document chunking and embedding
This commit is contained in:
219
mcp-server/src/legal_mcp/server.py
Normal file
219
mcp-server/src/legal_mcp/server.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""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 = "",
|
||||
) -> str:
|
||||
"""יצירת תיק ערר חדש."""
|
||||
return await cases.case_create(
|
||||
case_number, title, appellants, respondents,
|
||||
subject, property_address, permit_number, committee_type,
|
||||
hearing_date, notes,
|
||||
)
|
||||
|
||||
|
||||
@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,
|
||||
) -> str:
|
||||
"""עדכון פרטי תיק."""
|
||||
return await cases.case_update(
|
||||
case_number, status, title, subject, notes,
|
||||
hearing_date, decision_date, tags,
|
||||
)
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
# 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_decision_template(case_number: str) -> str:
|
||||
"""תבנית מבנית להחלטה מלאה עם פרטי התיק."""
|
||||
return await drafting.get_decision_template(case_number)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def analyze_style() -> str:
|
||||
"""ניתוח סגנון על קורפוס ההחלטות של דפנה. מחלץ ושומר דפוסי כתיבה."""
|
||||
return await drafting.analyze_style()
|
||||
|
||||
|
||||
# Workflow
|
||||
@mcp.tool()
|
||||
async def workflow_status(case_number: str) -> str:
|
||||
"""סטטוס תהליך עבודה מלא לתיק."""
|
||||
return await workflow.workflow_status(case_number)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def processing_status() -> str:
|
||||
"""סטטוס כללי - מספר תיקים, מסמכים, chunks."""
|
||||
return await workflow.processing_status()
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
mcp.run(transport="stdio")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user