diff --git a/README.md b/README.md index 98aeef6..1c89338 100644 --- a/README.md +++ b/README.md @@ -1,133 +1,188 @@ -# Gitea MCP Server - -A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that enables AI assistants like Claude to interact directly with your Gitea instance. - -## Features - -- **Query Runners** - List runners, check status, view capabilities (OS, tools, disk space) -- **Monitor Workflows** - List runs, get job details, view logs -- **Manage Releases** - List releases, get assets, check download counts -- **AI-Friendly** - Structured JSON responses designed for AI consumption - -## Quick Start - -### 1. Download - -Download the latest binary from [Releases](https://git.marketally.com/gitcaddy/mcp-server/releases). - -### 2. Configure Claude Code - -Add to your Claude Code settings: - -**Option A: Command line arguments** -```json -{ - "mcpServers": { - "gitea": { - "command": "/path/to/gitea-mcp-server", - "args": ["--url", "https://git.marketally.com", "--token", "YOUR_API_TOKEN"] - } - } -} -``` - -**Option B: Environment variables** -```json -{ - "mcpServers": { - "gitea": { - "command": "/path/to/gitea-mcp-server", - "env": { - "GITEA_URL": "https://git.marketally.com", - "GITEA_TOKEN": "your-token-here" - } - } - } -} -``` - -### 3. Use It - -Ask Claude things like: -- "What runners are online?" -- "Show me the latest workflow runs for gitcaddy/act_runner" -- "Why did run #77 fail?" -- "What assets are in the v0.3.6 release?" - -## Available Tools - -| Tool | Description | -|------|-------------| -| `list_runners` | List all runners with status, capabilities, disk space | -| `get_runner` | Get detailed runner info by ID | -| `list_workflow_runs` | List workflow runs for a repository | -| `get_workflow_run` | Get run details with all jobs | -| `get_job_logs` | Get logs from a specific job | -| `list_releases` | List releases for a repository | -| `get_release` | Get release details with all assets | - -## Building from Source - -```bash -git clone https://git.marketally.com/gitcaddy/mcp-server.git -cd mcp-server -go build -o gitea-mcp-server . -``` - -### Cross-compile for all platforms - -```bash -# Linux -GOOS=linux GOARCH=amd64 go build -o gitea-mcp-server-linux-amd64 . - -# macOS Intel -GOOS=darwin GOARCH=amd64 go build -o gitea-mcp-server-darwin-amd64 . - -# macOS Apple Silicon -GOOS=darwin GOARCH=arm64 go build -o gitea-mcp-server-darwin-arm64 . - -# Windows -GOOS=windows GOARCH=amd64 go build -o gitea-mcp-server-windows-amd64.exe . -``` - -## Architecture - -``` -┌─────────────┐ stdio ┌──────────────────┐ HTTP ┌─────────────┐ -│ Claude Code │ ◄────────────► │ gitea-mcp-server │ ◄───────────► │ Gitea │ -│ (AI Tool) │ JSON-RPC │ (This binary) │ /api/v2/mcp │ Server │ -└─────────────┘ └──────────────────┘ └─────────────┘ -``` - -The MCP server: -1. Receives JSON-RPC requests over stdio from Claude Code -2. Forwards them to Gitea's `/api/v2/mcp` endpoint -3. Returns responses back to Claude Code - -## Configuration Options - -| Flag | Env Variable | Description | -|------|-------------|-------------| -| `--url` | `GITEA_URL` | Gitea server URL (required) | -| `--token` | `GITEA_TOKEN` | API token for authentication | -| `--debug` | - | Enable debug logging to stderr | - -## Obtaining an API Token - -1. Go to your Gitea instance → Settings → Applications -2. Generate a new token with appropriate scopes -3. Copy the token and use it in your configuration - -## Requirements - -- Gitea 1.26+ with GitCaddy enhancements (includes `/api/v2/mcp` endpoint) -- Go 1.21+ (for building from source) - -## License - -MIT License - See [LICENSE](LICENSE) file. - -## Related Projects - -- [GitCaddy Gitea](https://git.marketally.com/gitcaddy/gitea) - Enhanced Gitea fork with AI-friendly APIs -- [GitCaddy act_runner](https://git.marketally.com/gitcaddy/act_runner) - Enhanced runner with capabilities detection +# GitCaddy MCP Server + +A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that enables AI assistants like Claude to interact directly with your GitCaddy instance. + +## Features + +- **Query Runners** - List runners, check status, view capabilities (OS, tools, disk space) +- **Monitor Workflows** - List runs, get job details, view logs +- **Manage Releases** - List releases, get assets, check download counts +- **AI Learning** - Query error patterns, report solutions, help other AIs learn +- **AI-Friendly** - Structured JSON responses designed for AI consumption + +## Quick Start + +### 1. Download + +Download the latest binary from [Releases](https://git.marketally.com/gitcaddy/mcp-server/releases). + +### 2. Configure Claude Code + +Add to your Claude Code settings: + +**Option A: Command line arguments** +```json +{ + "mcpServers": { + "gitcaddy": { + "command": "/path/to/gitcaddy-mcp-server", + "args": ["--url", "https://git.marketally.com", "--token", "YOUR_API_TOKEN"] + } + } +} +``` + +**Option B: Environment variables** +```json +{ + "mcpServers": { + "gitcaddy": { + "command": "/path/to/gitcaddy-mcp-server", + "env": { + "GITCADDY_URL": "https://git.marketally.com", + "GITCADDY_TOKEN": "your-token-here" + } + } + } +} +``` + +### 3. Use It + +Ask Claude things like: +- "What runners are online?" +- "Show me the latest workflow runs for gitcaddy/act_runner" +- "Why did run #77 fail?" +- "What assets are in the v0.3.6 release?" +- "Are there any known solutions for NETSDK1147?" +- "Diagnose why job 456 failed" + +## Available Tools + +### Runner & Workflow Tools + +| Tool | Description | +|------|-------------| +| `list_runners` | List all runners with status, capabilities, disk space | +| `get_runner` | Get detailed runner info by ID | +| `list_workflow_runs` | List workflow runs for a repository | +| `get_workflow_run` | Get run details with all jobs | +| `get_job_logs` | Get logs from a specific job | +| `list_releases` | List releases for a repository | +| `get_release` | Get release details with all assets | + +### AI Learning Tools + +| Tool | Description | +|------|-------------| +| `get_error_patterns` | Query known CI/CD error patterns with diagnoses and solutions | +| `report_error_solution` | Report a solution that fixed an error (helps other AIs learn) | +| `report_solution_success` | Mark a solution as successful (improves ranking) | +| `get_compatibility_matrix` | See what project types work on which runners | +| `diagnose_job_failure` | Auto-analyze failed job logs and suggest solutions | + +## AI Learning System + +GitCaddy includes a collaborative AI learning system. When you encounter and fix CI/CD errors, you can report your solutions to help other AI assistants: + +``` +AI encounters error -> get_error_patterns -> finds solution -> applies fix + | + v + report_solution_success + (improves ranking) + +AI finds NEW solution -> report_error_solution -> stored in database + | + v + Other AIs can now find it +``` + +### Example: Diagnosing a Failed Build + +``` +User: "Why did job 789 fail?" + +Claude uses: diagnose_job_failure(owner="myorg", repo="myapp", job_id=789) + +Response: +{ + "extracted_errors": ["NETSDK1147", "XA5300"], + "diagnoses": [ + { + "pattern": "NETSDK1147", + "diagnosis": "The .NET MAUI Android workload is not installed", + "solution": "Install the workload: dotnet workload install maui-android", + "success_rate": 94.5 + } + ] +} +``` + +## Building from Source + +```bash +git clone https://git.marketally.com/gitcaddy/mcp-server.git +cd mcp-server +go build -o gitcaddy-mcp-server . +``` + +### Cross-compile for all platforms + +```bash +# Linux +GOOS=linux GOARCH=amd64 go build -o gitcaddy-mcp-server-linux-amd64 . + +# macOS Intel +GOOS=darwin GOARCH=amd64 go build -o gitcaddy-mcp-server-darwin-amd64 . + +# macOS Apple Silicon +GOOS=darwin GOARCH=arm64 go build -o gitcaddy-mcp-server-darwin-arm64 . + +# Windows +GOOS=windows GOARCH=amd64 go build -o gitcaddy-mcp-server-windows-amd64.exe . +``` + +## Architecture + +``` ++-------------+ stdio +------------------+ HTTP +-------------+ +| Claude Code | <------------> | gitcaddy-mcp | <-----------> | GitCaddy | +| (AI Tool) | JSON-RPC | (This binary) | /api/v2/mcp | Server | ++-------------+ +------------------+ +-------------+ +``` + +The MCP server: +1. Receives JSON-RPC requests over stdio from Claude Code +2. Forwards them to GitCaddy's `/api/v2/mcp` endpoint +3. Returns responses back to Claude Code + +## Configuration Options + +| Flag | Env Variable | Description | +|------|-------------|-------------| +| `--url` | `GITCADDY_URL` | GitCaddy server URL (required) | +| `--token` | `GITCADDY_TOKEN` | API token for authentication | +| `--debug` | - | Enable debug logging to stderr | + +**Note:** `GITEA_URL` and `GITEA_TOKEN` are also supported for backwards compatibility. + +## Obtaining an API Token + +1. Go to your GitCaddy instance -> Settings -> Applications +2. Generate a new token with appropriate scopes +3. Copy the token and use it in your configuration + +## Requirements + +- GitCaddy Server (Gitea 1.26+ with GitCaddy enhancements) +- Go 1.21+ (for building from source) + +## License + +MIT License - See [LICENSE](LICENSE) file. + +## Related Projects + +- [GitCaddy Server](https://git.marketally.com/gitcaddy/gitea) - Enhanced Gitea fork with AI-friendly APIs +- [GitCaddy Runner](https://git.marketally.com/gitcaddy/act_runner) - Enhanced runner with capabilities detection diff --git a/main.go b/main.go index ccb505a..98e0f54 100644 --- a/main.go +++ b/main.go @@ -1,153 +1,159 @@ -// Copyright 2026 MarketAlly. All rights reserved. -// SPDX-License-Identifier: MIT - -// Gitea MCP Server - Model Context Protocol server for Gitea Actions -// -// This standalone server implements the MCP protocol over stdio, -// proxying requests to a Gitea instance's /api/v2/mcp endpoint. -// -// Usage: -// -// gitea-mcp-server --url https://git.example.com --token YOUR_API_TOKEN -// -// Configure in Claude Code's settings.json: -// -// { -// "mcpServers": { -// "gitea": { -// "command": "gitea-mcp-server", -// "args": ["--url", "https://git.example.com", "--token", "YOUR_TOKEN"] -// } -// } -// } -package main - -import ( - "bufio" - "bytes" - "encoding/json" - "flag" - "fmt" - "io" - "net/http" - "os" - "time" -) - -var ( - giteaURL string - giteaToken string - debug bool -) - -func main() { - flag.StringVar(&giteaURL, "url", "", "Gitea server URL (e.g., https://git.example.com)") - flag.StringVar(&giteaToken, "token", "", "Gitea API token") - flag.BoolVar(&debug, "debug", false, "Enable debug logging to stderr") - flag.Parse() - - // Also check environment variables - if giteaURL == "" { - giteaURL = os.Getenv("GITEA_URL") - } - if giteaToken == "" { - giteaToken = os.Getenv("GITEA_TOKEN") - } - - if giteaURL == "" { - fmt.Fprintln(os.Stderr, "Error: --url or GITEA_URL is required") - os.Exit(1) - } - - debugLog("Gitea MCP Server starting") - debugLog("Connecting to: %s", giteaURL) - - // Read JSON-RPC messages from stdin, forward to Gitea, write responses to stdout - reader := bufio.NewReader(os.Stdin) - - for { - line, err := reader.ReadBytes('\n') - if err != nil { - if err == io.EOF { - debugLog("EOF received, exiting") - break - } - debugLog("Read error: %v", err) - continue - } - - line = bytes.TrimSpace(line) - if len(line) == 0 { - continue - } - - debugLog("Received: %s", string(line)) - - // Forward to Gitea's MCP endpoint - response, err := forwardToGitea(line) - if err != nil { - debugLog("Forward error: %v", err) - // Send error response - errorResp := map[string]interface{}{ - "jsonrpc": "2.0", - "id": nil, - "error": map[string]interface{}{ - "code": -32603, - "message": "Internal error", - "data": err.Error(), - }, - } - writeResponse(errorResp) - continue - } - - debugLog("Response: %s", string(response)) - - // Write response to stdout - fmt.Println(string(response)) - } -} - -func forwardToGitea(request []byte) ([]byte, error) { - mcpURL := giteaURL + "/api/v2/mcp" - - req, err := http.NewRequest("POST", mcpURL, bytes.NewReader(request)) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - if giteaToken != "" { - req.Header.Set("Authorization", "token "+giteaToken) - } - - client := &http.Client{Timeout: 30 * time.Second} - resp, err := client.Do(req) - if err != nil { - return nil, fmt.Errorf("http request: %w", err) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("read response: %w", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("http status %d: %s", resp.StatusCode, string(body)) - } - - return body, nil -} - -func writeResponse(resp interface{}) { - data, _ := json.Marshal(resp) - fmt.Println(string(data)) -} - -func debugLog(format string, args ...interface{}) { - if debug { - fmt.Fprintf(os.Stderr, "[DEBUG] "+format+"\n", args...) - } -} +// Copyright 2026 MarketAlly. All rights reserved. +// SPDX-License-Identifier: MIT + +// GitCaddy MCP Server - Model Context Protocol server for GitCaddy/Gitea Actions +// +// This standalone server implements the MCP protocol over stdio, +// proxying requests to a GitCaddy instance's /api/v2/mcp endpoint. +// +// Usage: +// +// gitcaddy-mcp-server --url https://git.example.com --token YOUR_API_TOKEN +// +// Configure in Claude Code's settings.json: +// +// { +// "mcpServers": { +// "gitcaddy": { +// "command": "gitcaddy-mcp-server", +// "args": ["--url", "https://git.example.com", "--token", "YOUR_TOKEN"] +// } +// } +// } +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "time" +) + +var ( + giteaURL string + giteaToken string + debug bool +) + +func main() { + flag.StringVar(&giteaURL, "url", "", "GitCaddy server URL (e.g., https://git.example.com)") + flag.StringVar(&giteaToken, "token", "", "GitCaddy API token") + flag.BoolVar(&debug, "debug", false, "Enable debug logging to stderr") + flag.Parse() + + // Check environment variables (GITCADDY_* preferred, GITEA_* for backwards compat) + if giteaURL == "" { + giteaURL = os.Getenv("GITCADDY_URL") + } + if giteaURL == "" { + giteaURL = os.Getenv("GITEA_URL") + } + if giteaToken == "" { + giteaToken = os.Getenv("GITCADDY_TOKEN") + } + if giteaToken == "" { + giteaToken = os.Getenv("GITEA_TOKEN") + } + + if giteaURL == "" { + fmt.Fprintln(os.Stderr, "Error: --url or GITCADDY_URL is required") + os.Exit(1) + } + + debugLog("GitCaddy MCP Server starting") + debugLog("Connecting to: %s", giteaURL) + + // Read JSON-RPC messages from stdin, forward to Gitea, write responses to stdout + reader := bufio.NewReader(os.Stdin) + + for { + line, err := reader.ReadBytes('\n') + if err != nil { + if err == io.EOF { + debugLog("EOF received, exiting") + break + } + debugLog("Read error: %v", err) + continue + } + + line = bytes.TrimSpace(line) + if len(line) == 0 { + continue + } + + debugLog("Received: %s", string(line)) + + // Forward to Gitea's MCP endpoint + response, err := forwardToGitea(line) + if err != nil { + debugLog("Forward error: %v", err) + // Send error response + errorResp := map[string]interface{}{ + "jsonrpc": "2.0", + "id": nil, + "error": map[string]interface{}{ + "code": -32603, + "message": "Internal error", + "data": err.Error(), + }, + } + writeResponse(errorResp) + continue + } + + debugLog("Response: %s", string(response)) + + // Write response to stdout + fmt.Println(string(response)) + } +} + +func forwardToGitea(request []byte) ([]byte, error) { + mcpURL := giteaURL + "/api/v2/mcp" + + req, err := http.NewRequest("POST", mcpURL, bytes.NewReader(request)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + if giteaToken != "" { + req.Header.Set("Authorization", "token "+giteaToken) + } + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("http request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("http status %d: %s", resp.StatusCode, string(body)) + } + + return body, nil +} + +func writeResponse(resp interface{}) { + data, _ := json.Marshal(resp) + fmt.Println(string(data)) +} + +func debugLog(format string, args ...interface{}) { + if debug { + fmt.Fprintf(os.Stderr, "[DEBUG] "+format+"\n", args...) + } +}