#gitea #mcp ![[Pasted image 20260105120732.png]] # Building an MCP Server: Integrating Gitea/GitHub with Claude > A comprehensive guide to creating a production-ready Model Context Protocol server that connects Claude AI with Git platforms for seamless GitOps automation. --- ## Table of Contents ```dataview TABLE WITHOUT ID link(file.link, section) as "Section", reading as "Reading Time" FROM "" WHERE file = this.file ``` - [[#Introduction to MCP|Introduction to MCP]] - [[#Architecture Overview|Architecture Overview]] - [[#Prerequisites & Setup|Prerequisites & Setup]] - [[#Project Structure|Project Structure]] - [[#Core Implementation|Core Implementation]] - [[#Git Platform API Integration|Git Platform API Integration]] - [[#Defining MCP Tools|Defining MCP Tools]] - [[#Request Handlers|Request Handlers]] - [[#Error Handling & Security|Error Handling & Security]] - [[#Testing & Deployment|Testing & Deployment]] - [[#Claude Desktop Integration|Claude Desktop Integration]] - [[#Real-World Usage Examples|Real-World Usage Examples]] --- ## Introduction to MCP The **Model Context Protocol (MCP)** is Anthropic's open standard for connecting AI assistants like Claude to external data sources and tools. Think of it as a universal adapter that allows Claude to seamlessly interact with your development infrastructure. > [!info] Why MCP matters Instead of Claude being limited to its training data and built-in capabilities, MCP servers extend Claude's reach to real-time systems, databases, APIs, and tools. This transforms Claude from a conversational assistant into an active participant in your workflows. ### What We're Building In this guide, we'll build a production-grade MCP server that connects Claude to Git platforms (Gitea/GitHub). Once integrated, Claude will be able to: - ✅ Create, read, update, and delete files in repositories - ✅ Manage branches and create pull requests - ✅ Automate Git operations with natural language commands - ✅ Enable true GitOps workflows where Claude commits infrastructure changes directly This isn't just a proof-of-concept. The server we build will be **production-ready**, with proper error handling, TypeScript type safety, logging, and security best practices. --- ## Architecture Overview ```mermaid graph TB A[Claude Desktop] -->|MCP Protocol| B[MCP Server] B -->|REST API| C[Gitea/GitHub] subgraph MCP Server D[Tool Definitions] E[Request Handlers] F[API Client] end B --> D B --> E B --> F style A fill:#00ff88,stroke:#00ff88,stroke-width:2px style B fill:#0088ff,stroke:#0088ff,stroke-width:2px style C fill:#ff0088,stroke:#ff0088,stroke-width:2px ``` ### Component Breakdown #### 1. Claude Desktop The client application that users interact with. It discovers and connects to MCP servers via configuration files. #### 2. MCP Server Your custom server implementation. It receives requests from Claude, processes them, and returns responses. Key responsibilities: - Registering available tools (operations Claude can perform) - Handling tool invocation requests - Managing authentication and API communication - Error handling and validation #### 3. Git Platform API The target system (Gitea/GitHub) that your server communicates with via REST APIs. ### Communication Flow ``` User: "Create a deployment.yaml for nginx" ↓ Claude Desktop: Understands intent, calls gitea_create_file tool ↓ MCP Server: Receives tool call, validates parameters ↓ Git API: Creates file, returns commit info ↓ MCP Server: Formats response ↓ Claude Desktop: Shows success message to user ``` --- ## Prerequisites & Setup ### Required Tools - **Node.js 18+**: Runtime for our TypeScript server - **TypeScript 5+**: For type-safe development - **Git platform access**: Gitea or GitHub with API access - **Claude Desktop**: The MCP client ### Initial Setup ```bash # Create project directory mkdir gitea-mcp-server cd gitea-mcp-server # Initialize npm project npm init -y # Install core dependencies npm install @modelcontextprotocol/sdk axios dotenv zod # Install dev dependencies npm install -D typescript @types/node ts-node # Initialize TypeScript npx tsc --init ``` ### Get API Access #### For Gitea: 1. Navigate to Settings → Applications 2. Generate New Token with `repository` permissions 3. Save the token securely #### For GitHub: 1. Settings → Developer settings → Personal access tokens 2. Generate new token (classic) with `repo` scope 3. Save the token securely > [!warning] Security Warning Never commit API tokens to version control. Always use environment variables or secure secret management. --- ## Project Structure ``` gitea-mcp-server/ ├── src/ │ ├── index.ts # Main server entry point │ ├── types.ts # TypeScript type definitions │ ├── gitea-client.ts # API client implementation │ ├── tools.ts # MCP tool definitions │ ├── handlers.ts # Tool request handlers │ └── utils.ts # Helper functions ├── package.json # Dependencies ├── tsconfig.json # TypeScript configuration ├── .env.example # Environment template └── README.md # Documentation ``` This structure separates concerns cleanly: - **index.ts**: Server initialization and MCP protocol handling - **types.ts**: All TypeScript interfaces and types - **gitea-client.ts**: Encapsulates all Git platform API calls - **tools.ts**: Declares what Claude can do (the "menu") - **handlers.ts**: Implements the actual tool logic - **utils.ts**: Logging, validation, helpers --- ## Core Implementation ### Step 1: Define TypeScript Types Strong typing is crucial for reliability. Start with comprehensive type definitions: ```typescript // src/types.ts export interface GiteaConfig { url: string; token: string; defaultOwner: string; defaultRepo: string; defaultBranch: string; author: { name: string; email: string; }; } export interface Repository { id: number; name: string; full_name: string; description: string; private: boolean; default_branch: string; html_url: string; clone_url: string; } export interface FileContent { name: string; path: string; sha: string; size: number; content: string; // Base64 encoded encoding: string; html_url: string; } export interface CreateFileParams { owner: string; repo: string; path: string; content: string; message: string; branch?: string; author?: { name: string; email: string; }; } // Error types export class GiteaError extends Error { constructor( message: string, public statusCode?: number, public response?: any ) { super(message); this.name = 'GiteaError'; } } ``` ### Step 2: Build the API Client Create a robust client for Git platform communication: ```typescript // src/gitea-client.ts import axios, { AxiosInstance } from 'axios'; import { GiteaConfig, CreateFileParams, FileContent } from './types.js'; export class GiteaClient { private client: AxiosInstance; private config: GiteaConfig; constructor(config: GiteaConfig) { this.config = config; this.client = axios.create({ baseURL: `${config.url}/api/v1`, headers: { Authorization: `token ${config.token}`, 'Content-Type': 'application/json', }, timeout: 30000, }); } async getFile(params: { owner: string; repo: string; path: string; branch?: string; }): Promise<FileContent> { const branch = params.branch || this.config.defaultBranch; const response = await this.client.get( `/repos/${params.owner}/${params.repo}/contents/${params.path}`, { params: { ref: branch } } ); // Decode base64 content response.data.content = Buffer.from( response.data.content, 'base64' ).toString('utf-8'); return response.data; } async createFile(params: CreateFileParams): Promise<any> { const branch = params.branch || this.config.defaultBranch; const author = params.author || this.config.author; // Encode content to base64 const content = Buffer.from(params.content).toString('base64'); const response = await this.client.post( `/repos/${params.owner}/${params.repo}/contents/${params.path}`, { content, message: params.message, branch, author: { name: author.name, email: author.email, }, } ); return response.data; } // Implement other methods: updateFile, deleteFile, // listRepos, createBranch, createPR, etc. } ``` > [!tip] Pro Tip The Git API expects file content in base64 encoding. Always encode before sending and decode when receiving. --- ## Git Platform API Integration ### Understanding Git Platform APIs Both Gitea and GitHub provide RESTful APIs, but there are subtle differences: #### Gitea API ``` Base URL: https://your-gitea.com/api/v1 Auth: Bearer token in Authorization header Endpoints: - GET /repos/{owner}/{repo}/contents/{path} - POST /repos/{owner}/{repo}/contents/{path} - PUT /repos/{owner}/{repo}/contents/{path} - DELETE /repos/{owner}/{repo}/contents/{path} ``` #### GitHub API ``` Base URL: https://api.github.com Auth: Bearer token in Authorization header Endpoints: Similar structure to Gitea Additional: Rate limiting (5000/hour authenticated) ``` ### Key API Operations #### 1. Create File ```typescript async createFile(params: CreateFileParams) { // File must not exist const content = Buffer.from(params.content).toString('base64'); return await this.client.post( `/repos/${params.owner}/${params.repo}/contents/${params.path}`, { content, message: params.message, branch: params.branch || 'main', } ); } ``` #### 2. Update File ```typescript async updateFile(params: UpdateFileParams) { // Must provide current file SHA // Get current file first to retrieve SHA const currentFile = await this.getFile({ owner: params.owner, repo: params.repo, path: params.path, }); const content = Buffer.from(params.content).toString('base64'); return await this.client.put( `/repos/${params.owner}/${params.repo}/contents/${params.path}`, { content, message: params.message, sha: currentFile.sha, // Required! branch: params.branch || 'main', } ); } ``` > [!warning] Critical File updates require the current SHA. This prevents conflicts from concurrent modifications. Always fetch the file first to get the latest SHA. #### 3. Create Branch ```typescript async createBranch(params: { owner: string; repo: string; branch: string; from?: string; }) { // Get source branch commit SHA const branches = await this.listBranches({ owner: params.owner, repo: params.repo, }); const sourceBranch = branches.find( b => b.name === (params.from || 'main') ); if (!sourceBranch) { throw new Error(`Source branch not found: ${params.from}`); } return await this.client.post( `/repos/${params.owner}/${params.repo}/branches`, { new_branch_name: params.branch, old_ref_name: params.from || 'main', } ); } ``` #### 4. Create Pull Request ```typescript async createPullRequest(params: { owner: string; repo: string; title: string; head: string; // Source branch base: string; // Target branch body?: string; }) { return await this.client.post( `/repos/${params.owner}/${params.repo}/pulls`, { title: params.title, head: params.head, base: params.base, body: params.body || '', } ); } ``` --- ## Defining MCP Tools Tools are the core of MCP - they define what operations Claude can perform. Each tool needs: - **Name**: Unique identifier (e.g., `gitea_create_file`) - **Description**: What the tool does (Claude uses this to decide when to call it) - **Input schema**: JSON Schema defining required and optional parameters ### Tool Definition Structure ```typescript // src/tools.ts export const tools = [ { name: 'gitea_create_file', description: 'Create a new file in repository and commit it', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: 'Repository owner (username or org)', }, repo: { type: 'string', description: 'Repository name', }, path: { type: 'string', description: 'File path (e.g., apps/nginx/deployment.yaml)', }, content: { type: 'string', description: 'File content', }, message: { type: 'string', description: 'Commit message (use conventional commits)', }, branch: { type: 'string', description: 'Branch name (defaults to main)', }, }, required: ['owner', 'repo', 'path', 'content', 'message'], }, }, { name: 'gitea_update_file', description: 'Update an existing file in repository', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: 'Repository owner' }, repo: { type: 'string', description: 'Repository name' }, path: { type: 'string', description: 'File path' }, content: { type: 'string', description: 'New content' }, message: { type: 'string', description: 'Commit message' }, branch: { type: 'string', description: 'Branch (optional)' }, }, required: ['owner', 'repo', 'path', 'content', 'message'], }, }, { name: 'gitea_get_file', description: 'Read file content from repository', inputSchema: { type: 'object', properties: { owner: { type: 'string' }, repo: { type: 'string' }, path: { type: 'string' }, branch: { type: 'string' }, }, required: ['owner', 'repo', 'path'], }, }, { name: 'gitea_create_branch', description: 'Create a new branch', inputSchema: { type: 'object', properties: { owner: { type: 'string' }, repo: { type: 'string' }, branch: { type: 'string', description: 'New branch name' }, from: { type: 'string', description: 'Source branch (default: main)' }, }, required: ['owner', 'repo', 'branch'], }, }, { name: 'gitea_create_pr', description: 'Create a pull request', inputSchema: { type: 'object', properties: { owner: { type: 'string' }, repo: { type: 'string' }, title: { type: 'string', description: 'PR title' }, head: { type: 'string', description: 'Source branch' }, base: { type: 'string', description: 'Target branch' }, body: { type: 'string', description: 'PR description' }, }, required: ['owner', 'repo', 'title', 'head', 'base'], }, }, ]; ``` > [!success] Best Practice Make tool descriptions clear and specific. Claude uses these to determine when to call each tool. Good descriptions lead to better tool selection. --- ## Request Handlers Handlers implement the actual logic for each tool. They receive parameters from Claude, execute operations, and return formatted responses. ### Handler Pattern ```typescript // src/handlers.ts import { GiteaClient } from './gitea-client.js'; export class ToolHandlers { constructor( private client: GiteaClient, private config: any ) {} async handleCreateFile(args: any) { // 1. Validate and prepare parameters const owner = args.owner || this.config.defaultOwner; const repo = args.repo || this.config.defaultRepo; // 2. Call API client const result = await this.client.createFile({ owner, repo, path: args.path, content: args.content, message: args.message, branch: args.branch, }); // 3. Format response for Claude return { content: [ { type: 'text', text: `✅ File created successfully! Path: ${result.content.path} Commit: ${result.commit.message} SHA: ${result.commit.sha} URL: ${result.content.html_url} The file has been committed and pushed to the repository.`, }, ], }; } async handleUpdateFile(args: any) { const owner = args.owner || this.config.defaultOwner; const repo = args.repo || this.config.defaultRepo; // Get current file for SHA const currentFile = await this.client.getFile({ owner, repo, path: args.path, branch: args.branch, }); // Update with current SHA const result = await this.client.updateFile({ owner, repo, path: args.path, content: args.content, message: args.message, sha: currentFile.sha, branch: args.branch, }); return { content: [ { type: 'text', text: `✅ File updated successfully! Path: ${result.content.path} Commit: ${result.commit.message} SHA: ${result.commit.sha} The changes have been committed and pushed.`, }, ], }; } async handleGetFile(args: any) { const file = await this.client.getFile({ owner: args.owner || this.config.defaultOwner, repo: args.repo || this.config.defaultRepo, path: args.path, branch: args.branch, }); return { content: [ { type: 'text', text: JSON.stringify({ path: file.path, content: file.content, sha: file.sha, size: file.size, }, null, 2), }, ], }; } } ``` ### Main Server Implementation ```typescript // src/index.ts import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { GiteaClient } from './gitea-client.js'; import { tools } from './tools.js'; import { ToolHandlers } from './handlers.js'; dotenv.config(); // Configuration const config = { url: process.env.GITEA_URL!, token: process.env.GITEA_TOKEN!, defaultOwner: process.env.GITEA_DEFAULT_OWNER || 'admin', defaultRepo: process.env.GITEA_DEFAULT_REPO || 'k3s-gitops', defaultBranch: process.env.GITEA_DEFAULT_BRANCH || 'main', author: { name: process.env.GIT_AUTHOR_NAME || 'Claude AI', email: process.env.GIT_AUTHOR_EMAIL || '[email protected]', }, }; // Initialize const giteaClient = new GiteaClient(config); const handlers = new ToolHandlers(giteaClient, config); // Create MCP server const server = new Server( { name: 'gitea', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Register handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'gitea_create_file': return await handlers.handleCreateFile(args || {}); case 'gitea_update_file': return await handlers.handleUpdateFile(args || {}); case 'gitea_get_file': return await handlers.handleGetFile(args || {}); // ... other handlers default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `❌ Error: ${error.message}`, }, ], isError: true, }; } }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('Gitea MCP Server running'); } main(); ``` --- ## Error Handling & Security ### Comprehensive Error Handling Production servers need robust error handling at multiple levels: ```typescript // Custom error classes export class GiteaError extends Error { constructor( message: string, public statusCode?: number, public response?: any ) { super(message); this.name = 'GiteaError'; } } export class NotFoundError extends GiteaError { constructor(resource: string) { super(`Resource not found: ${resource}`, 404); } } export class ConflictError extends GiteaError { constructor(message: string) { super(message, 409); } } export class AuthenticationError extends GiteaError { constructor() { super('Authentication failed. Check your token.', 401); } } // In API client private handleError(error: AxiosError): never { if (error.response) { const status = error.response.status; const data = error.response.data as any; if (status === 404) { throw new NotFoundError(data?.message || 'Resource'); } if (status === 401 || status === 403) { throw new AuthenticationError(); } if (status === 409) { throw new ConflictError( data?.message || 'Resource conflict' ); } throw new GiteaError( data?.message || 'API request failed', status, data ); } throw new GiteaError(`Cannot reach server: ${error.message}`); } ``` ### Input Validation ```typescript import { z } from 'zod'; // Define schemas const CreateFileSchema = z.object({ owner: z.string().min(1), repo: z.string().min(1), path: z.string().min(1).regex(/^[^/].*$/), // No leading slash content: z.string(), message: z.string().min(1), branch: z.string().optional(), }); // Validate in handlers async handleCreateFile(args: any) { // Validate input const params = CreateFileSchema.parse(args); // Sanitize path params.path = params.path.replace(/\.\.\//g, ''); // Proceed with operation const result = await this.client.createFile(params); // ... } ``` ### Security Best Practices > [!danger] Critical Security Considerations > > - **Token Storage**: Never hardcode tokens. Use environment variables. > - **Path Traversal**: Sanitize file paths to prevent `../` attacks. > - **Minimal Permissions**: Request only necessary API scopes. > - **Rate Limiting**: Implement backoff for API rate limits. > - **Logging**: Log errors but never log sensitive data (tokens, passwords). ```typescript // Secure configuration loading function loadConfig(): GiteaConfig { const required = ['GITEA_URL', 'GITEA_TOKEN']; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { throw new Error( `Missing required env vars: ${missing.join(', ')}` ); } return { url: process.env.GITEA_URL!, token: process.env.GITEA_TOKEN!, // Never log the token // ... }; } // Path sanitization function sanitizePath(path: string): string { // Remove leading/trailing slashes let clean = path.replace(/^\/+|\/+$/g, ''); // Remove double slashes clean = clean.replace(/\/+/g, '/'); // Remove ../ for security clean = clean.replace(/\.\.\//g, ''); return clean; } ``` --- ## Testing & Deployment ### Manual Testing ```bash # Build the project npm run build # Test server manually npm start # Server should start and log: # "Gitea MCP Server running" # Test with curl (if testing connection) curl -H "Authorization: token YOUR_TOKEN" \ https://your-gitea.com/api/v1/user ``` ### Integration Testing ```typescript // test/integration.test.ts import { GiteaClient } from '../src/gitea-client'; describe('GiteaClient', () => { let client: GiteaClient; beforeAll(() => { client = new GiteaClient({ url: process.env.TEST_GITEA_URL!, token: process.env.TEST_GITEA_TOKEN!, // ... }); }); it('should create a file', async () => { const result = await client.createFile({ owner: 'test', repo: 'test-repo', path: 'test.txt', content: 'Hello World', message: 'test: Add test file', }); expect(result.content.path).toBe('test.txt'); expect(result.commit.message).toBe('test: Add test file'); }); it('should handle file not found', async () => { await expect( client.getFile({ owner: 'test', repo: 'test-repo', path: 'nonexistent.txt', }) ).rejects.toThrow(NotFoundError); }); }); ``` ### Build for Production ```json // package.json { "scripts": { "build": "tsc", "start": "node build/index.js", "dev": "ts-node src/index.ts", "test": "jest", "lint": "eslint src/**/*.ts" } } ``` ### Deployment Options #### 1. Local Deployment (Windows/Mac) For Claude Desktop, the server runs locally on the user's machine. ```bash # Build once npm run build # Server is started automatically by Claude Desktop # via configuration in claude_desktop_config.json ``` #### 2. Remote Deployment For team usage, deploy to a server: ```bash # Using PM2 for process management npm install -g pm2 pm2 start build/index.js --name gitea-mcp pm2 save pm2 startup ``` #### 3. Docker Deployment ```dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY build ./build COPY .env ./.env CMD ["node", "build/index.js"] ``` --- ## Claude Desktop Integration ### Configuration File Location Claude Desktop discovers MCP servers through a configuration file: - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` ### Configuration Format ```json { "mcpServers": { "gitea": { "command": "node", "args": [ "C:\\path\\to\\gitea-mcp-server\\build\\index.js" ], "env": { "GITEA_URL": "https://git.thedevops.dev", "GITEA_TOKEN": "your-access-token", "GITEA_DEFAULT_OWNER": "admin", "GITEA_DEFAULT_REPO": "k3s-gitops", "GITEA_DEFAULT_BRANCH": "main", "GIT_AUTHOR_NAME": "Claude AI", "GIT_AUTHOR_EMAIL": "[email protected]" } } } } ``` > [!info] Configuration Notes > > - Use absolute paths for the server file > - On Windows, use double backslashes `\\` or forward slashes `/` > - Environment variables are passed directly to the server process > - Never commit this file with your token to version control ### Multiple MCP Servers You can configure multiple servers simultaneously: ```json { "mcpServers": { "gitea": { "command": "node", "args": ["C:\\path\\to\\gitea-mcp-server\\build\\index.js"], "env": { /* Gitea config */ } }, "github": { "command": "node", "args": ["C:\\path\\to\\github-mcp-server\\build\\index.js"], "env": { /* GitHub config */ } }, "kubernetes": { "command": "npx", "args": ["-y", "mcp-server-kubernetes"], "env": { "KUBECONFIG": "C:\\Users\\you\\.kube\\config" } } } } ``` ### Restarting After Changes After modifying the configuration or rebuilding the server: 1. Completely quit Claude Desktop (not just close the window) 2. Wait 2-3 seconds 3. Restart Claude Desktop 4. Wait for servers to initialize (~10 seconds) ### Troubleshooting Claude Integration #### Server Not Loading ```bash # Check logs (Windows) type %APPDATA%\Claude\logs\mcp.log # Check server starts manually cd path\to\gitea-mcp-server npm start # Should see: "Gitea MCP Server running" ``` #### Tools Not Appearing - Verify JSON syntax in config file (use JSONLint) - Check file paths are absolute and correct - Ensure `build/` directory exists with compiled JavaScript - Restart Claude Desktop completely #### Authentication Errors - Verify `GITEA_TOKEN` in config is correct - Check token has `repository` permissions - Test token manually with curl --- ## Real-World Usage Examples ### Example 1: Creating Kubernetes Manifests ``` User: "Create a deployment for nginx with 3 replicas in k3s-gitops" Claude: I'll create an nginx deployment with 3 replicas. [calls gitea_create_file] ✅ Created apps/nginx/deployment.yaml 📝 Commit: "feat(nginx): Add deployment with 3 replicas" 🔗 https://git.thedevops.dev/admin/k3s-gitops/... The deployment is now in your repository. ArgoCD will automatically deploy it to your cluster! ``` Claude automatically: - Generated proper YAML structure - Used conventional commit format - Placed file in correct directory - Committed and pushed to Git ### Example 2: Feature Branch Workflow ``` User: "Create a feature branch for prometheus monitoring and add all necessary manifests" Claude: I'll create a feature branch and add Prometheus. [calls gitea_create_branch: feature/add-prometheus] [calls gitea_create_file x4 with branch: feature/add-prometheus] ✅ Created branch: feature/add-prometheus ✅ Added 4 manifests: - apps/prometheus/deployment.yaml - apps/prometheus/service.yaml - apps/prometheus/configmap.yaml - apps/prometheus/servicemonitor.yaml 📝 All files committed with: "feat(prometheus): Add complete monitoring stack" User: "Now create a PR to main" Claude: [calls gitea_create_pr] ✅ Pull Request #15 created! Title: "feat: Add Prometheus monitoring stack" From: feature/add-prometheus → main Review at: https://git.thedevops.dev/admin/k3s-gitops/pulls/15 ``` ### Example 3: Bulk Updates ``` User: "Update all ingress files to use cert-manager for TLS certificates" Claude: I'll update the ingress files. [calls gitea_get_tree for apps/] [finds 8 ingress.yaml files] For each ingress: [calls gitea_get_file] [modifies to add cert-manager annotation] [calls gitea_update_file] ✅ Updated 8 ingress files: - apps/grafana/ingress.yaml - apps/argocd/ingress.yaml - apps/jenkins/ingress.yaml - apps/prometheus/ingress.yaml - apps/loki/ingress.yaml - apps/vault/ingress.yaml - apps/harbor/ingress.yaml - apps/minio/ingress.yaml 📝 Commit: "chore: Add cert-manager TLS to all ingresses" All changes are committed and ArgoCD will sync them! ``` ### Example 4: Configuration Updates ``` User: "Scale loki StatefulSet to 5 replicas and increase storage to 20Gi" Claude: [calls gitea_get_file: apps/loki/statefulset.yaml] [modifies replicas: 5 and storage: 20Gi] [calls gitea_update_file] ✅ Updated apps/loki/statefulset.yaml Changes: • Replicas: 3 → 5 • Storage: 10Gi → 20Gi 📝 Commit: "chore(loki): Scale to 5 replicas and increase storage" ArgoCD will apply these changes to your cluster. ``` ### Example 5: Documentation Generation ``` User: "Create a README for the monitoring namespace explaining the architecture" Claude: [generates comprehensive README] [calls gitea_create_file: apps/monitoring/README.md] ✅ Created apps/monitoring/README.md Includes: • Architecture overview • Component descriptions • Configuration guide • Troubleshooting tips • Useful commands 📝 Commit: "docs(monitoring): Add comprehensive README" ``` ### Benefits in Production > [!success] Real Impact > > - **Speed**: Infrastructure changes in seconds vs minutes > - **Accuracy**: Claude generates syntactically correct YAML > - **GitOps**: Everything goes through Git, maintaining audit trail > - **Consistency**: Conventional commits, proper structure > - **Automation**: ArgoCD deploys changes automatically > - **Documentation**: Claude can document as it builds --- ## Conclusion Building an MCP server transforms Claude from a conversational assistant into an active participant in your development workflow. The architecture we've explored enables: - **Natural Language GitOps**: Manage infrastructure with conversational commands - **Automated Workflows**: Complex multi-step operations handled seamlessly - **Production-Ready**: Type-safe, error-handled, secure implementation - **Extensible**: Easy to add new tools and capabilities ### Next Steps 1. **Extend Functionality**: Add support for issues, releases, webhooks 2. **Multi-Platform**: Support both Gitea and GitHub simultaneously 3. **Advanced Features**: Implement caching, batch operations, conflict resolution 4. **Team Deployment**: Deploy server centrally for team access 5. **Custom Workflows**: Build specialized tools for your specific needs ### Key Takeaways - MCP servers bridge AI and real-world systems - TypeScript provides type safety and excellent DX - Proper error handling is crucial for production use - Security considerations must be built-in from the start - The possibilities for automation are virtually limitless The full source code for this MCP server is production-ready and includes comprehensive error handling, logging, and security features. You can extend it to support additional Git platforms, add more sophisticated workflows, or integrate with other systems in your infrastructure. **Happy building!** 🚀 --- ## Metadata ```dataview TABLE author as "Author", date as "Date", reading_time as "Reading Time", category as "Category" FROM "" WHERE file = this.file ``` ## Tags #mcp #gitea #github #claude #devops #gitops #typescript #automation #kubernetes #argocd --- _Created by Vladimiras Levinas | Lead DevOps Engineer_ _© 2026 | Built with production experience from real-world GitOps automation_