Fix workflow: use setup-go@v4 for Gitea Actions compatibility
Some checks failed
Release / build (amd64, darwin) (push) Has been cancelled
Release / build (amd64, windows) (push) Has been cancelled
Release / build (amd64, linux) (push) Has been cancelled
Release / build (arm64, linux) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / build (arm64, darwin) (push) Has been cancelled
Some checks failed
Release / build (amd64, darwin) (push) Has been cancelled
Release / build (amd64, windows) (push) Has been cancelled
Release / build (amd64, linux) (push) Has been cancelled
Release / build (arm64, linux) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / build (arm64, darwin) (push) Has been cancelled
This commit is contained in:
77
.gitea/workflows/release.yml
Normal file
77
.gitea/workflows/release.yml
Normal file
@@ -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 }}
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
||||||
133
README.md
Normal file
133
README.md
Normal file
@@ -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
|
||||||
3
go.mod
Normal file
3
go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module git.marketally.com/gitcaddy/mcp-server
|
||||||
|
|
||||||
|
go 1.21
|
||||||
153
main.go
Normal file
153
main.go
Normal file
@@ -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...)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user