This commit is contained in:
2026-02-11 18:16:00 +01:00
parent dd7bbd1f9a
commit d82fe87113
25 changed files with 120 additions and 4230 deletions

34
AGENTS.md Normal file
View File

@@ -0,0 +1,34 @@
# Repository Guidelines
## Project Structure & Module Organization
Core application code lives in `src/aegis_gitea_mcp/`:
- `server.py` contains FastAPI routes and MCP/SSE endpoints.
- `auth.py`, `config.py`, `audit.py`, and `gitea_client.py` handle security, settings, logging, and Gitea API access.
- `tools/` contains MCP tool implementations (for example `tools/repository.py`).
Tests are in `tests/` and follow module-level coverage for auth, config, server, and integration flows. Utility scripts (key generation/rotation checks) are in `scripts/`. Container assets are in `docker/` with runtime orchestration in `docker-compose.yml`.
## Build, Test, and Development Commands
- `make install`: install runtime dependencies.
- `make install-dev`: install dev dependencies and pre-commit hooks.
- `make run`: run the server locally (`python -m aegis_gitea_mcp.server`).
- `make test`: run pytest with coverage output.
- `make lint`: run `ruff` + `mypy`.
- `make format`: run `black` and auto-fix lint issues.
- `make docker-up` / `make docker-down`: start/stop local container stack.
## Coding Style & Naming Conventions
Use Python 3.10+ with 4-space indentation and type hints for production code. Keep lines within 100 chars (Black/Ruff setting). Modules and functions use `snake_case`; classes use `PascalCase`; constants use `UPPER_SNAKE_CASE`. Prefer explicit exceptions and preserve exception chaining (`raise ... from exc`) when wrapping errors.
## Testing Guidelines
Framework: `pytest` with `pytest-asyncio` and coverage (`--cov=aegis_gitea_mcp`). Place tests under `tests/` using `test_*.py` naming and `test_*` function names. Add or update tests for behavior changes, especially around authentication, API error paths, and MCP tool responses.
## Commit & Pull Request Guidelines
Prefer Conventional Commit style used in history (`feat:`, `fix:`, `docs:`, `test:`). Keep subjects imperative and specific (avoid messages like `update` or `quick fix`). PRs should include:
- what changed and why,
- linked issue(s) if available,
- test/lint evidence (`make test`, `make lint`),
- notes for config/security impact when touching auth, keys, or `.env` behavior.
## Security & Configuration Tips
Never commit secrets. Use `.env` (see `.env.example`) for `GITEA_TOKEN` and `MCP_API_KEYS`. Use `scripts/generate_api_key.py`, `scripts/rotate_api_key.py`, and `scripts/check_key_age.py` for API key lifecycle management.

View File

@@ -1,456 +0,0 @@
# AegisGitea MCP - Architecture Documentation
---
## System Overview
```
┌─────────────────────────────────────────────────────────────────────┐
│ ChatGPT Business │
│ (AI Assistant Interface) │
│ │
│ User: "Show me the files in my-repo" │
└────────────────────────────┬────────────────────────────────────────┘
│ HTTPS (MCP over SSE)
│ Tool: get_file_tree(owner, repo)
┌─────────────────────────────────────────────────────────────────────┐
│ Reverse Proxy (Traefik/Nginx) │
│ TLS Termination │
└────────────────────────────┬────────────────────────────────────────┘
│ HTTP
┌─────────────────────────────────────────────────────────────────────┐
│ AegisGitea MCP Server (Docker) │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ FastAPI Application │ │
│ │ │ │
│ │ Endpoints: │ │
│ │ - GET /health (Health check) │ │
│ │ - GET /mcp/tools (List available tools) │ │
│ │ - POST /mcp/tool/call (Execute tool) │ │
│ │ - GET /mcp/sse (Server-sent events) │ │
│ └───────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴───────────────────────────────────────┐ │
│ │ MCP Protocol Handler │ │
│ │ - Tool validation │ │
│ │ - Request/response mapping │ │
│ │ - Correlation ID management │ │
│ └───────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴───────────────────────────────────────┐ │
│ │ Tool Implementations │ │
│ │ │ │
│ │ - list_repositories() - get_repository_info() │ │
│ │ - get_file_tree() - get_file_contents() │ │
│ └───────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌──────────────┬────────┴────────┬─────────────────────────────┐ │
│ │ │ │ │ │
│ │ ┌───────────▼───────┐ ┌─────▼──────┐ ┌────────────────┐ │ │
│ │ │ Gitea Client │ │ Config │ │ Audit Logger │ │ │
│ │ │ - Auth │ │ Manager │ │ - Structured │ │ │
│ │ │ - API calls │ │ - Env vars│ │ - JSON logs │ │ │
│ │ │ - Error handling│ │ - Defaults│ │ - Correlation │ │ │
│ │ └───────────┬───────┘ └────────────┘ └────────┬───────┘ │ │
│ │ │ │ │ │
│ └──────────────┼────────────────────────────────────┼─────────┘ │
│ │ │ │
└─────────────────┼────────────────────────────────────┼───────────┘
│ Gitea API │
│ (Authorization: token XXX) │ Audit Logs
▼ ▼
┌─────────────────────────────────────┐ ┌──────────────────────────┐
│ Gitea Instance │ │ Persistent Volume │
│ (Self-hosted VCS) │ │ /var/log/aegis-mcp/ │
│ │ │ audit.log │
│ Repositories: │ └──────────────────────────┘
│ ┌─────────────────────────────┐ │
│ │ org/repo-1 (bot has access)│ │
│ │ org/repo-2 (bot has access)│ │
│ │ org/private (NO ACCESS) │ │
│ └─────────────────────────────┘ │
│ │
│ Bot User: aegis-bot │
│ Permissions: Read-only │
└─────────────────────────────────────┘
```
---
## Component Responsibilities
### 1. ChatGPT (External)
**Responsibility**: Initiate explicit tool calls based on user requests
- Receives MCP tool definitions
- Constructs tool call requests
- Presents results to user
- Human-in-the-loop decision making
### 2. Reverse Proxy
**Responsibility**: TLS termination and routing
- Terminates HTTPS connections
- Routes to MCP server container
- Handles SSL certificates
- Optional: IP filtering, rate limiting
### 3. AegisGitea MCP Server (Core)
**Responsibility**: MCP protocol implementation and policy enforcement
#### 3a. FastAPI Application
- HTTP server with async support
- Server-Sent Events endpoint
- Health and status endpoints
- Request routing
#### 3b. MCP Protocol Handler
- Tool definition management
- Request validation
- Response formatting
- Correlation ID tracking
#### 3c. Tool Implementations
- Repository discovery
- File tree navigation
- File content retrieval
- Bounded, single-purpose operations
#### 3d. Gitea Client
- Async HTTP client for Gitea API
- Bot user authentication
- Error handling and retries
- Response parsing
#### 3e. Config Manager
- Environment variable loading
- Validation with Pydantic
- Default values
- Type safety
#### 3f. Audit Logger
- Structured JSON logging
- Correlation ID tracking
- Timestamp (UTC)
- Append-only logs
### 4. Gitea Instance
**Responsibility**: Authorization and data storage
- Source of truth for permissions
- Repository data storage
- Bot user management
- Access control enforcement
### 5. Persistent Volume
**Responsibility**: Audit log storage
- Durable storage for audit logs
- Survives container restarts
- Accessible for review/analysis
---
## Data Flow: Tool Invocation
```
1. User Request
├─> "Show me files in org/my-repo"
└─> ChatGPT decides to call: get_file_tree(owner="org", repo="my-repo")
2. MCP Request
├─> POST /mcp/tool/call
├─> Body: {"tool": "get_file_tree", "arguments": {"owner": "org", "repo": "my-repo"}}
└─> Generate correlation_id: uuid4()
3. Audit Log (Entry)
├─> Log: tool_invocation
├─> tool_name: "get_file_tree"
├─> repository: "org/my-repo"
└─> status: "pending"
4. Gitea API Call
├─> GET /api/v1/repos/org/my-repo/git/trees/main
├─> Header: Authorization: token XXX
└─> Response: {"tree": [...files...]}
5. Authorization Check
├─> 200 OK → Bot has access
├─> 403 Forbidden → Log access_denied, raise error
└─> 404 Not Found → Repository doesn't exist or no access
6. Response Processing
├─> Extract file tree
├─> Transform to simplified format
└─> Apply size/count limits
7. Audit Log (Success)
├─> Log: tool_invocation
├─> status: "success"
└─> params: {"count": 42}
8. MCP Response
├─> 200 OK
├─> Body: {"success": true, "result": {...files...}}
└─> correlation_id: same as request
9. ChatGPT Processing
├─> Receive file tree data
├─> Format for user presentation
└─> "Here are the files in org/my-repo: ..."
```
---
## Security Boundaries
```
┌───────────────────────────────────────────────────────────────┐
│ Trust Boundary 1 │
│ (Internet ↔ MCP Server) │
│ │
│ Controls: │
│ - HTTPS/TLS encryption │
│ - Reverse proxy authentication (optional) │
│ - Rate limiting │
│ - Firewall rules │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ Trust Boundary 2 │
│ (MCP Server ↔ Gitea API) │
│ │
│ Controls: │
│ - Bot user token authentication │
│ - Gitea's access control (authoritative) │
│ - API request timeouts │
│ - Input validation │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ Trust Boundary 3 │
│ (Container ↔ Host System) │
│ │
│ Controls: │
│ - Non-root container user │
│ - Resource limits (CPU, memory) │
│ - No new privileges │
│ - Read-only filesystem (where possible) │
└───────────────────────────────────────────────────────────────┘
```
---
## Authorization Flow
```
┌──────────────────────────────────────────────────────────────────┐
│ AI requests access to "org/private-repo" │
└────────────────────────┬─────────────────────────────────────────┘
┌───────────────────────────────────────┐
│ MCP Server: Forward to Gitea API │
│ with bot user token │
└───────────────┬───────────────────────┘
┌───────────────────────────────────────┐
│ Gitea: Check bot user permissions │
│ for "org/private-repo" │
└───────────────┬───────────────────────┘
┌───────┴────────┐
│ │
Bot is collaborator? │
│ │
┌────────▼─────┐ ┌──────▼──────┐
│ YES │ │ NO │
│ (Read access)│ │ (No access) │
└────────┬─────┘ └──────┬──────┘
│ │
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Return data │ │ Return 403 │
│ Log: success │ │ Log: denied │
└───────────────┘ └─────────────────┘
```
**Key Insight**: The MCP server never makes authorization decisions - it only forwards requests and respects Gitea's response.
---
## Failure Modes & Handling
### 1. Gitea Unavailable
- **Detection**: HTTP connection error
- **Response**: Return error to ChatGPT
- **Logging**: Log connection failure
- **Recovery**: Automatic retry on next request
### 2. Invalid Bot Token
- **Detection**: 401 Unauthorized from Gitea
- **Response**: Log security event, return auth error
- **Logging**: High-severity security log
- **Recovery**: Operator must rotate token
### 3. Bot Lacks Permission
- **Detection**: 403 Forbidden from Gitea
- **Response**: Return authorization error
- **Logging**: Access denied event
- **Recovery**: Grant permission in Gitea UI
### 4. File Too Large
- **Detection**: File size exceeds MAX_FILE_SIZE_BYTES
- **Response**: Return size limit error
- **Logging**: Security event (potential abuse)
- **Recovery**: Increase limit or reject request
### 5. Network Timeout
- **Detection**: Request exceeds REQUEST_TIMEOUT_SECONDS
- **Response**: Return timeout error
- **Logging**: Log timeout event
- **Recovery**: Automatic retry possible
### 6. Rate Limit Exceeded
- **Detection**: Too many requests per minute
- **Response**: Return 429 Too Many Requests
- **Logging**: Log rate limit event
- **Recovery**: Wait and retry
---
## Scaling Considerations
### Vertical Scaling (Single Instance)
- **Current**: 128-512 MB RAM, minimal CPU
- **Bottleneck**: Gitea API response time
- **Max throughput**: ~100-200 requests/second
### Horizontal Scaling (Multiple Instances)
- **Stateless design**: Each instance independent
- **Load balancing**: Standard HTTP load balancer
- **Shared state**: None (all state in Gitea)
- **Audit logs**: Each instance writes to own log (or use centralized logging)
### Performance Optimization (Future)
- Add Redis caching layer
- Implement connection pooling
- Use HTTP/2 for Gitea API
- Batch multiple file reads
---
## Observability
### Metrics to Monitor
1. **Request rate**: Requests per minute
2. **Error rate**: Failed requests / total requests
3. **Response time**: P50, P95, P99 latency
4. **Gitea API health**: Success rate to Gitea
5. **Auth failures**: 401/403 responses
### Logs to Track
1. **Audit logs**: Every tool invocation
2. **Access denied**: Permission violations
3. **Security events**: Rate limits, size limits
4. **Errors**: Exceptions and failures
### Alerts to Configure
1. **High error rate**: > 5% errors
2. **Auth failures**: Any 401 responses
3. **Gitea unreachable**: Connection failures
4. **Disk space**: Audit logs filling disk
---
## Future Enhancements
### Phase 2: Extended Context
```
New Tools:
├── get_commits(owner, repo, limit)
├── get_commit_diff(owner, repo, sha)
├── list_issues(owner, repo)
├── get_issue(owner, repo, number)
├── list_pull_requests(owner, repo)
└── get_pull_request(owner, repo, number)
```
### Phase 3: Advanced Features
```
Capabilities:
├── Caching layer (Redis)
├── Webhook support for real-time updates
├── OAuth2 flow instead of static tokens
├── Per-client rate limiting
├── Multi-tenant support (multiple bot users)
└── GraphQL API for more efficient queries
```
---
## Deployment Patterns
### Pattern 1: Single Homelab Instance
```
[Homelab Server]
├── Gitea container
├── AegisGitea MCP container
└── Caddy reverse proxy
└── Exposes HTTPS endpoint
```
### Pattern 2: Kubernetes Deployment
```
[Kubernetes Cluster]
├── Namespace: aegis-mcp
├── Deployment: aegis-mcp (3 replicas)
├── Service: ClusterIP
├── Ingress: HTTPS with cert-manager
└── PersistentVolume: Audit logs
```
### Pattern 3: Cloud Deployment
```
[AWS/GCP/Azure]
├── Container service (ECS/Cloud Run/ACI)
├── Load balancer (ALB/Cloud Load Balancing)
├── Secrets manager (Secrets Manager/Secret Manager/Key Vault)
└── Log aggregation (CloudWatch/Cloud Logging/Monitor)
```
---
## Testing Strategy
### Unit Tests
- Configuration loading
- Gitea client methods
- Tool implementations
- Audit logging
### Integration Tests
- Full MCP protocol flow
- Gitea API interactions (mocked)
- Error handling paths
### End-to-End Tests
- Real Gitea instance
- Real bot user
- Real tool invocations
---
## Conclusion
This architecture prioritizes:
1. **Security**: Read-only, auditable, fail-safe
2. **Simplicity**: Straightforward data flow
3. **Maintainability**: Clear separation of concerns
4. **Observability**: Comprehensive logging
The design is intentionally boring and predictable - perfect for a security-critical system.

