generated from personal-projects/leo-claude-mktplace
feat: add merge tools, tests, and documentation
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>
This commit is contained in:
341
CLAUDE.md
Normal file
341
CLAUDE.md
Normal file
@@ -0,0 +1,341 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user