# CLAUDE.md - gitea-mcp ## Project Overview **Name:** gitea-mcp **Type:** Python package — MCP server for Gitea integration **Version:** 1.0.0 **Repository:** https://gitea.hotserv.cloud/personal-projects/gitea-mcp **Distribution:** Gitea PyPI at gitea.hotserv.cloud ## Project Description Standalone Python package providing 39 MCP (Model Context Protocol) tools for comprehensive Gitea operations. Designed for use with Claude Code and other MCP-compatible systems. **Core Features:** - 39 tools across 7 functional categories - Dual-mode operation (project and PMO/company modes) - Branch-aware security enforcement - Transport-agnostic architecture for multiple deployment options - Full type coercion and error handling ## Architecture ### Directory Structure ``` gitea_mcp/ ├── __init__.py # Public API: __version__, get_tool_definitions, create_tool_dispatcher ├── server.py # MCP stdio server entry point ├── config.py # Configuration loader (env files, auto-detection) ├── gitea_client.py # Synchronous Gitea REST API client ├── tool_registry.py # Tool definitions + dispatcher (transport-agnostic) └── tools/ ├── __init__.py ├── issues.py # Issue CRUD + aggregation (6 tools) ├── labels.py # Label management + suggestions (5 tools) ├── wiki.py # Wiki pages + lessons learned + RFC (7 tools) ├── milestones.py # Milestone CRUD (5 tools) ├── dependencies.py # Issue dependency tracking (4 tools) └── pull_requests.py # PR operations + merge (10 tools) tests/ ├── __init__.py ├── test_config.py ├── test_gitea_client.py ├── test_issues.py ├── test_labels.py ├── test_pull_requests.py # NEW: Merge tool tests docs/ (future) ``` ### Tool Categories | Category | Count | Tools | |----------|-------|-------| | Issue Management | 6 | list_issues, get_issue, create_issue, update_issue, add_comment, aggregate_issues | | Label Management | 5 | list_labels, get_labels, suggest_labels, create_label, create_label_smart | | Wiki & Lessons | 7 | list_wiki_pages, get_wiki_page, create_wiki_page, update_wiki_page, create_lesson, search_lessons, allocate_rfc_number | | Milestones | 5 | list_milestones, get_milestone, create_milestone, update_milestone, delete_milestone | | Dependencies | 4 | list_issue_dependencies, create_issue_dependency, remove_issue_dependency, get_execution_order | | Pull Requests | 7 | list_pull_requests, get_pull_request, get_pr_diff, get_pr_comments, create_pr_review, add_pr_comment, create_pull_request | | **PR Merge (NEW)** | **3** | **merge_pull_request, get_pr_merge_status, cancel_auto_merge** | | **Total** | **39** | — | ## Key Design Patterns ### Transport-Agnostic Architecture - Tool definitions and dispatcher logic live in `tool_registry.py`, not `server.py` - Can be used by multiple transports: stdio (Claude Code), HTTP (gitea-mcp-remote), direct import - Single source of truth for tool schemas and dispatch logic ### Type Coercion MCP serialization sometimes converts integers to strings and arrays to JSON strings. The `_coerce_types()` function in `tool_registry.py` handles: - **Integer fields**: `pr_number`, `milestone_id`, `issue_number`, etc. (string → int) - **Array fields**: `labels`, `tags`, `issue_numbers` (JSON string → list) - **Boolean fields**: `force_merge`, `delete_branch` (string → bool) ### Async/Executor Pattern All async wrappers in `tools/*.py` use: ```python loop = asyncio.get_event_loop() return await loop.run_in_executor(None, lambda: self.gitea.sync_method(...)) ``` This allows synchronous REST calls (`requests`) to be awaited in async contexts without blocking. ### Branch-Aware Security Checked in async wrappers in `tools/pull_requests.py`: ```python if not self._check_branch_permissions('merge_pull_request'): raise PermissionError(f"Cannot merge on branch '{branch}'...") ``` **Rules:** - Read operations: All branches allowed - main/master/prod/*: Read-only - staging/stage/*: Comments only - dev/feature/fix/*: Full access - Unknown: Read-only (fail-safe) ### Configuration Auto-Detection In `config.py`: - Reads from env vars: `GITEA_API_URL`, `GITEA_API_TOKEN`, `GITEA_REPO`, `GITEA_MODE` - Detects mode: Project if `GITEA_REPO` set, PMO if not set (org user) - Supports `.env` files for local development - Fallback defaults for local Gitea instances ## Development Commands ### Setup ```bash # Create virtual environment python3 -m venv .venv source .venv/bin/activate # Linux/Mac # or: .venv\Scripts\activate (Windows) # Install in development mode pip install -e ".[dev]" ``` ### Testing ```bash # Run all tests pytest tests/ -v # Run specific test file pytest tests/test_pull_requests.py -v # Quick test run pytest tests/ -q # With coverage pytest tests/ --cov=gitea_mcp --cov-report=html ``` ### Building ```bash # Install build tools pip install build # Build distribution python -m build # Output: dist/gitea-mcp-1.0.0.tar.gz and dist/gitea_mcp-1.0.0-py3-none-any.whl ``` ### Publishing ```bash # Install Twine pip install twine # Upload to Gitea PyPI twine upload \ --repository-url https://gitea.hotserv.cloud/api/packages/personal-projects/pypi \ --username your-gitea-username \ --password your-gitea-token \ dist/* ``` ## Common Tasks ### Adding a New Tool 1. **Add method to client** (`gitea_client.py`): ```python def new_tool(self, ...) -> Dict: """Synchronous REST call.""" ``` 2. **Add async wrapper** (`tools/relevant_file.py`): ```python async def new_tool(self, ...) -> Dict: """Async wrapper with branch checks if write operation.""" if not self._check_branch_permissions('new_tool'): raise PermissionError(...) loop = asyncio.get_event_loop() return await loop.run_in_executor(None, lambda: self.gitea.new_tool(...)) ``` 3. **Register tool** (`tool_registry.py`): - Add `Tool()` to `_get_all_tool_definitions()` - Add dispatch case in `create_tool_dispatcher()` - Update `_coerce_types()` if new types added 4. **Write tests** (`tests/test_something.py`): - Mock the client - Test the method directly - Test type coercion - Test error cases 5. **Update docs**: - Add to README.md tools table - Update CHANGELOG.md - Update CLAUDE.md if architecture changes ### Running Locally ```bash # Set environment variables export GITEA_API_TOKEN="your-token" export GITEA_REPO="owner/repo" # Test CLI python -m gitea_mcp.server < /dev/null # Or use in a script python3 << 'EOF' from gitea_mcp.gitea_client import GiteaClient from gitea_mcp.tool_registry import get_tool_definitions client = GiteaClient() tools = get_tool_definitions() print(f"Available tools: {len(tools)}") for tool in tools[:5]: print(f" - {tool.name}") EOF ``` ### Testing Type Coercion ```python from gitea_mcp.tool_registry import _coerce_types result = _coerce_types({ 'pr_number': '42', 'force_merge': 'true', 'delete_branch': 'false' }) assert result['pr_number'] == 42 assert result['force_merge'] is True assert result['delete_branch'] is False ``` ## Git Workflow ### Branches - **main**: Stable releases only - **development**: Feature integration (default) - **feat/\***: Feature branches from development - **fix/\***: Bug fix branches from development ### Commit Message Format ``` type(scope): short description Longer explanation if needed. Fixes: #123 (if applicable) ``` Types: `feat`, `fix`, `test`, `docs`, `refactor`, `perf`, `chore` Example: ``` feat(merge): add merge_pull_request tool - Support 5 merge strategies: merge, rebase, squash, etc. - Add get_pr_merge_status for mergeable check - Add cancel_auto_merge for scheduled merges - Include type coercion for force_merge and delete_branch - Add comprehensive tests Closes: #10 ``` ## Dependencies ### Core - `requests>=2.31.0` — HTTP client for Gitea API - `mcp>=0.10.0` — Model Context Protocol ### Development - `pytest>=7.0.0` — Testing framework - `pytest-asyncio` — Async test support - `build` — Package building - `twine` — PyPI publishing - `black` — Code formatting - `flake8` — Linting - `mypy` — Type checking (optional) ## Performance Considerations - All API calls use `requests` Session with connection pooling - Async wrappers use executor pattern (non-blocking) - PMO mode caches org membership to reduce API calls - Lesson searches use wiki page content for tags (linear scan, acceptable for <100 pages) ## Security - **Authentication**: Token-based (GitHub-compatible Gitea API) - **Branch checks**: Enforced in async wrappers - **Input validation**: `_parse_repo()` validates owner/repo format - **Error handling**: Comprehensive logging, HTTP errors propagated with context ## Known Limitations 1. **Wiki operations**: Linear scan of pages for lesson search (fine for <100 pages) 2. **Merge status**: Returns mergeable flag if available, not computed in real-time 3. **PMO mode**: Requires user to have org-level access (not repo-specific tokens) 4. **Execution order**: `get_execution_order()` uses simple topological sort (no cycle detection) ## Future Enhancements - [ ] Webhook support for event-driven workflows - [ ] Caching layer for frequently accessed data - [ ] GraphQL support (if Gitea adds it) - [ ] Batch operations for bulk issue/PR updates - [ ] Custom field support for Gitea enterprise ## Related Projects - **leo-claude-mktplace**: Marketplace of Claude integrations (includes gitea-mcp) - **gitea-mcp-remote**: HTTP transport wrapper (uses gitea-mcp as library) - **Gitea**: https://gitea.io ## Origin Extracted from [leo-claude-mktplace](https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace) v9.1.0 Original location: `mcp-servers/gitea/` v1.3.0 Module renamed: `mcp_server` → `gitea_mcp` for clarity and npm-style naming All existing functionality preserved. Tests added for new merge tools.