fix: align example config with reviewer merger separation

This commit is contained in:
2026-07-08 02:19:37 -04:00
parent a863928661
commit 76d1417c28
5 changed files with 75 additions and 8 deletions
+12 -1
View File
@@ -22,9 +22,12 @@ launched with exactly one static execution profile:
| Namespace (MCP server name) | Profile (role) | Typical use |
|-----------------------------|----------------|-------------|
| `gitea-author` | an author profile | implement issues, push branches, open PRs, comment |
| `gitea-reviewer` | a reviewer profile | review, approve/request changes, merge |
| `gitea-reviewer` | a reviewer profile | review, approve/request changes |
| `gitea-merger` | a merger profile | merge PRs after approval and verification |
| `gitea-reconciler` | a reconciler profile | close already-landed open PRs after ancestry proof (#304 profile; #310 close tool) |
Review and merge are separate workflow roles. A reviewer approval is not merge authorization.
Properties:
- **One process, one credential.** Each namespace authenticates as exactly
@@ -106,6 +109,14 @@ syntax to the client):
"GITEA_MCP_CONFIG": "<path-to-profiles.json>",
"GITEA_MCP_PROFILE": "<reviewer-profile-name>"
}
},
"gitea-merger": {
"command": "<path-to>/venv/bin/python3",
"args": ["<path-to>/mcp_server.py"],
"env": {
"GITEA_MCP_CONFIG": "<path-to-profiles.json>",
"GITEA_MCP_PROFILE": "<merger-profile-name>"
}
}
}
}
+2 -1
View File
@@ -311,7 +311,8 @@ To make Gitea MCP profile activation and runtime identity state explicit, the fo
### 2. Dual MCP Namespaces Recommendation
For security-sensitive or high-risk tasks, the preferred safety model uses separate, isolated MCP server instances (namespaces/sessions) launched with static profiles:
- `gitea-author`: Exposes tools configured with author permissions; cannot perform approvals or merges.
- `gitea-reviewer`: Exposes tools configured with reviewer permissions; used for PR reviews and merges.
- `gitea-reviewer`: Exposes tools configured with reviewer permissions; used for PR reviews. Review and merge are separate workflow roles. A reviewer approval is not merge authorization.
- `gitea-merger`: Exposes tools configured with merger permissions; used for PR merges.
This layout maintains physical separation of credentials and prevents privilege escalation within a single session.
This is the model accepted in #139; deployment details, rationale, and client
setup live in
+12 -3
View File
@@ -16,12 +16,21 @@ bind an authenticated Gitea identity to an allowed operation set.
- **Allowed:** branch create/push, PR create, issue comment/create/close, repo commit, read.
- **Forbidden:** PR approve, merge, request_changes.
### Reviewer / merger
### Reviewer
- **Profile:** `prgs-reviewer`
- **Typical identity:** `sysadmin`
- **Allowed:** PR review/approve/merge/request_changes, issue comment, read.
- **Forbidden:** branch push, PR create, repo commit.
- **Allowed:** PR review/approve/request_changes, issue comment, read.
- **Forbidden:** branch push, PR create, repo commit, PR merge.
Review and merge are separate workflow roles. A reviewer approval is not merge authorization.
### Merger
- **Profile:** `prgs-merger`
- **Typical identity:** `sysadmin`
- **Allowed:** PR merge, issue comment, read.
- **Forbidden:** branch push, PR create, repo commit, PR approve, PR review, PR request_changes.
## Configuration
+15 -3
View File
@@ -52,8 +52,19 @@
"execution_profile": "example-reviewer",
"audit_label": "example-reviewer",
"auth": { "type": "keychain", "id": "example-gitea-reviewer-token" },
"allowed_operations": ["read", "review", "comment", "issue.comment", "approve", "request_changes", "merge"],
"forbidden_operations": ["branch", "commit", "push", "open_pr"]
"allowed_operations": ["read", "review", "comment", "issue.comment", "approve", "request_changes"],
"forbidden_operations": ["branch", "commit", "push", "open_pr", "merge"]
},
"example-merger": {
"enabled": true,
"context": "example-context",
"role": "merger",
"username": "reviewer-user",
"execution_profile": "example-merger",
"audit_label": "example-merger",
"auth": { "type": "keychain", "id": "example-gitea-reviewer-token" },
"allowed_operations": ["read", "comment", "issue.comment", "merge"],
"forbidden_operations": ["branch", "commit", "push", "open_pr", "approve", "review", "request_changes"]
}
},
"projects": {
@@ -63,7 +74,8 @@
"default_owner": "Example-Org",
"default_repo": "Example-Repo",
"default_author_profile": "example-author",
"default_reviewer_profile": "example-reviewer"
"default_reviewer_profile": "example-reviewer",
"default_merger_profile": "example-merger"
}
},
"rules": {
+34
View File
@@ -435,6 +435,40 @@ class ValidateConfigTests(unittest.TestCase):
with open(example, encoding="utf-8") as fh:
self.assertEqual(gitea_config.validate_config(json.load(fh)), [])
def test_repo_example_file_reviewer_vs_merger_boundaries(self):
example_path = __import__("pathlib").Path(__file__).resolve().parent.parent \
/ "gitea-mcp.v2-contexts.example.json"
with open(example_path, encoding="utf-8") as fh:
cfg = json.load(fh)
# 1. Config includes separate reviewer and merger examples
self.assertIn("example-reviewer", cfg["profiles"])
self.assertIn("example-merger", cfg["profiles"])
reviewer = cfg["profiles"]["example-reviewer"]
merger = cfg["profiles"]["example-merger"]
# 2. Example reviewer config does not include merge capability
reviewer_allowed = reviewer.get("allowed_operations") or []
reviewer_forbidden = reviewer.get("forbidden_operations") or []
self.assertNotIn("merge", reviewer_allowed)
self.assertNotIn("gitea.pr.merge", reviewer_allowed)
self.assertIn("merge", reviewer_forbidden)
# 3. Example reviewer can resolve review task, cannot resolve merge task
# 4. Example merger can resolve merge task
rev_review_ok, _ = gitea_config.check_operation("gitea.pr.review", reviewer_allowed, reviewer_forbidden)
rev_merge_ok, _ = gitea_config.check_operation("gitea.pr.merge", reviewer_allowed, reviewer_forbidden)
merg_allowed = merger.get("allowed_operations") or []
merg_forbidden = merger.get("forbidden_operations") or []
merg_merge_ok, _ = gitea_config.check_operation("gitea.pr.merge", merg_allowed, merg_forbidden)
self.assertTrue(rev_review_ok, "example-reviewer must allow review")
self.assertFalse(rev_merge_ok, "example-reviewer must not allow merge")
self.assertTrue(merg_merge_ok, "example-merger must allow merge")
def test_broken_contexts_config_reports_problems(self):
data = contexts_config()
data["profiles"]["prgs-author"]["context"] = "nope"