mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-12 04:54:27 +00:00
multi-fixes
This commit is contained in:
+122
-31
@@ -172,11 +172,9 @@ func (e *Engine) performASTEdit(ctx context.Context, edit *ASTEdit, apply bool)
|
||||
diff := e.generateDiff(string(content), string(newContent), edit.File)
|
||||
|
||||
result := &EditResult{
|
||||
Success: true,
|
||||
Diff: diff,
|
||||
OriginalContent: string(content),
|
||||
NewContent: string(newContent),
|
||||
Applied: false,
|
||||
Success: true,
|
||||
Diff: diff,
|
||||
Applied: false,
|
||||
}
|
||||
|
||||
// Apply changes if requested
|
||||
@@ -231,11 +229,9 @@ func (e *Engine) performTextEdit(_ context.Context, edit *ASTEdit, apply bool) (
|
||||
diff := e.generateDiff(string(content), string(newContent), edit.File)
|
||||
|
||||
result := &EditResult{
|
||||
Success: true,
|
||||
Diff: diff,
|
||||
OriginalContent: string(content),
|
||||
NewContent: string(newContent),
|
||||
Applied: false,
|
||||
Success: true,
|
||||
Diff: diff,
|
||||
Applied: false,
|
||||
}
|
||||
|
||||
// Apply changes if requested
|
||||
@@ -568,14 +564,22 @@ func indentContent(content string, indent string) string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// diffLine represents a single line in the diff with its type and content.
|
||||
type diffLine struct {
|
||||
op diffmatchpatch.Operation
|
||||
text string // line content without trailing newline
|
||||
oldN int // 1-based line number in original (0 if insert)
|
||||
newN int // 1-based line number in modified (0 if delete)
|
||||
}
|
||||
|
||||
// generateDiff creates a unified diff between original and modified content.
|
||||
// Uses line-level Myers diff algorithm for accurate and readable diffs.
|
||||
// Uses line-level Myers diff algorithm and outputs a proper unified diff
|
||||
// with context lines (3 before/after each change, merging close hunks).
|
||||
func (e *Engine) generateDiff(original, modified, filename string) string {
|
||||
dmp := e.dmp
|
||||
|
||||
// Use line-level diffing: encode each line as a single character,
|
||||
// diff the encoded strings, then decode back to real lines.
|
||||
// This prevents character-level diffs from splitting lines incorrectly.
|
||||
chars1, chars2, lineArray := dmp.DiffLinesToChars(original, modified)
|
||||
diffs := dmp.DiffMain(chars1, chars2, false)
|
||||
diffs = dmp.DiffCharsToLines(diffs, lineArray)
|
||||
@@ -583,30 +587,117 @@ func (e *Engine) generateDiff(original, modified, filename string) string {
|
||||
// Cleanup for readability
|
||||
diffs = dmp.DiffCleanupSemantic(diffs)
|
||||
|
||||
// Convert to unified diff format
|
||||
// Flatten diffs into individual lines with line numbers
|
||||
var lines []diffLine
|
||||
oldLine := 1
|
||||
newLine := 1
|
||||
for _, d := range diffs {
|
||||
rawLines := strings.SplitAfter(d.Text, "\n")
|
||||
for _, raw := range rawLines {
|
||||
if raw == "" {
|
||||
continue
|
||||
}
|
||||
text := strings.TrimSuffix(raw, "\n")
|
||||
switch d.Type {
|
||||
case diffmatchpatch.DiffEqual:
|
||||
lines = append(lines, diffLine{op: d.Type, text: text, oldN: oldLine, newN: newLine})
|
||||
oldLine++
|
||||
newLine++
|
||||
case diffmatchpatch.DiffDelete:
|
||||
lines = append(lines, diffLine{op: d.Type, text: text, oldN: oldLine})
|
||||
oldLine++
|
||||
case diffmatchpatch.DiffInsert:
|
||||
lines = append(lines, diffLine{op: d.Type, text: text, newN: newLine})
|
||||
newLine++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Identify indices of changed lines
|
||||
const contextSize = 3
|
||||
var changedIndices []int
|
||||
for i, l := range lines {
|
||||
if l.op != diffmatchpatch.DiffEqual {
|
||||
changedIndices = append(changedIndices, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(changedIndices) == 0 {
|
||||
return "" // no changes
|
||||
}
|
||||
|
||||
// Build inclusion ranges: for each changed line, include contextSize lines before/after.
|
||||
// Merge overlapping or adjacent ranges (gap <= 2*contextSize = 6 context lines).
|
||||
type indexRange struct{ start, end int } // inclusive
|
||||
var ranges []indexRange
|
||||
for _, ci := range changedIndices {
|
||||
rStart := ci - contextSize
|
||||
if rStart < 0 {
|
||||
rStart = 0
|
||||
}
|
||||
rEnd := ci + contextSize
|
||||
if rEnd >= len(lines) {
|
||||
rEnd = len(lines) - 1
|
||||
}
|
||||
if len(ranges) > 0 && rStart <= ranges[len(ranges)-1].end+1 {
|
||||
// Merge with previous range
|
||||
ranges[len(ranges)-1].end = rEnd
|
||||
} else {
|
||||
ranges = append(ranges, indexRange{rStart, rEnd})
|
||||
}
|
||||
}
|
||||
|
||||
// Emit unified diff
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(fmt.Sprintf("--- %s\n", filename))
|
||||
buf.WriteString(fmt.Sprintf("+++ %s\n", filename))
|
||||
|
||||
for _, diff := range diffs {
|
||||
// SplitAfter preserves the trailing \n on each line, so we can
|
||||
// distinguish real lines from a trailing empty split artifact.
|
||||
lines := strings.SplitAfter(diff.Text, "\n")
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove trailing newline for display — we add our own.
|
||||
cleanLine := strings.TrimSuffix(line, "\n")
|
||||
|
||||
switch diff.Type {
|
||||
case diffmatchpatch.DiffDelete:
|
||||
buf.WriteString(fmt.Sprintf("-%s\n", cleanLine))
|
||||
case diffmatchpatch.DiffInsert:
|
||||
buf.WriteString(fmt.Sprintf("+%s\n", cleanLine))
|
||||
for _, r := range ranges {
|
||||
// Determine hunk header line numbers
|
||||
var oldStart, oldCount, newStart, newCount int
|
||||
for i := r.start; i <= r.end; i++ {
|
||||
l := lines[i]
|
||||
switch l.op {
|
||||
case diffmatchpatch.DiffEqual:
|
||||
buf.WriteString(fmt.Sprintf(" %s\n", cleanLine))
|
||||
if oldCount == 0 {
|
||||
oldStart = l.oldN
|
||||
}
|
||||
if newCount == 0 {
|
||||
newStart = l.newN
|
||||
}
|
||||
oldCount++
|
||||
newCount++
|
||||
case diffmatchpatch.DiffDelete:
|
||||
if oldCount == 0 {
|
||||
oldStart = l.oldN
|
||||
}
|
||||
if newCount == 0 {
|
||||
// Set newStart from context or next available
|
||||
newStart = l.oldN // approximate
|
||||
}
|
||||
oldCount++
|
||||
case diffmatchpatch.DiffInsert:
|
||||
if newCount == 0 {
|
||||
newStart = l.newN
|
||||
}
|
||||
if oldCount == 0 {
|
||||
oldStart = l.newN // approximate
|
||||
}
|
||||
newCount++
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", oldStart, oldCount, newStart, newCount))
|
||||
|
||||
for i := r.start; i <= r.end; i++ {
|
||||
l := lines[i]
|
||||
switch l.op {
|
||||
case diffmatchpatch.DiffEqual:
|
||||
buf.WriteString(fmt.Sprintf(" %s\n", l.text))
|
||||
case diffmatchpatch.DiffDelete:
|
||||
buf.WriteString(fmt.Sprintf("-%s\n", l.text))
|
||||
case diffmatchpatch.DiffInsert:
|
||||
buf.WriteString(fmt.Sprintf("+%s\n", l.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,9 @@ var DefaultServerConfigs = map[protocol.Language]ServerConfig{
|
||||
protocol.LangCpp: {
|
||||
Command: []string{"clangd"},
|
||||
},
|
||||
protocol.LangRust: {
|
||||
Command: []string{"rust-analyzer"},
|
||||
},
|
||||
}
|
||||
|
||||
// AllowedLSPBinaries is a whitelist of allowed LSP server binary names.
|
||||
@@ -602,6 +605,8 @@ func languageToLSPID(lang protocol.Language) string {
|
||||
return "c"
|
||||
case protocol.LangCpp:
|
||||
return "cpp"
|
||||
case protocol.LangRust:
|
||||
return "rust"
|
||||
default:
|
||||
return string(lang)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ func ExtractDocComment(n *sitter.Node, content []byte, lang protocol.Language) *
|
||||
return extractCDocComment(n, content)
|
||||
case protocol.LangElixir:
|
||||
return extractElixirDocComment(n, content)
|
||||
case protocol.LangRust:
|
||||
return extractRustDocComment(n, content)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -551,6 +553,64 @@ func cleanPythonDocstring(doc string) string {
|
||||
return strings.TrimSpace(doc)
|
||||
}
|
||||
|
||||
// extractRustDocComment extracts Rust documentation comments (/// style).
|
||||
func extractRustDocComment(n *sitter.Node, content []byte) *DocComment {
|
||||
comments := collectPrecedingComments(n, content, []string{"line_comment"})
|
||||
if len(comments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter for /// doc comments only
|
||||
var docComments []*sitter.Node
|
||||
for _, c := range comments {
|
||||
text := GetNodeText(c, content)
|
||||
trimmed := strings.TrimSpace(text)
|
||||
if strings.HasPrefix(trimmed, "///") {
|
||||
docComments = append(docComments, c)
|
||||
}
|
||||
}
|
||||
|
||||
if len(docComments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var parts []string
|
||||
var raw []string
|
||||
startLine := -1
|
||||
endLine := -1
|
||||
|
||||
for _, c := range docComments {
|
||||
text := GetNodeText(c, content)
|
||||
raw = append(raw, text)
|
||||
|
||||
if startLine == -1 {
|
||||
startLine = int(c.StartPoint().Row) + 1
|
||||
}
|
||||
endLine = int(c.EndPoint().Row) + 1
|
||||
|
||||
// Clean /// prefix
|
||||
cleaned := strings.TrimSpace(text)
|
||||
cleaned = strings.TrimPrefix(cleaned, "///")
|
||||
if len(cleaned) > 0 && cleaned[0] == ' ' {
|
||||
cleaned = cleaned[1:]
|
||||
}
|
||||
parts = append(parts, cleaned)
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &DocComment{
|
||||
Text: strings.Join(parts, "\n"),
|
||||
Raw: strings.Join(raw, "\n"),
|
||||
Style: CommentStyleDoxygen,
|
||||
Tags: nil,
|
||||
StartLine: startLine,
|
||||
EndLine: endLine,
|
||||
}
|
||||
}
|
||||
|
||||
// extractElixirDocComment extracts Elixir documentation from @doc and @moduledoc attributes.
|
||||
// Elixir uses module attributes like @doc and @moduledoc for documentation.
|
||||
func extractElixirDocComment(n *sitter.Node, content []byte) *DocComment {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/smacker/go-tree-sitter/html"
|
||||
"github.com/smacker/go-tree-sitter/javascript"
|
||||
"github.com/smacker/go-tree-sitter/python"
|
||||
"github.com/smacker/go-tree-sitter/rust"
|
||||
"github.com/smacker/go-tree-sitter/typescript/typescript"
|
||||
|
||||
"github.com/lukaszraczylo/mcp-filepuff/pkg/errors"
|
||||
@@ -117,10 +118,12 @@ func getLanguage(lang protocol.Language) (*sitter.Language, error) {
|
||||
return html.GetLanguage(), nil
|
||||
case protocol.LangElixir:
|
||||
return elixir.GetLanguage(), nil
|
||||
case protocol.LangRust:
|
||||
return rust.GetLanguage(), nil
|
||||
default:
|
||||
return nil, errors.New(errors.ErrInvalidLanguage, fmt.Sprintf("language %s is not supported", lang)).
|
||||
WithContext("language", string(lang)).
|
||||
WithRemediation("Supported languages: Go, TypeScript, JavaScript, Python, C, C++, HTML, Vue, Elixir")
|
||||
WithRemediation("Supported languages: Go, TypeScript, JavaScript, Python, C, C++, HTML, Vue, Elixir, Rust")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ func ExtractSymbols(tree *sitter.Tree, content []byte, lang protocol.Language, f
|
||||
return extractCSymbols(root, content, filename)
|
||||
case protocol.LangElixir:
|
||||
return extractElixirSymbols(root, content, filename)
|
||||
case protocol.LangRust:
|
||||
return extractRustSymbols(root, content, filename)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -714,6 +716,133 @@ func extractElixirProtocol(n *sitter.Node, content []byte, filename string) *pro
|
||||
}
|
||||
}
|
||||
|
||||
// extractRustSymbols extracts symbols from Rust code.
|
||||
func extractRustSymbols(root *sitter.Node, content []byte, filename string) []protocol.Symbol {
|
||||
var symbols []protocol.Symbol
|
||||
|
||||
WalkTree(root, func(n *sitter.Node) bool {
|
||||
var symbol *protocol.Symbol
|
||||
|
||||
switch n.Type() {
|
||||
case "function_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolFunction,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "struct_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolStruct,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "enum_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolEnum,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "trait_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolTrait,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "impl_item":
|
||||
symbol = extractRustImpl(n, content, filename)
|
||||
case "type_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolType,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "const_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolConstant,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "static_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolVariable,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "macro_definition":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content) + " (macro)",
|
||||
Kind: protocol.SymbolFunction,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
case "mod_item":
|
||||
nameNode := n.ChildByFieldName("name")
|
||||
if nameNode != nil {
|
||||
symbol = &protocol.Symbol{
|
||||
Name: GetNodeText(nameNode, content),
|
||||
Kind: protocol.SymbolModule,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if symbol != nil {
|
||||
if doc := ExtractDocComment(n, content, protocol.LangRust); doc != nil {
|
||||
symbol.Doc = FormatDocComment(doc)
|
||||
}
|
||||
symbols = append(symbols, *symbol)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return symbols
|
||||
}
|
||||
|
||||
// extractRustImpl extracts an impl block symbol from Rust code.
|
||||
func extractRustImpl(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||
typeNode := n.ChildByFieldName("type")
|
||||
traitNode := n.ChildByFieldName("trait")
|
||||
|
||||
var name string
|
||||
if traitNode != nil && typeNode != nil {
|
||||
name = "impl " + GetNodeText(traitNode, content) + " for " + GetNodeText(typeNode, content)
|
||||
} else if typeNode != nil {
|
||||
name = "impl " + GetNodeText(typeNode, content)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &protocol.Symbol{
|
||||
Name: name,
|
||||
Kind: protocol.SymbolType,
|
||||
Location: NodeLocation(n, filename),
|
||||
}
|
||||
}
|
||||
|
||||
// extractElixirImpl extracts a protocol implementation.
|
||||
func extractElixirImpl(n *sitter.Node, content []byte, filename string) *protocol.Symbol {
|
||||
// defimpl Protocol, for: Type do ... end
|
||||
|
||||
@@ -56,7 +56,7 @@ func (s *Server) handleASTQuery(ctx context.Context, request mcp.CallToolRequest
|
||||
// Find files to search based on language
|
||||
exts := languageToExtensions(language)
|
||||
if len(exts) == 0 {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("unsupported language: %s (supported: go, typescript, javascript, python, c, cpp, html, vue, elixir)", language)), nil
|
||||
return mcp.NewToolResultError(fmt.Sprintf("unsupported language: %s (supported: go, typescript, javascript, python, c, cpp, html, vue, elixir, rust)", language)), nil
|
||||
}
|
||||
|
||||
var allResults []query.MatchResult
|
||||
@@ -205,6 +205,10 @@ func symbolKindIcon(kind protocol.SymbolKind) string {
|
||||
return "mod"
|
||||
case protocol.SymbolPackage:
|
||||
return "pkg"
|
||||
case protocol.SymbolEnum:
|
||||
return "enum"
|
||||
case protocol.SymbolTrait:
|
||||
return "trait"
|
||||
default:
|
||||
return "sym"
|
||||
}
|
||||
@@ -231,6 +235,8 @@ func languageToExtensions(language string) []string {
|
||||
return []string{".vue"}
|
||||
case "elixir":
|
||||
return []string{".ex", ".exs"}
|
||||
case "rust":
|
||||
return []string{".rs"}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
+50
-10
@@ -89,7 +89,8 @@ func (s *Server) registerTools() {
|
||||
// Register ping tool for health checks
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("ping",
|
||||
mcp.WithDescription("Health check - returns pong to verify the server is running"),
|
||||
mcp.WithDescription("Health check - returns pong to verify the server is running.\n\n"+
|
||||
"Returns: \"pong\" text string."),
|
||||
mcp.WithReadOnlyHintAnnotation(true),
|
||||
),
|
||||
s.handlePing,
|
||||
@@ -99,7 +100,10 @@ func (s *Server) registerTools() {
|
||||
if s.searcher != nil {
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("file_search",
|
||||
mcp.WithDescription("Search for text patterns in files using ripgrep. Supports regex patterns, file type filtering, and context lines."),
|
||||
mcp.WithDescription("Search for text patterns in files using ripgrep. Supports regex patterns, file type filtering, and context lines.\n\n"+
|
||||
"Returns: Results grouped by file with match context. Format: \"Found N matches in M files:\" followed by file sections, "+
|
||||
"each with matching lines prefixed by \"L{line}│\" and context lines prefixed by \" │\".\n\n"+
|
||||
"Example: {\"pattern\": \"func.*Error\", \"file_types\": [\"go\"], \"max_results\": 20}"),
|
||||
mcp.WithReadOnlyHintAnnotation(true),
|
||||
mcp.WithString("pattern",
|
||||
mcp.Required(),
|
||||
@@ -133,7 +137,16 @@ func (s *Server) registerTools() {
|
||||
// Register file_read tool
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("file_read",
|
||||
mcp.WithDescription("Read a file's contents with optional line range and AST symbol summary"),
|
||||
mcp.WithDescription("Read a file's contents with optional line range and AST symbol summary.\n\n"+
|
||||
"Returns: File content with numbered lines (format: \" 12│ line text\"). "+
|
||||
"When include_ast=true: prepends symbol summary (\"**file.go** (N lines, go)\\nSymbols:\\n func Name L12\\n struct Config L45\"). "+
|
||||
"When symbols_only=true: returns only the symbol summary (~95% fewer tokens). "+
|
||||
"When max_lines is set: truncates output with \"[... N more lines omitted]\" notice.\n\n"+
|
||||
"Examples:\n"+
|
||||
" Full file: {\"path\": \"main.go\"}\n"+
|
||||
" With AST: {\"path\": \"main.go\", \"include_ast\": true}\n"+
|
||||
" Symbols only: {\"path\": \"main.go\", \"include_ast\": true, \"symbols_only\": true}\n"+
|
||||
" Line range: {\"path\": \"main.go\", \"line_start\": 10, \"line_end\": 50}"),
|
||||
mcp.WithReadOnlyHintAnnotation(true),
|
||||
mcp.WithString("path",
|
||||
mcp.Required(),
|
||||
@@ -161,7 +174,13 @@ func (s *Server) registerTools() {
|
||||
// Register ast_query tool
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("ast_query",
|
||||
mcp.WithDescription("Search for AST patterns in code files. Use code patterns with $VAR placeholders to match and capture code structures like functions, classes, and types."),
|
||||
mcp.WithDescription("Search for AST patterns in code files. Use code patterns with $VAR placeholders to match and capture code structures like functions, classes, and types.\n\n"+
|
||||
"Returns: \"Found N match(es):\" followed by entries in format \"**file:line** (node_type)\" with code blocks "+
|
||||
"and captured variables ($NAME=value). Returns \"No matches found.\" when no results.\n\n"+
|
||||
"Examples:\n"+
|
||||
" Go error funcs: {\"pattern\": \"func $NAME($$$ARGS) error\", \"language\": \"go\"}\n"+
|
||||
" Python classes: {\"pattern\": \"class $NAME: $$$BODY\", \"language\": \"python\"}\n"+
|
||||
" Named function: {\"pattern\": \"func $NAME($$$ARGS)\", \"language\": \"go\", \"name_exact\": \"NewServer\"}"),
|
||||
mcp.WithReadOnlyHintAnnotation(true),
|
||||
mcp.WithString("pattern",
|
||||
mcp.Required(),
|
||||
@@ -169,7 +188,7 @@ func (s *Server) registerTools() {
|
||||
),
|
||||
mcp.WithString("language",
|
||||
mcp.Required(),
|
||||
mcp.Description("Target language: go, typescript, javascript, python, c, cpp, html, vue, elixir"),
|
||||
mcp.Description("Target language: go, typescript, javascript, python, c, cpp, html, vue, elixir, rust"),
|
||||
),
|
||||
mcp.WithArray("paths",
|
||||
mcp.Description("Paths to search in (defaults to workspace root)"),
|
||||
@@ -197,7 +216,10 @@ func (s *Server) registerTools() {
|
||||
// Register symbol_at tool
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("symbol_at",
|
||||
mcp.WithDescription("Get information about the symbol at a specific position in a file. Returns type, documentation, and definition location using LSP when available."),
|
||||
mcp.WithDescription("Get information about the symbol at a specific position in a file. Returns type, documentation, and definition location using LSP when available.\n\n"+
|
||||
"Returns: \"**Symbol Information**\" followed by hover/type information from LSP, or \"**Symbol Information** (AST fallback)\" "+
|
||||
"with node type and text when LSP unavailable. Returns \"No symbol information available at this position.\" when nothing is found.\n\n"+
|
||||
"Example: {\"file\": \"server.go\", \"line\": 45, \"column\": 6}"),
|
||||
mcp.WithReadOnlyHintAnnotation(true),
|
||||
mcp.WithString("file",
|
||||
mcp.Required(),
|
||||
@@ -218,7 +240,10 @@ func (s *Server) registerTools() {
|
||||
// Register find_definition tool
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("find_definition",
|
||||
mcp.WithDescription("Find the definition of the symbol at a specific position. Uses LSP to locate where a function, variable, type, etc. is defined."),
|
||||
mcp.WithDescription("Find the definition of the symbol at a specific position. Uses LSP to locate where a function, variable, type, etc. is defined.\n\n"+
|
||||
"Returns: \"Found N definition(s):\" with entries showing \"**file:line:column**\" and a 3-line code preview "+
|
||||
"with the target line marked by \">\". Returns \"No definition found.\" when the symbol has no definition.\n\n"+
|
||||
"Example: {\"file\": \"handler.go\", \"line\": 23, \"column\": 10}"),
|
||||
mcp.WithReadOnlyHintAnnotation(true),
|
||||
mcp.WithString("file",
|
||||
mcp.Required(),
|
||||
@@ -239,7 +264,10 @@ func (s *Server) registerTools() {
|
||||
// Register find_references tool
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("find_references",
|
||||
mcp.WithDescription("Find all references to the symbol at a specific position. Uses LSP to locate all usages of a function, variable, type, etc."),
|
||||
mcp.WithDescription("Find all references to the symbol at a specific position. Uses LSP to locate all usages of a function, variable, type, etc.\n\n"+
|
||||
"Returns: \"Found N reference(s):\" grouped by file, each showing \"**file** (count)\" with locations as "+
|
||||
"\"L{line}:{column}\". Returns \"No references found.\" when no usages exist.\n\n"+
|
||||
"Example: {\"file\": \"types.go\", \"line\": 5, \"column\": 6}"),
|
||||
mcp.WithReadOnlyHintAnnotation(true),
|
||||
mcp.WithString("file",
|
||||
mcp.Required(),
|
||||
@@ -264,7 +292,13 @@ func (s *Server) registerTools() {
|
||||
// Register edit tools
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("edit_preview",
|
||||
mcp.WithDescription("Preview an edit without applying it. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++), and text-based editing for other files (Markdown, JSON, YAML, config files, etc.)."),
|
||||
mcp.WithDescription("Preview an edit without applying it. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++, Rust), and text-based editing for other files (Markdown, JSON, YAML, config files, etc.).\n\n"+
|
||||
"Returns: \"**Edit Preview**\" followed by a unified diff showing proposed changes. Does not modify the file. "+
|
||||
"For code files: uses AST-aware mode with syntax validation. For other files: uses text-based mode.\n\n"+
|
||||
"Examples:\n"+
|
||||
" AST mode: {\"file\": \"main.go\", \"operation\": \"replace\", \"selector_kind\": \"function_declaration\", \"selector_name\": \"Hello\", \"new_content\": \"func Hello() {\\n\\treturn\\n}\"}\n"+
|
||||
" Text mode: {\"file\": \"README.md\", \"operation\": \"replace\", \"selector_text\": \"## Old Header\", \"new_content\": \"## New Header\"}\n"+
|
||||
" Line range: {\"file\": \"config.yaml\", \"operation\": \"replace\", \"selector_line\": 5, \"selector_line_end\": 10, \"new_content\": \"key: value\"}"),
|
||||
mcp.WithString("file",
|
||||
mcp.Required(),
|
||||
mcp.Description("Path to the file to edit"),
|
||||
@@ -306,7 +340,13 @@ func (s *Server) registerTools() {
|
||||
|
||||
s.mcp.AddTool(
|
||||
mcp.NewTool("edit_apply",
|
||||
mcp.WithDescription("Apply an edit to a file. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++) with syntax validation, and text-based editing for other files (Markdown, JSON, YAML, config files, etc.)."),
|
||||
mcp.WithDescription("Apply an edit to a file. Uses AST-aware editing for code files (Go, TypeScript, JavaScript, Python, C, C++, Rust) with syntax validation, and text-based editing for other files (Markdown, JSON, YAML, config files, etc.).\n\n"+
|
||||
"Returns: \"**Edit Applied Successfully**\" followed by a unified diff of the changes made. "+
|
||||
"For code files, validates syntax before writing — returns an error if the edit would produce invalid syntax.\n\n"+
|
||||
"Examples:\n"+
|
||||
" AST mode: {\"file\": \"main.go\", \"operation\": \"replace\", \"selector_kind\": \"function_declaration\", \"selector_name\": \"Hello\", \"new_content\": \"func Hello() {\\n\\treturn\\n}\"}\n"+
|
||||
" Text mode: {\"file\": \"README.md\", \"operation\": \"replace\", \"selector_text\": \"## Old Header\", \"new_content\": \"## New Header\"}\n"+
|
||||
" Line range: {\"file\": \"config.yaml\", \"operation\": \"replace\", \"selector_line\": 5, \"selector_line_end\": 10, \"new_content\": \"key: value\"}"),
|
||||
mcp.WithString("file",
|
||||
mcp.Required(),
|
||||
mcp.Description("Path to the file to edit"),
|
||||
|
||||
Reference in New Issue
Block a user