# 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 Agathos 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 agathos 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 ### Agathos Infrastructure - [Agathos Overview](agathos.md) - Complete infrastructure documentation - [Ansible Best Practices](ansible.md) - Deployment patterns and structure - [Miranda Host](agathos.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**: Agathos Infrastructure **Host**: Miranda (MCP Docker Host) **Status**: Red Panda Approved™ ✓