generated from personal-projects/leo-claude-mktplace
Added 3 new PR merge tools to complete v1.0.0: - merge_pull_request: Merge PR with 5 strategies (merge, rebase, rebase-merge, squash, fast-forward-only) - get_pr_merge_status: Check if PR is mergeable - cancel_auto_merge: Cancel scheduled auto-merge Changes: - New merge methods in GiteaClient (gitea_client.py) - New async wrappers in PullRequestTools with branch checks (tools/pull_requests.py) - Tool definitions and dispatch routing in tool_registry.py - Boolean type coercion for force_merge and delete_branch parameters - Comprehensive test suite with 18 tests (test_pull_requests.py) - Full documentation: README.md, CHANGELOG.md, CLAUDE.md Features: - 5 merge strategies with full Gitea API support - Branch-aware security enforcement - Type coercion handles MCP string serialization - 100% test coverage for merge operations Result: - Total tools: 39 (7 PR operations + 3 merge = 10 PR tools) - All tests passing (18 new merge tests + 60 existing tests) - Ready for v1.0.0 release Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
342 lines
9.9 KiB
Markdown
342 lines
9.9 KiB
Markdown
# 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.
|