2026-05-29 18:12:45 +00:00
package tools
import (
"context"
2026-08-07 16:58:24 -06:00
"code.nochebuena.dev/einherjar/mcp/internal/envspec"
2026-05-29 18:12:45 +00:00
"code.nochebuena.dev/einherjar/mcp/internal/index"
"code.nochebuena.dev/einherjar/mcp/internal/rules"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type validateSnippetInput struct {
Code string `json:"code" jsonschema:"Go source code to validate against Einherjar conventions. A full file is preferred; a partial body will be wrapped automatically."`
}
type validateSnippetOutput struct {
Findings [] rules . Finding `json:"findings"`
Summary string `json:"summary"`
}
2026-08-07 16:58:24 -06:00
func registerValidateSnippet ( s * mcp . Server , idx * index . Index ) {
// Inject the real framework env-var names so config.unknown-env-var can reject
// invented/misspelled EINHERJAR_* tags instead of guessing.
rules . SetKnownEnvVars ( envspec . KnownNames ( idx ))
2026-05-29 18:12:45 +00:00
mcp . AddTool ( s , & mcp . Tool {
Name : "validate_snippet" ,
Description : "Validate a Go snippet against Einherjar wiring conventions: lifecycle setup, logger configuration, env-var handling, server registration. Findings are advisory, not a substitute for go vet or the project's tests." ,
}, func ( ctx context . Context , req * mcp . CallToolRequest , args validateSnippetInput ) ( * mcp . CallToolResult , validateSnippetOutput , error ) {
findings := rules . Run ( args . Code )
if findings == nil {
findings = [] rules . Finding {}
}
summary := summarise ( findings )
out := validateSnippetOutput { Findings : findings , Summary : summary }
return jsonText ( out ), out , nil
})
}
func summarise ( fs [] rules . Finding ) string {
if len ( fs ) == 0 {
return "No issues found — snippet follows Einherjar conventions."
}
var errs , warns , infos int
for _ , f := range fs {
switch f . Severity {
case rules . SeverityError :
errs ++
case rules . SeverityWarning :
warns ++
case rules . SeverityInfo :
infos ++
}
}
return pluralise ( errs , "error" , "errors" ) + ", " +
pluralise ( warns , "warning" , "warnings" ) + ", " +
pluralise ( infos , "note" , "notes" )
}
func pluralise ( n int , singular , plural string ) string {
if n == 1 {
return "1 " + singular
}
return itoa ( n ) + " " + plural
}
func itoa ( n int ) string {
if n == 0 {
return "0"
}
neg := n < 0
if neg {
n = - n
}
var buf [ 20 ] byte
i := len ( buf )
for n > 0 {
i --
buf [ i ] = byte ( '0' + n % 10 )
n /= 10
}
if neg {
i --
buf [ i ] = '-'
}
return string ( buf [ i :])
}