Merge pull request 'feat: add read-only gitea_whoami authenticated-user lookup (#11)' (#20) from feature/11-gitea-authenticated-user-lookup into master
Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
@@ -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_close_issue` | Close an issue by number |
|
||||||
| `gitea_list_issues` | List issues with state/label filters |
|
| `gitea_list_issues` | List issues with state/label filters |
|
||||||
| `gitea_view_issue` | Get full details of a single issue |
|
| `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_mark_issue` | Claim/release an issue (start/done) |
|
||||||
| `gitea_list_labels` | List all available labels in a repository |
|
| `gitea_list_labels` | List all available labels in a repository |
|
||||||
| `gitea_create_label` | Create a new label with custom color |
|
| `gitea_create_label` | Create a new label with custom color |
|
||||||
|
|||||||
@@ -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()
|
@mcp.tool()
|
||||||
def gitea_mark_issue(
|
def gitea_mark_issue(
|
||||||
issue_number: int,
|
issue_number: int,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from mcp_server import ( # noqa: E402
|
|||||||
gitea_edit_pr,
|
gitea_edit_pr,
|
||||||
gitea_get_file,
|
gitea_get_file,
|
||||||
gitea_commit_files,
|
gitea_commit_files,
|
||||||
|
gitea_whoami,
|
||||||
)
|
)
|
||||||
|
|
||||||
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
|
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
|
||||||
@@ -466,5 +467,52 @@ class TestCommitFiles(unittest.TestCase):
|
|||||||
self.assertEqual(payload["files"], files)
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user