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

Merged
sysadmin merged 1 commits from feature/11-gitea-authenticated-user-lookup into master 2026-07-01 12:00:18 -05:00
3 changed files with 96 additions and 0 deletions
Showing only changes of commit 03e28c159e - Show all commits
+1
View File
@@ -50,6 +50,7 @@ Any MCP-compatible agent (Antigravity, Claude Code, etc.) can call these tools n
| `gitea_close_issue` | Close an issue by number |
| `gitea_list_issues` | List issues with state/label filters |
| `gitea_view_issue` | Get full details of a single issue |
| `gitea_whoami` | Read-only: identify the authenticated Gitea account (safe metadata only) |
| `gitea_mark_issue` | Claim/release an issue (start/done) |
| `gitea_list_labels` | List all available labels in a repository |
| `gitea_create_label` | Create a new label with custom color |
+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,
+48
View File
@@ -26,6 +26,7 @@ from mcp_server import ( # noqa: E402
gitea_edit_pr,
gitea_get_file,
gitea_commit_files,
gitea_whoami,
)
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
@@ -466,5 +467,52 @@ class TestCommitFiles(unittest.TestCase):
self.assertEqual(payload["files"], files)
# ---------------------------------------------------------------------------
# Whoami (authenticated-user identity lookup)
# ---------------------------------------------------------------------------
class TestWhoami(unittest.TestCase):
@patch("mcp_server.api_request")
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_returns_safe_identity(self, _auth, mock_api):
mock_api.return_value = {
"id": 42,
"login": "reviewer-bot",
"full_name": "Reviewer Bot",
"email": "reviewer@example.com",
}
result = gitea_whoami(remote="prgs")
self.assertTrue(result["authenticated"])
self.assertEqual(result["username"], "reviewer-bot")
self.assertEqual(result["display_name"], "Reviewer Bot")
self.assertEqual(result["user_id"], 42)
self.assertEqual(result["server"], "https://gitea.prgs.cc")
self.assertEqual(result["remote"], "prgs")
# Read-only: GET against the authenticated-user endpoint.
call_args = mock_api.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertTrue(call_args[0][1].endswith("/api/v1/user"))
@patch("mcp_server.api_request")
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_never_exposes_secrets(self, _auth, mock_api):
mock_api.return_value = {"id": 1, "login": "someone"}
result = gitea_whoami(remote="prgs")
blob = repr(result).lower()
for secret in ("token", "authorization", "basic ", "password", FAKE_AUTH.lower()):
self.assertNotIn(secret, blob)
@patch("mcp_server.api_request")
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_fails_closed_without_login(self, _auth, mock_api):
mock_api.return_value = {"id": 1} # no 'login'
with self.assertRaises(RuntimeError):
gitea_whoami(remote="prgs")
def test_rejects_unknown_remote(self):
with self.assertRaises(ValueError):
gitea_whoami(remote="nope")
if __name__ == "__main__":
unittest.main()