SkillAgentSearch skills...

go-surgeon

Deterministic Go code editing for LLM agents. AST-based CLI and MCP server that replaces Edit, Read, and Grep on .go files for Claude Code, Cursor, and any agent.

Install / Use

claude mcp add JLugagne -- npx -y github:JLugagne/go-surgeon

If the server publishes to npm under a different name, use that package instead — check the repo README.

About this skill
🔌

MCP Server

Model Context Protocol server

Quality Score

72/100

Supported Platforms

Claude Code
Claude Desktop
Cursor
GitHub Copilot
Gemini CLI
<div align="center">

go-surgeon

Deterministic Go code editing for LLM agents. No text patching. No broken builds.

Your agent shouldn't edit Go with Edit, Read, Grep, or Bash.

Go isn't just text, it's a tree of declarations. go-surgeon gives your agent a real AST-based toolkit — precise symbol lookup, structural edits, automatic goimports — exposed as an MCP server it uses instead of generic file tools.

One tool call per edit. Valid Go every time.

Go MCP License: MIT

Quick StartWhyMCPHighlightsSafety

</div>

The problem nobody admits

Ask your agent to update a single function in a 200-line Go file. Watch what happens:

  1. It Reads the file, finds the function, plans the edit
  2. It calls Edit with a string replacement — misses a trailing tab
  3. The patch fails. It re-reads the file. Tries again with the whole function body
  4. This time it forgets the context.Context import
  5. go build fails. It edits the import block — badly. Curly brace drift.
  6. Three turns later you have a working file and no idea what changed.

Every Go dev using an LLM agent has lived this. The problem isn't the model's reasoning — text-level patching is fundamentally wrong for a structured language. Indentation, imports, braces: these aren't content, they're grammar. And grammar breaks loudly.

The fix

update(object="func", file="internal/catalog/domain/book.go", identifier="NewBook", content="""
func NewBook(title, author string) (*Book, error) {
    return &Book{Title: title, Author: author}, nil
}
""")
✅ SUCCESS (update func): Updated NewBook in internal/catalog/domain/book.go

Located by AST identifier. Replaced by structural edit. Imports handled by goimports automatically. The agent stops counting tabs and starts shipping logic.


Why go-surgeon

1. It replaces generic file tools for Go — everywhere

The MCP server ships with instructions telling the agent: for any .go file, use these tools instead of Edit / Write / Read / Grep / Glob / Bash. No more sed on Go source. No more grep -r that misses method receivers. No more Edit that forgets imports.

2. Edits are atomic, not conversational

Every tool is a structured operation. Either it succeeds or you get a clear error like ERROR (update func): node 'Book.Validate' not found in .... No silent half-edits. No "it kind of worked".

3. Your agent never manages imports or formatting

Content is raw Go source — no package declaration, no imports, no indentation. goimports runs on every mutation. An entire category of agent mistakes, permanently eliminated.

4. Interfaces and mocks stay in sync

interface action=add and interface action=update regenerate a function-field mock atomically. The compile-time assertion (var _ Repo = (*MockRepo)(nil)) blocks drift. scaffold kind=interface_from_type pulls an interface out of an existing struct in one command.

5. Edits can be as granular as a single field or line

The unified patch tool makes scoped edits with a target selector: edit inside a function body, add/rename/retype a single struct field, add or remove a single interface method and regenerate the mock — all without re-emitting the whole declaration.

6. Type-aware references and renames across the module

find_definition, find_references, and rename_symbol resolve the target via go/packages so they only touch identifiers that bind to the same types.Object — not other symbols that happen to share a name. They also accept module= to resolve into a dependency.


When go-surgeon helps vs. when Edit is fine

Use go-surgeon for:

  • Exploring unfamiliar Go code (symbol body=true context=file gives you a function body + full file outline in one call)
  • Resolving a file:line build/stack diagnostic to a declaration (symbol file=... at_line=...)
  • Structural edits: adding/renaming struct fields, interface methods, managing imports
  • Batch edits across many functions or files in one atomic operation
  • Multi-step sessions where AST validation prevents compounding errors
  • Any edit where import management matters

Edit is fine or better when:

  • Single-line tweak in a file you already have open and know well
  • Files outside Go: .yaml, .md, .sh, Dockerfile, etc.
  • One-off prototyping where you don't need AST guarantees
  • 3-line changes where the 200–500 ms MCP overhead isn't amortized

Each go-surgeon MCP call adds ~200–500 ms overhead vs a direct Edit. The break-even is roughly 5+ structural edits, or any task requiring AST-level guarantees (import management, type-aware renames, struct/interface modifications). For a single 3-line tweak in a file you already know, Edit is faster.


Install

Linux / macOS

curl -fsSL https://raw.githubusercontent.com/JLugagne/go-surgeon/main/install.sh | sh

Installs the latest release binary to ~/.local/bin (no root required). Override with INSTALL_DIR:

INSTALL_DIR=/usr/local/bin curl -fsSL https://raw.githubusercontent.com/JLugagne/go-surgeon/main/install.sh | sh

Self-update — once installed, keep the binary current with:

go-surgeon upgrade

Homebrew / Scoop / Windows — coming soon.


Quick Start

# Verify installation
go-surgeon --version

# Run as MCP server (stdio)
go-surgeon mcp

# Or use the CLI directly
go-surgeon graph
go-surgeon symbol BookHandler.Handle --body

Configure your MCP client (example for Claude Code / Cursor):

