Files
gitea-mcp-extended/operation/operation.go
Andrew Miller 60f05e5f1e
Some checks failed
release-nightly / publish-nightly (push) Failing after 44s
release / goreleaser (push) Failing after 37s
release / publish-packages (push) Has been skipped
feat: rename to gitea-mcp-extended and add package publishing workflows
Rename the fork from gitea-mcp to gitea-mcp-extended to reflect the
significantly expanded tool coverage (299 vs upstream's 93 tools).

- Rename Go module path and all import references
- Rename binary to gitea-mcp-extended in Makefile, Dockerfile, .gitignore
- Point .goreleaser.yaml gitea_urls to git.lethalbits.com
- Replace release-tag workflow with goreleaser + Generic Package Registry publishing
- Replace release-nightly workflow with cross-platform build + nightly package publishing
- Update CLAUDE.md project description and tool count
2026-03-05 13:04:02 -05:00

182 lines
4.9 KiB
Go

package operation
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/actions"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/admin"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/issue"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/label"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/milestone"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/miscellaneous"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/notification"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/organization"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/packages"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/pull"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/repo"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/search"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/settings"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/timetracking"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/user"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/version"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/operation/wiki"
mcpContext "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/context"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/flag"
"git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/log"
"github.com/mark3labs/mcp-go/server"
)
var mcpServer *server.MCPServer
func RegisterTool(s *server.MCPServer) {
// User Tool
s.AddTools(user.Tool.Tools()...)
// Actions Tool
s.AddTools(actions.Tool.Tools()...)
// Repo Tool
s.AddTools(repo.Tool.Tools()...)
// Issue Tool
s.AddTools(issue.Tool.Tools()...)
// Label Tool
s.AddTools(label.Tool.Tools()...)
// Milestone Tool
s.AddTools(milestone.Tool.Tools()...)
// Pull Tool
s.AddTools(pull.Tool.Tools()...)
// Search Tool
s.AddTools(search.Tool.Tools()...)
// Version Tool
s.AddTools(version.Tool.Tools()...)
// Wiki Tool
s.AddTools(wiki.Tool.Tools()...)
// Time Tracking Tool
s.AddTools(timetracking.Tool.Tools()...)
// Organization Tool
s.AddTools(organization.Tool.Tools()...)
// Notification Tool
s.AddTools(notification.Tool.Tools()...)
// Settings Tool
s.AddTools(settings.Tool.Tools()...)
// Package Tool
s.AddTools(packages.Tool.Tools()...)
// Miscellaneous Tool
s.AddTools(miscellaneous.Tool.Tools()...)
// Admin Tool
s.AddTools(admin.Tool.Tools()...)
s.DeleteTools("")
}
// parseAuthToken extracts the token from an Authorization header.
// Supports "Bearer <token>" (case-insensitive per RFC 7235) and
// Gitea-style "token <token>" formats.
// Returns the token and true if valid, empty string and false otherwise.
func parseAuthToken(authHeader string) (string, bool) {
if len(authHeader) > 7 && strings.EqualFold(authHeader[:7], "Bearer ") {
token := strings.TrimSpace(authHeader[7:])
if token != "" {
return token, true
}
}
if len(authHeader) > 6 && strings.EqualFold(authHeader[:6], "token ") {
token := strings.TrimSpace(authHeader[6:])
if token != "" {
return token, true
}
}
return "", false
}
func getContextWithToken(ctx context.Context, r *http.Request) context.Context {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return ctx
}
token, ok := parseAuthToken(authHeader)
if !ok {
return ctx
}
return context.WithValue(ctx, mcpContext.TokenContextKey, token)
}
func Run() error {
mcpServer = newMCPServer(flag.Version)
RegisterTool(mcpServer)
switch flag.Mode {
case "stdio":
if err := server.ServeStdio(
mcpServer,
); err != nil {
return err
}
case "http":
httpServer := server.NewStreamableHTTPServer(
mcpServer,
server.WithLogger(log.New()),
server.WithHeartbeatInterval(30*time.Second),
server.WithHTTPContextFunc(getContextWithToken),
)
log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
// Graceful shutdown setup
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
shutdownDone := make(chan struct{})
go func() {
<-sigCh
log.Infof("Shutdown signal received, gracefully stopping HTTP server...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := httpServer.Shutdown(shutdownCtx); err != nil {
log.Errorf("HTTP server shutdown error: %v", err)
}
close(shutdownDone)
}()
if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
return err
}
<-shutdownDone // Wait for shutdown to finish
default:
return fmt.Errorf("invalid transport type: %s. Must be 'stdio' or 'http'", flag.Mode)
}
return nil
}
func newMCPServer(version string) *server.MCPServer {
return server.NewMCPServer(
"Gitea MCP Server",
version,
server.WithToolCapabilities(true),
server.WithLogging(),
server.WithRecovery(),
)
}