View File

@@ -1,408 +0,0 @@
# Authentication System Implementation Summary
**Branch:** `feature/authentication-system`
**Status:** ✅ Complete and Pushed
**Commit:** `eeaad74`
---
## 🎉 What Was Built
A complete API key authentication system that ensures only YOUR ChatGPT Business workspace can access your self-hosted Gitea MCP server.
---
## 📦 Files Created
### Core Authentication Module
- **`src/aegis_gitea_mcp/auth.py`** (215 lines)
- `APIKeyValidator` class with constant-time comparison
- Rate limiting (5 failures per IP per 5 minutes)
- Failed attempt tracking
- Bearer token extraction and validation
- `generate_api_key()` function for secure key generation
### Key Management Scripts
- **`scripts/generate_api_key.py`** (125 lines)
- Interactive key generation wizard
- Metadata tracking (description, creation date, expiration)
- .env configuration snippet output
- Optional metadata file storage
- **`scripts/rotate_api_key.py`** (155 lines)
- Guided key rotation with automatic backup
- Grace period support (multi-key transition)
- Old .env backup before changes
- Step-by-step instructions
- **`scripts/check_key_age.py`** (130 lines)
- Automated expiration monitoring
- Warning system (14 days, 7 days, expired)
- Cron-ready exit codes
- Metadata file parsing
### Documentation
- **`AUTH_SETUP.md`** (620 lines)
- Complete authentication setup guide
- Security features overview
- Troubleshooting guide
- Monitoring and best practices
- **`CHATGPT_SETUP.md`** (520 lines)
- ChatGPT Business integration guide
- Step-by-step configuration
- Example commands to try
- Multi-user setup instructions
- **`KEY_ROTATION.md`** (600 lines)
- Automated rotation procedures
- Manual rotation step-by-step
- Emergency rotation (compromised key)
- Multi-user rotation strategies
---
## 🔧 Files Modified
### Configuration
- **`src/aegis_gitea_mcp/config.py`**
- Added `auth_enabled: bool` (default: True)
- Added `mcp_api_keys: List[str]` (comma-separated parsing)
- Added `max_auth_failures: int` (default: 5)
- Added `auth_failure_window: int` (default: 300 seconds)
- Validation: Keys must be at least 32 characters
- Validation: At least one key required if auth enabled
### Server
- **`src/aegis_gitea_mcp/server.py`**
- Added authentication middleware
- Validates all `/mcp/*` endpoints (excludes `/health` and `/`)
- Extracts Bearer token from Authorization header
- Returns 401 with helpful error messages on auth failure
- Logs authentication status on startup
### Infrastructure
- **`docker-compose.yml`**
- Added Traefik labels for automatic HTTPS
- Added rate limiting middleware (60 req/min per IP)
- Added security headers (HSTS, CSP, X-Frame-Options)
- Connected to external Traefik network
- Added `MCP_DOMAIN` environment variable support
- **`.env.example`**
- Added `AUTH_ENABLED` (default: true)
- Added `MCP_API_KEYS` (required if auth enabled)
- Added `MCP_DOMAIN` (for Traefik routing)
- Added `MAX_AUTH_FAILURES` (default: 5)
- Added `AUTH_FAILURE_WINDOW` (default: 300)
- Documented multi-key configuration
- **`.gitignore`**
- Added `keys/` (metadata storage)
- Added `.env.backup-*` (rotation backups)
- Added `*.key` (key files)
- **`Makefile`**
- Added `make generate-key` command
- Added `make rotate-key` command
- Added `make check-key-age` command
- Updated help text
---
## 🔐 Security Features Implemented
### 1. Authentication
- ✅ Bearer token validation
- ✅ Constant-time key comparison (prevents timing attacks)
- ✅ Multi-key support (rotation grace periods)
- ✅ Minimum 32-character keys (64 recommended)
- ✅ No authentication on health checks (monitoring-friendly)
### 2. Rate Limiting
- ✅ 5 failed attempts per IP before blocking
- ✅ 5-minute time window (configurable)
- ✅ In-memory tracking (resets on restart)
- ✅ High-severity security events logged
### 3. Audit Logging
- ✅ All auth attempts logged (success and failure)
- ✅ Client IP and user agent captured
- ✅ Key hints logged (first 8 + last 4 chars only)
- ✅ Correlation IDs for request tracking
- ✅ Timestamps in UTC
### 4. Network Security
- ✅ Traefik labels for automatic HTTPS
- ✅ Security headers (HSTS, X-Frame-Options, CSP)
- ✅ Rate limiting at proxy level (60/min per IP)
- ✅ External network isolation
---
## 📊 Statistics
| Metric | Value |
|--------|-------|
| **Total Lines Added** | ~2,263 |
| **Python Code** | ~900 lines |
| **Documentation** | ~1,740 lines |
| **Scripts** | 3 |
| **Docs** | 3 |
| **Config Changes** | 5 files |
| **New Modules** | 1 (auth.py) |
---
## 🚀 Quick Start (For Users)
### Step 1: Generate API Key
```bash
make generate-key
```
### Step 2: Add to .env
```bash
echo "MCP_API_KEYS=<your-generated-key>" >> .env
```
### Step 3: Restart Server
```bash
docker-compose restart aegis-mcp
```
### Step 4: Configure ChatGPT Business
- Go to ChatGPT Settings > MCP Servers
- Add custom header:
```
Authorization: Bearer <your-generated-key>
```
### Step 5: Test
```
Ask ChatGPT: "List my Gitea repositories"
```
---
## 🧪 Testing Checklist
### Manual Testing Required
Before merging to main:
- [ ] Generate API key with `make generate-key`
- [ ] Add key to `.env` file
- [ ] Start server: `docker-compose up -d`
- [ ] Test without key (should return 401):
```bash
curl https://mcp.yourdomain.com/mcp/tools
```
- [ ] Test with invalid key (should return 401):
```bash
curl -H "Authorization: Bearer invalid-key" https://mcp.yourdomain.com/mcp/tools
```
- [ ] Test with valid key (should return 200):
```bash
curl -H "Authorization: Bearer <valid-key>" https://mcp.yourdomain.com/mcp/tools
```
- [ ] Test rate limiting (6 failed attempts, should block)
- [ ] Test key rotation with `make rotate-key`
- [ ] Test key age check with `make check-key-age`
- [ ] Configure ChatGPT and test actual usage
- [ ] Check audit logs show auth events:
```bash
docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log | grep auth
```
---
## 📝 Migration Guide (For Existing Installations)
### Breaking Changes
1. **Authentication now enabled by default**
- Old installations without `MCP_API_KEYS` will fail to start
- Must generate and configure API key
2. **Environment variable required**
- `MCP_API_KEYS` must be set if `AUTH_ENABLED=true` (default)
- Minimum 32 characters, 64 recommended
### Migration Steps
```bash
# 1. Pull latest code
git pull origin feature/authentication-system
# 2. Generate API key
make generate-key
# 3. Update .env
echo "MCP_API_KEYS=<generated-key>" >> .env
# 4. Restart
docker-compose down
docker-compose up -d
# 5. Update ChatGPT configuration
# Add Authorization header in ChatGPT settings
# 6. Verify
curl -H "Authorization: Bearer <key>" https://mcp.yourdomain.com/mcp/tools
```
### Rollback Plan
If issues arise:
```bash
# Temporarily disable authentication
echo "AUTH_ENABLED=false" >> .env
docker-compose restart aegis-mcp
# Server will log warning but allow all requests
# Fix configuration, then re-enable auth
```
---
## 🔍 Code Review Notes
### Architecture Decisions
1. **Bearer Token over OAuth2**
- Simpler for single-user/small team
- ChatGPT Business compatible
- Easy to rotate and manage
2. **In-Memory Rate Limiting**
- Sufficient for single-instance deployment
- Resets on restart (acceptable tradeoff)
- Can be upgraded to Redis if needed
3. **Plaintext Keys in .env**
- Current: Keys stored in plaintext in `.env`
- Future: Can add hashing with minimal refactoring
- Mitigation: File permissions, container isolation
4. **No Database for Key Metadata**
- Optional metadata files in `keys/` directory
- Simple, no additional dependencies
- Easy to backup and version control (metadata only)
### Security Considerations
- ✅ Constant-time comparison prevents timing attacks
- ✅ Keys never logged in full (only hints)
- ✅ Rate limiting prevents brute force
- ✅ Audit logging for accountability
- ⚠️ Keys in `.env` require filesystem protection
- ⚠️ In-memory rate limits reset on restart
### Performance Impact
- Negligible (< 1ms per request for auth check)
- Constant-time comparison slightly slower than `==` but necessary
- No database queries needed
---
## 🎯 Success Criteria
All objectives met:
- ✅ Only authorized ChatGPT workspaces can access MCP server
- ✅ API keys easy to generate, rotate, and manage
- ✅ Comprehensive audit logging of all auth attempts
- ✅ Zero downtime key rotation support
- ✅ Rate limiting prevents abuse
- ✅ Complete documentation for setup and usage
- ✅ Traefik integration with HTTPS and security headers
- ✅ Multi-key support for team environments
- ✅ Emergency rotation procedures documented
---
## 🚦 Next Steps
### Immediate (Before Merge)
1. Manual testing of all features
2. Verify documentation accuracy
3. Test on clean installation
4. Update main README.md to reference AUTH_SETUP.md
### Future Enhancements (Not in This PR)
1. Discord/Slack webhooks for alerts (`alerts.py`)
2. Prometheus metrics endpoint (`metrics.py`)
3. Automated tests for authentication (`tests/test_auth.py`)
4. Key hashing instead of plaintext storage
5. Redis-based rate limiting for multi-instance
6. OAuth2 flow for enterprise environments
7. Web dashboard for key management
---
## 📞 Pull Request Link
Create PR at:
```
https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP/pulls/new/feature/authentication-system
```
**Suggested PR Title:**
```
feat: Add API key authentication for ChatGPT Business exclusive access
```
**Suggested PR Description:**
```
Implements comprehensive Bearer token authentication to ensure only
authorized ChatGPT workspaces can access the self-hosted Gitea MCP server.
## Features
- API key validation with rate limiting
- Key management scripts (generate, rotate, check age)
- Traefik integration with HTTPS and security headers
- Comprehensive documentation (AUTH_SETUP.md, CHATGPT_SETUP.md, KEY_ROTATION.md)
- Multi-key support for rotation grace periods
## Security
- Constant-time key comparison
- Failed attempt tracking (5 per IP per 5 min)
- Comprehensive audit logging
- No keys logged in full
## Breaking Changes
- `MCP_API_KEYS` environment variable now required
- Authentication enabled by default
## Migration
See AUTHENTICATION_IMPLEMENTATION_SUMMARY.md for complete migration guide.
## Testing
Manual testing checklist provided in summary document.
```
---
## 🙏 Acknowledgments
Built based on requirements for:
- ChatGPT Business workspace exclusive access
- Self-hosted Gitea private instance
- Traefik (Pangolin) reverse proxy
- Security-first, audit-focused design
---
**Status:** ✅ Ready for Review and Testing
**Branch:** `feature/authentication-system`
**Commit:** `eeaad74`
**Files Changed:** 13 (8 added, 5 modified)
**Lines Added:** ~2,263
---
**Great work! The authentication system is complete, documented, and pushed to the remote repository.** 🎉

View File

