Initial commit: Gitea MCP Server

This commit is contained in:
2026-04-10 21:57:19 -07:00
commit 1f27844dce
119 changed files with 25001 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
package params
import (
"fmt"
"strconv"
)
// GetString extracts a required string parameter from MCP tool arguments.
func GetString(args map[string]any, key string) (string, error) {
val, ok := args[key].(string)
if !ok {
return "", fmt.Errorf("%s is required", key)
}
return val, nil
}
// GetOptionalString extracts an optional string parameter with a default value.
func GetOptionalString(args map[string]any, key, defaultVal string) string {
if val, ok := args[key].(string); ok {
return val
}
return defaultVal
}
// GetStringSlice extracts an optional string slice parameter from MCP tool arguments.
func GetStringSlice(args map[string]any, key string) []string {
val, ok := args[key]
if !ok {
return nil
}
sliceVal, ok := val.([]any)
if !ok {
return nil
}
out := make([]string, 0, len(sliceVal))
for _, item := range sliceVal {
if s, ok := item.(string); ok {
out = append(out, s)
}
}
return out
}
// GetPagination extracts page and perPage parameters, returning them as ints.
func GetPagination(args map[string]any, defaultPageSize int64) (page, pageSize int) {
return int(GetOptionalInt(args, "page", 1)), int(GetOptionalInt(args, "perPage", defaultPageSize))
}
// ToInt64 converts a value to int64, accepting both float64 (JSON number) and
// string representations. Returns false if the value cannot be converted.
func ToInt64(val any) (int64, bool) {
switch v := val.(type) {
case float64:
return int64(v), true
case string:
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, false
}
return i, true
default:
return 0, false
}
}
// GetIndex extracts a required integer parameter from MCP tool arguments.
// It accepts both numeric (float64 from JSON) and string representations.
// This provides better UX for LLM callers that may naturally use strings
// for identifiers like issue/PR numbers.
func GetIndex(args map[string]any, key string) (int64, error) {
val, exists := args[key]
if !exists {
return 0, fmt.Errorf("%s is required", key)
}
if i, ok := ToInt64(val); ok {
return i, nil
}
if s, ok := val.(string); ok {
return 0, fmt.Errorf("%s must be a valid integer (got %q)", key, s)
}
return 0, fmt.Errorf("%s must be a number or numeric string", key)
}
// GetInt64Slice extracts a required int64 slice parameter from MCP tool arguments.
func GetInt64Slice(args map[string]any, key string) ([]int64, error) {
raw, ok := args[key].([]any)
if !ok {
return nil, fmt.Errorf("%s (array of IDs) is required", key)
}
out := make([]int64, 0, len(raw))
for _, v := range raw {
id, ok := ToInt64(v)
if !ok {
return nil, fmt.Errorf("invalid ID in %s array", key)
}
out = append(out, id)
}
return out, nil
}
// GetOptionalInt extracts an optional integer parameter from MCP tool arguments.
// Returns defaultVal if the key is missing or the value cannot be parsed.
// Accepts both float64 (JSON number) and string representations.
func GetOptionalInt(args map[string]any, key string, defaultVal int64) int64 {
val, exists := args[key]
if !exists {
return defaultVal
}
if i, ok := ToInt64(val); ok {
return i
}
return defaultVal
}
// GetOptionalBool extracts an optional boolean parameter from MCP tool arguments.
// Returns defaultVal if the key is missing or the value cannot be parsed.
// Accepts bool, float64 (1=true, 0=false), and string representations.
func GetOptionalBool(args map[string]any, key string, defaultVal bool) bool {
val, exists := args[key]
if !exists {
return defaultVal
}
switch v := val.(type) {
case bool:
return v
case float64:
return v != 0
case string:
if b, err := strconv.ParseBool(v); err == nil {
return b
}
}
return defaultVal
}
+161
View File
@@ -0,0 +1,161 @@
package params
import (
"strings"
"testing"
)
func TestToInt64(t *testing.T) {
tests := []struct {
name string
val any
want int64
ok bool
}{
{"float64", float64(42), 42, true},
{"float64 zero", float64(0), 0, true},
{"float64 negative", float64(-5), -5, true},
{"string", "123", 123, true},
{"string zero", "0", 0, true},
{"string negative", "-10", -10, true},
{"invalid string", "abc", 0, false},
{"decimal string", "1.5", 0, false},
{"bool", true, 0, false},
{"nil", nil, 0, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := ToInt64(tt.val)
if ok != tt.ok {
t.Errorf("ToInt64() ok = %v, want %v", ok, tt.ok)
}
if got != tt.want {
t.Errorf("ToInt64() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetOptionalInt(t *testing.T) {
tests := []struct {
name string
args map[string]any
key string
defaultVal int64
want int64
}{
{"present float64", map[string]any{"page": float64(3)}, "page", 1, 3},
{"present string", map[string]any{"page": "5"}, "page", 1, 5},
{"missing key", map[string]any{}, "page", 1, 1},
{"invalid string", map[string]any{"page": "abc"}, "page", 1, 1},
{"invalid type", map[string]any{"page": true}, "page", 1, 1},
{"zero value", map[string]any{"id": float64(0)}, "id", 99, 0},
{"string zero", map[string]any{"id": "0"}, "id", 99, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetOptionalInt(tt.args, tt.key, tt.defaultVal)
if got != tt.want {
t.Errorf("GetOptionalInt() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetIndex(t *testing.T) {
tests := []struct {
name string
args map[string]any
key string
wantIndex int64
wantErr bool
errMsg string
}{
{
name: "valid float64",
args: map[string]any{"index": float64(123)},
key: "index",
wantIndex: 123,
wantErr: false,
},
{
name: "valid string",
args: map[string]any{"index": "456"},
key: "index",
wantIndex: 456,
wantErr: false,
},
{
name: "valid string with large number",
args: map[string]any{"index": "999999"},
key: "index",
wantIndex: 999999,
wantErr: false,
},
{
name: "missing parameter",
args: map[string]any{},
key: "index",
wantErr: true,
errMsg: "index is required",
},
{
name: "invalid string (not a number)",
args: map[string]any{"index": "abc"},
key: "index",
wantErr: true,
errMsg: "must be a valid integer",
},
{
name: "invalid string (decimal)",
args: map[string]any{"index": "12.34"},
key: "index",
wantErr: true,
errMsg: "must be a valid integer",
},
{
name: "invalid type (bool)",
args: map[string]any{"index": true},
key: "index",
wantErr: true,
errMsg: "must be a number or numeric string",
},
{
name: "invalid type (map)",
args: map[string]any{"index": map[string]string{"foo": "bar"}},
key: "index",
wantErr: true,
errMsg: "must be a number or numeric string",
},
{
name: "custom key name",
args: map[string]any{"pr_index": "789"},
key: "pr_index",
wantIndex: 789,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotIndex, err := GetIndex(tt.args, tt.key)
if tt.wantErr {
if err == nil {
t.Errorf("GetIndex() expected error but got nil")
return
}
if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
t.Errorf("GetIndex() error = %v, want error containing %q", err, tt.errMsg)
}
return
}
if err != nil {
t.Errorf("GetIndex() unexpected error = %v", err)
return
}
if gotIndex != tt.wantIndex {
t.Errorf("GetIndex() = %v, want %v", gotIndex, tt.wantIndex)
}
})
}
}