#!/usr/bin/env python3 """ Vela Platform — MCP Server (stdio transport). Provides tools and resources for interacting with the Vela Platform data. Includes component-level pricing breakdown for each offer. Usage: python mcp/server.py """ import json import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend")) from database import SessionLocal from models import Service, ServiceComponent, Transaction OPEX = 800.0 def compute_pnl(month: str) -> dict: """Compute P&L for a given month.""" db = SessionLocal() try: transactions = db.query(Transaction).filter(Transaction.month == month).all() total_revenue = sum(t.revenue for t in transactions) total_cost = sum(t.cost for t in transactions) gross_profit = total_revenue - total_cost gross_margin = (gross_profit / total_revenue * 100) if total_revenue > 0 else 0.0 net_profit = gross_profit - OPEX net_margin = (net_profit / total_revenue * 100) if total_revenue > 0 else 0.0 return { "month": month, "total_revenue": round(total_revenue, 2), "total_cost": round(total_cost, 2), "gross_profit": round(gross_profit, 2), "gross_margin_pct": round(gross_margin, 2), "opex": OPEX, "net_profit": round(net_profit, 2), "net_margin_pct": round(net_margin, 2), } finally: db.close() def get_services() -> list: """Get all services with their component breakdowns.""" db = SessionLocal() try: services = db.query(Service).order_by(Service.name).all() return [ { "id": s.id, "name": s.name, "description": s.description, "sell_price": s.sell_price, "cost_price": s.cost_price, "active": s.active, "margin": round(s.sell_price - s.cost_price, 2), "components": [ { "id": c.id, "name": c.name, "unit_cost": c.unit_cost, "unit_sell": c.unit_sell, "quantity": c.quantity, "row_cost": round(c.unit_cost * c.quantity, 2), "row_sell": round(c.unit_sell * c.quantity, 2), "notes": c.notes, } for c in s.components ], } for s in services ] finally: db.close() def get_service(service_id: int) -> dict: """Get a single service with components.""" db = SessionLocal() try: s = db.query(Service).filter(Service.id == service_id).first() if not s: return {"error": "Service not found"} return { "id": s.id, "name": s.name, "description": s.description, "sell_price": s.sell_price, "cost_price": s.cost_price, "active": s.active, "components": [ { "id": c.id, "name": c.name, "unit_cost": c.unit_cost, "unit_sell": c.unit_sell, "quantity": c.quantity, "row_cost": round(c.unit_cost * c.quantity, 2), "row_sell": round(c.unit_sell * c.quantity, 2), } for c in s.components ], } finally: db.close() def add_component(service_id: int, name: str, unit_cost: float, unit_sell: float, quantity: int = 1, notes: str = "") -> dict: """Add a component to a service.""" db = SessionLocal() try: svc = db.query(Service).filter(Service.id == service_id).first() if not svc: return {"error": "Service not found"} comp = ServiceComponent( service_id=service_id, name=name, unit_cost=unit_cost, unit_sell=unit_sell, quantity=quantity, notes=notes, ) db.add(comp) db.flush() svc.recompute_prices() db.commit() db.refresh(comp) return { "id": comp.id, "name": comp.name, "unit_cost": comp.unit_cost, "unit_sell": comp.unit_sell, "quantity": comp.quantity, } finally: db.close() def update_component(component_id: int, name: str = None, unit_cost: float = None, unit_sell: float = None, quantity: int = None, notes: str = None) -> dict: """Update a component.""" db = SessionLocal() try: comp = db.query(ServiceComponent).filter(ServiceComponent.id == component_id).first() if not comp: return {"error": "Component not found"} if name is not None: comp.name = name if unit_cost is not None: comp.unit_cost = unit_cost if unit_sell is not None: comp.unit_sell = unit_sell if quantity is not None: comp.quantity = quantity if notes is not None: comp.notes = notes db.flush() svc = db.query(Service).filter(Service.id == comp.service_id).first() if svc: svc.recompute_prices() db.commit() return {"ok": True, "id": comp.id} finally: db.close() def delete_component(component_id: int) -> dict: """Delete a component.""" db = SessionLocal() try: comp = db.query(ServiceComponent).filter(ServiceComponent.id == component_id).first() if not comp: return {"error": "Component not found"} svc = db.query(Service).filter(Service.id == comp.service_id).first() db.delete(comp) db.flush() if svc: svc.recompute_prices() db.commit() return {"ok": True} finally: db.close() def add_transaction(service_id: int, quantity: int, month: str, notes: str = "") -> dict: """Add a new transaction.""" db = SessionLocal() try: service = db.query(Service).filter(Service.id == service_id).first() if not service or not service.active: return {"error": "Invalid or inactive service"} txn = Transaction( service_id=service_id, quantity=quantity, month=month, notes=notes, created_by=1, ) db.add(txn) db.commit() db.refresh(txn) return { "id": txn.id, "service_name": txn.service.name, "quantity": txn.quantity, "revenue": txn.revenue, "cost": txn.cost, "month": txn.month, "notes": txn.notes, } finally: db.close() def get_dashboard() -> dict: """Get current month dashboard overview.""" from datetime import datetime, timezone current_month = datetime.now(timezone.utc).strftime("%Y-%m") pnl = compute_pnl(current_month) db = SessionLocal() try: month_rows = ( db.query(Transaction.month) .distinct() .order_by(Transaction.month.desc()) .limit(12) .all() ) month_list = [row[0] for row in reversed(month_rows)] trend = [compute_pnl(m) for m in month_list] return {"current_month": pnl, "trend": trend} finally: db.close() # --------------------------------------------------------------------------- # MCP Protocol Handler # --------------------------------------------------------------------------- def handle_request(request: dict) -> dict: method = request.get("method", "") req_id = request.get("id") if method == "initialize": return { "jsonrpc": "2.0", "id": req_id, "result": { "protocolVersion": "2024-11-05", "capabilities": {"tools": {}, "resources": {}}, "serverInfo": {"name": "vela-platform", "version": "1.0.0"}, }, } if method == "tools/list": return { "jsonrpc": "2.0", "id": req_id, "result": { "tools": [ { "name": "get_pnl", "description": "Get P&L data for a given month (YYYY-MM format).", "inputSchema": { "type": "object", "properties": { "month": {"type": "string", "description": "Month in YYYY-MM format"} }, "required": ["month"], }, }, { "name": "get_services", "description": "List all offers (services) with full component pricing breakdown.", "inputSchema": {"type": "object", "properties": {}}, }, { "name": "get_service", "description": "Get a single offer (service) with its component breakdown.", "inputSchema": { "type": "object", "properties": { "service_id": {"type": "integer", "description": "Service ID"} }, "required": ["service_id"], }, }, { "name": "add_component", "description": "Add a pricing component (RAM, HDD, Transport, etc.) to an offer.", "inputSchema": { "type": "object", "properties": { "service_id": {"type": "integer"}, "name": {"type": "string", "description": "Component name"}, "unit_cost": {"type": "number", "description": "Unit cost price"}, "unit_sell": {"type": "number", "description": "Unit sell price"}, "quantity": {"type": "integer", "description": "Quantity, default 1"}, "notes": {"type": "string", "description": "Optional notes"}, }, "required": ["service_id", "name", "unit_cost", "unit_sell"], }, }, { "name": "update_component", "description": "Update a pricing component. Only provided fields are changed.", "inputSchema": { "type": "object", "properties": { "component_id": {"type": "integer"}, "name": {"type": "string"}, "unit_cost": {"type": "number"}, "unit_sell": {"type": "number"}, "quantity": {"type": "integer"}, "notes": {"type": "string"}, }, "required": ["component_id"], }, }, { "name": "delete_component", "description": "Delete a pricing component from an offer.", "inputSchema": { "type": "object", "properties": { "component_id": {"type": "integer"} }, "required": ["component_id"], }, }, { "name": "add_transaction", "description": "Add a new transaction (sale).", "inputSchema": { "type": "object", "properties": { "service_id": {"type": "integer"}, "quantity": {"type": "integer"}, "month": {"type": "string", "description": "YYYY-MM format"}, "notes": {"type": "string"}, }, "required": ["service_id", "quantity", "month"], }, }, { "name": "get_dashboard", "description": "Get current month dashboard overview with 12-month trend.", "inputSchema": {"type": "object", "properties": {}}, }, ] }, } if method == "resources/list": return { "jsonrpc": "2.0", "id": req_id, "result": { "resources": [ { "uri": "vela://pnl/current", "name": "Current Month P&L", "description": "Profit & Loss for the current month", "mimeType": "application/json", }, { "uri": "vela://services", "name": "Service List with Components", "description": "All offers with component pricing breakdown", "mimeType": "application/json", }, { "uri": "vela://services/{id}", "name": "Single Service Detail", "description": "A specific offer with its components", "mimeType": "application/json", }, ] }, } if method == "resources/read": uri = request.get("params", {}).get("uri", "") if uri == "vela://pnl/current": from datetime import datetime, timezone month = datetime.now(timezone.utc).strftime("%Y-%m") content = json.dumps(compute_pnl(month), indent=2) elif uri == "vela://services": content = json.dumps(get_services(), indent=2) elif uri.startswith("vela://services/"): try: svc_id = int(uri.split("/")[-1]) content = json.dumps(get_service(svc_id), indent=2) except (ValueError, IndexError): return { "jsonrpc": "2.0", "id": req_id, "error": {"code": -32602, "message": f"Invalid resource URI: {uri}"}, } else: return { "jsonrpc": "2.0", "id": req_id, "error": {"code": -32602, "message": f"Unknown resource: {uri}"}, } return { "jsonrpc": "2.0", "id": req_id, "result": { "contents": [{"uri": uri, "mimeType": "application/json", "text": content}] }, } if method == "tools/call": tool_name = request.get("params", {}).get("name", "") args = request.get("params", {}).get("arguments", {}) try: if tool_name == "get_pnl": result = compute_pnl(args["month"]) elif tool_name == "get_services": result = get_services() elif tool_name == "get_service": result = get_service(args["service_id"]) elif tool_name == "add_component": result = add_component( args["service_id"], args["name"], args["unit_cost"], args["unit_sell"], args.get("quantity", 1), args.get("notes", ""), ) elif tool_name == "update_component": result = update_component( args["component_id"], args.get("name"), args.get("unit_cost"), args.get("unit_sell"), args.get("quantity"), args.get("notes"), ) elif tool_name == "delete_component": result = delete_component(args["component_id"]) elif tool_name == "add_transaction": result = add_transaction( args["service_id"], args["quantity"], args["month"], args.get("notes", ""), ) elif tool_name == "get_dashboard": result = get_dashboard() else: return { "jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Unknown tool: {tool_name}"}, } return { "jsonrpc": "2.0", "id": req_id, "result": { "content": [{"type": "text", "text": json.dumps(result, indent=2)}] }, } except Exception as e: return { "jsonrpc": "2.0", "id": req_id, "result": { "content": [{"type": "text", "text": json.dumps({"error": str(e)})}], "isError": True, }, } if "id" not in request: return None return { "jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Method not found: {method}"}, } def main(): from main import seed_database seed_database() sys.stderr.write("Vela Platform MCP Server starting...\n") sys.stderr.flush() for line in sys.stdin: line = line.strip() if not line: continue try: request = json.loads(line) response = handle_request(request) if response is not None: sys.stdout.write(json.dumps(response) + "\n") sys.stdout.flush() except json.JSONDecodeError as e: sys.stderr.write(f"JSON parse error: {e}\n") sys.stderr.flush() if __name__ == "__main__": main()