@@ -1,437 +0,0 @@
# Authentication Setup Guide
This guide walks you through setting up API key authentication for AegisGitea MCP to ensure only your ChatGPT workspace can access your self-hosted Gitea instance.
---
## Overview
AegisGitea MCP uses **Bearer Token authentication** to secure access:
- **API Keys**: Cryptographically secure 64-character tokens
- **Header-based**: Keys sent via `Authorization: Bearer <key>` header
- **Multi-key support**: Multiple keys for rotation grace periods
- **Rate limiting**: Failed auth attempts trigger IP-based rate limits
- **Audit logging**: All auth attempts logged for security monitoring
---
## Quick Start (5 minutes)
### 1. Generate API Key
```bash
# Using Make
make generate-key
# Or directly
python3 scripts/generate_api_key.py
```
This will:
- Generate a secure 64-character API key
- Show you the key (save it immediately!)
- Provide `.env` configuration snippet
- Optionally save metadata (not the key itself) for tracking
**Example output:**
```
✓ API Key Generated Successfully!
API KEY:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
📋 Next Steps:
1. Add this key to your .env file
2. Restart the MCP server
3. Configure ChatGPT Business
```
### 2. Add Key to .env
```bash
# Edit .env
nano .env
# Add/update this line:
MCP_API_KEYS=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
```
### 3. Restart MCP Server
```bash
docker-compose restart aegis-mcp
# Verify authentication is enabled
docker-compose logs aegis-mcp | grep "authentication"
```
Expected log output:
```
API key authentication ENABLED (1 key(s) configured)
```
### 4. Test Authentication
```bash
# Without key - should fail with 401
curl https://mcp.yourdomain.com/mcp/tools
# With valid key (header) - should succeed
curl -H "Authorization: Bearer YOUR_KEY_HERE" \
https://mcp.yourdomain.com/mcp/tools
# With valid key (query param) - should succeed
curl "https://mcp.yourdomain.com/mcp/tools?api_key=YOUR_KEY_HERE"
```
---
## Configuration Options
### Environment Variables
```bash
# Enable/disable authentication
AUTH_ENABLED=true # Set to false ONLY for testing
# API keys (comma-separated for multiple)
MCP_API_KEYS=key1 # Single key
MCP_API_KEYS=key1,key2,key3 # Multiple keys (rotation grace period)
# Rate limiting
MAX_AUTH_FAILURES=5 # Max failed attempts before blocking
AUTH_FAILURE_WINDOW=300 # Time window in seconds (5 min)
```
### Multiple Keys (Rotation Grace Period)
During key rotation, you can temporarily allow multiple keys:
```bash
# Old key
MCP_API_KEYS=old-key-here
# Add new key (both work)
MCP_API_KEYS=old-key-here,new-key-here
# After updating ChatGPT, remove old key
MCP_API_KEYS=new-key-here
```
---
## Security Features
### 1. Constant-Time Comparison
Keys are compared using `hmac.compare_digest()` to prevent timing attacks.
### 2. Rate Limiting
- **Threshold**: 5 failed attempts per IP
- **Window**: 5 minutes (configurable)
- **Action**: Reject all requests from that IP until window expires
- **Logging**: High-severity security event logged
### 3. Audit Logging
Every authentication attempt logs:
```json
{
"timestamp": "2026-01-29T12:34:56.789Z",
"event": "api_authentication",
"status": "success",
"client_ip": "203.0.113.42",
"user_agent": "ChatGPT-User/1.0",
"key_hint": "a1b2c3d4...e1f2"
}
```
Failed attempts:
```json
{
"timestamp": "2026-01-29T12:34:56.789Z",
"event": "access_denied",
"reason": "invalid_api_key",
"client_ip": "203.0.113.42"
}
```
### 4. Key Format Validation
- Minimum length: 32 characters
- Recommended length: 64 characters
- Format: Hexadecimal string (0-9, a-f)
---
## Troubleshooting
### Issue: "No API keys configured" error
**Symptoms:**
```
docker-compose logs: "No API keys configured in environment"
```
**Solution:**
1. Check `.env` file exists and contains `MCP_API_KEYS`
2. Ensure no typos (`MCP_API_KEYS` not `MCP_API_KEY`)
3. Verify key is at least 32 characters
4. Restart container after updating `.env`
### Issue: "Invalid API key" on valid key
**Possible causes:**
1. **Whitespace in .env**: Remove spaces around `=`
```bash
# Wrong
MCP_API_KEYS = key-here
# Correct
MCP_API_KEYS=key-here
```
2. **Key truncated**: Ensure entire 64-char key is copied
```bash
# Check key length
echo -n "your-key-here" | wc -c
# Should output: 64
```
3. **Container not restarted**: Always restart after changing `.env`
```bash
docker-compose restart aegis-mcp
```
### Issue: Rate limit blocking legitimate requests
**Symptoms:**
```
"Too many failed authentication attempts"
```
**Solution:**
1. Check audit logs for failed attempts:
```bash
docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log | grep "invalid_api_key"
```
2. Wait 5 minutes for rate limit to reset
3. If accidentally blocked yourself:
```bash
# Restart container (clears in-memory rate limits)
docker-compose restart aegis-mcp
```
4. Adjust rate limit settings in `.env` if needed:
```bash
MAX_AUTH_FAILURES=10
AUTH_FAILURE_WINDOW=600
```
### Issue: ChatGPT can't connect after adding auth
See [CHATGPT_SETUP.md](CHATGPT_SETUP.md) for detailed ChatGPT configuration.
**Quick check:**
1. Verify key in ChatGPT settings matches `.env`
2. Check Authorization header format: `Bearer <key>` (with space)
3. Test manually with curl first
---
## Monitoring Authentication
### View All Auth Attempts
```bash
# All auth events
docker-compose exec aegis-mcp grep "api_authentication" /var/log/aegis-mcp/audit.log
# Failed attempts only
docker-compose exec aegis-mcp grep "access_denied" /var/log/aegis-mcp/audit.log
# Rate limit triggers
docker-compose exec aegis-mcp grep "auth_rate_limit_exceeded" /var/log/aegis-mcp/audit.log
```
### Real-Time Monitoring
```bash
# Follow auth events
docker-compose exec aegis-mcp tail -f /var/log/aegis-mcp/audit.log | grep "auth"
```
### Weekly Summary Script
```bash
#!/bin/bash
# Save as scripts/auth_summary.sh
CONTAINER="aegis-gitea-mcp"
LOG_PATH="/var/log/aegis-mcp/audit.log"
echo "=== Weekly Auth Summary ==="
echo ""
echo "Total auth attempts:"
docker exec $CONTAINER grep "api_authentication" $LOG_PATH | wc -l
echo ""
echo "Successful:"
docker exec $CONTAINER grep "api_authentication.*success" $LOG_PATH | wc -l
echo ""
echo "Failed:"
docker exec $CONTAINER grep "access_denied.*invalid_api_key" $LOG_PATH | wc -l
echo ""
echo "Rate limited IPs:"
docker exec $CONTAINER grep "auth_rate_limit_exceeded" $LOG_PATH | wc -l
```
---
## Best Practices
### 1. Key Storage
- ✅ Store in `.env` file (never commit to git)
- ✅ Use password manager for backup
- ✅ Save key metadata (not key itself) for tracking
- ❌ Never hardcode in application code
- ❌ Never share via unencrypted channels
### 2. Key Rotation
- **Schedule**: Every 90 days (automated check available)
- **Method**: Use `make rotate-key` for guided rotation
- **Grace period**: Temporarily allow both old and new keys
- **Verification**: Test new key before removing old key
See [KEY_ROTATION.md](KEY_ROTATION.md) for detailed rotation procedures.
### 3. Access Control
- **Single workspace**: One key for your ChatGPT Business account
- **Multiple users**: One key per user (track with metadata)
- **Revocation**: Remove key from `.env` and restart
### 4. Monitoring
- **Weekly**: Review auth logs for anomalies
- **Monthly**: Check key age with `make check-key-age`
- **Quarterly**: Rotate keys
- **Alerts**: Set up notifications for rate limit events
---
## Advanced Configuration
### Disable Authentication (Testing Only)
**⚠️ WARNING: Only for isolated test environments**
```bash
# .env
AUTH_ENABLED=false
```
Server will log critical warning:
```
API key authentication DISABLED - server is open to all requests!
```
### Custom Rate Limiting
```bash
# More lenient (for high-traffic)
MAX_AUTH_FAILURES=20
AUTH_FAILURE_WINDOW=600 # 10 minutes
# More strict (for high-security)
MAX_AUTH_FAILURES=3
AUTH_FAILURE_WINDOW=1800 # 30 minutes
```
### Key Hashing (Future Enhancement)
Currently keys are stored in plaintext in `.env`. Future versions will support hashed keys in database.
**Current security model:**
- ✅ Keys never logged in full (only first 8 + last 4 chars)
- ✅ `.env` file protected by filesystem permissions
- ✅ Container runs as non-root user
- ⚠️ Ensure `.env` has restrictive permissions: `chmod 600 .env`
---
## Security Checklist
Before going to production:
- [ ] API key generated with `make generate-key` (not manually typed)
- [ ] Key is exactly 64 characters
- [ ] `.env` file has restrictive permissions (`chmod 600 .env`)
- [ ] `.env` is in `.gitignore` (verify: `git status` shows no .env)
- [ ] Container restarted after adding key
- [ ] Authentication enabled (check logs for "ENABLED" message)
- [ ] Test curl command succeeds with key, fails without
- [ ] ChatGPT configured with Authorization header
- [ ] Rate limiting tested (try 6 failed attempts)
- [ ] Audit logs capturing auth events
- [ ] Key rotation reminder set (90 days)
- [ ] Backup key stored securely (password manager)
---
## FAQ
**Q: Can I use the same key for multiple ChatGPT workspaces?**
A: Yes, but not recommended. Generate separate keys for auditability:
```bash
MCP_API_KEYS=workspace1-key,workspace2-key
```
**Q: How do I revoke access immediately?**
A: Remove key from `.env` and restart:
```bash
# Edit .env (remove old key)
docker-compose restart aegis-mcp
```
Access is revoked instantly after restart.
**Q: Does rate limiting affect valid keys?**
A: No. Rate limiting only applies to IPs that fail authentication. Valid keys are never rate limited.
**Q: Can I see which key was used for each request?**
A: Yes. Audit logs include `key_hint` (first 8 + last 4 characters):
```json
{"key_hint": "a1b2c3d4...e1f2"}
```
**Q: What happens if I lose my API key?**
A: Generate a new one with `make generate-key`, update `.env`, restart server, update ChatGPT config. The old key stops working immediately after restart.
---
## Next Steps
- [ChatGPT Setup Guide](CHATGPT_SETUP.md) - Configure ChatGPT Business
- [Key Rotation Guide](KEY_ROTATION.md) - Automated rotation procedures
- [Security Policy](SECURITY.md) - Overall security best practices
---
**Need help?** Open an issue in the Gitea repository.

View File

@@ -1,419 +0,0 @@
# ChatGPT Business Setup Guide
Complete guide for connecting ChatGPT Business to your secured AegisGitea MCP server.
---
## Prerequisites
- ✅ AegisGitea MCP server deployed and running
- ✅ API key generated (see [AUTH_SETUP.md](AUTH_SETUP.md))
- ✅ Traefik configured with HTTPS (or reverse proxy with TLS)
- ✅ ChatGPT Business or Developer subscription
---
## Setup Steps
### Step 1: Verify MCP Server is Running
```bash
# Check container status
docker-compose ps
# Should show "Up" status
aegis-gitea-mcp Up 0.0.0.0:8080->8080/tcp
```
### Step 2: Test Authentication
```bash
# Replace YOUR_API_KEY with your actual key
curl "https://mcp.yourdomain.com/mcp/tools?api_key=YOUR_API_KEY"
# Expected: JSON response with available tools
# If error: Check AUTH_SETUP.md troubleshooting
```
### Step 3: Configure ChatGPT Business
1. **Open ChatGPT Settings**
- Click your profile icon (bottom left)
- Select "Settings"
- Navigate to "Beta Features" or "Integrations"
2. **Add MCP Server**
- Look for "Model Context Protocol" or "MCP Servers"
- Click "Add Server" or "+"
3. **Enter Server Details**
```
Name: AegisGitea MCP
URL: https://mcp.yourdomain.com?api_key=YOUR_API_KEY_HERE
Type: HTTP/SSE (Server-Sent Events)
Auth: None (or Mixed, leave OAuth fields empty)
```
4. **Save Configuration**
### Step 4: Test Connection
Start a new ChatGPT conversation and try:
```
List my Gitea repositories
```
**Expected Response:**
```
I found X repositories in your Gitea instance:
1. org/repo-name - Description here
2. org/another-repo - Another description
...
```
**If it fails**, see Troubleshooting section below.
---
## Verification Checklist
After setup, verify:
- [ ] ChatGPT shows "Connected" status for AegisGitea MCP
- [ ] Test command "List my Gitea repositories" works
- [ ] Audit logs show successful authentication:
```bash
docker-compose logs aegis-mcp | grep "api_authentication.*success"
```
- [ ] Can read file contents:
```
Show me the README.md file from org/repo-name
```
- [ ] Can browse repository structure:
```
What files are in the src/ directory of org/repo-name?
```
---
## Example Commands
Once connected, try these commands in ChatGPT:
### Repository Discovery
```
What repositories do I have access to?
List all my Gitea repositories
Show me my private repositories
```
### Repository Information
```
Tell me about the org/my-repo repository
What's the default branch of org/my-repo?
When was org/my-repo last updated?
```
### File Operations
```
Show me the file tree of org/my-repo
What files are in the src/ directory of org/my-repo?
Read the README.md file from org/my-repo
Show me the contents of src/main.py in org/my-repo
```
### Code Understanding
```
Explain what the main function does in org/my-repo/src/main.py
Summarize the architecture of org/my-repo
What dependencies does org/my-repo use?
Find all TODO comments in org/my-repo
```
---
## Troubleshooting
### Issue: "Cannot connect to MCP server"
**Check 1: Server is running**
```bash
docker-compose ps
docker-compose logs aegis-mcp
```
**Check 2: Domain/URL is correct**
```bash
curl https://mcp.yourdomain.com/health
# Should return: {"status": "healthy"}
```
**Check 3: Firewall/Network**
- Ensure port 443 is open
- Check Traefik is routing correctly
- Verify DNS resolves correctly
### Issue: "Authentication failed"
**Check 1: MCP Server URL format**
Correct:
```
https://mcp.yourdomain.com?api_key=YOUR_KEY
```
Wrong:
```
https://mcp.yourdomain.com (missing api_key)
https://mcp.yourdomain.com?api_key= (empty key)
https://mcp.yourdomain.com?api_key=short (too short)
```
**Check 2: Key matches .env**
```bash
# Show configured keys (first 12 chars only for security)
docker-compose exec aegis-mcp printenv MCP_API_KEYS | cut -c1-12
```
Compare with your ChatGPT key (first 12 chars).
**Check 3: Server logs**
```bash
docker-compose logs aegis-mcp | tail -50
```
Look for:
- `invalid_api_key` → Key doesn't match
- `missing_api_key` → api_key not provided
- `invalid_key_format` → Key too short
### Issue: "No repositories visible"
**Verify bot user has access:**
1. Log into Gitea
2. Go to repository Settings > Collaborators
3. Confirm bot user is listed with Read permission
4. If not, add bot user as collaborator
**Test manually:**
```bash
curl "https://mcp.yourdomain.com/mcp/tool/call?api_key=YOUR_KEY" \
-X POST \
-H "Content-Type: application/json" \
-d '{"tool": "list_repositories", "arguments": {}}'
```
### Issue: "Rate limited"
**Symptoms:**
```
Too many failed authentication attempts
```
**Solution:**
1. Wait 5 minutes
2. Verify API key is correct
3. Check audit logs:
```bash
docker-compose exec aegis-mcp grep "auth_rate_limit" /var/log/aegis-mcp/audit.log
```
4. If accidentally locked out:
```bash
docker-compose restart aegis-mcp
```
### Issue: ChatGPT says "Tool not available"
**Verify tools are registered:**
```bash
curl "https://mcp.yourdomain.com/mcp/tools?api_key=YOUR_KEY"
```
Should return JSON with tools array.
---
## Security Best Practices
### 1. API Key Management
- ✅ Store key in password manager
- ✅ Never share key in chat or messages
- ✅ Rotate key every 90 days
- ❌ Don't paste key in public ChatGPT conversations
- ❌ Don't screenshot ChatGPT settings with key visible
### 2. Workspace Separation
If multiple team members:
```
User A: Use key_a1b2c3...
User B: Use key_b4c5d6...
User C: Use key_c7d8e9...
```
Configure in `.env`:
```bash
MCP_API_KEYS=key_a1b2c3...,key_b4c5d6...,key_c7d8e9...
```
Each user configures their own ChatGPT with their specific key.
### 3. Monitoring Usage
Weekly check:
```bash
# Who's using the MCP server?
docker-compose exec aegis-mcp grep "api_authentication" /var/log/aegis-mcp/audit.log | \
grep "success" | \
tail -20
```
Look for `key_hint` to identify which key was used.
### 4. Incident Response
If key is compromised:
1. **Immediate:** Remove from `.env`
```bash
# Edit .env (remove compromised key)
docker-compose restart aegis-mcp
```
2. **Generate new key**
```bash
make generate-key
```
3. **Update ChatGPT** with new key
4. **Check audit logs** for unauthorized access:
```bash
docker-compose exec aegis-mcp grep "key_hint: <compromised_key_hint>" /var/log/aegis-mcp/audit.log
```
---
## Advanced Configuration
### Custom MCP Tool Calls (For Developers)
You can call tools directly via API:
```bash
# List repositories
curl -X POST "https://mcp.yourdomain.com/mcp/tool/call?api_key=YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_repositories",
"arguments": {}
}'
# Get file contents
curl -X POST "https://mcp.yourdomain.com/mcp/tool/call?api_key=YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_file_contents",
"arguments": {
"owner": "org",
"repo": "my-repo",
"filepath": "README.md"
}
}'
```
### Webhook Integration (Future)
For automated workflows, you can integrate MCP tools into CI/CD:
```yaml
# Example GitHub Actions (future enhancement)
- name: Sync to Gitea via MCP
run: |
curl -X POST "https://mcp.yourdomain.com/mcp/tool/call?api_key=${{ secrets.MCP_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{...}'
```
---
## Performance Tips
### 1. Rate Limiting Awareness
Traefik limits: **60 requests/minute**
For heavy usage:
```yaml
# docker-compose.yml
labels:
- "traefik.http.middlewares.aegis-ratelimit.ratelimit.average=120"
```
### 2. Large Files
Files >1MB are rejected by default. To increase:
```bash
# .env
MAX_FILE_SIZE_BYTES=5242880 # 5MB
```
### 3. Batch Operations
Instead of:
```
Show me file1.py
Show me file2.py
Show me file3.py
```
Use:
```
Show me all Python files in the src/ directory
```
---
## FAQ
**Q: Can I use this with Claude or other AI assistants?**
A: Currently optimized for ChatGPT Business, but the MCP protocol is standard. Other AI assistants with MCP support should work with the same configuration.
**Q: Do I need a separate key for each ChatGPT conversation?**
A: No. One key per user/workspace. All conversations in that workspace use the same key.
**Q: Can ChatGPT modify my repositories?**
A: **No**. AegisGitea MCP is read-only by design. ChatGPT can only read code, never write/commit/push.
**Q: What happens if I hit the rate limit?**
A: You'll receive a 429 error. Wait 1 minute and try again. The limit is per IP, not per key.
**Q: Can I use this from mobile ChatGPT app?**
A: Yes, if your ChatGPT Business account syncs to mobile. The MCP configuration follows your account.
**Q: How do I disconnect ChatGPT?**
A: Go to ChatGPT Settings > MCP Servers > Remove "AegisGitea MCP"
---
## Next Steps
- [Key Rotation Guide](KEY_ROTATION.md) - Rotate keys every 90 days
- [Authentication Setup](AUTH_SETUP.md) - Detailed auth configuration
- [Security Policy](SECURITY.md) - Best practices and threat model
---
**Happy coding with AI-assisted Gitea access!** 🚀

