Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

82 lines
2.7 KiB
Go

package mcp
import (
"context"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/zzet/gortex/internal/lint"
)
func (s *Server) registerLintTools() {
s.addTool(
mcp.NewTool("lint_file",
mcp.WithDescription("Runs the available external linters/formatters against a file and returns normalized diagnostics — an LSP-free, on-demand syntax/lint check that complements get_diagnostics (which needs a running language server). Built-in linters: gofmt (Go), ruff (Python), shellcheck (shell). A linter that is not installed is reported under linters_skipped, never as an error. Use after an edit to confirm a file is syntactically sound, or to surface lint issues a structural check would miss."),
mcp.WithString("path", mcp.Required(), mcp.Description("File to lint (absolute, or repo-relative).")),
mcp.WithString("language", mcp.Description("Override the language (go, python, shell). Inferred from the file extension when omitted.")),
mcp.WithNumber("timeout_ms", mcp.Description("Per-linter timeout in milliseconds (default 5000).")),
),
s.handleLintFile,
)
}
func (s *Server) handleLintFile(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
rawPath, err := req.RequireString("path")
if err != nil {
return mcp.NewToolResultError("path is required"), nil
}
absPath, relPath, resolveErr := s.resolveFilePath(rawPath)
if resolveErr != nil {
return mcp.NewToolResultError(resolveErr.Error()), nil
}
lang := req.GetString("language", "")
if lang == "" {
lang = lint.LanguageForPath(absPath)
}
if lang == "" {
return s.respondJSONOrTOON(ctx, req, map[string]any{
"path": relPath,
"language": "",
"healthy": true,
"diagnostics": []any{},
"linters_ran": []any{},
"linters_skipped": []any{},
"note": "no built-in linter is configured for this file type",
})
}
timeout := time.Duration(req.GetInt("timeout_ms", 5000)) * time.Millisecond
result := lint.NewRegistry().Run(ctx, absPath, lang, timeout)
// Report diagnostics against the repo-relative path the caller passed.
for i := range result.Diagnostics {
result.Diagnostics[i].File = relPath
}
errCount, warnCount := 0, 0
for _, d := range result.Diagnostics {
switch d.Severity {
case lint.SeverityError:
errCount++
case lint.SeverityWarning:
warnCount++
}
}
return s.respondJSONOrTOON(ctx, req, map[string]any{
"path": relPath,
"language": lang,
"healthy": errCount == 0,
"diagnostics": result.Diagnostics,
"linters_ran": result.Ran,
"linters_skipped": result.Skipped,
"summary": map[string]int{
"errors": errCount,
"warnings": warnCount,
"total": len(result.Diagnostics),
},
})
}