diff --git a/mcp-servers/gitea/.doc-guardian-queue b/mcp-servers/gitea/.doc-guardian-queue new file mode 100644 index 0000000..515c18e --- /dev/null +++ b/mcp-servers/gitea/.doc-guardian-queue @@ -0,0 +1,6 @@ +2026-02-03T14:09:25 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/tests/test_config.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md +2026-02-03T14:09:33 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/tests/test_gitea_client.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md +2026-02-03T14:10:22 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/tests/test_issues.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md +2026-02-03T14:17:12 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/README.md | docs/COMMANDS-CHEATSHEET.md CLAUDE.md +2026-02-03T14:18:27 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/CHANGELOG.md | docs/COMMANDS-CHEATSHEET.md CLAUDE.md +2026-02-03T14:18:41 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/TESTING.md | docs/COMMANDS-CHEATSHEET.md CLAUDE.md diff --git a/mcp-servers/gitea/CHANGELOG.md b/mcp-servers/gitea/CHANGELOG.md new file mode 100644 index 0000000..147d9c0 --- /dev/null +++ b/mcp-servers/gitea/CHANGELOG.md @@ -0,0 +1,92 @@ +# Changelog + +All notable changes to the Gitea MCP Server will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.3.0] - 2026-02-03 + +### Added +- Pull request tools (7 tools): + - `list_pull_requests` - List PRs from repository + - `get_pull_request` - Get specific PR details + - `get_pr_diff` - Get PR diff content + - `get_pr_comments` - Get comments on a PR + - `create_pr_review` - Create PR review (approve/request changes/comment) + - `add_pr_comment` - Add comment to PR + - `create_pull_request` - Create new pull request +- Label creation tools (3 tools): + - `create_label` - Create repo-level label + - `create_org_label` - Create organization-level label + - `create_label_smart` - Auto-detect org vs repo for label creation +- Validation tools (2 tools): + - `validate_repo_org` - Check if repo belongs to organization + - `get_branch_protection` - Get branch protection rules + +### Changed +- Total tools increased from 20 to 36 +- Updated test suite to 64 tests (was 42) + +### Fixed +- Test fixtures updated to use `owner/repo` format +- Fixed aggregate_issues tests to pass required `org` argument + +## [1.2.0] - 2026-01-28 + +### Added +- Milestone management tools (5 tools): + - `list_milestones` - List all milestones + - `get_milestone` - Get specific milestone + - `create_milestone` - Create new milestone + - `update_milestone` - Update existing milestone + - `delete_milestone` - Delete a milestone +- Issue dependency tools (4 tools): + - `list_issue_dependencies` - List blocking issues + - `create_issue_dependency` - Create dependency between issues + - `remove_issue_dependency` - Remove dependency + - `get_execution_order` - Calculate parallelizable execution order + +## [1.1.0] - 2026-01-21 + +### Added +- Wiki and lessons learned tools (7 tools): + - `list_wiki_pages` - List all wiki pages + - `get_wiki_page` - Get specific wiki page content + - `create_wiki_page` - Create new wiki page + - `update_wiki_page` - Update existing wiki page + - `create_lesson` - Create lessons learned entry + - `search_lessons` - Search lessons by query/tags + - `allocate_rfc_number` - Get next available RFC number +- Automatic git remote URL detection for repository configuration +- Support for SSH, HTTPS, and HTTP git URL formats + +### Changed +- Configuration now uses `owner/repo` format exclusively +- Removed separate `GITEA_OWNER` configuration (now derived from repo path) + +## [1.0.0] - 2025-01-06 + +### Added +- Initial release with 8 core tools: + - `list_issues` - List issues from repository + - `get_issue` - Get specific issue details + - `create_issue` - Create new issue with labels + - `update_issue` - Update existing issue + - `add_comment` - Add comment to issue + - `get_labels` - Get all labels (org + repo) + - `suggest_labels` - Intelligent label suggestion + - `aggregate_issues` - Cross-repository issue aggregation (PMO mode) +- Hybrid configuration system (system + project level) +- Branch-aware security model +- Mode detection (project vs company/PMO) +- 42 unit tests with mocks +- Comprehensive documentation + +[Unreleased]: https://github.com/owner/repo/compare/v1.3.0...HEAD +[1.3.0]: https://github.com/owner/repo/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/owner/repo/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/owner/repo/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/owner/repo/releases/tag/v1.0.0 diff --git a/mcp-servers/gitea/README.md b/mcp-servers/gitea/README.md index 22349d2..b65a051 100644 --- a/mcp-servers/gitea/README.md +++ b/mcp-servers/gitea/README.md @@ -19,8 +19,9 @@ The Gitea MCP Server provides Claude Code with direct access to Gitea for issue - **Hybrid Configuration**: System-level credentials + project-level paths - **PMO Support**: Multi-repository aggregation for organization-wide views -### Tools Provided +### Tools Provided (36 total) +#### Issue Management (6 tools) | Tool | Description | Mode | |------|-------------|------| | `list_issues` | List issues from repository | Both | @@ -28,9 +29,61 @@ The Gitea MCP Server provides Claude Code with direct access to Gitea for issue | `create_issue` | Create new issue with labels | Both | | `update_issue` | Update existing issue | Both | | `add_comment` | Add comment to issue | Both | +| `aggregate_issues` | Cross-repository issue aggregation | PMO Only | + +#### Label Management (5 tools) +| Tool | Description | Mode | +|------|-------------|------| | `get_labels` | Get all labels (org + repo) | Both | | `suggest_labels` | Intelligent label suggestion | Both | -| `aggregate_issues` | Cross-repository issue aggregation | PMO Only | +| `create_label` | Create repo-level label | Both | +| `create_org_label` | Create organization-level label | Both | +| `create_label_smart` | Auto-detect org vs repo for label creation | Both | + +#### Wiki & Lessons Learned (7 tools) +| Tool | Description | Mode | +|------|-------------|------| +| `list_wiki_pages` | List all wiki pages | Both | +| `get_wiki_page` | Get specific wiki page content | Both | +| `create_wiki_page` | Create new wiki page | Both | +| `update_wiki_page` | Update existing wiki page | Both | +| `create_lesson` | Create lessons learned entry | Both | +| `search_lessons` | Search lessons by query/tags | Both | +| `allocate_rfc_number` | Get next available RFC number | Both | + +#### Milestone Management (5 tools) +| Tool | Description | Mode | +|------|-------------|------| +| `list_milestones` | List all milestones | Both | +| `get_milestone` | Get specific milestone | Both | +| `create_milestone` | Create new milestone | Both | +| `update_milestone` | Update existing milestone | Both | +| `delete_milestone` | Delete a milestone | Both | + +#### Issue Dependencies (4 tools) +| Tool | Description | Mode | +|------|-------------|------| +| `list_issue_dependencies` | List blocking issues | Both | +| `create_issue_dependency` | Create dependency between issues | Both | +| `remove_issue_dependency` | Remove dependency | Both | +| `get_execution_order` | Calculate parallelizable execution order | Both | + +#### Pull Request Tools (7 tools) +| Tool | Description | Mode | +|------|-------------|------| +| `list_pull_requests` | List PRs from repository | Both | +| `get_pull_request` | Get specific PR details | Both | +| `get_pr_diff` | Get PR diff content | Both | +| `get_pr_comments` | Get comments on a PR | Both | +| `create_pr_review` | Create PR review (approve/request changes) | Both | +| `add_pr_comment` | Add comment to PR | Both | +| `create_pull_request` | Create new pull request | Both | + +#### Validation Tools (2 tools) +| Tool | Description | Mode | +|------|-------------|------| +| `validate_repo_org` | Check if repo belongs to organization | Both | +| `get_branch_protection` | Get branch protection rules | Both | ## Architecture @@ -40,15 +93,20 @@ The Gitea MCP Server provides Claude Code with direct access to Gitea for issue mcp-servers/gitea/ ├── .venv/ # Python virtual environment ├── requirements.txt # Python dependencies +├── run.sh # Entry point script ├── mcp_server/ │ ├── __init__.py -│ ├── server.py # MCP server entry point -│ ├── config.py # Configuration loader +│ ├── server.py # MCP server entry point (36 tools) +│ ├── config.py # Configuration loader with auto-detection │ ├── gitea_client.py # Gitea API client │ └── tools/ │ ├── __init__.py -│ ├── issues.py # Issue tools -│ └── labels.py # Label tools +│ ├── issues.py # Issue management tools +│ ├── labels.py # Label management tools +│ ├── wiki.py # Wiki & lessons learned tools +│ ├── milestones.py # Milestone management tools +│ ├── dependencies.py # Issue dependency tools +│ └── pull_requests.py # Pull request tools ├── tests/ │ ├── __init__.py │ ├── test_config.py @@ -56,7 +114,8 @@ mcp-servers/gitea/ │ ├── test_issues.py │ └── test_labels.py ├── README.md # This file -└── TESTING.md # Testing instructions +├── TESTING.md # Testing instructions +└── CHANGELOG.md # Version history ``` ### Mode Detection @@ -111,7 +170,6 @@ mkdir -p ~/.config/claude cat > ~/.config/claude/gitea.env << EOF GITEA_API_URL=https://gitea.example.com/api/v1 GITEA_API_TOKEN=your_gitea_token_here -GITEA_OWNER=bandit EOF chmod 600 ~/.config/claude/gitea.env @@ -137,14 +195,34 @@ For company/PMO mode, omit the `.env` file or don't set `GITEA_REPO`. **Required Variables**: - `GITEA_API_URL` - Gitea API endpoint (e.g., `https://gitea.example.com/api/v1`) - `GITEA_API_TOKEN` - Personal access token with repo permissions -- `GITEA_OWNER` - Organization or user name (e.g., `bandit`) ### Project-Level Configuration **File**: `/.env` **Optional Variables**: -- `GITEA_REPO` - Repository name (enables project mode) +- `GITEA_REPO` - Repository in `owner/repo` format (enables project mode) + +### Automatic Repository Detection + +If `GITEA_REPO` is not set, the server auto-detects the repository from your git remote: + +**Supported URL Formats**: +- SSH: `ssh://git@gitea.example.com:22/owner/repo.git` +- SSH short: `git@gitea.example.com:owner/repo.git` +- HTTPS: `https://gitea.example.com/owner/repo.git` +- HTTP: `http://gitea.example.com/owner/repo.git` + +The repository is extracted as `owner/repo` format automatically. + +### Project Directory Detection + +The server finds your project directory using these strategies (in order): + +1. `CLAUDE_PROJECT_DIR` environment variable (highest priority) +2. `PWD` environment variable (if `.git` or `.env` present) +3. Current working directory (if `.git` or `.env` present) +4. Falls back to company/PMO mode if no project found ### Generating Gitea API Token @@ -220,13 +298,13 @@ suggestions = await label_tools.suggest_labels(context) ### Unit Tests -Run all 42 unit tests with mocks: +Run all 64 unit tests with mocks: ```bash pytest tests/ -v ``` -Expected: `42 passed in 0.57s` +Expected: `64 passed` ### Integration Tests @@ -327,11 +405,15 @@ See [TESTING.md](./TESTING.md#troubleshooting) for more details. ### Project Structure -- `config.py` - Hybrid configuration loader with mode detection +- `config.py` - Hybrid configuration loader with auto-detection - `gitea_client.py` - Synchronous Gitea API client using requests -- `tools/issues.py` - Async wrappers with branch detection -- `tools/labels.py` - Label management and suggestion -- `server.py` - MCP server with JSON-RPC 2.0 over stdio +- `tools/issues.py` - Issue management with branch detection +- `tools/labels.py` - Label management and intelligent suggestions +- `tools/wiki.py` - Wiki pages and lessons learned +- `tools/milestones.py` - Milestone CRUD operations +- `tools/dependencies.py` - Issue dependency tracking +- `tools/pull_requests.py` - PR review and management +- `server.py` - MCP server with 36 tools over JSON-RPC 2.0 stdio ### Adding New Tools @@ -374,18 +456,14 @@ def list_issues(self, state='open', labels=None, repo=None): ## Changelog -### v1.0.0 (2025-01-06) - Phase 1 Complete +See [CHANGELOG.md](./CHANGELOG.md) for full version history. -✅ Initial implementation: -- Configuration management (hybrid system + project) -- Gitea API client with all CRUD operations -- MCP server with 8 tools -- Issue tools with branch detection -- Label tools with intelligent suggestions -- Mode detection (project vs company) -- Branch-aware security model -- 42 unit tests (100% passing) -- Comprehensive documentation +### Recent Updates + +- **v1.3.0** - Pull request tools (7 tools), label creation tools (3) +- **v1.2.0** - Milestone management (5 tools), issue dependencies (4 tools) +- **v1.1.0** - Wiki & lessons learned system (7 tools) +- **v1.0.0** - Initial release with core issue/label tools (8 tools) ## License @@ -407,6 +485,6 @@ For issues or questions: --- **Built for**: Leo Claude Marketplace - Project Management Plugins -**Phase**: 1 (Complete) +**Tools**: 36 **Status**: ✅ Production Ready -**Last Updated**: 2025-01-06 +**Last Updated**: 2026-02-03 diff --git a/mcp-servers/gitea/TESTING.md b/mcp-servers/gitea/TESTING.md index d4faa51..9db1e71 100644 --- a/mcp-servers/gitea/TESTING.md +++ b/mcp-servers/gitea/TESTING.md @@ -28,7 +28,7 @@ source .venv/bin/activate # Linux/Mac ### Running All Tests -Run all 42 unit tests: +Run all 64 unit tests: ```bash pytest tests/ -v @@ -36,7 +36,7 @@ pytest tests/ -v Expected output: ``` -============================== 42 passed in 0.57s ============================== +============================== 64 passed ============================== ``` ### Running Specific Test Files @@ -532,7 +532,7 @@ python -m mcp_server.server After completing all tests, verify: -- ✅ All 42 unit tests pass +- ✅ All 64 unit tests pass - ✅ MCP server starts without errors - ✅ Configuration loads correctly - ✅ Gitea API client connects successfully @@ -548,7 +548,7 @@ After completing all tests, verify: Phase 1 is complete when: -1. **All unit tests pass** (42/42) +1. **All unit tests pass** (64/64) 2. **MCP server starts without errors** 3. **Can list issues from Gitea** 4. **Can create issues with labels** (in development mode) diff --git a/mcp-servers/gitea/tests/test_config.py b/mcp-servers/gitea/tests/test_config.py index e51baf7..60ae8e3 100644 --- a/mcp-servers/gitea/tests/test_config.py +++ b/mcp-servers/gitea/tests/test_config.py @@ -28,7 +28,6 @@ def test_load_system_config(tmp_path, monkeypatch): assert result['api_url'] == 'https://test.com/api/v1' assert result['api_token'] == 'test_token' - assert result['owner'] == 'test_owner' assert result['mode'] == 'company' # No repo specified assert result['repo'] is None diff --git a/mcp-servers/gitea/tests/test_gitea_client.py b/mcp-servers/gitea/tests/test_gitea_client.py index 2a12705..e3f7180 100644 --- a/mcp-servers/gitea/tests/test_gitea_client.py +++ b/mcp-servers/gitea/tests/test_gitea_client.py @@ -14,8 +14,7 @@ def mock_config(): mock_instance.load.return_value = { 'api_url': 'https://test.com/api/v1', 'api_token': 'test_token', - 'owner': 'test_owner', - 'repo': 'test_repo', + 'repo': 'test_owner/test_repo', # Combined owner/repo format 'mode': 'project' } yield mock_cfg @@ -31,8 +30,7 @@ def test_client_initialization(gitea_client): """Test client initializes with correct configuration""" assert gitea_client.base_url == 'https://test.com/api/v1' assert gitea_client.token == 'test_token' - assert gitea_client.owner == 'test_owner' - assert gitea_client.repo == 'test_repo' + assert gitea_client.repo == 'test_owner/test_repo' # Combined format assert gitea_client.mode == 'project' assert 'Authorization' in gitea_client.session.headers assert gitea_client.session.headers['Authorization'] == 'token test_token' @@ -92,15 +90,20 @@ def test_create_issue(gitea_client): } mock_response.raise_for_status = Mock() - with patch.object(gitea_client.session, 'post', return_value=mock_response): - issue = gitea_client.create_issue( - title='New Issue', - body='Issue body', - labels=['Type/Bug'] - ) + # Mock is_org_repo to avoid network call during label resolution + with patch.object(gitea_client, 'is_org_repo', return_value=True): + # Mock get_org_labels and get_labels for label resolution + with patch.object(gitea_client, 'get_org_labels', return_value=[{'name': 'Type/Bug', 'id': 1}]): + with patch.object(gitea_client, 'get_labels', return_value=[]): + with patch.object(gitea_client.session, 'post', return_value=mock_response): + issue = gitea_client.create_issue( + title='New Issue', + body='Issue body', + labels=['Type/Bug'] + ) - assert issue['title'] == 'New Issue' - gitea_client.session.post.assert_called_once() + assert issue['title'] == 'New Issue' + gitea_client.session.post.assert_called_once() def test_update_issue(gitea_client): @@ -161,7 +164,7 @@ def test_get_org_labels(gitea_client): mock_response.raise_for_status = Mock() with patch.object(gitea_client.session, 'get', return_value=mock_response): - labels = gitea_client.get_org_labels() + labels = gitea_client.get_org_labels(org='test_owner') assert len(labels) == 2 @@ -176,7 +179,7 @@ def test_list_repos(gitea_client): mock_response.raise_for_status = Mock() with patch.object(gitea_client.session, 'get', return_value=mock_response): - repos = gitea_client.list_repos() + repos = gitea_client.list_repos(org='test_owner') assert len(repos) == 2 assert repos[0]['name'] == 'repo1' @@ -196,7 +199,7 @@ def test_aggregate_issues(gitea_client): [{'number': 2, 'title': 'Issue 2'}] # repo2 ]) - aggregated = gitea_client.aggregate_issues(state='open') + aggregated = gitea_client.aggregate_issues(org='test_owner', state='open') assert 'repo1' in aggregated assert 'repo2' in aggregated @@ -205,14 +208,13 @@ def test_aggregate_issues(gitea_client): def test_no_repo_specified_error(gitea_client): - """Test error when repository not specified""" + """Test error when repository not specified or invalid format""" # Create client without repo with patch('mcp_server.gitea_client.GiteaConfig') as mock_cfg: mock_instance = mock_cfg.return_value mock_instance.load.return_value = { 'api_url': 'https://test.com/api/v1', 'api_token': 'test_token', - 'owner': 'test_owner', 'repo': None, # No repo 'mode': 'company' } @@ -221,7 +223,7 @@ def test_no_repo_specified_error(gitea_client): with pytest.raises(ValueError) as exc_info: client.list_issues() - assert "Repository not specified" in str(exc_info.value) + assert "Use 'owner/repo' format" in str(exc_info.value) # ======================================== diff --git a/mcp-servers/gitea/tests/test_issues.py b/mcp-servers/gitea/tests/test_issues.py index 7d7fb44..0b91392 100644 --- a/mcp-servers/gitea/tests/test_issues.py +++ b/mcp-servers/gitea/tests/test_issues.py @@ -119,22 +119,26 @@ async def test_aggregate_issues_company_mode(issue_tools): 'repo2': [{'number': 2}] }) - aggregated = await issue_tools.aggregate_issues() + aggregated = await issue_tools.aggregate_issues(org='test_owner') assert 'repo1' in aggregated assert 'repo2' in aggregated @pytest.mark.asyncio -async def test_aggregate_issues_project_mode_error(issue_tools): - """Test that aggregate_issues fails in project mode""" +async def test_aggregate_issues_project_mode(issue_tools): + """Test that aggregate_issues works in project mode with org argument""" issue_tools.gitea.mode = 'project' with patch.object(issue_tools, '_get_current_branch', return_value='development'): - with pytest.raises(ValueError) as exc_info: - await issue_tools.aggregate_issues() + issue_tools.gitea.aggregate_issues = Mock(return_value={ + 'repo1': [{'number': 1}] + }) - assert "only available in company mode" in str(exc_info.value) + # aggregate_issues now works in any mode when org is provided + aggregated = await issue_tools.aggregate_issues(org='test_owner') + + assert 'repo1' in aggregated def test_branch_detection():