From 970c02c877b6d096be2be2c296059d6b3ad298da Mon Sep 17 00:00:00 2001 From: logikonline Date: Sun, 11 Jan 2026 16:10:16 -0500 Subject: [PATCH] Fix workflow: use setup-go@v4 for Gitea Actions compatibility --- .gitea/workflows/release.yml | 77 ++++++++++++++++++ LICENSE | 21 +++++ README.md | 133 ++++++++++++++++++++++++++++++ go.mod | 3 + main.go | 153 +++++++++++++++++++++++++++++++++++ 5 files changed, 387 insertions(+) create mode 100644 .gitea/workflows/release.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..e1b3a04 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,77 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: linux-latest + strategy: + matrix: + include: + - goos: linux + goarch: amd64 + - goos: linux + goarch: arm64 + - goos: darwin + goarch: amd64 + - goos: darwin + goarch: arm64 + - goos: windows + goarch: amd64 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache: false + + - name: Build + shell: bash + run: | + VERSION="${GITHUB_REF_NAME#v}" + EXT="" + if [ "${{ matrix.goos }}" = "windows" ]; then + EXT=".exe" + fi + echo "Building for ${{ matrix.goos }}/${{ matrix.goarch }} version ${VERSION}" + CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \ + go build -ldflags "-s -w -X main.version=${VERSION}" \ + -o "gitea-mcp-server-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}" + ls -la gitea-mcp-server-* + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: gitea-mcp-server-${{ matrix.goos }}-${{ matrix.goarch }} + path: gitea-mcp-server-* + + release: + needs: build + runs-on: linux-latest + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v3 + with: + path: artifacts + + - name: Prepare release files + shell: bash + run: | + mkdir -p release + find artifacts -type f -name 'gitea-mcp-server-*' -exec mv {} release/ \; + cd release && sha256sum * > checksums.txt + ls -la + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: release/* + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..85e6fb4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 MarketAlly + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..98aeef6 --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +# 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 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6f7514f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.marketally.com/gitcaddy/mcp-server + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..ccb505a --- /dev/null +++ b/main.go @@ -0,0 +1,153 @@ +// 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...) + } +}