From 16f013f2ed14b00db22d20616be4fb12d96d9394 Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 14 Oct 2026 06:05:49 +0000 Subject: [PATCH] mcp: org tools (create, add member, org repo) --- mcp/tea_mcp/server.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/mcp/tea_mcp/server.py b/mcp/tea_mcp/server.py index 911e578..043a303 100644 --- a/mcp/tea_mcp/server.py +++ b/mcp/tea_mcp/server.py @@ -288,6 +288,40 @@ async def tea_repo_create(name: str, description: str = "", private: bool = True return {"created": True, "full_name": r.get("full_name"), "url": r.get("html_url")} +@mcp.tool(description="Create an organisation (you become its owner).", annotations={"destructiveHint": False}) +async def tea_org_create(name: str, description: str = "", visibility: str = "public") -> dict[str, Any]: + if not NAME.fullmatch(name): + raise InputError("name: letters, digits, . _ -") + if visibility not in {"public", "limited", "private"}: + raise InputError("visibility public|limited|private") + o = await gitea("POST", "orgs", json={"username": name, "description": description[:255], "visibility": visibility, + "repo_admin_change_team_access": True}) + return {"created": True, "org": o.get("username"), "url": f"{GITEA_URL}/{o.get('username')}"} + + +@mcp.tool(description="Add a user to an organisation team (default: Owners). You must be an owner of the org.", + annotations={"destructiveHint": False}) +async def tea_org_add_member(org: str, user: str, team: str = "Owners") -> dict[str, Any]: + if not (NAME.fullmatch(org) and NAME.fullmatch(user) and NAME.fullmatch(team)): + raise InputError("org, user, team: letters, digits, . _ -") + teams = await gitea("GET", f"orgs/{org}/teams", params={"limit": 50}) + match = next((t for t in teams or [] if t.get("name", "").lower() == team.lower()), None) + if not match: + raise InputError(f"no team {team!r} in {org}") + await gitea("PUT", f"teams/{match['id']}/members/{user}") + return {"org": org, "team": match["name"], "user": user, "added": True} + + +@mcp.tool(description="Create a repository under an organisation you own.", annotations={"destructiveHint": False}) +async def tea_org_repo_create(org: str, name: str, description: str = "", private: bool = False, + auto_init: bool = True) -> dict[str, Any]: + if not (NAME.fullmatch(org) and NAME.fullmatch(name)): + raise InputError("org, name: letters, digits, . _ -") + r = await gitea("POST", f"orgs/{org}/repos", json={"name": name, "description": description[:255], "private": private, + "auto_init": auto_init, "default_branch": "main"}) + return {"created": True, "full_name": r.get("full_name"), "url": r.get("html_url")} + + @mcp.tool(description="Read a file from a repository (text, up to 200 KB).", annotations={"readOnlyHint": True}) async def tea_file_get(repo: str, path: str, ref: str = "") -> dict[str, Any]: x = await gitea("GET", f"{repo_path(repo)}/contents/{file_path(path)}", params={"ref": ref} if ref else None)