View File

@@ -1,343 +0,0 @@
# AegisGitea MCP Deployment Guide
This guide walks you through deploying AegisGitea MCP in a production environment.
---
## Prerequisites
1. **Self-hosted Gitea instance** (running and accessible)
2. **Docker and Docker Compose** installed on your server
3. **Reverse proxy** (Traefik, Caddy, or Nginx) for TLS termination
4. **Bot user account** created in Gitea with read-only access
---
## Step 1: Create Gitea Bot User
1. Log into your Gitea instance as an admin
2. Create a new user account (e.g., `aegis-bot`)
3. **Important**: Do NOT grant admin privileges to this user
4. Generate an access token:
- Go to Settings > Applications
- Generate new token with `read:repository` scope only
- Save the token securely (you'll need it in Step 3)
---
## Step 2: Grant Repository Access
The bot user can only see repositories where it has been explicitly granted access:
### Method 1: Add as Collaborator (for individual repos)
1. Go to repository Settings > Collaborators
2. Add `aegis-bot` user
3. Set permission to **Read** only
### Method 2: Add to Organization Team (for multiple repos)
1. Create an organization team (e.g., "AI Reviewers")
2. Add `aegis-bot` to the team
3. Grant team **Read** access to desired repositories
**Result**: Only repositories where the bot is a collaborator are AI-visible.
---
## Step 3: Configure AegisGitea MCP
Clone the repository and set up environment:
```bash
# Clone repository
git clone https://your-gitea.com/your-org/AegisGitea-MCP.git
cd AegisGitea-MCP
# Copy environment template
cp .env.example .env
# Edit configuration
nano .env
```
### Required Configuration
```bash
# Gitea instance URL (must be accessible from Docker container)
GITEA_URL=https://gitea.example.com
# Bot user token from Step 1
GITEA_TOKEN=your-bot-token-here
# MCP server configuration
MCP_HOST=0.0.0.0
MCP_PORT=8080
# Logging
LOG_LEVEL=INFO
AUDIT_LOG_PATH=/var/log/aegis-mcp/audit.log
```
### Optional Security Configuration
```bash
# File size limit (bytes)
MAX_FILE_SIZE_BYTES=1048576 # 1MB
# API request timeout (seconds)
REQUEST_TIMEOUT_SECONDS=30
# Rate limiting (requests per minute)
RATE_LIMIT_PER_MINUTE=60
```
---
## Step 4: Deploy with Docker Compose
```bash
# Build and start container
docker-compose up -d
# Check logs
docker-compose logs -f aegis-mcp
# Verify health
curl http://localhost:8080/health
```
Expected output:
```json
{"status": "healthy"}
```
---
## Step 5: Configure Reverse Proxy
**Never expose the MCP server directly to the internet without TLS.**
### Example: Traefik
```yaml
# docker-compose.yml (add to aegis-mcp service)
labels:
- "traefik.enable=true"
- "traefik.http.routers.aegis-mcp.rule=Host(`mcp.example.com`)"
- "traefik.http.routers.aegis-mcp.entrypoints=websecure"
- "traefik.http.routers.aegis-mcp.tls.certresolver=letsencrypt"
- "traefik.http.services.aegis-mcp.loadbalancer.server.port=8080"
```
### Example: Caddy
```caddyfile
# Caddyfile
mcp.example.com {
reverse_proxy aegis-mcp:8080
}
```
### Example: Nginx
```nginx
# /etc/nginx/sites-available/aegis-mcp
server {
listen 443 ssl http2;
server_name mcp.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE support
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
chunked_transfer_encoding off;
}
}
```
---
## Step 6: Register with ChatGPT
1. Go to ChatGPT Settings > MCP Servers
2. Add new MCP server:
- **Name**: AegisGitea MCP
- **URL**: `https://mcp.example.com`
- **Type**: SSE (Server-Sent Events)
3. Test connection by asking ChatGPT:
```
List my Gitea repositories
```
---
## Verification Checklist
- [ ] Bot user created in Gitea
- [ ] Bot user has read-only token
- [ ] Bot user added as collaborator to desired repositories
- [ ] `.env` file configured with correct values
- [ ] Docker container running and healthy
- [ ] Reverse proxy configured with TLS
- [ ] MCP server accessible via HTTPS
- [ ] ChatGPT successfully connects to MCP server
- [ ] Audit logs are being written
---
## Security Best Practices
### 1. Token Management
- **Rotate tokens quarterly** or when team members leave
- Store tokens in a secrets manager (Vault, 1Password, etc.)
- Never commit tokens to version control
### 2. Network Security
- Use a firewall to restrict MCP server access
- Only allow HTTPS connections (port 443)
- Consider VPN or IP allowlisting for extra security
### 3. Monitoring
Monitor audit logs for unexpected activity:
```bash
# View recent audit events
docker-compose exec aegis-mcp tail -f /var/log/aegis-mcp/audit.log
# Search for specific repository access
docker-compose exec aegis-mcp grep "repository-name" /var/log/aegis-mcp/audit.log
```
### 4. Access Control
- Review bot user permissions monthly
- Remove access from archived repositories
- Audit which repositories are AI-visible
### 5. Updates
```bash
# Pull latest changes
git pull
# Rebuild container
docker-compose down
docker-compose build --no-cache
docker-compose up -d
```
---
## Troubleshooting
### Container won't start
```bash
# Check logs for errors
docker-compose logs aegis-mcp
# Verify environment variables
docker-compose config
```
### Authentication errors
```bash
# Test Gitea connection manually
curl -H "Authorization: token YOUR_TOKEN" https://gitea.example.com/api/v1/user
# If 401: Token is invalid or expired
# If 403: Token lacks necessary permissions
```
### ChatGPT can't connect
1. Verify reverse proxy is working:
```bash
curl https://mcp.example.com/health
```
2. Check firewall rules:
```bash
sudo ufw status
```
3. Review reverse proxy logs
### No repositories visible
- Verify bot user has been added as collaborator
- Check repository is not archived
- Confirm bot user permissions in Gitea UI
---
## Rollback Plan
If something goes wrong:
```bash
# Stop container
docker-compose down
# Remove container and volumes
docker-compose down -v
# Restore previous configuration
git checkout HEAD~1 .env
# Restart
docker-compose up -d
```
To completely disable AI access:
1. Remove bot user token in Gitea
2. Stop MCP container: `docker-compose down`
**The system is designed to be reversible.**
---
## Production Checklist
Before going live:
- [ ] All sensitive data in `.env` (not hardcoded)
- [ ] TLS configured and tested
- [ ] Audit logging enabled and accessible
- [ ] Resource limits set in docker-compose.yml
- [ ] Monitoring and alerting configured
- [ ] Backup strategy for audit logs
- [ ] Incident response plan documented
- [ ] Team trained on emergency procedures
---
## Support
For deployment issues:
1. Check logs: `docker-compose logs -f`
2. Review audit logs for access patterns
3. Open an issue in Gitea repository
4. Include sanitized logs (remove tokens!)
---
**Remember**: This system prioritizes security over convenience. When in doubt, restrict access first and expand gradually.

View File

@@ -1,548 +0,0 @@
# API Key Rotation Guide
Comprehensive guide for rotating API keys in AegisGitea MCP with zero downtime.
---
## Why Rotate Keys?
- **Security hygiene**: Regular rotation limits exposure window
- **Compliance**: Many security policies require quarterly rotation
- **Compromise mitigation**: If a key leaks, rotation limits damage
- **Audit trail**: New keys = clear timeline of when access changed
**Recommended schedule**: Every 90 days
---
## Automated Rotation (Recommended)
### Step 1: Check Key Age
```bash
make check-key-age
```
**Output:**
```
Key: a1b2c3d4...
Created: 2026-01-29 (85 days ago)
Expires: 2026-04-29 (in 5 days)
Status: ⚠️ WARNING
```
### Step 2: Rotate Key
```bash
make rotate-key
```
This script will:
1. Show current keys
2. Generate new 64-character key
3. Offer rotation strategies (replace or grace period)
4. Backup old `.env` file
5. Update `.env` with new key
6. Provide next steps
**Example:**
```
Found 1 existing key(s)
1. a1b2c3d4...e1f2
✓ New API key generated!
Rotation Strategy:
1. Replace all keys with new key (recommended)
2. Add new key, keep old keys (grace period)
3. Cancel
Choose option [1/2/3]: 2
✓ New key will be added (total: 2 keys)
⚠️ IMPORTANT: Remove old keys manually after updating ChatGPT config
```
### Step 3: Restart Server
```bash
docker-compose restart aegis-mcp
```
Verify both keys work:
```bash
# Test old key (should still work if using grace period)
curl -H "Authorization: Bearer OLD_KEY" \
https://mcp.yourdomain.com/mcp/tools
# Test new key (should work)
curl -H "Authorization: Bearer NEW_KEY" \
https://mcp.yourdomain.com/mcp/tools
```
### Step 4: Update ChatGPT
1. Go to ChatGPT Settings > MCP Servers
2. Edit "AegisGitea MCP"
3. Update Authorization header:
```
Authorization: Bearer NEW_KEY_HERE
```
4. Save
### Step 5: Verify & Clean Up
Test ChatGPT connection:
```
List my Gitea repositories
```
If successful, remove old key:
```bash
# Edit .env - remove old key, keep only new key
nano .env
# MCP_API_KEYS=old-key,new-key ← Remove old-key
# MCP_API_KEYS=new-key ← Keep only new key
# Restart
docker-compose restart aegis-mcp
```
Verify old key no longer works:
```bash
curl -H "Authorization: Bearer OLD_KEY" \
https://mcp.yourdomain.com/mcp/tools
# Should return 401 Unauthorized
```
---
## Manual Rotation (Step-by-Step)
If you prefer manual control:
### 1. Generate New Key
```bash
make generate-key
```
Save the output:
```
API KEY: b9c8d7e6f5g4h3i2...
```
### 2. Add to .env (Grace Period)
```bash
nano .env
# Before:
MCP_API_KEYS=a1b2c3d4e5f6g7h8...
# After (both keys work):
MCP_API_KEYS=a1b2c3d4e5f6g7h8...,b9c8d7e6f5g4h3i2...
```
### 3. Restart & Verify
```bash
docker-compose restart aegis-mcp
# Check logs
docker-compose logs aegis-mcp | grep "authentication"
# Should show: "API key authentication ENABLED (2 key(s) configured)"
```
### 4. Update ChatGPT
Update Authorization header with new key.
### 5. Remove Old Key
After confirming ChatGPT works with new key:
```bash
nano .env
# Remove old key
MCP_API_KEYS=b9c8d7e6f5g4h3i2...
docker-compose restart aegis-mcp
```
---
## Rotation Strategies
### Strategy 1: Immediate Replacement (No Downtime Risk)
**Use when:** You can update ChatGPT config immediately
1. Generate new key
2. Replace old key in `.env`
3. Restart server
4. Update ChatGPT within minutes
**Pros:** Clean, only one key at a time
**Cons:** Must update ChatGPT immediately or service breaks
### Strategy 2: Grace Period (Zero Downtime)
**Use when:** You need time to update ChatGPT config
1. Generate new key
2. Add new key, keep old key (both work)
3. Restart server
4. Update ChatGPT at your convenience
5. Remove old key after verification
**Pros:** Zero downtime, test thoroughly
**Cons:** Temporarily allows two keys
**Recommended grace period:** 24-48 hours max
### Strategy 3: Scheduled Maintenance Window
**Use when:** You want to coordinate with team
1. Announce maintenance window (e.g., Saturday 2 AM)
2. Generate new key
3. During window:
- Replace key in `.env`
- Restart server
- Update all team members' ChatGPT configs
4. Verify all users can access
**Pros:** Coordinated, everyone updates at once
**Cons:** Requires coordination
---
## Multi-User Rotation
If multiple team members use different keys:
### Option A: Rotate One Key at a Time
```bash
# Current state
MCP_API_KEYS=alice-key,bob-key,charlie-key
# Rotate Alice's key
MCP_API_KEYS=alice-new-key,bob-key,charlie-key
# Alice updates her ChatGPT, verify, continue
```
### Option B: Rotate All Keys Simultaneously
```bash
# Generate 3 new keys
make generate-key # Save as alice-new
make generate-key # Save as bob-new
make generate-key # Save as charlie-new
# Grace period
MCP_API_KEYS=alice-old,alice-new,bob-old,bob-new,charlie-old,charlie-new
# Each user updates
# After all verified, remove old keys
MCP_API_KEYS=alice-new,bob-new,charlie-new
```
---
## Automated Rotation Schedule
### Setup Cron Job
```bash
# Edit crontab
crontab -e
# Check key age weekly
0 9 * * MON /path/to/AegisGitea-MCP/scripts/check_key_age.py || echo "Keys need rotation"
# Or check daily at 2 AM
0 2 * * * cd /path/to/AegisGitea-MCP && make check-key-age
```
Exit codes:
- `0` = All keys OK
- `1` = Warning (rotation recommended in 14 days)
- `2` = Critical (rotation needed NOW)
### Email Notifications
```bash
#!/bin/bash
# Save as scripts/check_and_notify.sh
cd /path/to/AegisGitea-MCP
# Run check
make check-key-age > /tmp/key_status.txt 2>&1
STATUS=$?
if [ $STATUS -eq 2 ]; then
# Critical - send email
mail -s "[CRITICAL] AegisGitea API Key Expired" admin@example.com < /tmp/key_status.txt
elif [ $STATUS -eq 1 ]; then
# Warning - send email
mail -s "[WARNING] AegisGitea API Key Expiring Soon" admin@example.com < /tmp/key_status.txt
fi
```
### Discord/Slack Webhook
```bash
#!/bin/bash
# Save as scripts/notify_discord.sh
WEBHOOK_URL="https://discord.com/api/webhooks/..."
MESSAGE="⚠️ AegisGitea MCP: API key expires in 5 days. Run \`make rotate-key\`"
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"content\": \"$MESSAGE\"}"
```
---
## Emergency Rotation (Compromised Key)
If you suspect a key has been compromised:
### Immediate Actions (< 5 minutes)
1. **Revoke compromised key immediately**
```bash
# Edit .env - remove compromised key
nano .env
# Before
MCP_API_KEYS=compromised-key
# After
MCP_API_KEYS=
# Or temporarily disable auth
AUTH_ENABLED=false
docker-compose restart aegis-mcp
```
2. **Check audit logs for unauthorized access**
```bash
docker-compose exec aegis-mcp grep "compromised_key_hint" /var/log/aegis-mcp/audit.log
# Look for suspicious IPs, unusual times, unexpected repositories
```
3. **Generate new key**
```bash
make generate-key
```
4. **Update .env with new key**
```bash
MCP_API_KEYS=new-secure-key
AUTH_ENABLED=true
docker-compose restart aegis-mcp
```
5. **Update ChatGPT config**
### Investigation (< 30 minutes)
```bash
# When was compromised key last used?
docker-compose exec aegis-mcp grep "key_hint: compromised..." /var/log/aegis-mcp/audit.log | tail -1
# What repositories were accessed?
docker-compose exec aegis-mcp grep "compromised..." /var/log/aegis-mcp/audit.log | grep "repository"
# From which IPs?
docker-compose exec aegis-mcp grep "compromised..." /var/log/aegis-mcp/audit.log | grep "client_ip"
```
### Post-Incident (< 24 hours)
1. Document timeline of compromise
2. Review and improve key storage practices
3. Consider additional security measures (IP allowlisting, MFA)
4. Notify team if multi-user setup
---
## Troubleshooting Rotation
### Issue: Both old and new keys fail after rotation
**Cause:** Server not restarted or `.env` syntax error
**Fix:**
```bash
# Check .env syntax
cat .env | grep MCP_API_KEYS
# Should be: MCP_API_KEYS=key1,key2
# No spaces around =
# No quotes around keys
# Comma-separated, no spaces
# Restart
docker-compose restart aegis-mcp
# Check logs
docker-compose logs aegis-mcp | tail -20
```
### Issue: "No API keys configured" after rotation
**Cause:** Empty MCP_API_KEYS in `.env`
**Fix:**
```bash
# Verify .env has key
grep MCP_API_KEYS .env
# Should NOT be empty
# If empty, add key
echo "MCP_API_KEYS=your-key-here" >> .env
docker-compose restart aegis-mcp
```
### Issue: Old key still works after removal
**Cause:** Server not restarted or cached config
**Fix:**
```bash
# Force restart (not just reload)
docker-compose down
docker-compose up -d
# Verify old key fails
curl -H "Authorization: Bearer OLD_KEY" \
https://mcp.yourdomain.com/mcp/tools
# Should return 401
```
### Issue: Lost .env.backup file
**Cause:** Backup not created during rotation
**Prevention:**
```bash
# Always backup before manual edits
cp .env .env.backup-$(date +%Y%m%d-%H%M%S)
# The rotate script does this automatically
```
**Recovery:**
If you lost your key and no backup:
1. Generate new key
2. Update `.env`
3. Update ChatGPT
4. The old key is lost but system still works
---
## Best Practices Checklist
Before rotation:
- [ ] Check current key age: `make check-key-age`
- [ ] Backup `.env`: `cp .env .env.backup`
- [ ] Notify team if multi-user
- [ ] Schedule maintenance window if needed
During rotation:
- [ ] Generate new key: `make generate-key`
- [ ] Save key securely (password manager)
- [ ] Update `.env` with new key
- [ ] Restart server: `docker-compose restart`
- [ ] Verify new key works (curl test)
- [ ] Update ChatGPT config
- [ ] Test ChatGPT connection
After rotation:
- [ ] Remove old key from `.env` (if using grace period)
- [ ] Verify old key fails (curl test)
- [ ] Check audit logs for successful auth with new key
- [ ] Save key metadata: Save metadata when generating key
- [ ] Set calendar reminder for next rotation (90 days)
- [ ] Document rotation in changelog/runbook
---
## Key Metadata Tracking
When generating keys, save metadata:
```bash
make generate-key
# Choose "y" when prompted to save metadata
```
This creates `keys/key-<id>-<date>.txt`:
```
API Key Metadata
================
Key ID: a1b2c3d4...
Description: Production ChatGPT Key
Created: 2026-01-29T12:00:00+00:00
Expires: 2026-04-29T12:00:00+00:00
NOTE: The actual API key is NOT stored in this file for security.
Only metadata is saved for reference.
```
**Benefits:**
- Track key age automatically
- Audit key lifecycle
- Identify which key is which (if multiple)
- Automated expiration warnings
---
## FAQ
**Q: What's the grace period for rotation?**
A: 24-48 hours is recommended. Keep both keys working just long enough to update ChatGPT, then remove the old key.
**Q: Can I rotate keys without downtime?**
A: Yes! Use Strategy 2 (Grace Period). Both old and new keys work simultaneously during the transition.
**Q: How do I know which key is oldest?**
A: Use `make check-key-age` if you saved metadata. Otherwise, check audit logs for last usage.
**Q: Should I rotate after a team member leaves?**
A: Yes, if they had access to the shared key. Or better: use per-user keys and just remove their key.
**Q: Can I automate the entire rotation?**
A: Partially. You can automate generation and notification, but ChatGPT config update requires manual action.
---
## Next Steps
- [Authentication Setup](AUTH_SETUP.md) - Initial setup guide
- [ChatGPT Configuration](CHATGPT_SETUP.md) - Update ChatGPT after rotation
- [Security Policy](SECURITY.md) - Overall security best practices
---
**Set a reminder now:** Rotate your keys 90 days from today! 🔑

View File

@@ -1,362 +0,0 @@
# AegisGitea MCP - Project Summary
**Status**: Phase 1 Complete - Foundation Implemented
---
## What Was Built
A complete, production-ready implementation of AegisGitea MCP - a security-first Model Context Protocol server that enables controlled AI access to self-hosted Gitea repositories.
---
## Project Structure
```
AegisGitea-MCP/
├── src/aegis_gitea_mcp/ # Main application code
│ ├── __init__.py # Package initialization
│ ├── server.py # FastAPI server with MCP endpoints
│ ├── mcp_protocol.py # MCP protocol definitions
│ ├── config.py # Configuration management
│ ├── audit.py # Audit logging system
│ ├── gitea_client.py # Gitea API client
│ └── tools/ # MCP tool implementations
│ ├── __init__.py
│ └── repository.py # Repository access tools
├── tests/ # Test suite
│ ├── __init__.py
│ ├── conftest.py # Pytest configuration
│ └── test_config.py # Configuration tests
├── docker/ # Docker configuration
│ ├── Dockerfile # Multi-stage build
│ └── docker-compose.yml # Container orchestration
├── Documentation
│ ├── README.md # Main project documentation
│ ├── QUICKSTART.md # 5-minute setup guide
│ ├── DEPLOYMENT.md # Production deployment guide
│ ├── SECURITY.md # Security policy and best practices
│ └── PROJECT_SUMMARY.md # This file
├── Configuration
│ ├── .env.example # Environment variable template
│ ├── .gitignore # Git ignore patterns
│ ├── pyproject.toml # Python project configuration
│ ├── requirements.txt # Production dependencies
│ ├── requirements-dev.txt # Development dependencies
│ ├── Makefile # Development commands
│ └── docker-compose.yml # Root-level compose file
└── LICENSE # MIT License
```
---
## Implemented Features
### Phase 1: Foundation (COMPLETE)
#### Core Infrastructure
- [x] FastAPI-based MCP server
- [x] Server-Sent Events (SSE) endpoint for real-time communication
- [x] Health check and status endpoints
- [x] Structured logging with configurable levels
- [x] Environment-based configuration management
#### Security Features
- [x] Bot user authentication via access tokens
- [x] Dynamic authorization via Gitea permissions
- [x] Comprehensive audit logging (timestamp, tool, repo, target, correlation ID)
- [x] File size limits (configurable, default 1MB)
- [x] Request timeout protection
- [x] Input validation and error handling
- [x] Non-root Docker container execution
#### MCP Tools
- [x] `list_repositories` - List all bot-visible repositories
- [x] `get_repository_info` - Get repository metadata
- [x] `get_file_tree` - Browse repository file structure
- [x] `get_file_contents` - Read file contents with size limits
#### Gitea Integration
- [x] Async HTTP client with proper error handling
- [x] Bot user authentication and verification
- [x] Repository access control enforcement
- [x] File content retrieval with encoding handling
- [x] Tree/directory listing support
#### Developer Experience
- [x] Docker containerization with multi-stage builds
- [x] Docker Compose for easy deployment
- [x] Makefile with common development tasks
- [x] Pytest test suite with fixtures
- [x] Type hints and validation with Pydantic
- [x] Code quality tools (black, ruff, mypy)
- [x] Comprehensive documentation
---
## Technical Stack
| Component | Technology | Purpose |
|-----------|-----------|---------|
| **Server** | FastAPI + Uvicorn | Async HTTP server with SSE support |
| **HTTP Client** | httpx | Async Gitea API communication |
| **Validation** | Pydantic | Type-safe configuration and data models |
| **Logging** | structlog | Structured, machine-readable audit logs |
| **Containerization** | Docker | Isolated, reproducible deployment |
| **Testing** | pytest + pytest-asyncio | Comprehensive test coverage |
| **Code Quality** | black, ruff, mypy | Consistent code style and type safety |
---
## Architecture Highlights
### Separation of Concerns
```
ChatGPT ──HTTP/SSE──> MCP Server ──API──> Gitea
├──> Audit Logger (all actions logged)
├──> Config Manager (env-based settings)
└──> Tool Handlers (bounded operations)
```
### Security Model
1. **Authorization**: Fully delegated to Gitea (bot user permissions)
2. **Authentication**: Token-based, rotatable
3. **Auditability**: Every action logged with correlation IDs
4. **Safety**: Read-only, bounded operations, fail-safe defaults
### Key Design Decisions
- **No write operations**: Read-only by design, impossible to modify repositories
- **No global search**: All tools require explicit repository targeting
- **Dynamic permissions**: Changes in Gitea take effect immediately
- **Stateless server**: No session management, fully stateless
- **Explicit over implicit**: No hidden or automatic operations
---
## What's NOT Implemented (Future Phases)
### Phase 2: Extended Context (Planned)
- Commit history and diff viewing
- Issue and pull request access
- Branch listing and comparison
- Tag and release information
### Phase 3: Advanced Features (Future)
- Rate limiting per client (currently per-server)
- Webhook support for real-time updates
- Caching layer for performance
- Multi-tenant support
- OAuth2 flow instead of static tokens
---
## Testing Status
### Implemented Tests
- Configuration loading and validation
- Environment variable handling
- Default value verification
- Singleton pattern testing
### Test Coverage Needed
- Gitea client operations (requires mocking)
- MCP tool implementations
- Audit logging functionality
- Server endpoints and SSE
---
## Deployment Status
### Ready for Production
- ✅ Docker containerization
- ✅ Environment-based configuration
- ✅ Health checks and monitoring hooks
- ✅ Audit logging enabled
- ✅ Security hardening (non-root, resource limits)
- ✅ Documentation complete
### Needs Configuration
- ⚠️ Reverse proxy setup (Traefik/Caddy/Nginx)
- ⚠️ TLS certificates
- ⚠️ Bot user creation in Gitea
- ⚠️ Repository access grants
- ⚠️ Production environment variables
---
## Security Posture
### Implemented Safeguards
- Read-only operations only
- Bot user with minimal permissions
- Comprehensive audit logging
- File size limits
- Request timeouts
- Input validation
- Container security (non-root, no-new-privileges)
### Recommended Next Steps
- Set up log rotation for audit logs
- Implement monitoring/alerting on audit logs
- Regular token rotation policy
- Periodic access reviews
- Security training for operators
---
## Performance Characteristics
### Resource Usage (Typical)
- **Memory**: ~128-256 MB
- **CPU**: Minimal (async I/O bound)
- **Disk**: Audit logs grow over time (implement rotation)
- **Network**: Depends on file sizes and request frequency
### Scalability
- Stateless design allows horizontal scaling
- Async operations handle concurrent requests efficiently
- Rate limiting prevents abuse
---
## Next Steps for Deployment
1. **Setup Bot User** (5 min)
- Create `aegis-bot` user in Gitea
- Generate read-only access token
2. **Configure Environment** (2 min)
- Copy `.env.example` to `.env`
- Set `GITEA_URL` and `GITEA_TOKEN`
3. **Deploy Container** (1 min)
- Run `docker-compose up -d`
- Verify with `curl http://localhost:8080/health`
4. **Setup Reverse Proxy** (10-30 min)
- Configure Traefik/Caddy/Nginx
- Obtain TLS certificates
- Test HTTPS access
5. **Grant Repository Access** (2 min per repo)
- Add `aegis-bot` as collaborator
- Set Read permission
6. **Connect ChatGPT** (5 min)
- Add MCP server in ChatGPT settings
- Test with "List my Gitea repositories"
**Total time**: ~30-60 minutes for complete setup
---
## Success Criteria
This implementation successfully meets all Phase 1 objectives:
- ✅ Secure communication between ChatGPT and Gitea
- ✅ Bot user authentication working
- ✅ Dynamic authorization via Gitea
- ✅ Comprehensive audit logging
- ✅ Read-only operations enforced
- ✅ Production-ready deployment
- ✅ Complete documentation
---
## Maintainability
### Code Quality
- Type hints throughout
- Docstrings on all public functions
- Pydantic models for validation
- Structured error handling
- Separation of concerns
### Documentation
- Inline code comments where needed
- Comprehensive README
- Step-by-step deployment guide
- Security policy and best practices
- Quick start guide
### Testability
- Pytest framework set up
- Fixtures for common test scenarios
- Configuration reset between tests
- Mock-friendly architecture
---
## Known Limitations
1. **Audit Log Size**: Logs grow unbounded (implement rotation)
2. **Rate Limiting**: Per-server, not per-client
3. **Caching**: No caching layer (every request hits Gitea)
4. **Error Messages**: Could be more user-friendly
5. **Test Coverage**: Core logic tested, tools need more coverage
None of these are blockers for production use.
---
## Support and Maintenance
### Regular Maintenance Tasks
- **Weekly**: Review audit logs for anomalies
- **Monthly**: Review bot user permissions
- **Quarterly**: Rotate bot user token
- **As needed**: Update Docker images
### Monitoring Recommendations
- Track API response times
- Monitor error rates
- Alert on authentication failures
- Watch audit log size
---
## Final Notes
This project was built with **security, auditability, and simplicity** as the primary goals. The architecture intentionally avoids clever optimizations in favor of straightforward, auditable behavior.
**It's designed to be boring, predictable, and safe** - exactly what you want in a security-critical system.
---
## Questions for Stakeholders
Before going live, confirm:
1. **Bot user naming**: Is `aegis-bot` acceptable?
2. **Token rotation**: What's the policy (recommend: quarterly)?
3. **Audit log retention**: How long to keep logs (recommend: 90 days)?
4. **Access approval**: Who approves new repository access?
5. **Incident response**: Who responds to security alerts?
---
## Conclusion
**AegisGitea MCP is ready for production deployment.**
All Phase 1 objectives have been met, the system is fully documented, and security best practices have been implemented throughout. The next steps are configuration and deployment-specific rather than development work.
The foundation is solid, boring, and secure - ready to enable safe AI access to your private Gitea repositories.
---
**Project Status**: ✅ Phase 1 Complete - Ready for Deployment
**Last Updated**: January 29, 2026
**Version**: 0.1.0

View File

@@ -1,131 +0,0 @@
# Quick Start Guide
Get AegisGitea MCP running in 5 minutes.
---
## Prerequisites
- Docker and Docker Compose installed
- Self-hosted Gitea instance
- 5 minutes of your time
---
## Step 1: Create Bot User (2 minutes)
1. Log into your Gitea instance
2. Create a new user `aegis-bot` (or any name you prefer)
3. Go to Settings > Applications
4. Generate an access token with **read-only** permissions
5. Copy the token
---
## Step 2: Clone and Configure (1 minute)
```bash
# Clone repository
git clone <your-repo-url>
cd AegisGitea-MCP
# Configure environment
cp .env.example .env
nano .env
```
Edit `.env`:
```bash
GITEA_URL=https://your-gitea-instance.com
GITEA_TOKEN=your-bot-token-here
```
---
## Step 3: Start Server (1 minute)
```bash
docker-compose up -d
```
Verify it's running:
```bash
# Check logs
docker-compose logs -f
# Test health endpoint
curl http://localhost:8080/health
```
Expected response:
```json
{"status": "healthy"}
```
---
## Step 4: Grant Repository Access (1 minute)
1. Go to a repository in Gitea
2. Settings > Collaborators
3. Add `aegis-bot` user
4. Set permission to **Read**
---
## Step 5: Connect ChatGPT (Optional)
If using ChatGPT Business/Developer:
1. Go to ChatGPT Settings
2. Add MCP Server:
- **URL**: `http://localhost:8080` (or your domain)
- **Type**: HTTP/SSE
3. Test by asking: "List my Gitea repositories"
---
## What's Next?
- Read [DEPLOYMENT.md](DEPLOYMENT.md) for production setup
- Review [SECURITY.md](SECURITY.md) for security best practices
- Check audit logs: `docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log`
---
## Troubleshooting
### Container won't start
```bash
docker-compose logs aegis-mcp
```
Common issues:
- Invalid `GITEA_URL` or `GITEA_TOKEN` in `.env`
- Port 8080 already in use
- Gitea instance not accessible
### Bot can't see repositories
1. Verify bot user is added as collaborator
2. Check bot user has Read permission
3. Confirm repository is not archived
### ChatGPT can't connect
- Ensure MCP server is accessible from ChatGPT
- Check firewall rules
- Verify HTTPS is configured (required for production)
---
## Need Help?
- Check the [README.md](README.md) for detailed documentation
- Review logs for error messages
- Open an issue in the repository
---
**You're all set!** The AI can now securely access your Gitea repositories.

322
README.md
View File

@@ -1,322 +0,0 @@
# AegisGitea MCP
**A private, security-first MCP server for controlled AI access to self-hosted Gitea**
---
## Overview
AegisGitea MCP is a Model Context Protocol (MCP) server that enables controlled, auditable, read-only AI access to a self-hosted Gitea environment.
The system allows ChatGPT (Business / Developer environment) to inspect repositories, code, commits, issues, and pull requests **only through explicit MCP tool calls**, while all access control is dynamically managed through a dedicated bot user inside Gitea itself.
### Core Principles
- **Strong separation of concerns**: Clear boundaries between AI, MCP server, and Gitea
- **Least-privilege access**: Bot user has minimal necessary permissions
- **Full auditability**: Every AI action is logged with context
- **Dynamic authorization**: Access control via Gitea permissions (no redeployment needed)
- **Privacy-first**: Designed for homelab and private infrastructure
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ ChatGPT (Business/Developer) │
│ - Initiates explicit MCP tool calls │
│ - Human-in-the-loop decision making │
└────────────────────┬────────────────────────────────────────┘
│ HTTPS (MCP over SSE)
┌─────────────────────────────────────────────────────────────┐
│ AegisGitea MCP Server (Python, Docker) │
│ - Implements MCP protocol │
│ - Translates tool calls → Gitea API requests │
│ - Enforces access, logging, and safety constraints │
│ - Provides bounded, single-purpose tools │
└────────────────────┬────────────────────────────────────────┘
│ Gitea API (Bot User Token)
┌─────────────────────────────────────────────────────────────┐
│ Gitea Instance (Docker) │
│ - Source of truth for authorization │
│ - Hosts dedicated read-only bot user │
│ - Determines AI-visible repositories dynamically │
└─────────────────────────────────────────────────────────────┘
```
### Trust Model
| Component | Responsibility |
|-----------|----------------|
| **Gitea** | Authorization (what the AI can see) |
| **MCP Server** | Policy enforcement (how the AI accesses data) |
| **ChatGPT** | Decision initiation (when the AI acts) |
| **Human** | Final decision authority (why the AI acts) |
---
## Features
### Phase 1 — Foundation (Current)
- MCP protocol handling with SSE lifecycle
- Secure Gitea API communication via bot user token
- Health and readiness endpoints
- ChatGPT MCP registration flow
### Phase 2 — Authorization & Data Access (Planned)
- Repository discovery based on bot user permissions
- File tree and content retrieval with size limits
- Dynamic access control (changes in Gitea apply instantly)
### Phase 3 — Audit & Hardening (Planned)
- Comprehensive audit logging (timestamp, tool, repo, path, correlation ID)
- Request correlation and tracing
- Input validation and rate limiting
- Defensive bounds on all operations
### Phase 4 — Extended Context (Future)
- Commit history and diff inspection
- Issue and pull request visibility
- Full contextual understanding while maintaining read-only guarantees
---
## Authorization Model
### Bot User Strategy
A dedicated Gitea bot user represents "the AI":
- The MCP server authenticates as this user using a read-only token
- The bot user's repository permissions define AI visibility
- **No admin privileges**
- **No write permissions**
- **No implicit access**
This allows dynamic enable/disable of AI access **without restarting or reconfiguring** the MCP server.
**Example:**
```bash
# Grant AI access to a repository
git clone https://gitea.example.com/org/repo.git
cd repo
# Add bot user as collaborator with Read permission in Gitea UI
# Revoke AI access
# Remove bot user from repository in Gitea UI
```
---
## MCP Tool Design
All tools are:
- **Explicit**: Single-purpose, no hidden behavior
- **Deterministic**: Same input always produces same output
- **Bounded**: Size limits, path constraints, no wildcards
- **Auditable**: Full logging of every invocation
### Tool Categories
1. **Repository Discovery**
- List repositories visible to bot user
- Get repository metadata
2. **File Operations**
- Get file tree for a repository
- Read file contents (with size limits)
3. **Commit History** (Phase 4)
- List commits for a repository
- Get commit details and diffs
4. **Issues & PRs** (Phase 4)
- List issues and pull requests
- Read issue/PR details and comments
### Explicit Constraints
- No wildcard search tools
- No full-text indexing
- No recursive "read everything" operations
- No hidden or implicit data access
---
## Audit & Observability
Every MCP tool invocation logs:
- **Timestamp** (UTC)
- **Tool name**
- **Repository identifier**
- **Target** (path / commit / issue)
- **Correlation ID**
Logs are:
- Append-only
- Human-readable JSON
- Machine-parseable
- Stored locally by default
**Audit Philosophy**: The system must answer "What exactly did the AI see, and when?" without ambiguity.
---
## Deployment
### Prerequisites
- Docker and Docker Compose
- Self-hosted Gitea instance
- Gitea bot user with read-only access token
### Quick Start
```bash
# Clone repository
git clone https://gitea.example.com/your-org/AegisGitea-MCP.git
cd AegisGitea-MCP
# Configure environment
cp .env.example .env
# Edit .env with your Gitea URL and bot token
# Start MCP server
docker-compose up -d
# Check logs
docker-compose logs -f aegis-mcp
```
### Environment Variables
| Variable | Description | Required |
|----------|-------------|----------|
| `GITEA_URL` | Base URL of Gitea instance | Yes |
| `GITEA_TOKEN` | Bot user access token | Yes |
| `MCP_HOST` | MCP server listen host | No (default: 0.0.0.0) |
| `MCP_PORT` | MCP server listen port | No (default: 8080) |
| `LOG_LEVEL` | Logging verbosity | No (default: INFO) |
| `AUDIT_LOG_PATH` | Audit log file path | No (default: /var/log/aegis-mcp/audit.log) |
### Security Considerations
1. **Never expose the MCP server publicly** — use a reverse proxy with TLS
2. **Rotate bot tokens regularly**
3. **Monitor audit logs** for unexpected access patterns
4. **Keep Docker images updated**
5. **Use a dedicated bot user** — never use a personal account token
---
## Development
### Setup
```bash
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements-dev.txt
# Run tests
pytest tests/
# Run server locally
python -m aegis_gitea_mcp.server
```
### Project Structure
```
AegisGitea-MCP/
├── src/
│ └── aegis_gitea_mcp/
│ ├── __init__.py
│ ├── server.py # MCP server entry point
│ ├── mcp_protocol.py # MCP protocol implementation
│ ├── gitea_client.py # Gitea API client
│ ├── audit.py # Audit logging
│ ├── config.py # Configuration management
│ └── tools/ # MCP tool implementations
│ ├── __init__.py
│ ├── repository.py # Repository discovery tools
│ └── files.py # File access tools
├── tests/
│ ├── test_mcp_protocol.py
│ ├── test_gitea_client.py
│ └── test_tools.py
├── docker/
│ ├── Dockerfile
│ └── docker-compose.yml
├── .env.example
├── pyproject.toml
├── requirements.txt
├── requirements-dev.txt
└── README.md
```
---
## Non-Goals
Explicitly **out of scope**:
- No write access to Gitea (no commits, comments, merges, edits)
- No autonomous or background scanning
- No global search or unrestricted crawling
- No public exposure of repositories or credentials
- No coupling to GitHub or external VCS platforms
---
## Roadmap
- [x] Project initialization and architecture design
- [ ] **Phase 1**: MCP server foundation and Gitea integration
- [ ] **Phase 2**: Repository discovery and file access tools
- [ ] **Phase 3**: Audit logging and security hardening
- [ ] **Phase 4**: Commit history, issues, and PR support
---
## Contributing
This project prioritizes security and privacy. Contributions should:
1. Maintain read-only guarantees
2. Add comprehensive audit logging for new tools
3. Include tests for authorization and boundary cases
4. Document security implications
---
## License
MIT License - See LICENSE file for details
---
## Acknowledgments
Built on the [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic.
---
## Support
For issues, questions, or security concerns, please open an issue in the Gitea repository.
**Remember**: This is designed to be **boring, predictable, and safe** — not clever, not magical, and not autonomous.

View File

@@ -1,285 +0,0 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.1.x | :white_check_mark: |
---
## Security Principles
AegisGitea MCP is designed with security as the primary concern:
1. **Read-Only by Design**: No write operations to Gitea
2. **Least Privilege**: Bot user has minimal necessary permissions
3. **Explicit Access**: No implicit or hidden data access
4. **Full Auditability**: Every action is logged
5. **Fail-Safe**: Errors deny access rather than grant it
---
## Threat Model
### In Scope
- Unauthorized access to repositories
- Token compromise and misuse
- Data exfiltration via MCP tools
- Denial of service attacks
- API abuse and rate limiting bypass
### Out of Scope
- Physical access to server
- Social engineering attacks
- Compromise of Gitea instance itself
- ChatGPT platform security
---
## Security Features
### 1. Authorization
- **Dynamic**: Permissions managed in Gitea, not MCP server
- **Explicit**: Bot user must be added to each repository
- **Reversible**: Removing bot user immediately revokes access
### 2. Authentication
- Token-based authentication with Gitea
- No password storage
- Tokens should be rotated regularly
### 3. Audit Logging
Every tool invocation logs:
- Timestamp (UTC)
- Tool name
- Repository accessed
- Target file/path
- Request correlation ID
- Success/failure status
Logs are append-only and tamper-evident.
### 4. Input Validation
- File size limits enforced
- Path traversal protection
- Request timeout limits
- Rate limiting per minute
### 5. Container Security
- Runs as non-root user
- No unnecessary privileges
- Resource limits enforced
- Read-only filesystem where possible
---
## Reporting a Vulnerability
**Please do not report security vulnerabilities through public GitHub issues.**
Instead:
1. **Email**: security@youromain.com (replace with your actual contact)
2. **Subject**: `[SECURITY] AegisGitea MCP - Brief Description`
3. **Include**:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
### Response Timeline
- **24 hours**: Acknowledgment of report
- **7 days**: Initial assessment and severity rating
- **30 days**: Fix developed and tested
- **45 days**: Public disclosure (if applicable)
---
## Security Best Practices
### For Operators
1. **Token Management**
- Use dedicated bot user (never personal accounts)
- Rotate tokens quarterly
- Store tokens in secrets manager
- Never commit tokens to version control
2. **Network Security**
- Always use HTTPS with valid TLS certificates
- Never expose MCP server directly to internet
- Use reverse proxy for TLS termination
- Consider VPN or IP allowlisting
3. **Access Control**
- Review bot user permissions monthly
- Remove access from archived repositories
- Document which repositories are AI-visible
- Implement approval workflow for new access
4. **Monitoring**
- Review audit logs weekly
- Set up alerts for unusual access patterns
- Monitor for failed authentication attempts
- Track file access frequency
5. **Updates**
- Keep Docker images updated
- Monitor security advisories
- Test updates in staging first
- Maintain rollback capability
### For Developers
1. **Code Review**
- All changes require peer review
- Security-critical changes require 2+ reviewers
- Automated tests must pass
2. **Dependencies**
- Pin dependency versions
- Review dependency licenses
- Monitor for security advisories
- Use tools like `pip-audit` or `safety`
3. **Testing**
- Write tests for authorization logic
- Test boundary conditions
- Include negative test cases
- Fuzz test inputs
---
## Known Limitations
1. **Trust in Gitea**: Authorization depends on Gitea's access control
2. **Token Security**: Compromised token = compromised access until rotated
3. **Rate Limiting**: Current implementation is per-server, not per-client
4. **Audit Log Size**: Logs grow unbounded (implement rotation)
---
## Security Checklist
Before deploying to production:
- [ ] Bot user created with minimal permissions
- [ ] Access token generated with read-only scope
- [ ] TLS configured with valid certificate
- [ ] Reverse proxy properly configured
- [ ] Audit logging enabled and tested
- [ ] Resource limits set in Docker
- [ ] Firewall rules configured
- [ ] Monitoring and alerting set up
- [ ] Incident response plan documented
- [ ] Team trained on security procedures
---
## Incident Response
If you suspect a security breach:
### Immediate Actions (within 5 minutes)
1. **Isolate**: Stop the MCP container
```bash
docker-compose down
```
2. **Revoke**: Delete bot user token in Gitea
- Go to Gitea > Settings > Applications
- Delete the token immediately
3. **Preserve**: Save audit logs for analysis
```bash
docker cp aegis-gitea-mcp:/var/log/aegis-mcp/audit.log ./incident-$(date +%Y%m%d-%H%M%S).log
```
### Investigation (within 1 hour)
1. Review audit logs for unauthorized access
2. Check which repositories were accessed
3. Identify timeframe of suspicious activity
4. Document findings
### Remediation (within 24 hours)
1. Generate new bot user token
2. Review and update bot user permissions
3. Deploy updated configuration
4. Monitor for continued suspicious activity
5. Notify affected repository owners if necessary
### Post-Incident (within 1 week)
1. Conduct post-mortem analysis
2. Update security procedures
3. Implement additional safeguards
4. Document lessons learned
5. Train team on new procedures
---
## Compliance Considerations
### GDPR
- Audit logs may contain personal data (usernames, timestamps)
- Implement log retention policy (recommend 90 days)
- Provide mechanism for data deletion requests
### SOC 2
- Audit logging satisfies monitoring requirements
- Access control model supports least privilege
- Incident response procedures documented
### Internal Policies
- Adapt security practices to your organization's policies
- Document any deviations from standard procedures
- Obtain necessary approvals before deployment
---
## Security Roadmap
Future security enhancements (not yet implemented):
- [ ] Multi-factor authentication for bot token generation
- [ ] Per-client rate limiting (not just per-server)
- [ ] Automated audit log analysis and anomaly detection
- [ ] Integration with SIEM systems
- [ ] Encrypted audit logs
- [ ] Support for multiple bot users with different permissions
- [ ] OAuth2 flow instead of static tokens
- [ ] Content scanning for sensitive data patterns
---
## Acknowledgments
Security vulnerabilities responsibly disclosed by:
- (None yet - be the first!)
---
## Contact
Security Team: security@yourdomain.com
General Support: issues in Gitea repository
---
**Remember**: Security is a process, not a product. Stay vigilant.

View File

@@ -1,441 +0,0 @@
# Testing Guide
Comprehensive guide for testing AegisGitea MCP.
---
## Test Suite Overview
The project includes a complete test suite covering:
1. **Unit Tests** - Individual components (auth, config)
2. **Integration Tests** - Component interactions
3. **Server Tests** - API endpoints and middleware
4. **Manual Tests** - Real-world scenarios
---
## Quick Start
### Run All Tests
```bash
# Using test script (recommended)
./run_tests.sh
# Or using Make
make test
# Or directly with pytest
pytest tests/ -v
```
### Run Specific Test Files
```bash
# Authentication tests only
pytest tests/test_auth.py -v
# Server endpoint tests only
pytest tests/test_server.py -v
# Integration tests only
pytest tests/test_integration.py -v
```
### Run with Coverage
```bash
pytest tests/ --cov=aegis_gitea_mcp --cov-report=html
# View coverage report
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
```
---
## Test Modules
### 1. `test_auth.py` - Authentication Module Tests
**Coverage:**
- API key generation (length, format)
- Key hashing (SHA256)
- Bearer token extraction
- Constant-time comparison
- Rate limiting (per IP)
- Multiple keys support
- Auth enable/disable
**Key Tests:**
```python
test_generate_api_key() # 64-char hex key generation
test_validate_api_key_valid() # Valid key acceptance
test_validate_api_key_invalid() # Invalid key rejection
test_rate_limiting() # 5 attempts/5min limit
test_multiple_keys() # Comma-separated keys
```
**Run:**
```bash
pytest tests/test_auth.py -v
```
---
### 2. `test_server.py` - Server Endpoint Tests
**Coverage:**
- Middleware authentication
- Protected vs public endpoints
- Authorization header formats
- Rate limiting at HTTP level
- Error messages
**Key Tests:**
```python
test_health_endpoint_no_auth_required() # /health is public
test_list_tools_without_auth() # /mcp/tools requires auth
test_list_tools_with_valid_key() # Valid key works
test_auth_header_formats() # Bearer format validation
test_rate_limiting() # HTTP-level rate limit
```
**Run:**
```bash
pytest tests/test_server.py -v
```
---
### 3. `test_integration.py` - Integration Tests
**Coverage:**
- Complete authentication flow
- Key rotation simulation
- Multi-tool discovery
- Concurrent requests
- Error handling
**Key Tests:**
```python
test_complete_authentication_flow() # End-to-end flow
test_key_rotation_simulation() # Grace period handling
test_all_mcp_tools_discoverable() # Tool registration
test_concurrent_requests_different_ips() # Per-IP rate limiting
```
**Run:**
```bash
pytest tests/test_integration.py -v
```
---
### 4. `test_config.py` - Configuration Tests
**Coverage:**
- Environment variable loading
- Default values
- Validation rules
- Required fields
**Key Tests:**
```python
test_settings_from_env() # Env var parsing
test_settings_defaults() # Default values
test_settings_invalid_log_level() # Validation
```
**Run:**
```bash
pytest tests/test_config.py -v
```
---
## Manual Testing
### Prerequisites
```bash
# 1. Generate API key
make generate-key
# 2. Add to .env
echo "MCP_API_KEYS=your-key-here" >> .env
# 3. Build and start
docker-compose build
docker-compose up -d
```
### Test 1: Health Check (No Auth)
```bash
curl http://localhost:8080/health
# Expected: {"status": "healthy"}
```
### Test 2: Protected Endpoint Without Auth
```bash
curl http://localhost:8080/mcp/tools
# Expected: 401 Unauthorized
```
### Test 3: Protected Endpoint With Invalid Key
```bash
curl -H "Authorization: Bearer invalid-key-1234567890123456789012345678901234" \
http://localhost:8080/mcp/tools
# Expected: 401 Unauthorized with "Invalid API key" message
```
### Test 4: Protected Endpoint With Valid Key
```bash
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/mcp/tools
# Expected: 200 OK with JSON list of tools
```
### Test 5: Rate Limiting
```bash
# Run this script to trigger rate limit:
for i in {1..6}; do
curl -H "Authorization: Bearer wrong-key-12345678901234567890123456789012345" \
http://localhost:8080/mcp/tools
echo ""
done
# 6th request should return "Too many failed authentication attempts"
```
### Test 6: Key Rotation
```bash
# 1. Add second key to .env
MCP_API_KEYS=old-key,new-key
# 2. Restart
docker-compose restart aegis-mcp
# 3. Test both keys work
curl -H "Authorization: Bearer old-key" http://localhost:8080/mcp/tools
curl -H "Authorization: Bearer new-key" http://localhost:8080/mcp/tools
# 4. Remove old key
MCP_API_KEYS=new-key
# 5. Restart and verify only new key works
docker-compose restart aegis-mcp
curl -H "Authorization: Bearer old-key" http://localhost:8080/mcp/tools # Should fail
curl -H "Authorization: Bearer new-key" http://localhost:8080/mcp/tools # Should work
```
### Test 7: ChatGPT Integration
1. Configure ChatGPT Business with your API key
2. Ask: "List my Gitea repositories"
3. Verify response contains repository list
4. Check audit logs:
```bash
docker-compose exec aegis-mcp cat /var/log/aegis-mcp/audit.log | grep "api_authentication"
```
---
## Test Data
### Valid API Keys (for testing)
```python
# 64-character keys
KEY_A = "a" * 64
KEY_B = "b" * 64
KEY_C = "c" * 64
# Invalid keys (too short)
INVALID_SHORT = "short"
INVALID_32 = "x" * 31 # Less than 32 chars
```
### Test Environment Variables
```bash
# Minimal test environment
GITEA_URL=https://gitea.example.com
GITEA_TOKEN=test-token-12345
AUTH_ENABLED=true
MCP_API_KEYS=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# Auth disabled for testing
AUTH_ENABLED=false
MCP_API_KEYS= # Can be empty when disabled
```
---
## CI/CD Integration
### GitHub Actions Example
```yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements-dev.txt
- name: Run tests
run: |
pytest tests/ -v --cov=aegis_gitea_mcp
- name: Upload coverage
uses: codecov/codecov-action@v3
```
---
## Performance Testing
### Load Test Example
```bash
# Install Apache Bench
sudo apt-get install apache2-utils
# Test authenticated endpoint (100 requests, 10 concurrent)
ab -n 100 -c 10 \
-H "Authorization: Bearer YOUR_KEY" \
http://localhost:8080/mcp/tools
# Check results:
# - Requests per second
# - Mean time per request
# - Longest request
```
### Expected Performance
- **Health check**: < 10ms per request
- **List tools** (authenticated): < 50ms per request
- **Rate limit enforcement**: < 5ms overhead
---
## Troubleshooting Tests
### Issue: Import errors
```bash
# Solution: Install dev dependencies
pip install -r requirements-dev.txt
```
### Issue: "No module named 'aegis_gitea_mcp'"
```bash
# Solution: Set PYTHONPATH
export PYTHONPATH="${PYTHONPATH}:$(pwd)/src"
# Or run from project root
cd /path/to/AegisGitea-MCP
pytest tests/
```
### Issue: Tests pass locally but fail in Docker
```bash
# Solution: Run tests inside Docker
docker-compose exec aegis-mcp pytest /app/tests/
```
### Issue: Rate limiting tests interfere with each other
```bash
# Solution: Run tests with fresh validator each time
pytest tests/test_auth.py::test_rate_limiting -v --tb=short
```
---
## Code Coverage Goals
| Module | Target Coverage | Current |
|--------|----------------|---------|
| `auth.py` | > 90% | ✅ |
| `config.py` | > 85% | ✅ |
| `server.py` | > 80% | ✅ |
| `mcp_protocol.py` | > 90% | ✅ |
| `gitea_client.py` | > 70% | ⏳ Requires mocking |
| `audit.py` | > 80% | ⏳ Requires file I/O |
---
## Test Maintenance
### Adding New Tests
1. Create test file: `tests/test_<module>.py`
2. Follow existing patterns:
```python
def test_descriptive_name():
"""Clear description of what's being tested."""
# Arrange
# Act
# Assert
```
3. Run tests to ensure they pass
4. Update this doc with new test coverage
### Updating Tests
- Run full suite after changes: `./run_tests.sh`
- Check coverage: `pytest --cov`
- Update test data if needed
---
## Best Practices
1. **Isolation**: Each test should be independent
2. **Fixtures**: Use pytest fixtures for setup/teardown
3. **Naming**: `test_<what>_<scenario>_<expected>`
4. **Assertions**: One logical assertion per test
5. **Documentation**: Clear docstrings explaining purpose
---
## Next Steps
- [ ] Add tests for Gitea client (with mocking)
- [ ] Add tests for audit logging (with temp files)
- [ ] Add performance benchmarks
- [ ] Add end-to-end tests with real Gitea instance
- [ ] Set up continuous testing in CI/CD
---
**Test coverage is currently ~85% for core authentication and server modules. All critical paths are covered.**

View File

@@ -1,10 +1,9 @@
#!/usr/bin/env python3
"""Check API key age and alert if rotation needed."""
import os
import re
import sys
from datetime import datetime, timedelta, timezone
from datetime import datetime, timezone
from pathlib import Path
@@ -46,7 +45,7 @@ def main() -> None:
critical = []
for metadata_file in sorted(metadata_files):
with open(metadata_file, "r") as f:
with open(metadata_file, "r", encoding="utf-8") as f:
content = f.read()
# Extract metadata

View File

@@ -87,7 +87,7 @@ def main() -> None:
key_id = api_key[:12]
metadata_file = keys_dir / f"key-{key_id}-{created_at.strftime('%Y%m%d')}.txt"
with open(metadata_file, "w") as f:
with open(metadata_file, "w", encoding="utf-8") as f:
f.write(f"API Key Metadata\n")
f.write(f"================\n\n")
f.write(f"Key ID: {key_id}...\n")

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""Rotate API key for AegisGitea MCP."""
import os
import re
import sys
from datetime import datetime, timedelta, timezone
@@ -31,7 +30,7 @@ def main() -> None:
sys.exit(1)
# Read current .env
with open(env_file, "r") as f:
with open(env_file, "r", encoding="utf-8") as f:
env_content = f.read()
# Check if MCP_API_KEYS exists
@@ -115,13 +114,13 @@ def main() -> None:
# Backup old .env
backup_file = env_file.with_suffix(f".env.backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}")
with open(backup_file, "w") as f:
with open(backup_file, "w", encoding="utf-8") as f:
f.write(env_content)
print(f"✓ Backed up old .env to: {backup_file.name}")
# Write new .env
with open(env_file, "w") as f:
with open(env_file, "w", encoding="utf-8") as f:
f.write(new_env_content)
print(f"✓ Updated .env file with new key(s)")

View File

@@ -25,6 +25,7 @@ class AuditLogger:
# Ensure log directory exists
self.log_path.parent.mkdir(parents=True, exist_ok=True)
self._log_file = self._get_log_file()
# Configure structlog for audit logging
structlog.configure(
@@ -35,7 +36,7 @@ class AuditLogger:
],
wrapper_class=structlog.BoundLogger,
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(file=self._get_log_file()),
logger_factory=structlog.PrintLoggerFactory(file=self._log_file),
cache_logger_on_first_use=True,
)
@@ -45,6 +46,13 @@ class AuditLogger:
"""Get file handle for audit log."""
return open(self.log_path, "a", encoding="utf-8")
def close(self) -> None:
"""Close open audit log resources."""
try:
self._log_file.close()
except Exception:
pass
def log_tool_invocation(
self,
tool_name: str,
@@ -166,4 +174,6 @@ def get_audit_logger() -> AuditLogger:
def reset_audit_logger() -> None:
"""Reset global audit logger instance (primarily for testing)."""
global _audit_logger
if _audit_logger is not None:
_audit_logger.close()
_audit_logger = None

View File

@@ -1,7 +1,7 @@
"""Configuration management for AegisGitea MCP server."""
from pathlib import Path
from typing import List, Optional
from typing import Optional
from pydantic import Field, HttpUrl, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -112,7 +112,7 @@ class Settings(BaseSettings):
def validate_and_parse_api_keys(self) -> "Settings":
"""Parse and validate API keys if authentication is enabled."""
# Parse comma-separated keys into list
keys = []
keys: list[str] = []
if self.mcp_api_keys_raw and self.mcp_api_keys_raw.strip():
keys = [key.strip() for key in self.mcp_api_keys_raw.split(",") if key.strip()]
@@ -137,7 +137,7 @@ class Settings(BaseSettings):
return self
@property
def mcp_api_keys(self) -> List[str]:
def mcp_api_keys(self) -> list[str]:
"""Get parsed list of API keys."""
return getattr(self, "_mcp_api_keys", [])
@@ -155,7 +155,7 @@ def get_settings() -> Settings:
"""Get or create global settings instance."""
global _settings
if _settings is None:
_settings = Settings() # type: ignore
_settings = Settings()
return _settings

View File

@@ -69,7 +69,7 @@ class GiteaClient:
if self.client:
await self.client.aclose()
def _handle_response(self, response: Response, correlation_id: str) -> Dict[str, Any]:
def _handle_response(self, response: Response, correlation_id: str) -> Any:
"""Handle Gitea API response and raise appropriate exceptions.
Args:
@@ -148,12 +148,12 @@ class GiteaClient:
return user_data
except Exception as e:
except Exception as exc:
self.audit.log_tool_invocation(
tool_name="get_current_user",
correlation_id=correlation_id,
result_status="error",
error=str(e),
error=str(exc),
)
raise
@@ -190,12 +190,12 @@ class GiteaClient:
return repos
except Exception as e:
except Exception as exc:
self.audit.log_tool_invocation(
tool_name="list_repositories",
correlation_id=correlation_id,
result_status="error",
error=str(e),
error=str(exc),
)
raise
@@ -236,13 +236,13 @@ class GiteaClient:
return repo_data
except Exception as e:
except Exception as exc:
self.audit.log_tool_invocation(
tool_name="get_repository",
repository=repo_id,
correlation_id=correlation_id,
result_status="error",
error=str(e),
error=str(exc),
)
raise
@@ -314,14 +314,14 @@ class GiteaClient:
return file_data
except Exception as e:
except Exception as exc:
self.audit.log_tool_invocation(
tool_name="get_file_contents",
repository=repo_id,
target=filepath,
correlation_id=correlation_id,
result_status="error",
error=str(e),
error=str(exc),
)
raise
@@ -370,12 +370,12 @@ class GiteaClient:
return tree_data
except Exception as e:
except Exception as exc:
self.audit.log_tool_invocation(
tool_name="get_tree",
repository=repo_id,
correlation_id=correlation_id,
result_status="error",
error=str(e),
error=str(exc),
)
raise

View File

@@ -2,7 +2,7 @@
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field
class MCPTool(BaseModel):
@@ -14,9 +14,10 @@ class MCPTool(BaseModel):
..., alias="inputSchema", description="JSON Schema for tool input"
)
class Config:
populate_by_name = True
by_alias = True
model_config = ConfigDict(
populate_by_name=True,
serialize_by_alias=True,
)
class MCPToolCallRequest(BaseModel):

View File

@@ -40,9 +40,7 @@ app = FastAPI(
)
# Global settings and audit logger
# Note: auth_validator is fetched dynamically in middleware to support test resets
settings = get_settings()
audit = get_audit_logger()
# Note: access settings/audit logger dynamically to support test resets.
# Tool dispatcher mapping
@@ -115,6 +113,7 @@ async def authenticate_request(request: Request, call_next):
@app.on_event("startup")
async def startup_event() -> None:
"""Initialize server on startup."""
settings = get_settings()
logger.info(f"Starting AegisGitea MCP Server on {settings.mcp_host}:{settings.mcp_port}")
logger.info(f"Connected to Gitea instance: {settings.gitea_base_url}")
logger.info(f"Audit logging enabled: {settings.audit_log_path}")
@@ -180,6 +179,7 @@ async def call_tool(request: MCPToolCallRequest) -> JSONResponse:
Returns:
JSON response with tool execution result
"""
audit = get_audit_logger()
correlation_id = request.correlation_id or audit.log_tool_invocation(
tool_name=request.tool,
params=request.arguments,
@@ -312,6 +312,7 @@ async def sse_message_handler(request: Request) -> JSONResponse:
JSON response acknowledging the message
"""
try:
audit = get_audit_logger()
body = await request.json()
logger.info(f"Received MCP message via SSE POST: {body}")

View File

@@ -40,8 +40,8 @@ async def list_repositories_tool(gitea: GiteaClient, arguments: Dict[str, Any])
"count": len(simplified_repos),
}
except GiteaError as e:
raise Exception(f"Failed to list repositories: {str(e)}")
except GiteaError as exc:
raise RuntimeError(f"Failed to list repositories: {exc}") from exc
async def get_repository_info_tool(
@@ -84,8 +84,8 @@ async def get_repository_info_tool(
"clone_url": repo_data.get("clone_url", ""),
}
except GiteaError as e:
raise Exception(f"Failed to get repository info: {str(e)}")
except GiteaError as exc:
raise RuntimeError(f"Failed to get repository info: {exc}") from exc
async def get_file_tree_tool(gitea: GiteaClient, arguments: Dict[str, Any]) -> Dict[str, Any]:
@@ -129,8 +129,8 @@ async def get_file_tree_tool(gitea: GiteaClient, arguments: Dict[str, Any]) -> D
"count": len(simplified_tree),
}
except GiteaError as e:
raise Exception(f"Failed to get file tree: {str(e)}")
except GiteaError as exc:
raise RuntimeError(f"Failed to get file tree: {exc}") from exc
async def get_file_contents_tool(gitea: GiteaClient, arguments: Dict[str, Any]) -> Dict[str, Any]:
@@ -185,5 +185,5 @@ async def get_file_contents_tool(gitea: GiteaClient, arguments: Dict[str, Any])
"url": file_data.get("html_url", ""),
}
except GiteaError as e:
raise Exception(f"Failed to get file contents: {str(e)}")
except GiteaError as exc:
raise RuntimeError(f"Failed to get file contents: {exc}") from exc

View File

@@ -1,7 +1,5 @@
"""Pytest configuration and fixtures."""
import os
import tempfile
from pathlib import Path
from typing import Generator

View File

@@ -5,8 +5,8 @@ import pytest
from aegis_gitea_mcp.auth import (
APIKeyValidator,
generate_api_key,
hash_api_key,
get_validator,
hash_api_key,
reset_validator,
)
from aegis_gitea_mcp.config import reset_settings
@@ -141,7 +141,7 @@ def test_rate_limiting(validator):
client_ip = "192.168.1.1"
# First 5 attempts should be allowed
for i in range(5):
for _ in range(5):
is_valid, error = validator.validate_api_key("b" * 64, client_ip, "test-agent")
assert not is_valid
@@ -157,7 +157,7 @@ def test_rate_limiting_per_ip(validator):
ip2 = "192.168.1.2"
# Fail 5 times from IP1
for i in range(5):
for _ in range(5):
validator.validate_api_key("b" * 64, ip1, "test-agent")
# IP1 should be rate limited

View File

@@ -120,7 +120,7 @@ def test_concurrent_requests_different_ips(client):
tool_call_data = {"tool": "list_repositories", "arguments": {}}
# Make 5 failed attempts on protected endpoint
for i in range(5):
for _ in range(5):
response = client.post("/mcp/tool/call", headers=headers_invalid, json=tool_call_data)
assert response.status_code == 401

View File

@@ -203,7 +203,7 @@ def test_rate_limiting(client):
tool_data = {"tool": "list_repositories", "arguments": {}}
# Make 6 failed attempts on protected endpoint
for i in range(6):
for _ in range(6):
response = client.post(
"/mcp/tool/call", headers={"Authorization": "Bearer " + "x" * 64}, json=tool_data
)