Files
ouranos/docs/gitea_mcp.md

760 lines
18 KiB
Markdown

# Gitea MCP Server - Red Panda Approved™
Model Context Protocol (MCP) server providing programmatic access to Gitea repositories, issues, and pull requests. Deployed as a Docker container on Miranda (MCP Docker Host) in the Ouranos sandbox.
---
## Overview
The Gitea MCP Server exposes Gitea's functionality through the MCP protocol, enabling AI assistants and automation tools to interact with Git repositories, issues, pull requests, and other Gitea features.
| Property | Value |
|----------|-------|
| **Host** | Miranda (10.10.0.156) |
| **Service Port** | 25535 |
| **Container Port** | 8000 |
| **Transport** | HTTP |
| **Image** | `docker.gitea.com/gitea-mcp-server:latest` |
| **Gitea Instance** | https://gitea.ouranos.helu.ca |
| **Logging** | Syslog to port 51435 → Alloy → Loki |
### Purpose
- **Repository Operations**: Clone, read, and analyze repository contents
- **Issue Management**: Create, read, update, and search issues
- **Pull Request Workflow**: Manage PRs, reviews, and merges
- **Code Search**: Search across repositories and file contents
- **User/Organization Info**: Query user profiles and organization details
### Integration Points
```
AI Assistant (Cline/Claude Desktop)
↓ (MCP Protocol)
MCP Switchboard (Oberon)
↓ (HTTP)
Gitea MCP Server (Miranda:25535)
↓ (Gitea API)
Gitea Instance (Rosalind:22083)
```
---
## Architecture
### Deployment Model
**Container-Based**: Single Docker container managed via Docker Compose
**Directory Structure**:
```
/srv/gitea_mcp/
└── docker-compose.yml # Container orchestration
```
**System Integration**:
- **User/Group**: `gitea_mcp:gitea_mcp` (system user)
- **Ansible User Access**: Remote user added to gitea_mcp group
- **Permissions**: Directory mode 750, compose file mode 550
### Network Configuration
| Component | Port | Protocol | Purpose |
|-----------|------|----------|---------|
| External Access | 25535 | HTTP | MCP protocol endpoint |
| Container Internal | 8000 | HTTP | Service listening port |
| Syslog | 51435 | TCP | Log forwarding to Alloy |
### Logging Pipeline
```
Gitea MCP Container
↓ (Docker syslog driver)
Local Syslog (127.0.0.1:51435)
↓ (Alloy collection)
Loki (Prospero)
↓ (Grafana queries)
Grafana Dashboards
```
**Log Format**: RFC5424 (syslog_format variable)
**Log Tag**: `gitea-mcp`
---
## Prerequisites
### Infrastructure Requirements
1. **Miranda Host**: Docker engine installed and running
2. **Gitea Instance**: Accessible Gitea server (gitea.ouranos.helu.ca)
3. **Access Token**: Gitea personal access token with required permissions
4. **Monitoring Stack**: Alloy configured for syslog collection (port 51435)
### Required Permissions
**Gitea Access Token Scopes**:
- `repo`: Full repository access (read/write)
- `user`: Read user information
- `org`: Read organization information
- `issue`: Manage issues
- `pull_request`: Manage pull requests
**Token Creation**:
1. Log into Gitea → User Settings → Applications
2. Generate New Token → Select scopes
3. Copy token (shown only once)
4. Store in Ansible Vault as `vault_gitea_mcp_access_token`
### Ansible Dependencies
- `community.docker.docker_compose_v2` collection
- Docker Python SDK on Miranda
- Ansible Vault configured with password file
---
## Configuration
### Host Variables
All configuration is defined in `ansible/inventory/host_vars/miranda.incus.yml`:
```yaml
services:
- gitea_mcp # Enable service on this host
# Gitea MCP Configuration
gitea_mcp_user: gitea_mcp
gitea_mcp_group: gitea_mcp
gitea_mcp_directory: /srv/gitea_mcp
gitea_mcp_port: 25535
gitea_mcp_host: https://gitea.ouranos.helu.ca
gitea_mcp_access_token: "{{ vault_gitea_mcp_access_token }}"
gitea_mcp_syslog_port: 51435
```
### Variable Reference
| Variable | Purpose | Example |
|----------|---------|---------|
| `gitea_mcp_user` | Service system user | `gitea_mcp` |
| `gitea_mcp_group` | Service system group | `gitea_mcp` |
| `gitea_mcp_directory` | Service root directory | `/srv/gitea_mcp` |
| `gitea_mcp_port` | External port binding | `25535` |
| `gitea_mcp_host` | Gitea instance URL | `https://gitea.ouranos.helu.ca` |
| `gitea_mcp_access_token` | Gitea API token (vault) | `{{ vault_gitea_mcp_access_token }}` |
| `gitea_mcp_syslog_port` | Local syslog port | `51435` |
### Vault Configuration
Store the Gitea access token securely in `ansible/inventory/group_vars/all/vault.yml`:
```yaml
---
# Gitea MCP Server Access Token
vault_gitea_mcp_access_token: "your_gitea_access_token_here"
```
**Encrypt vault file**:
```bash
ansible-vault encrypt ansible/inventory/group_vars/all/vault.yml
```
**Edit vault file**:
```bash
ansible-vault edit ansible/inventory/group_vars/all/vault.yml
```
---
## Deployment
### Initial Deployment
**Prerequisites Check**:
```bash
# Verify Miranda has Docker
ansible miranda.incus -m command -a "docker --version"
# Verify Miranda is in inventory
ansible miranda.incus -m ping
# Check Gitea accessibility
curl -I https://gitea.ouranos.helu.ca
```
**Deploy Service**:
```bash
cd ansible/
# Deploy only Gitea MCP service
ansible-playbook gitea_mcp/deploy.yml
# Or deploy as part of full stack
ansible-playbook site.yml
```
**Deployment Process**:
1. ✓ Check service is enabled in host's `services` list
2. ✓ Create gitea_mcp system user and group
3. ✓ Add Ansible remote user to gitea_mcp group
4. ✓ Create /srv/gitea_mcp directory (mode 750)
5. ✓ Template docker-compose.yml (mode 550)
6. ✓ Reset SSH connection (apply group changes)
7. ✓ Start Docker container via docker-compose
### Deployment Output
**Expected Success**:
```
PLAY [Deploy Gitea MCP Server with Docker Compose] ****************************
TASK [Check if host has gitea_mcp service] ************************************
ok: [miranda.incus]
TASK [Create gitea_mcp group] *************************************************
changed: [miranda.incus]
TASK [Create gitea_mcp user] **************************************************
changed: [miranda.incus]
TASK [Add group gitea_mcp to Ansible remote_user] *****************************
changed: [miranda.incus]
TASK [Create gitea_mcp directory] *********************************************
changed: [miranda.incus]
TASK [Template docker-compose file] *******************************************
changed: [miranda.incus]
TASK [Reset SSH connection to apply group changes] ****************************
changed: [miranda.incus]
TASK [Start Gitea MCP service] ************************************************
changed: [miranda.incus]
PLAY RECAP ********************************************************************
miranda.incus : ok=8 changed=7 unreachable=0 failed=0
```
---
## Verification
### Container Status
**Check container is running**:
```bash
# Via Ansible
ansible miranda.incus -m command -a "docker ps | grep gitea-mcp"
# Direct SSH
ssh miranda.incus
docker ps | grep gitea-mcp
```
**Expected Output**:
```
CONTAINER ID IMAGE STATUS PORTS
abc123def456 docker.gitea.com/gitea-mcp-server:latest Up 2 minutes 0.0.0.0:25535->8000/tcp
```
### Service Connectivity
**Test MCP endpoint**:
```bash
# From Miranda
curl -v http://localhost:25535
# From other hosts
curl -v http://miranda.incus:25535
```
**Expected Response**: HTTP response indicating MCP server is listening
### Log Inspection
**Docker logs**:
```bash
ssh miranda.incus
docker logs gitea-mcp
```
**Centralized logs via Loki**:
```bash
# Via logcli (if installed)
logcli query '{job="syslog", container_name="gitea-mcp"}' --limit=50
# Via Grafana Explore
# Navigate to: https://grafana.ouranos.helu.ca
# Select Loki datasource
# Query: {job="syslog", container_name="gitea-mcp"}
```
### Functional Testing
**Test Gitea API access**:
```bash
# Enter container
ssh miranda.incus
docker exec -it gitea-mcp sh
# Test Gitea API connectivity (if curl available in container)
# Note: Container may not have shell utilities
```
**MCP Protocol Test** (from client):
```bash
# Using MCP inspector or client tool
mcp connect http://miranda.incus:25535
# Or test via MCP Switchboard
curl -X POST http://oberon.incus:22781/mcp/invoke \
-H "Content-Type: application/json" \
-d '{"server":"gitea","method":"list_repositories"}'
```
---
## Management
### Updating the Service
**Update container image**:
```bash
cd ansible/
# Re-run deployment (pulls latest image)
ansible-playbook gitea_mcp/deploy.yml
```
**Docker Compose will**:
1. Pull latest `docker.gitea.com/gitea-mcp-server:latest` image
2. Recreate container if image changed
3. Preserve configuration from docker-compose.yml
### Restarting the Service
**Via Docker Compose**:
```bash
ssh miranda.incus
cd /srv/gitea_mcp
docker compose restart
```
**Via Docker**:
```bash
ssh miranda.incus
docker restart gitea-mcp
```
**Via Ansible** (re-run deployment):
```bash
ansible-playbook gitea_mcp/deploy.yml
```
### Removing the Service
**Complete removal**:
```bash
cd ansible/
ansible-playbook gitea_mcp/remove.yml
```
**Remove playbook actions**:
1. Stop and remove Docker containers
2. Remove Docker volumes
3. Remove Docker images
4. Prune unused Docker images
5. Remove /srv/gitea_mcp directory
**Manual cleanup** (if needed):
```bash
ssh miranda.incus
# Stop and remove container
cd /srv/gitea_mcp
docker compose down -v --rmi all
# Remove directory
sudo rm -rf /srv/gitea_mcp
# Remove user/group (optional)
sudo userdel gitea_mcp
sudo groupdel gitea_mcp
```
### Configuration Changes
**Update Gitea host or port**:
1. Edit `ansible/inventory/host_vars/miranda.incus.yml`
2. Modify `gitea_mcp_host` or `gitea_mcp_port`
3. Re-run deployment: `ansible-playbook gitea_mcp/deploy.yml`
**Rotate access token**:
1. Generate new token in Gitea
2. Update vault: `ansible-vault edit ansible/inventory/group_vars/all/vault.yml`
3. Update `vault_gitea_mcp_access_token` value
4. Re-run deployment to update environment variable
---
## Troubleshooting
### Container Won't Start
**Symptom**: Container exits immediately or won't start
**Diagnosis**:
```bash
ssh miranda.incus
# Check container logs
docker logs gitea-mcp
# Check container status
docker ps -a | grep gitea-mcp
# Inspect container
docker inspect gitea-mcp
```
**Common Causes**:
- **Invalid Access Token**: Check `GITEA_ACCESS_TOKEN` in docker-compose.yml
- **Gitea Host Unreachable**: Verify `GITEA_HOST` is accessible from Miranda
- **Port Conflict**: Check if port 25535 is already in use
- **Image Pull Failure**: Check Docker registry connectivity
**Solutions**:
```bash
# Test Gitea connectivity
curl -I https://gitea.ouranos.helu.ca
# Check port availability
ss -tlnp | grep 25535
# Pull image manually
docker pull docker.gitea.com/gitea-mcp-server:latest
# Re-run deployment with verbose logging
ansible-playbook gitea_mcp/deploy.yml -vv
```
### Authentication Errors
**Symptom**: "401 Unauthorized" or "403 Forbidden" in logs
**Diagnosis**:
```bash
# Check token is correctly passed
ssh miranda.incus
docker exec gitea-mcp env | grep GITEA_ACCESS_TOKEN
# Test token manually
TOKEN="your_token_here"
curl -H "Authorization: token $TOKEN" https://gitea.ouranos.helu.ca/api/v1/user
```
**Solutions**:
1. Verify token scopes in Gitea (repo, user, org, issue, pull_request)
2. Regenerate token if expired or revoked
3. Update vault with new token
4. Re-run deployment
### Network Connectivity Issues
**Symptom**: Cannot connect to Gitea or MCP endpoint unreachable
**Diagnosis**:
```bash
# Test Gitea from Miranda
ssh miranda.incus
curl -v https://gitea.ouranos.helu.ca
# Test MCP endpoint from other hosts
curl -v http://miranda.incus:25535
# Check Docker network
docker network inspect bridge
```
**Solutions**:
- Verify Miranda can resolve and reach `gitea.ouranos.helu.ca`
- Check firewall rules on Miranda
- Verify port 25535 is not blocked
- Check Docker network configuration
### Logs Not Appearing in Loki
**Symptom**: No logs in Grafana from gitea-mcp container
**Diagnosis**:
```bash
# Check Alloy is listening on syslog port
ssh miranda.incus
ss -tlnp | grep 51435
# Check Alloy configuration
sudo systemctl status alloy
# Verify syslog driver is configured
docker inspect gitea-mcp | grep -A 10 LogConfig
```
**Solutions**:
1. Verify Alloy is running: `sudo systemctl status alloy`
2. Check Alloy syslog source configuration
3. Verify `gitea_mcp_syslog_port` matches Alloy config
4. Restart Alloy: `sudo systemctl restart alloy`
5. Restart container to reconnect syslog
### Permission Denied Errors
**Symptom**: Cannot access /srv/gitea_mcp or docker-compose.yml
**Diagnosis**:
```bash
ssh miranda.incus
# Check directory permissions
ls -la /srv/gitea_mcp
# Check user group membership
groups # Should show gitea_mcp group
# Check file ownership
ls -la /srv/gitea_mcp/docker-compose.yml
```
**Solutions**:
```bash
# Re-run deployment to fix permissions
ansible-playbook gitea_mcp/deploy.yml
# Manually fix if needed
sudo chown -R gitea_mcp:gitea_mcp /srv/gitea_mcp
sudo chmod 750 /srv/gitea_mcp
sudo chmod 550 /srv/gitea_mcp/docker-compose.yml
# Re-login to apply group changes
exit
ssh miranda.incus
```
### MCP Switchboard Integration Issues
**Symptom**: Switchboard cannot connect to Gitea MCP server
**Diagnosis**:
```bash
# Check switchboard configuration
ssh oberon.incus
cat /srv/mcp-switchboard/config.json | jq '.servers.gitea'
# Test connectivity from Oberon
curl -v http://miranda.incus:25535
```
**Solutions**:
1. Verify Gitea MCP server URL in switchboard config
2. Check network connectivity: Oberon → Miranda
3. Verify port 25535 is accessible
4. Restart MCP Switchboard after config changes
---
## MCP Protocol Integration
### Server Capabilities
The Gitea MCP Server exposes these resources and tools via the MCP protocol:
**Resources**:
- Repository information
- File contents
- Issue details
- Pull request data
- User profiles
- Organization information
**Tools**:
- `list_repositories`: List accessible repositories
- `get_repository`: Get repository details
- `list_issues`: Search and list issues
- `create_issue`: Create new issue
- `update_issue`: Modify existing issue
- `list_pull_requests`: List PRs in repository
- `create_pull_request`: Open new PR
- `search_code`: Search code across repositories
### Switchboard Configuration
**MCP Switchboard** on Oberon routes MCP requests to Gitea MCP Server.
**Configuration** (`/srv/mcp-switchboard/config.json`):
```json
{
"servers": {
"gitea": {
"command": null,
"args": [],
"url": "http://miranda.incus:25535",
"transport": "http"
}
}
}
```
### Client Usage
**From AI Assistant** (Claude Desktop, Cline, etc.):
The assistant can interact with Gitea repositories through natural language:
- "List all repositories in the organization"
- "Show me open issues in the ouranos repository"
- "Create an issue about improving documentation"
- "Search for 'ansible' in repository code"
**Direct MCP Client**:
```json
POST http://oberon.incus:22781/mcp/invoke
Content-Type: application/json
{
"server": "gitea",
"method": "list_repositories",
"params": {}
}
```
---
## Security Considerations
### Access Token Management
**Best Practices**:
- Store token in Ansible Vault (never in plain text)
- Use minimum required scopes for token
- Rotate tokens periodically
- Revoke tokens when no longer needed
- Use separate tokens for different services
**Token Rotation**:
```bash
# 1. Generate new token in Gitea
# 2. Update vault
ansible-vault edit ansible/inventory/group_vars/all/vault.yml
# 3. Re-deploy to update environment variable
ansible-playbook gitea_mcp/deploy.yml
# 4. Revoke old token in Gitea
```
### Network Security
**Isolation**:
- Service only accessible within Incus network (10.10.0.0/24)
- No direct external exposure (proxied through Switchboard)
- TLS handled by HAProxy (upstream) for external access
**Access Control**:
- Gitea enforces user/repository permissions
- MCP protocol authenticated by Switchboard
- Container runs as non-root user
### Audit and Monitoring
**Logging**:
- All requests logged to Loki via syslog
- Grafana dashboards for monitoring access patterns
- Alert on authentication failures
**Monitoring Queries**:
```logql
# All Gitea MCP logs
{job="syslog", container_name="gitea-mcp"}
# Authentication errors
{job="syslog", container_name="gitea-mcp"} |= "401" or |= "403"
# Error rate
rate({job="syslog", container_name="gitea-mcp"} |= "error" [5m])
```
---
## Performance Considerations
### Resource Usage
**Container Resources**:
- **Memory**: ~50-100 MB baseline
- **CPU**: Minimal (< 1% idle, spikes during API calls)
- **Disk**: ~100 MB for image, minimal runtime storage
**Scaling Considerations**:
- Single container sufficient for development/sandbox
- For production: Consider multiple replicas behind load balancer
- Gitea API rate limits apply to token (typically 5000 requests/hour)
### Optimization
**Caching**:
- Gitea MCP Server may cache repository metadata
- Restart container to clear cache if needed
**Connection Pooling**:
- Server maintains connection pool to Gitea API
- Reuses connections for better performance
---
## Related Documentation
### Ouranos Infrastructure
- [Ouranos Overview](ouranos.md) - Complete infrastructure documentation
- [Ansible Best Practices](ansible.md) - Deployment patterns and structure
- [Miranda Host](ouranos.md#miranda---mcp-docker-host) - MCP Docker host details
### Related Services
- [Gitea Service](gitea.md) - Gitea server deployment and configuration
- [MCP Switchboard](../ansible/mcp_switchboard/README.md) - MCP request routing
- [Grafana MCP](grafana_mcp.md) - Similar MCP server deployment
### External References
- [Gitea API Documentation](https://docs.gitea.com/api/1.21/) - Gitea REST API reference
- [Model Context Protocol Specification](https://spec.modelcontextprotocol.io/) - MCP protocol details
- [Gitea MCP Server Repository](https://gitea.com/gitea/mcp-server) - Upstream project
- [Docker Compose Documentation](https://docs.docker.com/compose/) - Container orchestration
---
## Maintenance Schedule
**Regular Tasks**:
- **Weekly**: Review logs for errors or anomalies
- **Monthly**: Update container image to latest version
- **Quarterly**: Rotate Gitea access token
- **As Needed**: Review and adjust token permissions
**Update Procedure**:
```bash
# Pull latest image and restart
ansible-playbook gitea_mcp/deploy.yml
# Verify new version
ssh miranda.incus
docker inspect gitea-mcp | jq '.[0].Config.Image'
```
---
**Last Updated**: February 2026
**Project**: Ouranos Infrastructure
**Host**: Miranda (MCP Docker Host)
**Status**: Red Panda Approved™ ✓