""" Module Registry — controls which tool modules are active. Enable/disable via .env: ENABLED_MODULES=documents,doctypes,users → whitelist mode MODULE_SCHEDULER=false → disable one module MODULE_GOVERNANCE=false → disable another Default: all modules enabled. """ import os from typing import Callable from mcp.types import Tool # ── Import all tool modules ────────────────────────────────────────────────── from frappe_mcp.tools import ( foundation, doctypes, documents, document_inspect, document_lifecycle, custom_fields, scripts, workflow_tools, users, admin, analytics, print_formats, email_templates, property_setters, naming_series, bulk_ops, files, activity, dashboards, webhooks, reports, scheduler, translations, business_actions, governance, ) # ── Module definitions ─────────────────────────────────────────────────────── # (module_key, module_object, description) ALL_MODULES: list[tuple[str, object, str]] = [ # Level 1 — Foundation ("foundation", foundation, "L1: ping, session_info, doctype_meta, permissions, modules"), # Level 2 — Read ("doctypes", doctypes, "L1-2: DocType CRUD — create, read, update, list"), ("documents", documents, "L2-3: Document CRUD — create, read, update, delete, submit, cancel"), ("document_inspect", document_inspect, "L2: Search, children, linked docs, timeline, count, attachments"), # Level 3-4 — Write + Lifecycle ("document_lifecycle", document_lifecycle, "L3-4: Child rows, rename, amend, duplicate, status, draft save"), ("custom_fields", custom_fields, "L2/9: Custom Fields — add, edit, remove, list"), ("scripts", scripts, "L9: Server Scripts + Client Scripts"), # Level 5 — Workflow ("workflow_tools", workflow_tools, "L5: Workflows, transitions, approvals"), # Level 6 — Reporting ("analytics", analytics, "L6: Aggregate, dashboard data, PDF render, number cards"), ("reports", reports, "L6/9: Query/Script Report CRUD"), ("print_formats", print_formats, "L6: Print Format templates"), # Level 7 — Bulk ("bulk_ops", bulk_ops, "L7: Bulk create/update/delete/submit/cancel/assign/tag"), # Level 8 — Business Actions ("business_actions", business_actions, "L8: ERPNext shortcuts — Sales, Buying, Stock, HR, Support, Projects"), # Level 9 — Admin / System Intelligence ("users", users, "L9: Users, Roles, DocType permissions"), ("admin", admin, "L9: Cache, system settings, SQL, workflows, scheduler"), ("property_setters", property_setters, "L9: Property Setters + Customize Form"), ("naming_series", naming_series, "L9: Naming Series — patterns and counters"), ("files", files, "L2/9: File Manager — upload, list, delete, move"), ("activity", activity, "L2/9: Comments, tags, assignments, ToDo, error logs"), ("dashboards", dashboards, "L6/9: Dashboards, charts, number cards, workspaces"), ("webhooks", webhooks, "L9: Webhooks + API Keys"), ("email_templates", email_templates, "L9: Email Templates + Notifications"), ("translations", translations, "L9: Translations, Assignment Rules, User Permissions"), ("scheduler", scheduler, "L9: Scheduled Jobs + Background Jobs"), # Level 10 — Safety & Governance ("governance", governance, "L10: Dry run, validate, risk score, audit log, rollback"), ] def _is_module_enabled(key: str) -> bool: """ Resolution order (first match wins): 1. MODULE_=false → disabled 2. MODULE_=true → enabled 3. ENABLED_MODULES=a,b → whitelist — only listed enabled 4. Default: enabled """ env_key = f"MODULE_{key.upper()}" explicit = os.environ.get(env_key, "").strip().lower() if explicit == "false": return False if explicit == "true": return True enabled_list = os.environ.get("ENABLED_MODULES", "").strip() if enabled_list: return key in [m.strip() for m in enabled_list.split(",")] return True def get_enabled_tools() -> list[Tool]: tools: list[Tool] = [] for key, module, _ in ALL_MODULES: if _is_module_enabled(key): tools.extend(module.tools()) return tools def get_enabled_handlers() -> dict[str, Callable]: handlers: dict[str, Callable] = {} for key, module, _ in ALL_MODULES: if _is_module_enabled(key): handlers.update(module.handlers()) return handlers def print_module_status(): print("\nFrappe MCP — Module Status") print("=" * 70) total_on = 0 total_all = 0 for key, module, desc in ALL_MODULES: enabled = _is_module_enabled(key) count = len(module.tools()) total_all += count if enabled: total_on += count status = "ON " if enabled else "OFF" print(f" [{status}] {key:<22} ({count:>2} tools) {desc}") print("=" * 70) print(f" Active: {total_on} tools | Total available: {total_all} tools\n")