feat: add read-only gitea_whoami authenticated-user lookup (#11)

Add a read-only MCP tool that calls Gitea's authenticated-user
endpoint (GET /api/v1/user) and returns safe identity metadata only:
username, display name, user id, email, server, and remote.

This lets future review/merge workflows prove which Gitea account the
MCP server is authenticated as, so self-review/self-merge can be
detected before acting — the blocker discovered during PR #8 dogfooding.

- Never returns the token, Authorization header, password, or secrets.
- Fails closed with a clear error if identity cannot be determined.
- No mutation; no profile switching; no review/approve/merge behavior.

Tests: identity mapping, secret-redaction, fail-closed, unknown-remote.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 12:42:37 -04:00
parent 952e308a18
commit 03e28c159e
3 changed files with 96 additions and 0 deletions
+47
View File
@@ -595,6 +595,53 @@ def gitea_view_issue(
}
@mcp.tool()
def gitea_whoami(
remote: str = "dadeschools",
host: str | None = None,
) -> dict:
"""Look up the Gitea account the MCP server is authenticated as.
Read-only. Calls Gitea's authenticated-user endpoint (GET /api/v1/user)
with the configured token and returns safe identity metadata only. Use
this to prove which account a mutating workflow (e.g. review/merge) would
act as, so self-review/self-merge can be detected before acting.
Never returns the token, Authorization header, password, or any other
secret material. Fails closed with a clear error if the identity cannot
be determined.
Args:
remote: Known instance — 'dadeschools' or 'prgs'.
host: Override the Gitea host.
Returns:
dict with 'authenticated', 'username', 'display_name', 'user_id',
'email', 'server', and 'remote'.
"""
if remote not in REMOTES:
raise ValueError(f"Unknown remote '{remote}'. Choose from: {list(REMOTES)}")
h = host or REMOTES[remote]["host"]
auth = _auth(h)
url = f"https://{h}/api/v1/user"
data = api_request("GET", url, auth)
if not data or not data.get("login"):
# Fail closed: never assume an identity we could not verify.
raise RuntimeError(
f"Could not determine the authenticated Gitea identity for {h}. "
"Verify the configured token is valid for this instance."
)
return {
"authenticated": True,
"username": data.get("login"),
"display_name": data.get("full_name") or None,
"user_id": data.get("id"),
"email": data.get("email") or None,
"server": f"https://{h}",
"remote": remote,
}
@mcp.tool()
def gitea_mark_issue(
issue_number: int,