chore: initial release v0.1.0
ci / test (push) Failing after 5s
ci / lint (push) Failing after 4s

forge-toolkit: хелперы для разработки MCP-серверов на Go.

- toolkit: args/schema/result/env/probe/server
- toolkit/validate: валидаторы синтаксиса
- configreload: live-reload конфига по контент-хэшу
- docs: ARCHITECTURE, quickstart, mcp-contract
- template/ и examples/hello-tool/
- CI (Gitea Actions), лицензия Apache-2.0
This commit is contained in:
Maksim Totmin
2026-09-14 00:23:08 +07:00
commit c398045664
43 changed files with 2581 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
package toolkit
import (
"encoding/json"
"fmt"
"strconv"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// RequestArgs распаковывает сырые аргументы tool-вызова в map[string]any.
// Битый JSON / пустые аргументы → пустая карта (валидация — в хендлерах).
func RequestArgs(req *mcp.CallToolRequest) map[string]any {
if req == nil || req.Params == nil || len(req.Params.Arguments) == 0 {
return map[string]any{}
}
var a map[string]any
if err := json.Unmarshal(req.Params.Arguments, &a); err != nil {
return map[string]any{}
}
return a
}
// GetString возвращает строковый аргумент или def. Не-строки приводятся к
// строке через %v (терпимость к числам/булевым от моделей).
func GetString(args map[string]any, key, def string) string {
v, ok := args[key]
if !ok || v == nil {
return def
}
if s, ok := v.(string); ok {
return s
}
return fmt.Sprintf("%v", v)
}
// RequireString возвращает непустой строковый аргумент или ошибку.
func RequireString(args map[string]any, key string) (string, error) {
v, ok := args[key]
if !ok || v == nil {
return "", fmt.Errorf("missing required argument '%s'", key)
}
s, ok := v.(string)
if !ok || s == "" {
return "", fmt.Errorf("argument '%s' must be a non-empty string", key)
}
return s, nil
}
// GetInt возвращает целочисленный аргумент или def. Принимает float64
// (обычный JSON-числовой тип), int и строку с числом.
func GetInt(args map[string]any, key string, def int) int {
v, ok := args[key]
if !ok || v == nil {
return def
}
switch n := v.(type) {
case float64:
return int(n)
case int:
return n
case string:
if i, err := strconv.Atoi(n); err == nil {
return i
}
}
return def
}
// GetBool возвращает булев аргумент или def.
func GetBool(args map[string]any, key string, def bool) bool {
v, ok := args[key]
if !ok || v == nil {
return def
}
if b, ok := v.(bool); ok {
return b
}
return def
}
// GetStringArray возвращает непустой срез непустых строк. ok=false, если
// аргумента нет, он не массив или после фильтрации пуст.
func GetStringArray(args map[string]any, key string) ([]string, bool) {
v, ok := args[key]
if !ok || v == nil {
return nil, false
}
arr, ok := v.([]any)
if !ok || len(arr) == 0 {
return nil, false
}
var out []string
for _, item := range arr {
if s, ok := item.(string); ok && s != "" {
out = append(out, s)
}
}
if len(out) == 0 {
return nil, false
}
return out, true
}
// GetIntArray возвращает непустой срез int (float64/int элементы).
func GetIntArray(args map[string]any, key string) ([]int, bool) {
v, ok := args[key]
if !ok || v == nil {
return nil, false
}
arr, ok := v.([]any)
if !ok || len(arr) == 0 {
return nil, false
}
var out []int
for _, item := range arr {
switch n := item.(type) {
case float64:
out = append(out, int(n))
case int:
out = append(out, n)
}
}
if len(out) == 0 {
return nil, false
}
return out, true
}
// GetStringMap разбирает объект со строковыми значениями (напр. заголовки).
func GetStringMap(args map[string]any, key string) map[string]string {
v, ok := args[key]
if !ok || v == nil {
return nil
}
m, ok := v.(map[string]any)
if !ok || len(m) == 0 {
return nil
}
out := make(map[string]string, len(m))
for k, val := range m {
if s, ok := val.(string); ok {
out[k] = s
}
}
if len(out) == 0 {
return nil
}
return out
}