Files
amacocian 8d159a9bcd
CI / build (push) Failing after 1s
Gitea tools improvement
2026-05-28 16:05:07 +02:00

105 lines
2.5 KiB
Go

package main
import (
"net/url"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// registerTools wires every tool onto server. Each `tools_*.go` file
// owns one domain (issues, pulls, actions, …); this file just calls
// into them and provides cross-domain helpers.
func registerTools(server *mcp.Server, api *giteaAPI) {
registerUserTools(server, api)
registerRepoTools(server, api)
registerContentTools(server, api)
registerTreeTools(server, api)
registerCommitTools(server, api)
registerIssueTools(server, api)
registerPullTools(server, api)
registerReleaseTools(server, api)
registerRefTools(server, api)
registerWikiTools(server, api)
registerActionsTools(server, api)
registerPackageTools(server, api)
registerOrgTools(server, api)
}
// clampInt returns dflt when v <= 0, else v clipped to [min, max].
func clampInt(v, min, max, dflt int) int {
if v <= 0 {
return dflt
}
if v < min {
return min
}
if v > max {
return max
}
return v
}
func firstLine(s string) string {
for i := 0; i < len(s); i++ {
if s[i] == '\n' {
return s[:i]
}
}
return s
}
// escapePathSegments escapes each segment of a file path individually
// so that slashes survive but per-segment specials (#, ?, etc.) get
// percent-encoded. url.PathEscape on the whole path would escape the
// slashes too and break Gitea's path-based routing.
func escapePathSegments(p string) string {
var b []byte
first := true
from := 0
for i := 0; i <= len(p); i++ {
if i == len(p) || p[i] == '/' {
seg := p[from:i]
if !first {
b = append(b, '/')
}
b = append(b, url.PathEscape(seg)...)
first = false
from = i + 1
}
}
return string(b)
}
// setIf sets q[k]=v when v is non-empty. Saves a lot of three-line
// if-non-empty blocks in the tool handlers.
func setIf(q url.Values, k, v string) {
if v != "" {
q.Set(k, v)
}
}
// htmlURLFromAPI converts an api.../api/v1/... URL into its
// corresponding HTML URL by stripping the `/api/v1` prefix. Returns
// the input unchanged if the prefix isn't found.
//
// Gitea's response objects for Actions runs/jobs/runners only carry
// the API URL; operators want the browser URL when sherlock surfaces
// these in chat output.
func htmlURLFromAPI(apiURL string) string {
const marker = "/api/v1"
i := indexOfMarker(apiURL, marker)
if i < 0 {
return apiURL
}
return apiURL[:i] + apiURL[i+len(marker):]
}
func indexOfMarker(s, sub string) int {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return i
}
}
return -1
}