chore: initial release v0.1.0
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:
@@ -0,0 +1,27 @@
|
||||
# template
|
||||
|
||||
Каркас MCP-сервера на `forge-toolkit`: конфиг, потокобезопасный `Manager`,
|
||||
один инструмент и корректный lifecycle.
|
||||
|
||||
## Как пользоваться
|
||||
|
||||
1. Скопируйте каталог и переименуйте модуль в `go.mod`
|
||||
(`example.com/forge-tool` → ваш путь).
|
||||
2. Замените доменную логику в `internal/tools/tools.go`.
|
||||
3. Соберите и проверьте:
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
go build -o forge-tool .
|
||||
./forge-tool --health # → ok
|
||||
```
|
||||
|
||||
## Подключение
|
||||
|
||||
Любой MCP-клиент — как обычный stdio-сервер:
|
||||
|
||||
```json
|
||||
{ "mcpServers": { "forge-tool": { "command": "/abs/path/forge-tool", "args": ["-config", "/abs/path/config.json"] } } }
|
||||
```
|
||||
|
||||
Контракт и правила — [`../docs/ARCHITECTURE.md`](../docs/ARCHITECTURE.md).
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"greeting": "hello"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
module example.com/forge-tool
|
||||
|
||||
go 1.27.0
|
||||
|
||||
require (
|
||||
git.totmin.ru/en2zmax/forge-toolkit v0.1.0
|
||||
github.com/modelcontextprotocol/go-sdk v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/jsonschema-go v0.4.3 // indirect
|
||||
github.com/segmentio/asm v1.1.3 // indirect
|
||||
github.com/segmentio/encoding v0.5.4 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
|
||||
replace git.totmin.ru/en2zmax/forge-toolkit => ..
|
||||
@@ -0,0 +1,24 @@
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=
|
||||
github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=
|
||||
github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts=
|
||||
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
|
||||
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
||||
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
|
||||
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
@@ -0,0 +1,61 @@
|
||||
// Package tools — доменный слой сервера: конфиг, Manager и инструменты.
|
||||
// Замените содержимое на свою задачу; каркасные хелперы берите из toolkit.
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.totmin.ru/en2zmax/forge-toolkit"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// Config — доменный конфиг сервера (пример). Замените поля на свои.
|
||||
type Config struct {
|
||||
Greeting string `json:"greeting"`
|
||||
}
|
||||
|
||||
// LoadConfig читает config.json и разворачивает ${VAR}. Пустые обязательные
|
||||
// поля лучше отвергать здесь (fail-closed), а не в обработчике.
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(toolkit.Expand(raw), &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.TrimSpace(cfg.Greeting) == "" {
|
||||
cfg.Greeting = "hello"
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// Manager — доменное состояние сервера. Инструменты вызываются конкурентно,
|
||||
// поэтому изменяемое состояние держите под sync.RWMutex.
|
||||
type Manager struct {
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
// NewManager создаёт Manager.
|
||||
func NewManager(cfg *Config) *Manager { return &Manager{cfg: cfg} }
|
||||
|
||||
// Close освобождает ресурсы (соединения, файлы). В каркасе — no-op.
|
||||
func (m *Manager) Close() {}
|
||||
|
||||
// RegisterAll регистрирует инструменты сервера.
|
||||
func RegisterAll(s *mcp.Server, mgr *Manager) {
|
||||
mcp.AddTool(s, &mcp.Tool{
|
||||
Name: "greet",
|
||||
Description: "Приветствие по имени. Без побочных эффектов.",
|
||||
InputSchema: toolkit.Schema(map[string]any{
|
||||
"name": toolkit.StrProps("имя", false),
|
||||
}, nil),
|
||||
}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
|
||||
name := toolkit.GetString(toolkit.RequestArgs(req), "name", "world")
|
||||
return toolkit.Text(mgr.cfg.Greeting + ", " + name + "!"), nil, nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Command forge-tool — каркас MCP-сервера на forge-toolkit.
|
||||
//
|
||||
// Скопируй каталог, переименуй модуль (go.mod) и замени доменную логику в
|
||||
// internal/tools на свою. Точка входа — toolkit.Health (дешёвый --health) и
|
||||
// toolkit.Run (stdio-цикл с graceful shutdown).
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"example.com/forge-tool/internal/tools"
|
||||
|
||||
"git.totmin.ru/en2zmax/forge-toolkit"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if toolkit.Health() {
|
||||
return
|
||||
}
|
||||
|
||||
configPath := flag.String("config", "config.json", "путь к config.json")
|
||||
flag.Parse()
|
||||
|
||||
cfg, err := tools.LoadConfig(*configPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "forge-tool: config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
mgr := tools.NewManager(cfg)
|
||||
defer mgr.Close()
|
||||
|
||||
if err := toolkit.Run("forge-tool", func(s *mcp.Server) {
|
||||
tools.RegisterAll(s, mgr)
|
||||
}); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "forge-tool: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user