# MCP Gitea Server Refactor: Compatibility Matrix & Staged Plan

- **Status:** Staging/Design (First phase of #65)
- **Issue:** #65 (Staged refactor of `mcp_server.py` into a modular package)
- **Date:** 2026-07-02

## 1. Overview and Refactoring Contract
The goal of this refactor is to split the monolith `mcp_server.py` (~1689 lines) into a clean, maintainable, and modular Python package (`gitea_tools`).

To ensure complete backward compatibility, we establish a strict contract:
* **No functional changes:** Code behaviour, API endpoint targets, parameter sets, and return formats must remain identical.
* **No gate bypasses:** Allowed operations, forbidden operations, identity resolving, and audit logging must continue to execute exactly as they do in the monolith.
* **Independent testing:** The full pytest suite must pass with 100% success after every single stage.

---

## 2. Compatibility Matrix

The following table documents every MCP tool's expected signature, parameters, return payload shape, and error behavior that must be preserved.

### 2.1 Issue & Label Management Tools

| Tool Name | Parameters | Return Payload Shape | Error Behavior / Edge Cases |
| :--- | :--- | :--- | :--- |
| `gitea_create_issue` | `title: str`, `body: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict containing issue details (`number`, `title`, `body`, `state`, `labels`, `assignee`, `url`) | Raises error on auth failure, missing parameters, or Gitea API validation error. |
| `gitea_close_issue` | `issue_number: int`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict detailing the closed issue. | Raises 404 if issue doesn't exist; fails closed if user has insufficient permission. |
| `gitea_list_issues` | `state: str`, `label: str \| None`, `limit: int`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | List of dicts representing matched issues. | Limits pagination per page and overall maximum caps. |
| `gitea_view_issue` | `issue_number: int`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict of detailed issue attributes. | Returns clear 404 error if not found. |
| `gitea_mark_issue` | `issue_number: int`, `action: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict indicating current label states (presence of `status:in-progress`). | Rejects unknown actions; fails if label doesn't exist on Gitea. |
| `gitea_list_labels` | `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | List of dicts representing labels. | Basic auth error fallback behavior. |
| `gitea_create_label` | `name: str`, `color: str`, `description: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict of created label properties. | Fails on duplicate names or invalid color hex formats. |
| `gitea_set_issue_labels` | `issue_number: int`, `labels: list[str]`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | List of all labels currently applied to the issue. | Fails closed if any label name does not exist. |

### 2.2 PR & Review Management Tools

| Tool Name | Parameters | Return Payload Shape | Error Behavior / Edge Cases |
| :--- | :--- | :--- | :--- |
| `gitea_create_pr` | `title: str`, `head: str`, `base: str`, `body: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict detailing the created PR. | Fails on missing branches, existing duplicate PR, or invalid base branch. |
| `gitea_list_prs` | `state: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | List of dicts representing open/closed PRs. | Standard limits apply. |
| `gitea_view_pr` | `pr_number: int`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict of detailed PR attributes. | Fails if PR does not exist. |
| `gitea_check_pr_eligibility` | `pr_number: int`, `action: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict: `{"eligible": bool, "reasons": list[str]}` | Non-gated, safe, read-only. Fails on invalid actions. |
| `gitea_submit_pr_review` | `pr_number: int`, `action: str`, `body: str`, `expected_head_sha: str \| None`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict of submitted review properties. | Rejects self-review; fails if head SHA has changed in the meantime. |
| `gitea_edit_pr` | `pr_number: int`, `title: str \| None`, `body: str \| None`, `state: str \| None`, `base: str \| None`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict of updated PR attributes. | Fails on invalid fields or if PR state transition is blocked. |
| `gitea_merge_pr` | `pr_number: int`, `confirmation: str`, `expected_head_sha: str \| None`, `expected_changed_files: list[str] \| None`, `do: str`, `title: str \| None`, `message: str \| None`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict of merge result details. | Fails if any gating eligibility checks fail (e.g. self-merge, wrong confirmation, SHA mismatch). |
| `gitea_review_pr` | `pr_number: int`, `event: str`, `body: str`, `merge: bool`, `merge_method: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict representing legacy review output. | Backward compatibility wrapper; delegates to review/merge logic. |
| `gitea_delete_branch` | `branch: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict indicating branch deletion status. | Fails on protected branches or non-existent refs. |

### 2.3 File, Identity, and Utility Tools

| Tool Name | Parameters | Return Payload Shape | Error Behavior / Edge Cases |
| :--- | :--- | :--- | :--- |
| `gitea_get_file` | `filepath: str`, `ref: str`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict containing metadata and Base64 content of the target file. | Fails if path or reference branch does not exist. |
| `gitea_commit_files` | `files: list[dict]`, `message: str`, `branch: str \| None`, `new_branch: str \| None`, `remote: str`, `host: str \| None`, `org: str \| None`, `repo: str \| None` | Dict describing commit hash and ref state. | Fails on file path conflicts or commit collisions. |
| `gitea_whoami` | `remote: str`, `host: str \| None` | Dict detailing verified login user (e.g., `sysadmin`). | Alias targets: `gitea_get_authenticated_user`, `gitea_get_current_user` must be preserved. |
| `gitea_get_profile` | `remote: str`, `host: str \| None`, `resolve_identity: bool` | Dict of loaded profile constraints and active configuration details. | Fails closed on invalid/missing profile specs. |
| `gitea_mirror_refs` | `apply: bool`, `force: bool` | Dict summarizing mirrored branch/tag logs. | Fails on Git CLI mirror action exceptions. |

---

## 3. Staged Refactoring Plan

We will perform the refactoring in five discrete stages. Each stage will land as its own independent PR to master, verifying that the codebase compiles and passes the complete test suite at each step.

### Stage 1: API and Client Core Extraction
* **Goal:** Extract common network request wrappers, pagination handlers, and HTTP exception conversions.
* **Target File:** `gitea_tools/client.py`
* **Contents:** `api_request`, `api_get_all`, HTTP error maps, and token/credential redaction helper `_redact`.

### Stage 2: Auth and Configuration Extraction
* **Goal:** Extract Gitea profile parsers, credential loading logic, and helper scripts.
* **Target File:** `gitea_tools/config.py`
* **Contents:** `get_auth_header`, `get_profile`, `repo_api_url`, and profile config schemas.

### Stage 3: Audit Logging and Security Gates
* **Goal:** Extract security filters, audit logging mechanisms, and metadata decorators.
* **Target File:** `gitea_tools/audit.py`
* **Contents:** `AuditSink`, `_audited`, and audit message templates.

### Stage 4: Tool Implementations (Domain-Driven Modules)
* **Goal:** Group and move the core implementation logic of the 24 tools out of `mcp_server.py`.
* **Target Files:**
  * `gitea_tools/issues.py` — Issues, labels, and mark status tools.
  * `gitea_tools/prs.py` — PRs, reviews, merge gating, and branch delete.
  * `gitea_tools/files.py` — File retrieval and atomic commits.
  * `gitea_tools/identity.py` — whoami and runtime profile descriptions.
  * `gitea_tools/utilities.py` — Mirroring scripts and miscellaneous tasks.

### Stage 5: Final Tool Registration Layer
* **Goal:** Clean up the root `mcp_server.py` to be a pure registration layer.
* **Contents:** Imports the modular functions from the `gitea_tools` package and wraps them inside the standard FastMCP `@mcp.tool()` decorators.