{
  "mcpServers": {
    "go-surgeon": {
      "command": "go-surgeon",
      "args": ["mcp"]
    }
  }
}

The server auto-advertises instructions telling the agent to use go-surgeon tools for every operation on .go files — no prompt engineering required on your side.


🔌 MCP Server

go-surgeon mcp

Tools over stdio, grouped by purpose:

| Tools | Purpose | |---|---| | overview, symbol | Explore packages and look up symbols — replaces Read / Grep / Glob. symbol also resolves a file:at_line diagnostic directly to its declaration. | | find_definition, find_references, rename_symbol | Type-aware cross-package symbol lookup and rename — powered by go/packages. All three accept module= to resolve into a dependency. | | create, update, delete | Add, replace, or remove a file, function, or struct by AST identifier — replaces Edit / Write. object="auto" infers from the content; delete object="file" removes the file from disk. | | patch | Unified surgical editor — one tool, five targets (function, struct, interface, file, decl). Scoped in-place edits without re-emitting whole declarations. Function ops include replace, insert_before/insert_after, delete, wrap, and set_signature (rewrite params/returns without touching the body). | | patch with items: [{...}] | Apply many patch operations to many targets in a single atomic call — useful when one refactor touches dozens of functions or structs. | | insert_call | Insert a single statement into a function body (before-return, end-of-body, or after:<marker>); auto-lifts out of nested scopes | | interface (action=add\|update\|delete) | Manage interfaces with auto-generated (and auto-deleted) mocks | | scaffold (kind=impl_from_interface\|mock_from_interface\|interface_from_type) | Generate stubs, standalone mocks, and extract interfaces from structs | | test, tag | Generate test skeletons and struct field tags | | build_check, test_run | Compile-verify and run tests in-loop. Both accept affected_by=<file> to narrow to the file's reverse-dep closure; test_run also accepts symbols=["pkg.MyFunc"] to auto-resolve owning packages and build a -run filter, plus verbosity=summary for compact output on large suites. | | execute_plan | Run up to 15 edits atomically from a YAML/JSON plan — supports every action type including every patch target | | batch_query | Run up to 10 read-only queries (symbol / overview / find_definition / find_references) in one round-trip | | (none — discovery is CLI-only) | Run go-surgeon discovery for the grouped catalog, go-surgeon discovery <tool> for detail. Run go-surgeon skill --out .claude/skills/go-surgeon/ to install go-surgeon as a Claude skill. |

Every write tool supports preview=true to return a unified diff without writing. Errors carry a structured {code, message} in StructuredContent so agents can retry on CONFLICT, NOT_FOUND, PATCH_FAILED, PATCH_REPLACE_NOT_APPLIED, PATCH_DROPPED_CONTENT, PATCH_PRODUCES_INVALID_GO, etc. without string-matching.

See USAGE.md for the full parameter reference.


Highlighted features

symbol body=true context=file — explore a 1000-line file in 4 calls

symbol with body=true and context="file" returns the full body of the target function and an outline of every sibling declaration in the same file — in one call.

symbol(query="BookHandler.Create", body=true, context="file")

This replaces what used to be: read the file, grep for the function, read again with offset, grep for related symbols. Measured on a 1000-line file: 4 calls instead of 15.

Use this as your first move when entering any unfamiliar file — you get the implementation you care about plus a map of everything around it.

symbol file=… at_line=… — resolve a build error to a declaration

When build_check or a stack trace gives you internal/foo/bar.go:142, you don't need to look up the symbol name. Pass the line and symbol returns the outermost named declaration that spans it:

symbol(file="internal/foo/bar.go", at_line=142, body=true)

Mutually exclusive with query/pattern. Saves the "grep for the function around this line" step entirely.

execute_plan — atomic multi-step refactors

Refactoring a feature often means changing a struct, updating three methods, regenerating a mock, and wiring a new call. Doing this as 8 separate Edit operations is where agents drift the most.

actions:
  - action: update_struct
    file: internal/catalog/domain/book.go
    identifier: Book
    content: |
      type Book struct {
          ID        BookID
          Title     string
          Status    BookStatus
          CreatedAt time.Time
      }
  - action: update_func
    file: internal/catalog/domain/book.go
    identifier: NewBook
    content: |
      func NewBook(title string, status BookStatus) (*Book, error) {
          return &Book{ID: NewBookID(), Title: title, Status: status}, nil
      }
  - action: update_interface
    file: internal/catalog/domain/repositories/book/book.go
    identifier: BookRepository
    mock_file: internal/catalog/domain/repositories/book/booktest/mock.go
    mock_name: MockBookRepository
    content: |
      type BookRepository interface {
          Create(ctx context.Context, book domain.Book) error
          UpdateStatus(ctx context.Context, id BookID, status BookStatus) error
      }
  - action: insert_call
    file: internal/catalog/app/init.go
    identifier: NewApp
    content: handlers.RegisterBookStatusHandler(mux, repo)
    position: before-return

One tool call. One success or one rollback. No drift between steps. Every individual patch_* action type is also accepted, so atomic multi-step plans can mix in-place patches with whole-declaration replacements.

patch — one tool, five targets, surgical edits

Classic AST edit tools make you resend the whole declaration to change one line. The unified patch tool applies scoped, text-match-or-line-target

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars3
CategoryAI
Updated1mo ago
Forks1

Languages

Go

Security Score

92/100

Audited on Jul 28, 2026

1 low