mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-06-13 02:51:20 +00:00
fixup! fixup! fixup! Add Docker usage instructions to README
This commit is contained in:
@@ -8,7 +8,7 @@ A Go-based MCP (Model Context Protocol) server for Claude Code providing intelli
|
||||
- **AST-Aware File Reading**: Read files with symbol extraction using Tree-sitter
|
||||
- **Code Pattern Matching**: Query code using patterns with capture placeholders
|
||||
- **LSP Integration**: Go-to-definition, find references, and symbol info via language servers
|
||||
- **Safe Editing**: AST-aware file editing with syntax validation and preview
|
||||
- **Safe Editing**: AST-aware file editing with syntax validation (edit_apply)
|
||||
- **Multi-Language Support**: Go, TypeScript, JavaScript, Python, C, C++, HTML, Vue, React
|
||||
- **Token Efficient**: Optimized for minimal token usage with symbols-only mode and output limiting
|
||||
|
||||
@@ -140,13 +140,13 @@ For optimal performance, keep the most frequently used tools loaded at startup a
|
||||
"filepuff": {
|
||||
"command": "mcp-filepuff",
|
||||
"args": ["-workspace", "."],
|
||||
"alwaysAllow": ["file_read", "file_search", "edit_apply", "edit_preview"]
|
||||
"alwaysAllow": ["file_read", "file_search", "edit_apply"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This keeps `file_read`, `file_search`, `edit_apply`, and `edit_preview` immediately available while deferring less frequently used tools (`ping`, `ast_query`, `symbol_at`, `find_definition`, `find_references`).
|
||||
This keeps `file_read`, `file_search`, and `edit_apply` immediately available while deferring less frequently used tools (`ping`, `ast_query`, `symbol_at`, `find_definition`, `find_references`).
|
||||
|
||||
#### System Prompt Guidance
|
||||
|
||||
@@ -158,7 +158,7 @@ You have access to filepuff MCP tools providing:
|
||||
- Fast regex search powered by ripgrep (file_search)
|
||||
- Structural code pattern matching across 9+ languages (ast_query)
|
||||
- LSP-powered go-to-definition, find-references, and symbol info (find_definition, find_references, symbol_at)
|
||||
- AST-aware file editing with syntax validation and preview (edit_preview, edit_apply)
|
||||
- AST-aware file editing with syntax validation (edit_apply)
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -241,7 +241,7 @@ When performing file operations, prefer filepuff MCP tools over built-in equival
|
||||
| Read files | `mcp__filepuff__file_read` | Read |
|
||||
| Search content | `mcp__filepuff__file_search` | Grep |
|
||||
| AST pattern search | `mcp__filepuff__ast_query` | Grep/Glob |
|
||||
| Edit files | `mcp__filepuff__edit_preview` + `mcp__filepuff__edit_apply` | Edit |
|
||||
| Edit files | `mcp__filepuff__edit_apply` | Edit |
|
||||
| Find definitions | `mcp__filepuff__find_definition` | Grep |
|
||||
| Find references | `mcp__filepuff__find_references` | Grep |
|
||||
| Symbol info | `mcp__filepuff__symbol_at` | - |
|
||||
@@ -399,8 +399,8 @@ Find all references to the symbol at a specific position.
|
||||
|
||||
---
|
||||
|
||||
### `edit_preview`
|
||||
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.).
|
||||
### `edit_apply`
|
||||
Apply an edit to a file. Uses AST-aware editing for code files with syntax validation, and text-based editing for other files.
|
||||
|
||||
**Parameters**:
|
||||
- `file` (required): Path to the file to edit
|
||||
@@ -420,13 +420,6 @@ Preview an edit without applying it. Uses AST-aware editing for code files (Go,
|
||||
- `selector_text`: Exact text to match (must be unique or use selector_index)
|
||||
- `selector_pattern`: Regex pattern to match
|
||||
|
||||
---
|
||||
|
||||
### `edit_apply`
|
||||
Apply an edit to a file. Uses AST-aware editing for code files with syntax validation, and text-based editing for other files.
|
||||
|
||||
**Parameters**: Same as `edit_preview`
|
||||
|
||||
**Example (AST mode - Go file)**:
|
||||
```json
|
||||
{
|
||||
@@ -544,8 +537,7 @@ make clean
|
||||
│ MCP Server │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Tools: file_search, file_read, ast_query, symbol_at, │
|
||||
│ find_definition, find_references, │
|
||||
│ edit_preview, edit_apply, ping │
|
||||
│ find_definition, find_references, edit_apply, ping │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Core Engines │
|
||||
├───────────┬─────────────┬────────────┬─────────────────┤
|
||||
@@ -746,7 +738,7 @@ flowchart TB
|
||||
C -->|Read| E[file_read]
|
||||
C -->|Query| F[ast_query]
|
||||
C -->|LSP| G[symbol_at<br/>find_definition<br/>find_references]
|
||||
C -->|Edit| H[edit_preview<br/>edit_apply]
|
||||
C -->|Edit| H[edit_apply]
|
||||
end
|
||||
|
||||
subgraph "Core Engines"
|
||||
@@ -819,7 +811,6 @@ The edit engine validates syntax before and after edits.
|
||||
|
||||
**Solution**:
|
||||
- Ensure `new_content` is syntactically valid for the target language
|
||||
- Use `edit_preview` first to see the proposed changes
|
||||
- Check that the selector matches exactly one node
|
||||
|
||||
#### Timeout Errors
|
||||
|
||||
@@ -175,34 +175,6 @@ func AllTools() []Tool {
|
||||
"Results grouped by file",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "edit_preview",
|
||||
Description: "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.).",
|
||||
Category: "Edit Operations",
|
||||
ReadOnly: true,
|
||||
Parameters: []ToolParameter{
|
||||
{Name: "file", Type: "string", Required: true, Description: "Path to the file to edit"},
|
||||
{Name: "operation", Type: "string", Required: true, Description: "Edit operation: replace, insert_before, insert_after, delete"},
|
||||
{Name: "new_content", Type: "string", Required: false, Description: "New content (required for replace/insert operations)"},
|
||||
{Name: "selector_kind", Type: "string", Required: false, Description: "AST node type to match (e.g., function_declaration, class_declaration). For code files only."},
|
||||
{Name: "selector_name", Type: "string", Required: false, Description: "Name of the symbol to match. For code files only."},
|
||||
{Name: "selector_line", Type: "number", Required: false, Description: "Line number (1-indexed). For AST mode: narrows search. For text mode: start of line range."},
|
||||
{Name: "selector_index", Type: "number", Required: false, Description: "Index of the match to use if multiple matches found (default: 0)"},
|
||||
{Name: "selector_line_end", Type: "number", Required: false, Description: "End line number for range selection (text mode). Used with selector_line."},
|
||||
{Name: "selector_text", Type: "string", Required: false, Description: "Exact text to match (text mode). Must be unique or use selector_index."},
|
||||
{Name: "selector_pattern", Type: "string", Required: false, Description: "Regex pattern to match (text mode). Must be unique or use selector_index."},
|
||||
},
|
||||
Examples: []string{
|
||||
`{"file": "server.go", "operation": "replace", "selector_kind": "function_declaration", "selector_name": "Hello", "new_content": "func Hello() {\\n\\tprintln(\\"New Hello\\")\\n}"}`,
|
||||
`{"file": "README.md", "operation": "replace", "selector_text": "## Installation", "new_content": "## Getting Started"}`,
|
||||
`{"file": "package.json", "operation": "replace", "selector_pattern": "\\"version\\":\\\\s*\\"[^\\"]+\\"", "new_content": "\\"version\\": \\"2.0.0\\""}`,
|
||||
},
|
||||
Notes: []string{
|
||||
"Returns a diff showing proposed changes",
|
||||
"Does not modify the file",
|
||||
"Use to validate changes before applying",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "edit_apply",
|
||||
Description: "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.).",
|
||||
|
||||
+4
-61
@@ -1,6 +1,6 @@
|
||||
# MCP Filepuff API Reference
|
||||
|
||||
> Auto-generated on 2026-01-28
|
||||
> Auto-generated on 2026-03-12
|
||||
|
||||
This document provides detailed API documentation for all MCP tools available in filepuff.
|
||||
|
||||
@@ -24,7 +24,6 @@ This document provides detailed API documentation for all MCP tools available in
|
||||
- [`find_references`](#find_references)
|
||||
|
||||
### Edit Operations
|
||||
- [`edit_preview`](#edit_preview)
|
||||
- [`edit_apply`](#edit_apply)
|
||||
|
||||
---
|
||||
@@ -47,10 +46,9 @@ Health check - returns pong to verify the server is running
|
||||
{"tool": "ping"}
|
||||
```
|
||||
|
||||
**Returns:** `"pong"` text string.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Returns: "pong"
|
||||
- Use to verify server connectivity
|
||||
|
||||
---
|
||||
@@ -85,8 +83,6 @@ Search for text patterns in files using ripgrep. Supports regex patterns, file t
|
||||
{"pattern": "TODO", "ignore_case": true, "context_lines": 3}
|
||||
```
|
||||
|
||||
**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 `" │"`.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Requires ripgrep (rg) to be installed
|
||||
@@ -131,8 +127,6 @@ Read a file's contents with optional line range and AST symbol summary
|
||||
{"path": "large_file.go", "max_lines": 100}
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- symbols_only mode reduces token usage by ~90-98%
|
||||
@@ -175,8 +169,6 @@ Search for AST patterns in code files. Use code patterns with $VAR placeholders
|
||||
{"pattern": "function $NAME($PROPS) { $$$BODY }", "language": "javascript", "name_matches": "^[A-Z]"}
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- $NAME captures identifiers
|
||||
@@ -208,8 +200,6 @@ Get information about the symbol at a specific position in a file. Returns type,
|
||||
{"file": "server.go", "line": 25, "column": 10}
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Requires LSP server for full type information
|
||||
@@ -237,11 +227,10 @@ Find the definition of the symbol at a specific position. Uses LSP to locate whe
|
||||
{"file": "server.go", "line": 42, "column": 15}
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Requires language server for the file type
|
||||
- Returns file path, line, and column of definition
|
||||
- Shows code preview at definition location
|
||||
|
||||
---
|
||||
@@ -271,8 +260,6 @@ Find all references to the symbol at a specific position. Uses LSP to locate all
|
||||
{"file": "server.go", "line": 42, "column": 15, "include_declaration": false}
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Requires language server for the file type
|
||||
@@ -282,49 +269,6 @@ Find all references to the symbol at a specific position. Uses LSP to locate all
|
||||
|
||||
### Edit Operations
|
||||
|
||||
#### `edit_preview`
|
||||
|
||||
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.).
|
||||
|
||||
🔒 **Read-only**: This tool does not modify files.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `file` | `string` | **Yes** | Path to the file to edit |
|
||||
| `operation` | `string` | **Yes** | Edit operation: replace, insert_before, insert_after, delete |
|
||||
| `new_content` | `string` | No | New content (required for replace/insert operations) |
|
||||
| `selector_kind` | `string` | No | AST node type to match (e.g., function_declaration, class_declaration). For code files only. |
|
||||
| `selector_name` | `string` | No | Name of the symbol to match. For code files only. |
|
||||
| `selector_line` | `number` | No | Line number (1-indexed). For AST mode: narrows search. For text mode: start of line range. |
|
||||
| `selector_index` | `number` | No | Index of the match to use if multiple matches found (default: 0) |
|
||||
| `selector_line_end` | `number` | No | End line number for range selection (text mode). Used with selector_line. |
|
||||
| `selector_text` | `string` | No | Exact text to match (text mode). Must be unique or use selector_index. |
|
||||
| `selector_pattern` | `string` | No | Regex pattern to match (text mode). Must be unique or use selector_index. |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```json
|
||||
{"file": "server.go", "operation": "replace", "selector_kind": "function_declaration", "selector_name": "Hello", "new_content": "func Hello() {\\n\\tprintln(\\"New Hello\\")\\n}"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"file": "README.md", "operation": "replace", "selector_text": "## Installation", "new_content": "## Getting Started"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"file": "package.json", "operation": "replace", "selector_pattern": "\\"version\\":\\\\s*\\"[^\\"]+\\"", "new_content": "\\"version\\": \\"2.0.0\\""}
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Use to validate changes before applying
|
||||
|
||||
---
|
||||
|
||||
#### `edit_apply`
|
||||
|
||||
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.).
|
||||
@@ -356,10 +300,9 @@ Apply an edit to a file. Uses AST-aware editing for code files (Go, TypeScript,
|
||||
{"file": "config.yaml", "operation": "replace", "selector_line": 5, "selector_line_end": 10, "new_content": "database:\\n host: production.db.example.com\\n port: 5432"}
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- For code files: validates syntax before and after edit
|
||||
- Preserves file permissions
|
||||
- Uses atomic writes for safety
|
||||
- File locking prevents concurrent edits
|
||||
|
||||
+3
-5
@@ -451,8 +451,7 @@ Review the edit request and ensure all required fields are provided.
|
||||
|
||||
**Remediation**:
|
||||
1. Validate `new_content` syntax independently
|
||||
2. Use `edit_preview` to see the proposed changes
|
||||
3. Ensure the edit maintains valid syntax
|
||||
2. Ensure the edit maintains valid syntax
|
||||
|
||||
---
|
||||
|
||||
@@ -622,9 +621,8 @@ mcp-filepuff -workspace /path/to/workspace
|
||||
**Symptoms**: Edit operation rejected with syntax error
|
||||
|
||||
**Solution**:
|
||||
1. Use `edit_preview` first to see proposed changes
|
||||
2. Validate `new_content` is syntactically correct
|
||||
3. Check that surrounding code structure is maintained
|
||||
1. Validate `new_content` is syntactically correct
|
||||
2. Check that surrounding code structure is maintained
|
||||
|
||||
### Scenario 3: Search Returns Too Many Results
|
||||
|
||||
|
||||
@@ -22,18 +22,13 @@ func unescapeNewlines(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// handleEditPreview handles the edit_preview tool.
|
||||
func (s *Server) handleEditPreview(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return s.handleEdit(ctx, request, false)
|
||||
}
|
||||
|
||||
// handleEditApply handles the edit_apply tool.
|
||||
func (s *Server) handleEditApply(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return s.handleEdit(ctx, request, true)
|
||||
return s.handleEdit(ctx, request)
|
||||
}
|
||||
|
||||
// handleEdit is the shared implementation for edit_preview and edit_apply.
|
||||
func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest, apply bool) (*mcp.CallToolResult, error) {
|
||||
// handleEdit performs an edit operation (always applies changes).
|
||||
func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
file, err := request.RequireString("file")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("file is required"), nil
|
||||
@@ -85,13 +80,8 @@ func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest, ap
|
||||
},
|
||||
}
|
||||
|
||||
// Perform edit
|
||||
var result *edit.EditResult
|
||||
if apply {
|
||||
result, err = s.editor.Apply(ctx, astEdit)
|
||||
} else {
|
||||
result, err = s.editor.Preview(ctx, astEdit)
|
||||
}
|
||||
// Perform edit (always apply)
|
||||
result, err := s.editor.Apply(ctx, astEdit)
|
||||
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("edit failed: %s", errors.SanitizeError(err))), nil
|
||||
@@ -103,11 +93,7 @@ func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest, ap
|
||||
|
||||
// Format output
|
||||
var output strings.Builder
|
||||
if apply {
|
||||
output.WriteString("**Edit Applied Successfully**\n\n")
|
||||
} else {
|
||||
output.WriteString("**Edit Preview**\n\n")
|
||||
}
|
||||
output.WriteString("**Edit Applied Successfully**\n\n")
|
||||
|
||||
output.WriteString("Diff:\n```diff\n")
|
||||
output.WriteString(result.Diff)
|
||||
|
||||
@@ -96,35 +96,17 @@ func Hello() string {
|
||||
}
|
||||
})
|
||||
|
||||
// Test 4: Edit preview and apply
|
||||
// Test 4: Edit workflow
|
||||
t.Run("edit_workflow", func(t *testing.T) {
|
||||
// Preview edit
|
||||
previewReq := mcp.CallToolRequest{}
|
||||
previewReq.Params.Arguments = map[string]interface{}{
|
||||
// Apply edit directly (preview removed to avoid confusing LLMs)
|
||||
applyReq := mcp.CallToolRequest{}
|
||||
applyReq.Params.Arguments = map[string]interface{}{
|
||||
"file": testFile,
|
||||
"operation": "replace",
|
||||
"selector_kind": "function_declaration",
|
||||
"selector_name": "Hello",
|
||||
"new_content": "func Hello() string {\n\treturn \"goodbye\"\n}",
|
||||
}
|
||||
previewResult, err := srv.handleEditPreview(ctx, previewReq)
|
||||
if err != nil {
|
||||
t.Errorf("handleEditPreview() error = %v", err)
|
||||
}
|
||||
if previewResult == nil {
|
||||
t.Fatal("handleEditPreview() returned nil")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify file unchanged after preview
|
||||
originalContent, _ := os.ReadFile(testFile)
|
||||
if string(originalContent) != content {
|
||||
t.Error("preview should not modify file")
|
||||
}
|
||||
|
||||
// Apply edit
|
||||
applyReq := mcp.CallToolRequest{}
|
||||
applyReq.Params.Arguments = previewReq.Params.Arguments
|
||||
applyResult, err := srv.handleEditApply(ctx, applyReq)
|
||||
if err != nil {
|
||||
t.Errorf("handleEditApply() error = %v", err)
|
||||
@@ -267,10 +249,8 @@ func TestMCPErrorResponses(t *testing.T) {
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "edit_missing_file",
|
||||
handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return srv.handleEdit(ctx, req, false)
|
||||
},
|
||||
name: "edit_missing_file",
|
||||
handler: srv.handleEditApply,
|
||||
setupReq: func() mcp.CallToolRequest {
|
||||
req := mcp.CallToolRequest{}
|
||||
req.Params.Arguments = map[string]interface{}{
|
||||
@@ -281,10 +261,8 @@ func TestMCPErrorResponses(t *testing.T) {
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "edit_missing_operation",
|
||||
handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return srv.handleEdit(ctx, req, false)
|
||||
},
|
||||
name: "edit_missing_operation",
|
||||
handler: srv.handleEditApply,
|
||||
setupReq: func() mcp.CallToolRequest {
|
||||
req := mcp.CallToolRequest{}
|
||||
req.Params.Arguments = map[string]interface{}{
|
||||
@@ -376,7 +354,7 @@ func Add(a, b int) int {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Preview edit
|
||||
// 3. Edit (no preview)
|
||||
editReq := mcp.CallToolRequest{}
|
||||
editReq.Params.Arguments = map[string]interface{}{
|
||||
"file": testFile,
|
||||
@@ -385,12 +363,12 @@ func Add(a, b int) int {
|
||||
"selector_name": "Add",
|
||||
"new_content": "func Add(a, b int) int {\n\treturn a + b + 1\n}",
|
||||
}
|
||||
editResult, err := srv.handleEditPreview(ctx, editReq)
|
||||
editResult, err := srv.handleEditApply(ctx, editReq)
|
||||
if err != nil {
|
||||
t.Fatalf("handleEditPreview() error = %v", err)
|
||||
t.Fatalf("handleEditApply() error = %v", err)
|
||||
}
|
||||
if editResult == nil {
|
||||
t.Fatal("handleEditPreview() returned nil")
|
||||
t.Fatal("handleEditApply() returned nil")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
@@ -290,54 +290,6 @@ 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++, 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"),
|
||||
),
|
||||
mcp.WithString("operation",
|
||||
mcp.Required(),
|
||||
mcp.Description("Edit operation: replace, insert_before, insert_after, delete"),
|
||||
),
|
||||
mcp.WithString("new_content",
|
||||
mcp.Description("New content (required for replace/insert operations)"),
|
||||
),
|
||||
// AST-mode selectors (for code files)
|
||||
mcp.WithString("selector_kind",
|
||||
mcp.Description("AST node type to match (e.g., function_declaration, class_declaration). For code files only."),
|
||||
),
|
||||
mcp.WithString("selector_name",
|
||||
mcp.Description("Name of the symbol to match. For code files only."),
|
||||
),
|
||||
// Shared selectors
|
||||
mcp.WithNumber("selector_line",
|
||||
mcp.Description("Line number (1-indexed). For AST mode: narrows search. For text mode: start of line range."),
|
||||
),
|
||||
mcp.WithNumber("selector_index",
|
||||
mcp.Description("Index of the match to use if multiple matches found (default: 0)"),
|
||||
),
|
||||
// Text-mode selectors (for non-code files or explicit text matching)
|
||||
mcp.WithNumber("selector_line_end",
|
||||
mcp.Description("End line number for range selection (text mode). Used with selector_line."),
|
||||
),
|
||||
mcp.WithString("selector_text",
|
||||
mcp.Description("Exact text to match (text mode). Must be unique or use selector_index."),
|
||||
),
|
||||
mcp.WithString("selector_pattern",
|
||||
mcp.Description("Regex pattern to match (text mode). Must be unique or use selector_index."),
|
||||
),
|
||||
),
|
||||
s.handleEditPreview,
|
||||
)
|
||||
|
||||
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++, Rust) with syntax validation, and text-based editing for other files (Markdown, JSON, YAML, config files, etc.).\n\n"+
|
||||
|
||||
@@ -283,56 +283,6 @@ func Goodbye() error {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleEditPreview(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a test file
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
content := `package main
|
||||
|
||||
func Hello() {
|
||||
println("Hello")
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(testFile, []byte(content), 0600); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
cfg := &config.Config{WorkspaceRoot: tmpDir, EnableLSP: false}
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
|
||||
|
||||
srv, err := New(cfg, logger)
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := mcp.CallToolRequest{}
|
||||
req.Params.Arguments = map[string]interface{}{
|
||||
"file": testFile,
|
||||
"operation": "replace",
|
||||
"selector_kind": "function_declaration",
|
||||
"selector_name": "Hello",
|
||||
"new_content": "func Hello() {\n\tprintln(\"Goodbye\")\n}",
|
||||
}
|
||||
|
||||
result, err := srv.handleEdit(ctx, req, false)
|
||||
if err != nil {
|
||||
t.Errorf("handleEdit(preview) error = %v", err)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("handleEdit(preview) returned nil result")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify file was NOT modified (it's just a preview)
|
||||
fileContent, _ := os.ReadFile(testFile)
|
||||
if string(fileContent) != content {
|
||||
t.Error("handleEdit(preview) should not modify the file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleEditApply(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
@@ -366,20 +316,20 @@ func Hello() {
|
||||
"new_content": "func Hello() {\n\tprintln(\"Goodbye\")\n}",
|
||||
}
|
||||
|
||||
result, err := srv.handleEdit(ctx, req, true)
|
||||
result, err := srv.handleEditApply(ctx, req)
|
||||
if err != nil {
|
||||
t.Errorf("handleEdit(apply) error = %v", err)
|
||||
t.Errorf("handleEditApply error = %v", err)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("handleEdit(apply) returned nil result")
|
||||
t.Fatal("handleEditApply returned nil result")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify file WAS modified
|
||||
fileContent, _ := os.ReadFile(testFile)
|
||||
if string(fileContent) == content {
|
||||
t.Error("handleEdit(apply) should modify the file")
|
||||
t.Error("handleEditApply should modify the file")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user