diff --git a/mcp-servers/gitea/mcp_server/gitea_client.py b/mcp-servers/gitea/mcp_server/gitea_client.py index 00bf677..7b9d4e2 100644 --- a/mcp-servers/gitea/mcp_server/gitea_client.py +++ b/mcp-servers/gitea/mcp_server/gitea_client.py @@ -110,8 +110,14 @@ class GiteaClient: def _resolve_label_ids(self, label_names: List[str], owner: str, repo: str) -> List[int]: """Convert label names to label IDs.""" - org_labels = self.get_org_labels(owner) - repo_labels = self.get_labels(f"{owner}/{repo}") + full_repo = f"{owner}/{repo}" + + # Only fetch org labels if repo belongs to an organization + org_labels = [] + if self.is_org_repo(full_repo): + org_labels = self.get_org_labels(owner) + + repo_labels = self.get_labels(full_repo) all_labels = org_labels + repo_labels label_map = {label['name']: label['id'] for label in all_labels} label_ids = [] diff --git a/mcp-servers/gitea/mcp_server/tools/labels.py b/mcp-servers/gitea/mcp_server/tools/labels.py index be633c2..14e91c0 100644 --- a/mcp-servers/gitea/mcp_server/tools/labels.py +++ b/mcp-servers/gitea/mcp_server/tools/labels.py @@ -27,20 +27,27 @@ class LabelTools: self.gitea = gitea_client async def get_labels(self, repo: Optional[str] = None) -> Dict[str, List[Dict]]: - """Get all labels (org + repo). Repo must be 'owner/repo' format.""" + """Get all labels (org + repo if org-owned, repo-only if user-owned).""" 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')") - org = target_repo.split('/')[0] - - org_labels = await loop.run_in_executor( + # Check if repo belongs to an organization or user + is_org = await loop.run_in_executor( None, - lambda: self.gitea.get_org_labels(org) + lambda: self.gitea.is_org_repo(target_repo) ) + org_labels = [] + if is_org: + org = target_repo.split('/')[0] + org_labels = await loop.run_in_executor( + None, + lambda: self.gitea.get_org_labels(org) + ) + repo_labels = await loop.run_in_executor( None, lambda: self.gitea.get_labels(target_repo) diff --git a/mcp-servers/gitea/tests/test_labels.py b/mcp-servers/gitea/tests/test_labels.py index c161fae..df0c9b3 100644 --- a/mcp-servers/gitea/tests/test_labels.py +++ b/mcp-servers/gitea/tests/test_labels.py @@ -10,7 +10,8 @@ from mcp_server.tools.labels import LabelTools def mock_gitea_client(): """Fixture providing mocked Gitea client""" client = Mock() - client.repo = 'test_repo' + client.repo = 'test_org/test_repo' + client.is_org_repo = Mock(return_value=True) return client @@ -244,3 +245,58 @@ async def test_suggest_labels_multiple_categories(): # Should have Source assert any('Source/' in label for label in suggestions) + + +@pytest.mark.asyncio +async def test_get_labels_org_owned_repo(): + """Test getting labels for organization-owned repository""" + mock_client = Mock() + mock_client.repo = 'myorg/myrepo' + mock_client.is_org_repo = Mock(return_value=True) + mock_client.get_org_labels = Mock(return_value=[ + {'name': 'Type/Bug', 'id': 1}, + {'name': 'Type/Feature', 'id': 2} + ]) + mock_client.get_labels = Mock(return_value=[ + {'name': 'Component/Backend', 'id': 3} + ]) + + tools = LabelTools(mock_client) + result = await tools.get_labels() + + # Should fetch both org and repo labels + mock_client.is_org_repo.assert_called_once_with('myorg/myrepo') + mock_client.get_org_labels.assert_called_once_with('myorg') + mock_client.get_labels.assert_called_once_with('myorg/myrepo') + + assert len(result['organization']) == 2 + assert len(result['repository']) == 1 + assert result['total_count'] == 3 + + +@pytest.mark.asyncio +async def test_get_labels_user_owned_repo(): + """Test getting labels for user-owned repository (no org labels)""" + mock_client = Mock() + mock_client.repo = 'lmiranda/personal-portfolio' + mock_client.is_org_repo = Mock(return_value=False) + mock_client.get_labels = Mock(return_value=[ + {'name': 'bug', 'id': 1}, + {'name': 'enhancement', 'id': 2} + ]) + + tools = LabelTools(mock_client) + result = await tools.get_labels() + + # Should check if org repo + mock_client.is_org_repo.assert_called_once_with('lmiranda/personal-portfolio') + + # Should NOT call get_org_labels for user-owned repos + mock_client.get_org_labels.assert_not_called() + + # Should still get repo labels + mock_client.get_labels.assert_called_once_with('lmiranda/personal-portfolio') + + assert len(result['organization']) == 0 + assert len(result['repository']) == 2 + assert result['total_count'] == 2