105 lines
2.5 KiB
Go
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
|
|
}
|