Initial commit: Gitea MCP Server
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/errors"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/log"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
ListActionRunnersToolName = "list_action_runners"
|
||||
)
|
||||
|
||||
// ActionRunner represents a self-hosted action runner
|
||||
// This is a local type since Gitea SDK v0.23.2 doesn't include it
|
||||
type ActionRunner struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
Status string `json:"status"`
|
||||
Online bool `json:"online"`
|
||||
Busy bool `json:"busy"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
LastOnline string `json:"last_online,omitempty"`
|
||||
}
|
||||
|
||||
// ActionRunnersResponse represents the API response for listing runners
|
||||
type ActionRunnersResponse struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Runners []*ActionRunner `json:"runners"`
|
||||
}
|
||||
|
||||
var (
|
||||
ListActionRunnersTool = mcp.NewTool(
|
||||
ListActionRunnersToolName,
|
||||
mcp.WithDescription("List self-hosted action runners for a repository. Shows runner status, labels, and availability. Filter by status (online/offline). Note: Requires Gitea 1.23+; Gitea 1.22.5 does not support Actions API."),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("status", mcp.Description("optional status filter (online, offline, busy, idle)"), mcp.Enum("online", "offline", "busy", "idle")),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30), mcp.Min(1)),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: ListActionRunnersTool,
|
||||
Handler: listActionRunnersFn,
|
||||
})
|
||||
}
|
||||
|
||||
func listActionRunnersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called listActionRunnersFn")
|
||||
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(errors.TranslateError(err, map[string]string{
|
||||
"operation": "ListActionRunners",
|
||||
"param": "owner",
|
||||
}))
|
||||
}
|
||||
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(errors.TranslateError(err, map[string]string{
|
||||
"operation": "ListActionRunners",
|
||||
"param": "repo",
|
||||
}))
|
||||
}
|
||||
|
||||
statusFilter, _ := req.GetArguments()["status"].(string)
|
||||
page, pageSize := params.GetPagination(req.GetArguments(), 30)
|
||||
|
||||
// Use REST API to get runners
|
||||
apiPath := fmt.Sprintf("/repos/%s/%s/actions/runners", owner, repo)
|
||||
query := url.Values{
|
||||
"page": []string{fmt.Sprintf("%d", page)},
|
||||
"per_page": []string{fmt.Sprintf("%d", pageSize)},
|
||||
}
|
||||
|
||||
var runnersResp ActionRunnersResponse
|
||||
statusCode, err := gitea.DoJSON(ctx, http.MethodGet, apiPath, query, nil, &runnersResp)
|
||||
if err != nil {
|
||||
if statusCode == http.StatusNotFound {
|
||||
// Gitea 1.22.5 doesn't have Actions API - return empty list with message
|
||||
return to.TextResult(map[string]interface{}{
|
||||
"total_count": 0,
|
||||
"runners": []interface{}{},
|
||||
"note": "Actions API not available in Gitea 1.22.5. Requires Gitea 1.23+.",
|
||||
})
|
||||
}
|
||||
return to.ErrorResult(errors.TranslateError(err, map[string]string{
|
||||
"operation": "ListActionRunners",
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
}))
|
||||
}
|
||||
|
||||
// Filter by status if requested
|
||||
filteredRunners := make([]*ActionRunner, 0, len(runnersResp.Runners))
|
||||
if statusFilter != "" {
|
||||
for _, runner := range runnersResp.Runners {
|
||||
switch statusFilter {
|
||||
case "online":
|
||||
if runner.Online {
|
||||
filteredRunners = append(filteredRunners, runner)
|
||||
}
|
||||
case "offline":
|
||||
if !runner.Online {
|
||||
filteredRunners = append(filteredRunners, runner)
|
||||
}
|
||||
case "busy":
|
||||
if runner.Busy {
|
||||
filteredRunners = append(filteredRunners, runner)
|
||||
}
|
||||
case "idle":
|
||||
if runner.Online && !runner.Busy {
|
||||
filteredRunners = append(filteredRunners, runner)
|
||||
}
|
||||
default:
|
||||
if runner.Status == statusFilter {
|
||||
filteredRunners = append(filteredRunners, runner)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredRunners = runnersResp.Runners
|
||||
}
|
||||
|
||||
result := slimActionRunners(filteredRunners)
|
||||
|
||||
return to.TextResult(result)
|
||||
}
|
||||
|
||||
func slimActionRunners(runners []*ActionRunner) map[string]interface{} {
|
||||
if len(runners) == 0 {
|
||||
return map[string]interface{}{
|
||||
"total_count": 0,
|
||||
"runners": []interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
slimmed := make([]map[string]interface{}, 0, len(runners))
|
||||
for _, runner := range runners {
|
||||
slimmed = append(slimmed, slimActionRunner(runner))
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"total_count": len(runners),
|
||||
"runners": slimmed,
|
||||
}
|
||||
}
|
||||
|
||||
func slimActionRunner(runner *ActionRunner) map[string]interface{} {
|
||||
if runner == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"id": runner.ID,
|
||||
"name": runner.Name,
|
||||
"uuid": runner.UUID,
|
||||
"status": runner.Status,
|
||||
"online": runner.Online,
|
||||
"busy": runner.Busy,
|
||||
}
|
||||
|
||||
if runner.Version != "" {
|
||||
result["version"] = runner.Version
|
||||
}
|
||||
|
||||
if len(runner.Labels) > 0 {
|
||||
result["labels"] = runner.Labels
|
||||
} else {
|
||||
result["labels"] = []string{}
|
||||
}
|
||||
|
||||
if runner.LastOnline != "" {
|
||||
result["last_online"] = runner.LastOnline
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user