feat: add read-only Gitea task capability resolver tool (#141)
This commit is contained in:
+146
@@ -2742,6 +2742,152 @@ def gitea_mirror_refs(
|
||||
"return_code": result.returncode,
|
||||
}
|
||||
|
||||
@mcp.tool()
|
||||
def gitea_resolve_task_capability(
|
||||
task: str,
|
||||
remote: str = "dadeschools",
|
||||
host: str | None = None,
|
||||
) -> dict:
|
||||
"""Read-only: Resolve which capability, profile, and namespace is required for a Gitea task.
|
||||
|
||||
Helps the client or LLM determine the correct namespace or profile before acting,
|
||||
and returns exact next action instructions if the current session is not authorized.
|
||||
|
||||
Args:
|
||||
task: The task/action to check (e.g. review_pr, create_issue).
|
||||
remote: Known remote instance name.
|
||||
host: Optional override for the Gitea host.
|
||||
"""
|
||||
TASK_MAP = {
|
||||
"create_issue": {
|
||||
"permission": "gitea.issue.create",
|
||||
"role": "author",
|
||||
},
|
||||
"comment_issue": {
|
||||
"permission": "gitea.issue.comment",
|
||||
"role": "author",
|
||||
},
|
||||
"close_issue": {
|
||||
"permission": "gitea.issue.close",
|
||||
"role": "author",
|
||||
},
|
||||
"claim_issue": {
|
||||
"permission": "gitea.issue.write",
|
||||
"role": "author",
|
||||
},
|
||||
"create_branch": {
|
||||
"permission": "gitea.branch.create",
|
||||
"role": "author",
|
||||
},
|
||||
"push_branch": {
|
||||
"permission": "gitea.branch.push",
|
||||
"role": "author",
|
||||
},
|
||||
"create_pr": {
|
||||
"permission": "gitea.pr.create",
|
||||
"role": "author",
|
||||
},
|
||||
"comment_pr": {
|
||||
"permission": "gitea.pr.comment",
|
||||
"role": "author",
|
||||
},
|
||||
"review_pr": {
|
||||
"permission": "gitea.pr.review",
|
||||
"role": "reviewer",
|
||||
},
|
||||
"merge_pr": {
|
||||
"permission": "gitea.pr.merge",
|
||||
"role": "reviewer",
|
||||
},
|
||||
"delete_branch": {
|
||||
"permission": "gitea.branch.delete",
|
||||
"role": "author",
|
||||
},
|
||||
}
|
||||
|
||||
if task not in TASK_MAP:
|
||||
raise ValueError(f"Unknown task/action: '{task}' (fail closed)")
|
||||
|
||||
required_permission = TASK_MAP[task]["permission"]
|
||||
required_role = TASK_MAP[task]["role"]
|
||||
|
||||
profile = get_profile()
|
||||
config = gitea_config.load_config()
|
||||
|
||||
h = host or (REMOTES.get(remote, {}).get("host") if remote in REMOTES else None)
|
||||
username = _authenticated_username(h) if h else None
|
||||
|
||||
# Load active permissions
|
||||
active_allowed = profile.get("allowed_operations") or []
|
||||
active_forbidden = profile.get("forbidden_operations") or []
|
||||
|
||||
# Check if allowed in current session
|
||||
allowed_in_current_session, _ = gitea_config.check_operation(
|
||||
required_permission, active_allowed, active_forbidden
|
||||
)
|
||||
|
||||
# Find matching configured profiles
|
||||
matching_profiles = []
|
||||
if config and "profiles" in config:
|
||||
for p_name, p_data in config["profiles"].items():
|
||||
p_allowed = p_data.get("allowed_operations") or []
|
||||
p_forbidden = p_data.get("forbidden_operations") or []
|
||||
p_allowed_n = []
|
||||
for op in p_allowed:
|
||||
try:
|
||||
p_allowed_n.append(gitea_config.normalize_operation(op))
|
||||
except Exception:
|
||||
pass
|
||||
p_forbidden_n = []
|
||||
for op in p_forbidden:
|
||||
try:
|
||||
p_forbidden_n.append(gitea_config.normalize_operation(op))
|
||||
except Exception:
|
||||
pass
|
||||
ok, _ = gitea_config.check_operation(required_permission, p_allowed_n, p_forbidden_n)
|
||||
if ok:
|
||||
matching_profiles.append(p_name)
|
||||
|
||||
switching = gitea_config.is_runtime_switching_enabled()
|
||||
different_namespace_required = False
|
||||
next_safe_action = "None; ready for operations."
|
||||
|
||||
if not allowed_in_current_session:
|
||||
if switching:
|
||||
different_namespace_required = False
|
||||
next_safe_action = f"Switch to a profile that has the required permission by calling gitea_activate_profile (matching configured profiles: {matching_profiles})."
|
||||
else:
|
||||
different_namespace_required = True
|
||||
if required_role == "reviewer":
|
||||
next_safe_action = (
|
||||
"Switch to the reviewer MCP session (e.g. gitea-reviewer) which has reviewer permissions configured, "
|
||||
"or ask the operator to update GITEA_MCP_PROFILE to a reviewer profile."
|
||||
)
|
||||
elif required_role == "author":
|
||||
next_safe_action = (
|
||||
"Switch to the author MCP session (e.g. gitea-author) which has author permissions configured, "
|
||||
"or ask the operator to update GITEA_MCP_PROFILE to an author profile."
|
||||
)
|
||||
else:
|
||||
next_safe_action = (
|
||||
f"Relaunch the server with GITEA_MCP_PROFILE set to a profile that has the required permission (e.g. one of: {matching_profiles}) "
|
||||
"or use the corresponding MCP namespace."
|
||||
)
|
||||
|
||||
return {
|
||||
"requested_task": task,
|
||||
"required_operation_permission": required_permission,
|
||||
"required_role_kind": required_role,
|
||||
"active_profile": profile["profile_name"],
|
||||
"active_identity": username,
|
||||
"active_profile_allowed_operations": active_allowed,
|
||||
"allowed_in_current_session": allowed_in_current_session,
|
||||
"matching_configured_profile": matching_profiles,
|
||||
"runtime_switching_supported": switching,
|
||||
"different_mcp_namespace_required": different_namespace_required,
|
||||
"exact_safe_next_action": next_safe_action,
|
||||
}
|
||||
|
||||
|
||||
# ── Entry point ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user