Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f059b6f458 | |||
| 4b350fe967 | |||
| 1d770b45c9 | |||
| fd473c298c | |||
| 44f75cf6e0 | |||
| 01da7b9736 | |||
| 57a46d9bf8 | |||
| b1709b47cd | |||
| 2ec5ed0ff7 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
61
README.md
61
README.md
@@ -4,8 +4,10 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that e
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Query Runners** - List runners, check status, view capabilities (OS, tools, disk space)
|
- **Query Runners** - List runners, check status, view capabilities, monitor queue depth
|
||||||
- **Monitor Workflows** - List runs, get job details, view logs
|
- **Manage Workflows** - List, trigger, rerun, cancel, and approve workflow runs
|
||||||
|
- **View Logs** - Get job logs with automatic error extraction for failed jobs
|
||||||
|
- **Access Artifacts** - List and download workflow artifacts
|
||||||
- **Manage Releases** - List releases, get assets, check download counts
|
- **Manage Releases** - List releases, get assets, check download counts
|
||||||
- **AI Learning** - Query error patterns, report solutions, help other AIs learn
|
- **AI Learning** - Query error patterns, report solutions, help other AIs learn
|
||||||
- **AI-Friendly** - Structured JSON responses designed for AI consumption
|
- **AI-Friendly** - Structured JSON responses designed for AI consumption
|
||||||
@@ -53,23 +55,74 @@ Ask Claude things like:
|
|||||||
- "What runners are online?"
|
- "What runners are online?"
|
||||||
- "Show me the latest workflow runs for gitcaddy/act_runner"
|
- "Show me the latest workflow runs for gitcaddy/act_runner"
|
||||||
- "Why did run #77 fail?"
|
- "Why did run #77 fail?"
|
||||||
|
- "Rerun the failed jobs in run #77"
|
||||||
|
- "Cancel run #80"
|
||||||
|
- "Trigger the build.yml workflow on the main branch"
|
||||||
|
- "What workflows are available in myorg/myrepo?"
|
||||||
|
- "Show me the build.yml workflow file"
|
||||||
|
- "Validate the build.yml workflow in myorg/myrepo"
|
||||||
|
- "What artifacts were produced by run #77?"
|
||||||
|
- "What's the queue depth for each runner label?"
|
||||||
|
- "Approve the workflow run from the fork PR"
|
||||||
- "What assets are in the v0.3.6 release?"
|
- "What assets are in the v0.3.6 release?"
|
||||||
- "Are there any known solutions for NETSDK1147?"
|
- "Are there any known solutions for NETSDK1147?"
|
||||||
- "Diagnose why job 456 failed"
|
- "Diagnose why job 456 failed"
|
||||||
|
- "What secrets are available for myorg/myrepo?"
|
||||||
|
- "List all NuGet packages for myorg"
|
||||||
|
- "What are the package defaults for myorg?"
|
||||||
|
- "List all repos for myorg"
|
||||||
|
- "List open issues for gitcaddy/server"
|
||||||
|
- "Show me issue #42 in myorg/myrepo"
|
||||||
|
|
||||||
## Available Tools
|
## Available Tools
|
||||||
|
|
||||||
### Runner & Workflow Tools
|
### Runner Tools
|
||||||
|
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `list_runners` | List all runners with status, capabilities, disk space |
|
| `list_runners` | List all runners with status, capabilities, disk space |
|
||||||
| `get_runner` | Get detailed runner info by ID |
|
| `get_runner` | Get detailed runner info by ID |
|
||||||
|
| `get_queue_depth` | Get waiting jobs per runner label (capacity insight) |
|
||||||
|
|
||||||
|
### Workflow Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `list_workflows` | List available workflow files in a repository (includes validation status) |
|
||||||
|
| `get_workflow_file` | Get the YAML content of a workflow file |
|
||||||
|
| `validate_workflow` | Validate a workflow YAML file for parse errors (from repo or raw content) |
|
||||||
| `list_workflow_runs` | List workflow runs for a repository |
|
| `list_workflow_runs` | List workflow runs for a repository |
|
||||||
| `get_workflow_run` | Get run details with all jobs |
|
| `get_workflow_run` | Get run details with all jobs |
|
||||||
| `get_job_logs` | Get logs from a specific job |
|
| `get_job_logs` | Get logs from a specific job (auto-extracts errors for failed jobs) |
|
||||||
|
| `trigger_workflow` | Manually trigger a workflow_dispatch workflow with inputs |
|
||||||
|
| `rerun_workflow` | Rerun a completed workflow or specific failed job |
|
||||||
|
| `cancel_workflow_run` | Cancel a running workflow and all its jobs |
|
||||||
|
| `approve_workflow` | Approve a workflow run that requires approval (fork PRs) |
|
||||||
|
|
||||||
|
### Artifact Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `list_artifacts` | List artifacts from a workflow run |
|
||||||
|
| `get_artifact_download_url` | Get the download URL for a specific artifact |
|
||||||
|
|
||||||
|
### Repository & Issue Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `list_repos` | List repositories for an owner (org or user) |
|
||||||
|
| `list_issues` | List issues for a repository with pagination and state filtering |
|
||||||
|
| `get_issue` | Get issue details including body content and comments |
|
||||||
|
|
||||||
|
### Release & Package Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
| `list_releases` | List releases for a repository |
|
| `list_releases` | List releases for a repository |
|
||||||
| `get_release` | Get release details with all assets |
|
| `get_release` | Get release details with all assets |
|
||||||
|
| `list_secrets` | List secret names and descriptions (not values) for global, org, and repo scopes |
|
||||||
|
| `list_packages` | List packages for an owner or globally with version and visibility info |
|
||||||
|
| `get_package_defaults` | Get preconfigured package defaults (authors, company, copyright, icon, URLs) for an org |
|
||||||
|
|
||||||
### AI Learning Tools
|
### AI Learning Tools
|
||||||
|
|
||||||
|
|||||||
77
main.go
77
main.go
@@ -69,10 +69,11 @@ func main() {
|
|||||||
debugLog("Connecting to: %s", giteaURL)
|
debugLog("Connecting to: %s", giteaURL)
|
||||||
|
|
||||||
// Read JSON-RPC messages from stdin, forward to Gitea, write responses to stdout
|
// Read JSON-RPC messages from stdin, forward to Gitea, write responses to stdout
|
||||||
|
// Supports both raw JSON lines and Content-Length framed messages (Claude Code uses framing)
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
line, err := reader.ReadBytes('\n')
|
line, err := readMessage(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
debugLog("EOF received, exiting")
|
debugLog("EOF received, exiting")
|
||||||
@@ -109,8 +110,8 @@ func main() {
|
|||||||
|
|
||||||
debugLog("Response: %s", string(response))
|
debugLog("Response: %s", string(response))
|
||||||
|
|
||||||
// Write response to stdout
|
// Write response to stdout with Content-Length framing
|
||||||
fmt.Println(string(response))
|
writeFramed(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,9 +148,77 @@ func forwardToGitea(request []byte) ([]byte, error) {
|
|||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readMessage reads a JSON-RPC message from stdin.
|
||||||
|
// Handles both raw JSON lines and Content-Length framed messages.
|
||||||
|
// Claude Code sends: Content-Length: N\r\n\r\n{json}
|
||||||
|
func readMessage(reader *bufio.Reader) ([]byte, error) {
|
||||||
|
// Peek at first bytes to detect format
|
||||||
|
for {
|
||||||
|
line, err := reader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed := bytes.TrimSpace(line)
|
||||||
|
if len(trimmed) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it starts with '{', it's a raw JSON line
|
||||||
|
if trimmed[0] == '{' {
|
||||||
|
return trimmed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it starts with "Content-Length:", read the framed message
|
||||||
|
if bytes.HasPrefix(bytes.ToLower(trimmed), []byte("content-length:")) {
|
||||||
|
// Parse content length
|
||||||
|
parts := bytes.SplitN(trimmed, []byte(":"), 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lengthStr := bytes.TrimSpace(parts[1])
|
||||||
|
var contentLength int
|
||||||
|
fmt.Sscanf(string(lengthStr), "%d", &contentLength)
|
||||||
|
if contentLength <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read until empty line (end of headers)
|
||||||
|
for {
|
||||||
|
headerLine, err := reader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(bytes.TrimSpace(headerLine)) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read exactly contentLength bytes
|
||||||
|
body := make([]byte, contentLength)
|
||||||
|
_, err := io.ReadFull(reader, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown line, skip
|
||||||
|
debugLog("Skipping unknown line: %s", string(trimmed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func writeResponse(resp interface{}) {
|
func writeResponse(resp interface{}) {
|
||||||
data, _ := json.Marshal(resp)
|
data, _ := json.Marshal(resp)
|
||||||
fmt.Println(string(data))
|
writeFramed(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFramed(data []byte) {
|
||||||
|
// MCP stdio transport uses newline-delimited JSON (NDJSON), not LSP-style
|
||||||
|
// Content-Length framing. Claude Code and other MCP clients reject
|
||||||
|
// Content-Length headers on stdio, so we emit one JSON object per line.
|
||||||
|
fmt.Fprintf(os.Stdout, "%s\n", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugLog(format string, args ...interface{}) {
|
func debugLog(format string, args ...interface{}) {
|
||||||
|
|||||||
Reference in New Issue
Block a user