Skip to main content
Glama
main.py9.55 kB
"""FastAPI application implementing the RentSmart MCP service. The service exposes several endpoints required by the Puch AI platform: * `/health` – A simple heartbeat endpoint. * `/validate` – Checks a bearer token and returns a dummy phone number. * `/tool/generate_agreement` – Fills a rental agreement template and returns a publicly accessible PDF link along with metadata. * `/tool/generate_rent_receipt` – Fills a rent receipt template and returns a PDF link and metadata. * `/tool/stamp_duty_info` – Provides basic stamp duty information for different Indian states. Internally the service reads text templates from the `templates` directory, performs simple string substitution and then creates a minimal PDF file using custom logic. This avoids the need for heavy PDF dependencies and keeps the entire project self‑contained. """ # app/main.py import os import uuid from pathlib import Path from typing import Dict, Optional from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse, Response from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, validator from fpdf import FPDF # ------------------------------------------------------------------- # Config & paths # ------------------------------------------------------------------- VALID_TOKEN = os.environ.get("RENTSMART_VALID_TOKEN", "something_secret") BASE_DIR = Path(__file__).resolve().parent FILES_DIR = BASE_DIR.parent / "files" AGREEMENT_DIR = FILES_DIR / "agreements" RECEIPT_DIR = FILES_DIR / "receipts" AGREEMENT_DIR.mkdir(parents=True, exist_ok=True) RECEIPT_DIR.mkdir(parents=True, exist_ok=True) STAMP_DUTY_DATA: Dict[str, Dict[str, str]] = { "Karnataka": {"stamp_duty": "1% of annual rent + deposit", "link": "https://kaverionline.karnataka.gov.in/"}, "Maharashtra": {"stamp_duty": "0.25% of annual rent + deposit", "link": "https://gras.mahakosh.gov.in/"}, "Tamil Nadu": {"stamp_duty": "1% of annual rent", "link": "https://tnreginet.gov.in/"}, "Delhi": {"stamp_duty": "2% of average annual rent", "link": "https://www.shcilestamp.com/"}, } # ------------------------------------------------------------------- # App (single instance) # ------------------------------------------------------------------- app = FastAPI(title="RentSmart MCP", version="0.1.0") # CORS & static files app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] ) app.mount("/files", StaticFiles(directory=str(FILES_DIR)), name="files") # Root routes so load balancers / Puch won’t 404 on connect @app.get("/", response_class=HTMLResponse) async def index(): return """ <h2>RentSmart MCP</h2> <ul> <li><a href="/health">/health</a></li> <li><a href="/tools">/tools</a></li> </ul> """ @app.head("/") async def head_root(): return Response(status_code=200) @app.options("/{rest_of_path:path}") async def options_any(rest_of_path: str): return Response(status_code=204) # Root POST — some clients (incl. Puch) hit POST / during handshake @app.post("/") async def post_root(): return {"status": "ok"} # ------------------------------------------------------------------- # Helpers & models # ------------------------------------------------------------------- def format_rupee(value: str) -> str: try: return f"{int(str(value).replace(',', '').strip()):,}" except Exception: return value def generate_verification_code(length: int = 6) -> str: return uuid.uuid4().hex[:length].upper() def fill_template(template_path: Path, context: Dict[str, str]) -> str: template = template_path.read_text(encoding="utf-8") return template.format_map(context) def create_pdf_from_text(text: str, file_path: Path) -> None: text = text.replace("₹", "Rs.") pdf = FPDF() pdf.set_auto_page_break(auto=True, margin=15) pdf.add_page() pdf.set_font("Arial", size=12) for line in text.splitlines(): pdf.cell(0, 10, txt=line, ln=1) pdf.output(str(file_path)) class AgreementRequest(BaseModel): landlord: str; tenant: str; address: str rent: str; deposit: str; start_date: str; duration_months: str @validator("rent", "deposit", pre=True) def _to_str(cls, v): return str(v) class ReceiptRequest(BaseModel): landlord: str; tenant: str; address: str amount: str; month: str; year: str payment_mode: Optional[str] = ""; remarks: Optional[str] = "" @validator("amount", pre=True) def _to_str_amount(cls, v): return str(v) class StampDutyRequest(BaseModel): state: Optional[str] = None # ------------------------------------------------------------------- # Required endpoints # ------------------------------------------------------------------- @app.get("/health") async def health() -> Dict[str, str]: return {"status": "ok"} @app.api_route("/validate", methods=["GET", "POST"]) @app.post("/validate") async def validate(request: Request): # 1) Check for Bearer token in header auth_header = request.headers.get("Authorization", "") token = auth_header.replace("Bearer ", "").strip() # 2) If not present, fallback to ?token= query param if not token: token = request.query_params.get("token", "") # 3) Compare against expected token if token != VALID_TOKEN: raise HTTPException(status_code=401, detail="Unauthorized") return {"phone": "919999999999"} @app.get("/tools") def list_tools(): return { "server_id": "rentsmart-mcp", "name": "RentSmart MCP", "version": "1.0", "tools": [ {"name": "validate", "method": "POST", "path": "/validate", "description": "Validate bearer token and return the caller phone.", "input_schema": {"type": "object", "properties": {}}}, {"name": "generate_agreement", "method": "POST", "path": "/tool/generate_agreement", "description": "Generate a rental agreement PDF.", "input_schema": {"type": "object", "required": ["landlord","tenant","address","rent","deposit","start_date","duration_months"], "properties": {"landlord":{"type":"string"},"tenant":{"type":"string"},"address":{"type":"string"}, "rent":{"type":"string"},"deposit":{"type":"string"},"start_date":{"type":"string"}, "duration_months":{"type":"string"}}}}, {"name": "generate_rent_receipt", "method": "POST", "path": "/tool/generate_rent_receipt", "description": "Generate a monthly rent receipt.", "input_schema": {"type": "object", "required": ["landlord","tenant","address","amount","month","year"], "properties": {"landlord":{"type":"string"},"tenant":{"type":"string"},"address":{"type":"string"}, "amount":{"type":"string"},"month":{"type":"string"},"year":{"type":"string"}, "payment_mode":{"type":"string"},"remarks":{"type":"string"}}}} ] } def _gen_response(request: Request, subdir: str, filename: str, doc_id: str, code: str) -> Dict[str, str]: base = str(request.base_url).rstrip("/") link = f"{base}/files/{subdir}/{filename}" return {"answer": f"Your document is ready: {link}", "link": link, "id": doc_id, "verification_code": code} @app.post("/tool/generate_agreement") async def generate_agreement(req: AgreementRequest, request: Request) -> Dict[str, str]: doc_id = uuid.uuid4().hex[:8] code = generate_verification_code() ctx = {"landlord": req.landlord, "tenant": req.tenant, "address": req.address, "rent": format_rupee(req.rent), "deposit": format_rupee(req.deposit), "start_date": req.start_date, "duration_months": req.duration_months, "id": doc_id, "verification_code": code} filled = fill_template(BASE_DIR / "templates" / "agreement_template.txt", ctx) filename = f"agreement_{doc_id}.pdf" create_pdf_from_text(filled, AGREEMENT_DIR / filename) return _gen_response(request, "agreements", filename, doc_id, code) @app.post("/tool/generate_rent_receipt") async def generate_rent_receipt(req: ReceiptRequest, request: Request) -> Dict[str, str]: doc_id = uuid.uuid4().hex[:8] code = generate_verification_code() ctx = {"landlord": req.landlord, "tenant": req.tenant, "address": req.address, "amount": format_rupee(req.amount), "month": req.month, "year": req.year, "payment_mode": req.payment_mode or "", "remarks": req.remarks or "", "id": doc_id, "verification_code": code} filled = fill_template(BASE_DIR / "templates" / "receipt_template.txt", ctx) filename = f"receipt_{doc_id}.pdf" create_pdf_from_text(filled, RECEIPT_DIR / filename) return _gen_response(request, "receipts", filename, doc_id, code) @app.post("/tool/stamp_duty_info") async def stamp_duty_info(req: StampDutyRequest) -> Dict[str, object]: if req.state: key = next((k for k in STAMP_DUTY_DATA if k.lower() == req.state.lower()), None) if not key: return {"answer": f"Sorry, I don’t have stamp duty information for {req.state}."} return {"answer": f"Stamp duty information for {key}: {STAMP_DUTY_DATA[key]['stamp_duty']}.", "data": {key: STAMP_DUTY_DATA[key]}} return {"answer": "Stamp duty information for available states.", "data": STAMP_DUTY_DATA}

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/rashhh22/rentsmart_mcp'

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