Files
ouranos/docs/gitea_mcp.md

18 KiB

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:

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:

---
# Gitea MCP Server Access Token
vault_gitea_mcp_access_token: "your_gitea_access_token_here"

Encrypt vault file:

ansible-vault encrypt ansible/inventory/group_vars/all/vault.yml

Edit vault file:

ansible-vault edit ansible/inventory/group_vars/all/vault.yml

Deployment

Initial Deployment

Prerequisites Check:

# 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:

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:

# 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:

# 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:

ssh miranda.incus
docker logs gitea-mcp

Centralized logs via Loki:

# 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:

# 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):

# 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:

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:

ssh miranda.incus
cd /srv/gitea_mcp
docker compose restart

Via Docker:

ssh miranda.incus
docker restart gitea-mcp

Via Ansible (re-run deployment):

ansible-playbook gitea_mcp/deploy.yml

Removing the Service

Complete removal:

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):

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:

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:

# 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:

# 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:

# 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:

# 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:

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:

# 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:

# 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):

{
  "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:

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:

# 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:

# 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

Ouranos Infrastructure

External References


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:

# 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™ ✓