"""System CRUD + record-association MCP tools — wrappers over services/systems.py. A System is a per-project, reusable, self-describing subsystem/area that any record (note, task, or issue) can be associated with — so research, build-work, and corrective work line up under the same area and recurring problem-spots are visible. The service enforces the multi-user ACL (project permission); these tools are thin wrappers. Sentinels (match the milestone/task tool conventions): - name="" / description="" / color="" / status="" → "leave unchanged" on update - order_index=-1 → "leave unchanged" on update (0 is a valid order_index) """ from __future__ import annotations from scribe.mcp._context import current_user_id from scribe.services import systems as systems_svc async def create_system( project_id: int, name: str, description: str = "", color: str = "", ) -> dict: """Create a System (a reusable, self-describing subsystem/area) in a project. Associate records with it via the `system_ids` arg on create/update_task and create/update_note. Args: project_id: The project this system belongs to (required). name: Short label (required). description: What the system is and how it's used — a name is rarely enough. color: Optional UI accent (hex), or empty. """ uid = current_user_id() system = await systems_svc.create_system( uid, project_id=project_id, name=name, description=description or None, color=color or None, ) if system is None: raise ValueError(f"cannot create system in project {project_id} (no write access)") return system.to_dict() async def list_systems(project_id: int, include_archived: bool = False) -> dict: """List a project's systems (active by default), ordered by order_index. Pass include_archived=True to include archived systems. """ uid = current_user_id() rows = await systems_svc.list_systems(uid, project_id, include_archived=include_archived) return {"systems": [s.to_dict() for s in rows]} async def get_system(system_id: int) -> dict: """Fetch a System plus the records associated with it. Returns the system, plus its associated records split into `issues`, `tasks` (work/plan), and `notes`. """ uid = current_user_id() system = await systems_svc.get_system(uid, system_id) if system is None: raise ValueError(f"system {system_id} not found") records = await systems_svc.list_records_for_system(uid, system_id) issues, tasks, notes = [], [], [] for r in records: d = r.to_dict() if r.status is None: notes.append(d) elif r.task_kind == "issue": issues.append(d) else: tasks.append(d) data = system.to_dict() data["issues"] = issues data["tasks"] = tasks data["notes"] = notes return data async def update_system( system_id: int, name: str = "", description: str = "", color: str = "", status: str = "", order_index: int = -1, ) -> dict: """Update a System. Only explicitly provided fields change. Args: status: 'active' or 'archived'. Archive a system to retire it without losing history; archived systems hide from default lists. order_index: display position (0-based); -1 = leave unchanged. """ uid = current_user_id() fields: dict = {} if name: fields["name"] = name if description: fields["description"] = description if color: fields["color"] = color if status: fields["status"] = status if order_index >= 0: fields["order_index"] = order_index system = await systems_svc.update_system(uid, system_id, **fields) if system is None: raise ValueError(f"system {system_id} not found or no write access") return system.to_dict() async def list_system_records( system_id: int, kind: str = "", open_only: bool = False ) -> dict: """List records associated with a System. Args: kind: filter by task_kind — 'issue', 'work', or 'plan'. Omit for all. open_only: limit to tasks not done/cancelled (e.g. open issues only). """ uid = current_user_id() rows = await systems_svc.list_records_for_system( uid, system_id, kind=kind or None, open_only=open_only, ) return {"records": [r.to_dict() for r in rows]} async def delete_system(system_id: int) -> dict: """Soft-delete a System (recoverable). Its record associations are removed.""" uid = current_user_id() ok = await systems_svc.delete_system(uid, system_id) if not ok: raise ValueError(f"system {system_id} not found or no write access") return {"message": f"System {system_id} deleted."} def register(mcp) -> None: for fn in ( create_system, list_systems, get_system, update_system, list_system_records, delete_system, ): mcp.tool(name=fn.__name__)(fn)