From b017db83a158f4d9a93012f1aaf2dd6d41fff40c Mon Sep 17 00:00:00 2001 From: lmiranda Date: Fri, 23 Jan 2026 12:15:14 -0500 Subject: [PATCH] feat(gitea): add organization-level label creation - Add create_org_label() method to gitea_client.py for org-level labels - Add create_label_smart() to labels.py that auto-detects correct level - Register both tools in server.py - Update labels-sync.md to use create_label_smart Label level detection: - Organization: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/* - Repository: Component/*, Tech/* Co-Authored-By: Claude Opus 4.5 --- mcp-servers/gitea/mcp_server/gitea_client.py | 34 ++++++++++ mcp-servers/gitea/mcp_server/server.py | 70 +++++++++++++++++++- mcp-servers/gitea/mcp_server/tools/labels.py | 67 +++++++++++++++++++ plugins/projman/commands/labels-sync.md | 12 +++- 4 files changed, 179 insertions(+), 4 deletions(-) diff --git a/mcp-servers/gitea/mcp_server/gitea_client.py b/mcp-servers/gitea/mcp_server/gitea_client.py index bf88e09..3e077af 100644 --- a/mcp-servers/gitea/mcp_server/gitea_client.py +++ b/mcp-servers/gitea/mcp_server/gitea_client.py @@ -621,6 +621,40 @@ class GiteaClient: response.raise_for_status() return response.json() + def create_org_label( + self, + org: str, + name: str, + color: str, + description: Optional[str] = None + ) -> Dict: + """ + Create a new label at the organization level. + + Organization labels are shared across all repositories in the org. + Use this for workflow labels (Type, Priority, Complexity, Effort, etc.) + + Args: + org: Organization name + name: Label name (e.g., 'Type/Bug', 'Priority/High') + color: Hex color code (with or without #) + description: Optional label description + + Returns: + Created label dictionary + """ + url = f"{self.base_url}/orgs/{org}/labels" + data = { + 'name': name, + 'color': color.lstrip('#') # Remove # if present + } + if description: + data['description'] = description + logger.info(f"Creating organization label '{name}' in {org}") + response = self.session.post(url, json=data) + response.raise_for_status() + return response.json() + # ======================================== # PULL REQUEST OPERATIONS # ======================================== diff --git a/mcp-servers/gitea/mcp_server/server.py b/mcp-servers/gitea/mcp_server/server.py index 3bfbba6..5b58190 100644 --- a/mcp-servers/gitea/mcp_server/server.py +++ b/mcp-servers/gitea/mcp_server/server.py @@ -622,13 +622,65 @@ class GiteaMCPServer: ), Tool( name="create_label", - description="Create a new label in the repository", + description="Create a new label in the repository (for repo-specific labels like Component/*, Tech/*)", inputSchema={ "type": "object", "properties": { "name": { "type": "string", - "description": "Label name" + "description": "Label name (e.g., 'Component/Backend', 'Tech/Python')" + }, + "color": { + "type": "string", + "description": "Label color (hex code)" + }, + "description": { + "type": "string", + "description": "Label description" + }, + "repo": { + "type": "string", + "description": "Repository name (owner/repo format)" + } + }, + "required": ["name", "color"] + } + ), + Tool( + name="create_org_label", + description="Create a new label at organization level (for workflow labels like Type/*, Priority/*, Complexity/*, Effort/*)", + inputSchema={ + "type": "object", + "properties": { + "org": { + "type": "string", + "description": "Organization name" + }, + "name": { + "type": "string", + "description": "Label name (e.g., 'Type/Bug', 'Priority/High')" + }, + "color": { + "type": "string", + "description": "Label color (hex code)" + }, + "description": { + "type": "string", + "description": "Label description" + } + }, + "required": ["org", "name", "color"] + } + ), + Tool( + name="create_label_smart", + description="Create a label at the appropriate level (org or repo) based on category. Org: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/*. Repo: Component/*, Tech/*", + inputSchema={ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Label name (e.g., 'Type/Bug', 'Component/Backend')" }, "color": { "type": "string", @@ -880,6 +932,20 @@ class GiteaMCPServer: arguments.get('description'), arguments.get('repo') ) + elif name == "create_org_label": + result = self.client.create_org_label( + arguments['org'], + arguments['name'], + arguments['color'], + arguments.get('description') + ) + elif name == "create_label_smart": + result = await self.label_tools.create_label_smart( + arguments['name'], + arguments['color'], + arguments.get('description'), + arguments.get('repo') + ) # Pull Request tools elif name == "list_pull_requests": result = await self.pr_tools.list_pull_requests(**arguments) diff --git a/mcp-servers/gitea/mcp_server/tools/labels.py b/mcp-servers/gitea/mcp_server/tools/labels.py index 23fde00..3fb87a7 100644 --- a/mcp-servers/gitea/mcp_server/tools/labels.py +++ b/mcp-servers/gitea/mcp_server/tools/labels.py @@ -259,3 +259,70 @@ class LabelTools: return lookup[category_lower][value_lower] return None + + # Organization-level label categories (workflow labels shared across repos) + ORG_LABEL_CATEGORIES = {'agent', 'complexity', 'effort', 'efforts', 'priority', 'risk', 'source', 'type'} + + # Repository-level label categories (project-specific labels) + REPO_LABEL_CATEGORIES = {'component', 'tech'} + + async def create_label_smart( + self, + name: str, + color: str, + description: Optional[str] = None, + repo: Optional[str] = None + ) -> Dict: + """ + Create a label at the appropriate level (org or repo) based on category. + + Organization labels: Agent, Complexity, Effort, Priority, Risk, Source, Type + Repository labels: Component, Tech + + Args: + name: Label name (e.g., 'Type/Bug', 'Component/Backend') + color: Hex color code + description: Optional label description + repo: Repository in 'owner/repo' format + + Returns: + Created label dictionary with 'level' key indicating where it was created + """ + loop = asyncio.get_event_loop() + + target_repo = repo or self.gitea.repo + if not target_repo or '/' not in target_repo: + raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')") + + # Parse category from label name + category = None + if '/' in name: + category = name.split('/')[0].lower().rstrip('s') + elif ':' in name: + category = name.split(':')[0].strip().lower().rstrip('s') + + # Determine level + owner = target_repo.split('/')[0] + is_org = await loop.run_in_executor( + None, + lambda: self.gitea.is_org_repo(target_repo) + ) + + # If it's an org repo and the category is an org-level category, create at org level + if is_org and category in self.ORG_LABEL_CATEGORIES: + result = await loop.run_in_executor( + None, + lambda: self.gitea.create_org_label(owner, name, color, description) + ) + result['level'] = 'organization' + logger.info(f"Created organization label '{name}' in {owner}") + else: + # Create at repo level + result = await loop.run_in_executor( + None, + lambda: self.gitea.create_label(name, color, description, target_repo) + ) + result['level'] = 'repository' + logger.info(f"Created repository label '{name}' in {target_repo}") + + return result diff --git a/plugins/projman/commands/labels-sync.md b/plugins/projman/commands/labels-sync.md index eec495d..02b2004 100644 --- a/plugins/projman/commands/labels-sync.md +++ b/plugins/projman/commands/labels-sync.md @@ -62,12 +62,20 @@ Verify these required label categories exist: ### Step 6: Create Missing Labels (if any) -For each missing required label, call: +Use `create_label_smart` which automatically creates labels at the correct level: +- **Organization level**: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/* +- **Repository level**: Component/*, Tech/* ``` -mcp__plugin_projman_gitea__create_label(repo=REPO_NAME, name="Type: Bug", color="d73a4a") +mcp__plugin_projman_gitea__create_label_smart(repo=REPO_NAME, name="Type/Bug", color="d73a4a") ``` +This automatically detects whether to create at org or repo level based on the category. + +**Alternative (explicit control):** +- Org labels: `create_org_label(org="org-name", name="Type/Bug", color="d73a4a")` +- Repo labels: `create_label(repo=REPO_NAME, name="Component/Backend", color="5319e7")` + Use the label format that matches existing labels in the repo (slash `/` or colon-space `: `). ### Step 7: Report Results