Skip to main content
Glama

AST Code Editor MCP Server

A robust, language-agnostic Model Context Protocol (MCP) server that provides AI coding agents with the ability to edit files surgically via Abstract Syntax Trees (AST) instead of relying on token-heavy, brittle search-and-replace or diff operations.

Why AST Edits?

Every non-AST edit format — search/replace, unified diff, whole-file rewrite — requires the model to copy text perfectly from a file it saw once. One whitespace mismatch on a 4,000-line file and the edit fails. AST edits sidestep the problem entirely: the model names the target (e.g., LRUCache.get) and provides the new code; the parser figures out where it lives.

Geometric AGI benchmarked every major format across 4 models and 29 edit tasks. AST edits were the only format to hit 100% correctness on 3 of 4 models, with 18x fewer output tokens than whole-file rewrite, and zero format failures. Full methodology and results: AST Edits: The Code Editing Format Nobody Uses.

Credits

This MCP server was inspired by research from Jack Foxabbott and the team at Geometric AGI. Their full findings, benchmark suite, and data are available here:

Related MCP server: code-index-mcp

Estimated Token Savings

Per-edit output token savings versus other common edit formats:

Edit size

File size

vs whole-file rewrite

vs unified diff

vs search/replace

1-line tweak

100 LoC

3–5x

~1.5x

~1.5x

Function body rewrite

500 LoC

8–12x

2–3x

2–3x

Function body rewrite

4,000 LoC

15–20x

3–5x

3–5x

Add 2 lines to a function

any size

~20x (via insert_in_body(at="top") / insert_in_body(at="bottom"))

5–10x

3–5x

Per-read input token savings versus reading the entire file:

Read task

File size

AST reader tool

vs full file read

One function's source

500 LoC

read_symbol

~20x fewer tokens

One function's source

2,000 LoC

read_symbol

~50-100x fewer tokens

Class API (10 methods, no bodies)

500 LoC

read_symbol(depth="interface")

~10x fewer tokens

Import block only

any size

read_imports

~20-50x fewer tokens

Structural overview (names + line numbers)

any size

list_symbols

~15-30x fewer tokens

One function's signature

any size

read_symbol(depth="signature")

~50-200x fewer tokens

For daily agent users, a realistic 40-60% reduction in total tokens per session is achievable, on average (combining output savings from surgical edits with input savings from targeted reads).

The savings come from four compounding effects:

  • Output tokens: Using insert_in_body (at="top" / at="bottom", or anchored with after= / before=) for small additions instead of rewriting whole function bodies

  • Input tokens: Using read_symbol (depth="full" / "interface") / read_imports to read only what's needed instead of entire files (~10-20x fewer input tokens per read)

  • Discovery: list_symbols / read_symbol(depth="signature") instead of reading whole files

  • Zero format failures: AST edits never fail on whitespace drift, eliminating retry loops that plague other formats.

Supported Languages & Capabilities

Language

Extensions

Structural edits

Comments

Docstrings

Notes

Python

.py

✅

✅ #

✅ function/class

Decorators preserved. Nested classes (decorated ones too) are listed with their members (class A.B, method A.B.m). Module-level dict/list literals editable via add_key / replace_value / delete_key / append_to_array (nested dicts addressed by key path, e.g. CONFIG."db"); comments in a list are not elements, so append_to_array puts the comma before them. Module-level variables (X = 1, Y: int = 2) are listed as variable and class attributes as field (A.x), at their first assignment; replace_value replaces their value. @overload stubs directly before their function are one symbol with it, listed once at the first stub. A name defined again (a redefinition, a @property setter) is listed again, and the name targets the first.

JavaScript

.js, .jsx, .mjs, .cjs

✅

✅ // + /* ... */

—

Exported declarations (export function, export class, export default function c) are listed and targeted by their own names, an anonymous export default function () {} / export default class {} as default; the export is part of the symbol, so read_symbol shows it and replace_function content should keep it. Generator functions (function* g()) and each name of a const/let/var bound to an arrow function or function expression (const f = (x) => {...}, exported or not) are listed and targeted as functions: read_symbol, replace_function, delete_symbol, the comment tools, insert_sibling and the parameter tools work on them, and the body tools on those with a { } body (one whose body is an expression, (x) => x * 2, is refused: rewrite it with replace_function). Their signature is const f = (x) =>, and replace_signature replaces from the const when the new signature starts with it, else only (x) =>; a lone unparenthesized parameter gets parentheses from the parameter tools (x => ... becomes (x, y) => ..., or () => ... when it is removed). Module-level const/let/var object literals (exported or not) editable via add_key / replace_value / delete_key; nested objects addressed by dotted path (CONFIG.server). Other data is listed too: each name of a const as constant and of a let/var as variable, and class fields as field (A.x, A.#y, static, decorated and declare ones), which the whole-symbol tools and replace_value (its value alone) take; a method or function wins over a class field or var of its name (static run = false; beside run() {}), and a setter beside its getter is not listed (the name targets the getter). Computed and quoted names are targets as listed (E.[Symbol.iterator], E.'a.b'). A comment after a symbol on its last line (function f() {} // f) is no part of it, as in the other languages. CommonJS require statements count as imports for add_import.

TypeScript

.ts, .tsx

✅

✅ // + /* ... */

—

Exports, generators and functions bound to names as for JavaScript. Abstract classes (abstract class A, exported or not) are classes for every tool, and their abstract methods are listed (an overloaded one once) and targeted (A.m) like the others, replace_signature included (the final ; stays); enums, type aliases and interfaces are listed as enum, type and interface and can be read, replaced, deleted, commented and used as insert_sibling targets. Interfaces are treated as classes for add_method / add_field, which refuse an enum or type alias (rewrite those with replace_function); a type alias has no depth="signature". A type alias or interface that shares its name with a value in one scope, as in zod's export const User = z.object(...) with export type User = z.infer<typeof User> (or an interface and a function), is targeted with its keyword, type User or interface Props (listed as type type User; with a dotted path the keyword goes first, type N.User), and the plain name targets the value. Overload signatures directly before their implementation (through export and declare, comments and the implementation's decorators between them; a run of signatures alone too) are one symbol with it, listed once at the first signature: read_symbol, replace_function and delete_symbol take them all, insert_sibling(position="before") and edit_leading_comment go above the first, and the signature depths, replace_signature, the parameter tools and the body tools work on the implementation. Namespaces (namespace N {}, module N {}) are listed as namespace with their classes and functions (N.C, N.f); one opened again, or merged with the class, function or enum of its name, is one container: the name targets the class, function or enum, its members and the namespace's are found and listed under it, and none of this is ambiguous. declare declarations are listed like plain ones (read_symbol shows the declare), and an abstract method reads with its ;. A namespace's interface ends with its }, without a comment after it. .tsx files are parsed with the TSX grammar, so JSX is valid there. Object literals editable as for JavaScript, including { ... } as const and satisfies T.

C

.c, .h

✅

✅ // + /* ... */ (single + multi-line)

—

.h defaults to C — use .hpp/.hxx/.hh for C++ headers (adding C++-only syntax to a .h file that parses as C is refused by the save-time syntax check). A struct, union or enum ends with the ; after its }: read_symbol shows it, replace_function content should end with };, and delete_symbol and insert_sibling(position="after") take it, also when it is on a line of its own. Listed as struct, union, enum, function, macro (#define, function-like ones over several lines too), type (typedefs; an anonymous typedef struct {...} P; lists its fields as P.x and takes add_field), field, variable and constant (const but not a pointer), including what #if/#ifdef blocks (include guards too) and extern "C" hold; a struct defined in a declaration or typedef is shown whole by read_symbol (struct S {...} s;, typedef struct N {...} N;). A definition wins over its forward declaration (struct S;), prototype or extern declaration in either order, and is what list and target; a prototype alone is a bodiless function (replace_signature and the parameter tools work on it). A definition outside #if/#ifdef blocks wins over one inside (#if 0), and of alternative definitions in #ifdef/#else branches the first is listed and targeted. A struct, union or enum that shares its name with a function, variable or typedef is targeted with its keyword (struct stat, listed as struct struct stat), and the plain name targets the other symbol (typedef struct N {...} N; stays one symbol, and a typedef of a struct declared apart, typedef struct Node Node;, is Node); add_method and add_field look only at containers, so stat is the struct there. Without overloading, f(int) only checks the parameter types. replace_value replaces the value of a global (int g = 1;, int a[] = {1}) or a macro's text (#define N 3), and gives one to a declaration without one (a C struct's field cannot have one and is refused); for a name that a macro shares with a function, the name is the macro and the function is name(types). A type that is only used (struct S *p;) and variables inside function bodies are not targets.

C++

.cpp, .cc, .cxx, .hpp, .hxx, .hh

✅

✅ // + /* ... */ (single + multi-line)

—

Supports class, struct, union, enum, namespace; Class::method qualified names resolve correctly. Classes end with their ; as in C. A template's template <...> header is part of its symbol, as decorators are: read_symbol shows it, replace_function content should include it, delete_symbol removes it, a leading comment and insert_sibling(position="before") go above it, and replace_signature keeps it unless the new signature starts with template. Template functions, classes and member templates are listed at the line of their header, an explicit specialization (template <> int id<int>(int)) under the name it specializes, and a definition outside its class (int A::f(), template <typename T> T Box<T>::get()) as a function under its qualified name (A::f, Box::get, A::A; the last name alone, or a shorter tail, still finds it when nothing else in the scope has that name). Overloads take their parameter types, written as declared without names, defaults or attributes, spacing free (A.m(int), A.m(const std::string &, int), A::A(int), A.operator()(int)), and method qualifiers where only those differ (A.get(int) const); spellings of one type match (unsigned and unsigned int, char const * and const char *, int[] and int *, a const on the parameter itself, struct S * and S *), so a prototype and a definition that spell it differently are one function. The bare name of an overloaded function, and a name several qualified names end with, are refused with the targets to use. A class or namespace declared with its scope (struct A::B {...}, namespace a::b {}) is A::B, and class X targets a class that shares its name with another symbol. Everything listed for C (constexpr variables as constant too), plus class, namespace (members as ns.f; a namespace opened again in its scope is listed once with the members of all its blocks, and a target finds a definition in any of them), nested classes (class A.B), using aliases as type, static members, methods declared in their class, and structured bindings (auto [x, y] = p;). Destructors and operators are named as written without spaces, ~A, operator==, operator(), operator bool, and targeted as A.~A or A.operator== (any spacing matches); find_references finds them.

Ruby

.rb

✅

✅ #

—

Classes, modules, instance methods, singleton_method (class methods via def self.foo). require/require_relative/load/autoload recognized as imports. A statement that opens a heredoc (X = <<~EOS) ends with the heredoc's body, for read_symbol and the whole-symbol edits. An endless method (def f = x) has no body for the body tools. Classes and modules nested in modules are listed with their members, private def m and class << self methods too (read_symbol shows the private). Embedded documents (=begin ... =end at column 0) stay at column 0 when content is re-indented, and an edit that would leave a =begin indented is refused; one directly above a method is its leading comment. A Sorbet signature directly above a method (sig { ... }, sig do ... end) is part of it, as a decorator is, and the signature depths start at its def. Constants are listed as constant and locals as variable. A class reopened in one scope is listed for each block, and the name targets the first; a dotted target finds a member in any of them. Interfaces show every method as its signature plus ..., empty and endless ones too.

Go

.go

✅

✅ // + /* ... */

—

struct, interface, functions, methods. Methods are addressed by receiver: Cache.Get resolves to the top-level func (c *Cache) Get(...). Grouped import (...) blocks supported. Type aliases (type T = int) are listed as type, and every name of a var/const as variable/constant. read_symbol shows the type, var or const of a declaration of one spec (a spec in a type (...) group alone) and a closure's key (RunE: func() error {...}). remove_parameter removes one name of a, b int, keeping the others' comments. A var x = func(...) {...} is a function for the parameter, body and signature tools (listed as variable; one of several values, var p, q = func() {}, func() {}, has its own signature). replace_value on a constant without a value that the constants below it repeat is refused, as a value would change theirs.

Java

.java

✅

✅ // + /* ... */ + Javadoc /** ... */

—

class, interface, enum, record; methods, constructors, fields. Annotations (@Override, @Deprecated) travel with their method on edits (wrapped in the modifiers node); replace_signature keeps them unless the new signature includes its own, and also works on interface and abstract methods, which have no body. Enum methods (nested in enum_body_declarations) discovered via BFS in list_symbols. Fields and interface constants are listed as field A.x and targetable; a field that shares its name with a method is the name's target, and the method is A.x(). Overloaded methods and constructors take their parameter types (A.m(int), A.m(String...), A.join(List<String>, String), A.A()), without modifiers and annotations; the bare name is refused with the targets to use. Nested classes, enums, interfaces and records are listed with their members, and a nested class whose name a field takes is left out, its members still listed (A.B.m).

JSON

.json

✅ (keys, values, arrays)

— (no comment syntax)

—

Numbers with a + in the exponent (6.02E+23), which the grammar cannot parse on its own, are read as whole values, so replace_value, delete_key and add_key beside them work as for any other number.

YAML

.yml, .yaml

✅ (keys, values, sequences)

✅ #

—

Block and flow sequences supported. Keys inside flow mappings are addressable by path (a: {x: 1} is a.x, nested ones too), so read_symbol, replace_value and delete_key work on them and they stay inline. add_key also adds to flow mappings ({a: 1}, {}) and to a key with no value (parent:). A key with no value can be read (as an empty string), commented, and given a value with replace_value (a: 3; content of several lines, or a block sequence or mapping, goes below the key one level deeper); an explicit key with no value (? a) gets its value on a : 3 line below it, in the column of the ?. In a multi-document file a path is looked up in each document in turn (add_key with parent_target="" adds to the first document with content). Quoted keys are matched by the name they spell, so 'it''s' is target it's and "caf\u00e9" is café. Removing the only item of a block sequence leaves key: [].

TOML

.toml

✅ (keys, values, arrays, tables)

✅ #

—

[table] headers (dotted and quoted ones too, [tool.uv], [a."b.c"]) addressable by their names for comment tools and add_key. Keys inside inline tables (t = { a = 1 } is t.a, nested ones too) and dotted keys (x.y = 1 is x.y) are addressable by path. add_key also adds to inline tables (t = { a = 1 }, t = {}).

Cross-cutting features:

  • Decorated functions and classes (Python and JS/TS @decorator, Java annotations): decorators are part of the symbol. They are preserved on body edits; read_symbol, replace_function and delete_symbol include them; a leading comment belongs above the first of them (edit_leading_comment puts it there and finds an existing one there), and insert_sibling(position="before") goes above them and that comment, so the comment stays on the symbol it documents. This holds for TypeScript methods too, whose decorators the grammar places beside the method in the class body, for decorated Python classes, for a C++ template's template <...> header and for a Ruby Sorbet sig. replace_signature keeps them as written unless the new signature includes its own, which then replace them.

  • Layout-aware add_method / add_field: the new member is indented like the class's existing members (falling back to the file's indent unit, then the language default: 4 spaces for Python and Java, a tab for Go, 2 spaces otherwise), keeping the content's relative indentation; in a class indented with tabs that indentation is written in tabs too (one per indentation step of the content), so a snippet written with spaces fits a tab-indented file. The lines that start inside a multi-line string literal of the content (a triple-quoted string, template literal, heredoc, raw string, C string continued with \) or a Ruby embedded document (=begin ... =end, at column 0) keep their text, here and in every tool that re-indents content (replace_function, replace_function_body, insert_in_body(at=...), insert_sibling, edit_leading_comment); a Python docstring, a Ruby <<~ heredoc and a Java text block, whose indentation the language strips, move with the code instead, every line by the same amount. A method follows the last member after one blank line, or none when the class's methods have no blank lines between them (a C++ member template counts as a method; TS interface members stay one per line) or the last member is a C++ access label (public:), and one-line content after stacked one-line members (fields) is stacked with them. In a C++ class whose last method is followed by an access label (public:, protected:, private:), the new method goes right after that method, spaced as methods are, so it gets the same access; a comment after that method on its line stays with it, a method inside an #if/#ifdef block (either branch) counts as the last method (the new one goes after the #endif), and a label moved off a method's line is indented like the class's other labels, or at the class's indentation. A C++ class with no label after its last method, or with no methods, gets the new method after its last member. Content in read_symbol's shape copied from another class (its first line without indentation, the later lines at the indentation they had there) is indented as a whole, a Python def without decorators only when it was read at the indentation of this class's members. A field goes before the first member (after a Python class docstring, and below the access label a C++ class body opens with, so it gets that section's access), with a blank line after it when that member is a method (a C++ member template counts), and a comment after the { on its line (class K { // NOLINT) stays there. In a Java enum both go after the constants, and a ; is added after them when they have none. No blank line is added before the closing brace, and a braced body on one line (class K {}, class K { a() {} }, Go struct{ A int }) is opened up over several lines, its existing members on a line of their own. A Python or Ruby class written on one line (class K: pass) is refused. Both tools look only at containers, so a name a struct or interface shares with a function (struct stat and int stat(...)) is the struct, a typedef of a struct declared apart (typedef struct Node Node;) is that struct, and a function target is refused (use insert_in_body).

  • Layout-aware add_key: in JSON objects, Python dicts, JS/TS object literals, YAML flow mappings and TOML inline tables the new entry follows the container's layout (one key per line at the existing indentation, inline objects stay inline, a trailing comma is kept, and a comment on the last entry's line stays there); an empty JS/TS or YAML {} becomes { key: value } (TOML: { key = value }). A YAML key with no value gets a nested block mapping one indentation level deeper (inside a flow mapping, an inline parent: { key: value }). YAML and TOML keys are quoted only when they need it: my key and a.b stay plain in YAML, while a:b, yes, 123 or a leading -/#/& get quotes (in the quote style most of the file uses); in TOML anything but a bare key (letters, digits, _, -) is quoted, and dots separate the parts of a dotted key, so b.c adds c under b and a name with a dot in it must be passed quoted ('"example.com"'). A JSON or YAML key passed already in quotes is used as given. A key the container already has is refused with a pointer to replace_value, for every format, naming a target that works (quoted where the key has a dot, inside flow mappings and inline tables too).

  • Layout-aware append_to_array: JSON arrays, TOML arrays, YAML flow sequences and Python lists keep their layout: an inline array stays inline ([1, 2] becomes [1, 2, 3], [] becomes [3]), an array with one element per line gets a new line at the elements' indentation (also one aligned under its [ whose ] closes the last line), a trailing comma is kept (the new element gets one too), and comments are not elements: one on the last element's line stays there, after the comma (2, # two), and a value that ends with a comment gets its comma before it (3, # new). A Python list whose [ and ] are on lines of their own with nothing between them gets the value on a line of its own, with a comma. A YAML block sequence gets a new - value item.

  • Config paths from the top level: a dotted config target is followed from the top of the file, one key per segment: a.b is key b under the top-level key a, never an a.b nested deeper (such as x.a.b), and in TOML a plain version is a top-level key, not one inside a [table] (use project.version). One path syntax covers JSON, YAML and TOML and every config tool (read_symbol, replace_value, add_key's parent_target, delete_key, append_to_array, remove_from_array and edit_leading_comment): a segment in double quotes is one key taken literally, dots included, its escapes resolved as in a JSON string ("editor.fontSize", a."b.c".d), and in TOML single quotes work too (a.'b.c'), as in a TOML dotted key, whose syntax TOML paths share. Keys compare by the names they spell, so "a.b" finds a.b: 1, 'a.b': 1 and "a.b": 1 in YAML, and [x.'y.z'] and [ x . "y.z" ] are both TOML table x."y.z". Keys inside YAML flow mappings and TOML inline tables are part of the path. Elements of arrays are not addressable by path (a.0 is a key named 0).

  • Byte-correct slicing: multi-byte characters (emoji, ═, →) handled safely in source text.

  • Import placement: add_import skips exact duplicates. A new import goes into the group it belongs with, a group being a run of imports, includes or requires with no blank line or other code between them (comments may be; a conditional of includes counts as one include, a CommonJS require as an import). The group is picked by language. Python: a group holding imports of the same top-level package, else the standard-library group for a standard-library module and the group of other modules for others, relative imports with relative imports. C/C++: <...> with <...>, "..." with "...", never a group of only the file's own header ("foo.h" in foo.cpp). Go: standard-library paths (no dot in the first path element) with standard-library paths, others with others, never with cgo's import "C". JS/TS: relative paths (./, ../) with relative paths, packages with packages, a require with requires. Java: static with static. Ruby: require_relative with require_relative. When several groups fit, the one with an import sharing the longest leading path with the new one wins (package parts, directories: from app.y import z goes with from app import x, #include <sys/types.h> with <sys/stat.h>), else the first; when none fits, the import goes after the file's last import. In a group that is already sorted it goes at its sorted place, else after the group's last import. Python sorts as isort and ruff do: module names without case, numbers in numeric order, import x lines before from x import lines in a group that orders them that way, else both together. The other languages sort by the path or module text (C/C++ includes as written, with their brackets or quotes). The import goes above the next import and the comment lines right above it, but below a comment above the group's first import, which heads the group; a comment that applies to the next line stays on its import even when the new import sorts first (@ts-ignore, @ts-expect-error, eslint or tslint disable-next-line, pylint disable-next, biome-ignore, deno-lint-ignore, prettier-ignore, NOLINTNEXTLINE, Flow suppressions, istanbul, c8 or v8 ignore next, Go //nolint and //lint:ignore, noinspection). The blank lines between groups stay as they are. Python from __future__ imports stay first: a new one goes after the existing ones, or before the file's first import, and other imports go after them; a from __future__ import that its group would place after code goes right after the module docstring, before any other statement, with a blank line on each side. Go: inside an import ( ... ) block the same rules apply to the block's blank-line groups of specs, an alias sorting by its path ("io" goes between "fmt" and "os"); a block written on one line (import ("fmt"; "os"), import ()) takes the spec inline at its sorted place; without a block, a spec given without its keyword ("fmt", f "path/filepath") is written as import "fmt"; and nothing goes between import "C" and its cgo preamble comment (the new import goes after import "C"). A file's first import goes after its preamble (a package clause, #! and encoding or magic comments, the module docstring, 'use strict' and other directives, TS /// lines, #pragma once, an include guard's #define, and a header comment set off by a blank line), with a blank line above it and one below it (two before a Python def or class).

  • Doc-comment-aware deletion: delete_symbol by default removes the contiguous leading comment block above the symbol (Godoc, Javadoc, #/// comment runs) so docs don't become orphaned. Opt out with include_leading_comments=False. The block ends at a blank line and at a comment line indented deeper than the symbol, which belongs to the code above it ( # end of f under f's last line, a commented-out YAML list item), for this and every tool that finds a leading comment: insert_sibling(position="before") goes below such a comment, and edit_leading_comment neither removes nor replaces it. Such comment lines right below a symbol's last line belong to it: delete_symbol removes them with it and insert_sibling(position="after") goes below them (read_symbol and replace_function leave them out). A Ruby =begin ... =end document directly above a symbol is its leading comment. Only real comments count, for this and every other tool that finds a leading comment: a line that starts with # or // inside a multi-line string, template literal or YAML block scalar is text. A #! line and the encoding or magic comments after it (# -*- coding: utf-8 -*-, # frozen_string_literal: true) at the top of a Python or Ruby file are never taken for a symbol's comment, by this or any other tool.

  • Layout-aware deletion: the blank lines that separated a deleted symbol go with it, so the rest keeps the file's spacing: deleting the first symbol of a file or class body leaves no blank lines at the top or after the {, deleting the last one leaves none before the closing brace (and the next top-level symbol keeps its own separation, such as Python's two blank lines), and between the two symbols that remain only the wider of the two gaps stays. Only empty lines and lines of spaces and tabs are blank: a form feed line (a Python page break) next to the symbol stays. A symbol that shares a line with other code (class A { a() {} b() {} }, x = 1; y = 2) is cut out of that line instead, with the ; or , that ends it (the last entry of a one-line literal takes the one before it: { a: 1, b: 2 } minus b is { a: 1 }) and a comment after it at the end of the line; the spaces that were before it stay between the code on both sides. replace_function likewise replaces only such a symbol's own text, and refuses content that ends with a # or // comment when code follows the symbol on its line.

  • Syntax-checked saves: before anything is written, the edited file is re-parsed with its tree-sitter grammar. An edit that would introduce a syntax error into a file that parsed cleanly is refused with the error's line and column (e.g. Edit refused: the result would have a syntax error at line 11, column 2 (missing '}')), and the file is left unchanged. Files that already had syntax errors are not guarded, so you can keep working on them. For .py files Python's own parser (that of the interpreter running the server) decides: it catches the indentation errors tree-sitter lets through and accepts newer syntax the grammar does not know (PEP 696 type parameter defaults). For .json files the json module overrules the grammar wherever it would reject valid JSON, and comments in JSONC files stay allowed; exponents written with a + (1e+2), which tree-sitter-json cannot parse, are masked before parsing, so files that use them are checked like any other. Other languages have no second parser, so valid code using syntax the grammar does not support (very new language features, macro-heavy C/C++) can be refused: if you are sure the code is valid, make that edit with your normal edit tool.

  • Line endings and byte order marks preserved: CRLF files stay CRLF and LF files stay LF, whichever tool makes the edit, and blank lines at the end of a file are kept as they were (deleting the last symbol does not leave extra ones behind); a file whose last line has no line break keeps it that way. A UTF-8 byte order mark stays at the start of the file, whatever the edit adds or removes at the top; it never shows up in tool output, and content that starts the file with one leaves a single mark. Files that are not UTF-8 (UTF-16, UTF-32, Latin-1), also UTF-16 or UTF-32 without a byte order mark and files with NUL bytes, are refused with a message saying so, and left unchanged. Write content, snippets and anchors with \n line breaks: inserted text is converted to the file's style, and a \n in a snippet or anchor matches a CRLF line break. Content is split into lines on \n (or CRLF) only, so a form feed, vertical tab, U+0085, U+2028 or U+2029 inside a string literal or comment stays where it was written. Only spaces and tabs count as indentation, so when content is re-indented a form feed at the start of a line (a Python page break, \fdef g():) or a U+2028 starting a line inside a string stays, and a form feed before the target's own def is not copied onto the new lines. In a file that mixes CRLF and LF, every line the edit does not rewrite keeps its own ending (the lines next to the edit too, though they read like lines it removed or added); a line it writes takes the ending of the lines it replaces when they agree, else of the lines on both sides when they agree, else the file's most common one.

  • Body snippets and anchors (replace_in_body, delete_in_body, insert_in_body(after=…, before=…)): a snippet matches its exact text (\n matching CRLF) where it neither starts nor ends inside a token of code, an identifier, number, keyword or operator: x = 1 is not found in max = 10, nor = 1 in a == 1, el in $el or foo in Ruby foo?; inside a comment or string (not in an interpolation's code) it may start or end between any two words, and it may end at a Python backslash continuation. Without an exact match, its lines match whole lines of the body with one indentation added to or taken from each of them, so a block copied at column 0 or deeper than in the file is found: blank lines match blank lines, spaces and tabs at line ends do not count, tabs and spaces differ, and a snippet that starts or ends in the middle of a line must match exactly. The text of a multi-line string is never shifted: a line that starts inside one is matched only as written in the file (Python docstrings, Ruby <<~ heredocs and Java text blocks, which move with the code, aside). An exact match wins, except that when every exact match is inside a string or comment, shifted matches in other places count too. The match must be unique: two or more places are refused with their line numbers (Snippet matches 2 places in body of 'f' (lines 2, 4)), and text found only inside longer tokens is refused saying so. A deletion that leaves its lines without code takes the whole lines, indentation and line break included; a comment after the deleted code on its last line stays, at the line's indentation; a Python body is never left without a statement (replace the snippet with pass). New text goes in indented as the snippet matched: moved with a shifted match's lines; after an exact one-line snippet that left out its line's indentation (or starts mid-line), its later lines given that indentation unless they are that deep already; after an exact snippet of several lines, or when already at the file's indentation there, as written; in Python, when one reading of the indentation does not parse and the other does, the other is taken. insert_in_body puts new text on lines of its own when the anchor ends its line (after) or starts it (before), indented like the anchor (new text at column 0 beside an anchor written with its indentation goes at the anchor's, and text in read_symbol's shape goes in as written), and inside the block that an after anchor's line opens (if (x) {, if x:); in the middle of a line, or where the new line would start in the text of a string or heredoc (not a docstring, <<~ heredoc or Java text block, whose lines are indented like code), it is placed byte for byte. Lines of new text that go into a string of the file keep their text.

  • Tab-indented code: every tool that writes source content over several lines (replace_function, replace_function_body, replace_in_body, insert_in_body, insert_sibling, add_top_level, add_method, add_field) writes the content's indentation steps in tabs where the file indents with tabs there: the place's indentation has a tab, or, at column 0, most of the indented lines of the code it replaces or goes beside start with one (the text of its strings and lines aligned under a bracket aside), or, with no such lines, the file is Go or mostly tab-indented. Each step of the content's own indentation becomes one tab; the text of multi-line strings, Ruby =begin documents, and the spaces that align a block comment's lines or a line continued inside a ( or [ stay as written. Config files are never converted.

Language-specific design decisions

A few tools have language-specific semantics where multiple reasonable interpretations exist. The chosen behavior is documented here for transparency:

add_field (Ruby and Go) — option (a): literal text passthrough

  • Ruby: add_field("LRUCache", " attr_accessor :capacity") inserts the literal string at the top of the class body. The tool does not auto-wrap bare names in attr_accessor — you provide the exact text you want (whether that's attr_accessor, attr_reader, @instance_var = nil in initialize, or CLASS_CONST = 42).

  • Go: add_field("Cache", "\tversion int") inserts the literal string inside the struct { ... } body. The tool does not infer types from bare names — you provide the full Go field declaration.

  • Rationale: consistent with how add_field works for other languages (Python, JS/TS, C++) where the caller provides the full source text. The alternative option (b) — auto-wrapping (e.g. attr_accessor :foo from the name foo) — would be more magical but harder to use for edge cases (typed fields, readonly fields, field with default value, etc.).

add_method (Go) — option (a): top-level sibling insertion

  • add_method("Cache", "func (c *Cache) Has(key string) bool { ... }") locates the type Cache struct { ... } declaration and inserts the new method immediately after it, at the top level (not inside the struct's braces), indented with tabs and with a blank line on each side.

  • Rationale: Go methods are lexically top-level, not nested inside their receiver type — this matches how Go code is actually written. The alternative option (b) — refusing because "Go methods aren't inside structs" — would be pedantically correct but force callers to use insert_sibling("Cache", content, position="after") instead, which loses the semantic signal that this is a method addition.

Tools Exposed

All tools require file_path to be an absolute path to an existing file.

Code editing — structural (Python, JS, TS, C, C++, Ruby, Go, Java)

Tool

Parameters

Description

replace_function

file_path, target, content

Replace a full function definition (signature + body + decorators, and a JS/TS export or C++ template <...> header): exactly what read_symbol returns, so its text can be edited and given back as is, and given back unchanged leaves the file byte for byte as it was; that shape may hold several members (a method replaced by two, a field by two fields, a method and a decorated one), each starting at the symbol's indentation once the one before has ended. Content written relative (first line at column 0, the rest indented from it) or at its file indentation is re-indented as before. When no line sits at the symbol's own indentation to tell these shapes apart, content whose later lines are all deeper than the symbol's indentation is taken as read_symbol's text and keeps them where they are (a 4-space method body for a 2-space class stays 4 spaces in); a decorator or annotation at the symbol's indentation counts only before the first line of code; at column 0 the shapes are one and content goes in as written. A comment before the symbol on its first line (/** @type {X} */ let x;) is no part of it and stays, and the content's later lines are indented as that line, or as the line where a block comment ending there ( */ void f() {) starts. A comment after the symbol on its last line stays unless the content ends with one, and so does a ; or , after it unless the content ends with it (a JS/TS class field's goes when the content is a method). A name declared together with others (int a = 1, b = 2;, struct S {...} s;) has that declaration replaced, and content that would drop the others is refused. A symbol that shares a line with other code has only its own text replaced (see Layout-aware deletion above). Refused in JSON, YAML and TOML files (use replace_value).

replace_function_body

file_path, target, content

Replace only the body of a function, preserving signature and decorators. A body written on one line with its signature or braces (function f() { return 1; }, def f(): return 1) is rebuilt over lines of its own, one level deeper than the function, as is content for an empty body ({ and } on lines of their own, and an empty Ruby method, def f over end or def h; end, or one holding only comments, which the content replaces); a comment that ends a one-line Python body stays at the end of the def's line (def b(): # old). A Ruby endless method (def f = x) is refused. In a Ruby method with rescue, else or ensure clauses only the code before the first clause is replaced and the clauses stay: def f\nrescue => e\n log(e)\nend with content 1 gives def f\n 1\nrescue => e\n log(e)\nend. With no code before the clause the content goes one level deeper than the def, as in an empty method; a clause on the same line as code moves to a line of its own; content with clauses of its own replaces the whole body, clauses included. When a one-line Ruby body is rebuilt, clauses that share a line with their code or the end are rebuilt too (def f; x; rescue; y; end becomes def f\n 1\nrescue\n y\nend); clauses already on lines of their own keep their text.

replace_signature

file_path, target, new_signature

Replace only the signature, preserving the body. new_signature is everything from the start of the definition up to its body (def get(self, key):, JS get(key, d) without the {). Existing decorators (Python, JS/TS), Java annotations and a C++ template <...> header are kept unless new_signature includes its own, which replace them. Indentation on the signature's first line is ignored, so "def get(self):" and " def get(self):" both work for a method; the lines after it may be indented relative to it ("@staticmethod\ndef get(key):") or written as read_symbol(depth="signature") returns them. For a JS/TS const f = (a) => {...}, "const f = (a, b) =>" replaces from the const on and "(a, b) =>" only the function's own part; likewise a closure's key (handler: (a) =>), a TS declare, a C/C++ typedef or extern "C", a Go type or var, a Ruby private and a Sorbet sig are replaced by a new signature that starts with its own, and kept otherwise (an export stays; a new closure key renames it). A TS overload group and a Python @overload group have the implementation's signature replaced, and a Ruby endless method keeps its = expression. A method declared without a body (a TS abstract method, a Java interface or abstract method, a C++ A() = default;, a Go function without one, a C/C++ prototype or a method declared in its class, int f(int);) is all signature but its final ;, which stays whether or not new_signature ends with one. A Go struct or interface type and an empty Ruby class, module or method take the header read_symbol(depth="signature") gives them (type A struct, class A < B); a variable or constant has no signature and is refused.

replace_in_body

file_path, target, old_snippet, new_snippet

Replace a snippet inside a function body, scoped to the target's body: its exact text at token boundaries, else its whole lines with their indentation shifted, matching exactly one place (see Body snippets and anchors above). new_snippet is indented as the snippet matched; '' deletes.

delete_in_body

file_path, target, snippet

Delete a snippet inside a function body, matched as replace_in_body matches one. Lines left without code go whole, with their indentation and line break; a comment after the snippet stays; a Python body keeps a statement.

insert_in_body

file_path, target, new_snippet, at | after | before

Insert a snippet inside a function body. Pass exactly ONE of: at="top" (prepend), at="bottom" (append), after=<snippet> (anchored), before=<snippet> (anchored). at inserts at the body's own indentation level even when the body ends in a nested block; a Python docstring stays first; a body written on one line with its signature or braces is refused (rewrite it with replace_function_body); an empty body (also an empty Ruby method) takes the content one level deeper than the function. An anchor is matched as a replace_in_body snippet; one that ends (after) or starts (before) its line gets the new text on lines of its own, indented like it and inside the block its line opens (see Body snippets and anchors above), else byte for byte, as does an anchor in a body on its signature's line. A line break at the start of new_snippet after an anchor that has none, and spaces after its last line break, are taken for the anchor line's own, and a final line break is added. In an empty body there is no code to anchor to (an anchor on its brace inserts byte for byte); use at="top" or at="bottom" there. after an anchor that ends with the body's closing brace is refused.

add_top_level

file_path, content, position="bottom"

Insert top-level content. position="bottom" appends at end of file (default), in a header inside its include guard, before the #endif; position="top" inserts after the preamble (as for a first import: package, #! and magic comments, directives, imports and includes, a header comment, the Python module docstring, a header's include guard) and before the first real declaration, without splitting a comment from the code it documents. The content is separated from its neighbours by one blank line, two in a Python module when either is a def or class (PEP 8); at the top, more blank lines the file had there stay below it; at the bottom, one-line content after one-line code that is stacked with the code above it is stacked too, but not after imports, includes or a package clause (unless it is an import itself). read_symbol's text of a nested symbol (recognised by its closing line or a decorator) is moved to column 0 as a whole.

add_method

file_path, class_target, content

Add a method at the end of a class body (in a C++ class, right after the last method when an access label follows it), indented like the class's members and separated as its methods are (see Layout-aware add_method / add_field above).

add_field

file_path, class_target, content

Add a field/attribute/member at the top of a class body (after a Python class docstring or a Java enum's constants, below the access label a C++ class body opens with), indented like the class's members.

insert_sibling

file_path, target, content, position

Insert content as a sibling of a named symbol. position="before" or "after"; "before" goes above the symbol's leading comment block, decorators, a JS/TS export and a C++ template <...> header. The new code goes into the gap between the target and the symbol on that side (with that symbol's comment, decorators and export) and is spaced like it: next to a function, class or other code over several lines, by that gap or the usual one when wider (one blank line, two at the top level of a Python module around a def or class, PEP 8); between one-line code (fields, constants, prototypes, macros) stacked without blank lines, stacked too (int f(void); int h(void); int g(void);), also after the last stacked field or before the first, and below stacked fields before a method; the end of a body or file, a C++ access label and a comment above the new code get no added blank lines. "before" keeps a #! line and encoding or magic comments first. "after" goes below comment lines right under the target that are indented deeper than it (they end its code), and below a block comment after the target on its last line that goes on over more lines. read_symbol's text is indented as a whole, also when it holds several members (see replace_function). Content is indented in tabs next to a tab-indented target, as for add_method. "before" is refused when other code comes before the target on its first line, "after" when other code follows it on its last line or the line starts with the code the target is part of (class A: x = 1), and empty content is refused, as is the tool in JSON, YAML and TOML files.

delete_symbol

file_path, target, include_leading_comments=True

Delete a function or class definition block (including decorators, a JS/TS export and a C++ template <...> header) and the blank lines that separated it (see Layout-aware deletion above). By default also consumes the contiguous leading comment block above the symbol and its decorators (Godoc, Javadoc /** ... */, # or // comments); pass include_leading_comments=False to keep it. A Ruby =begin document and a Sorbet sig above a method go too, and so do comment lines right below the symbol indented deeper than it. A comment before the symbol on its first line (/** @type {X} */ let x;) counts as a leading comment: by default it goes with the symbol, also when it is a block comment that began on an earlier line and when code follows the symbol on its line (/* c */ let x; let y; minus x is let y;); with include_leading_comments=False it stays, on a line of its own (/** @type {X} */ above the next line). A name declared together with others (int a, *b;, Java int x, y;, C++ auto [x, y] = p;, struct S {...} s;, Go var a, b = 1, 2, JS const a = 1, b = 2;) is refused, naming them, rather than deleted with them: give replace_function the declaration without that name instead. Refused in JSON, YAML and TOML files (use delete_key).

Parameters & signatures

Tool

Parameters

Description

add_parameter

file_path, target, parameter, position="end"

Add a parameter to a function signature (position: "start" or "end"; any other value is refused). A list written one parameter per line (aligned under the first one too) gets the new one on a line of its own at the parameters' indentation, with a trailing comma when the last parameter has one; comments are not parameters and stay on their lines. A list with two parameters on a line gets it inline (a, b, c). An empty list over two lines (def f(\n):) gets the parameter on a line of its own, one level deeper (with a trailing comma in Go, and in Python, JS and TS as the file's other lists have it). A name the function already has is refused (Parameter 'a' already exists in 'f'), names in destructuring patterns and Go groups included; several _ are fine in Go, Java and Ruby. A C or C++ (void) list gets the parameter in place of void (f(void) becomes f(int c); void *p is a parameter). "end" goes before the parameters that must stay last: a Python **kwargs (f(a, **kw) plus b is f(a, b, **kw); after *args or a bare * the new parameter is keyword-only), a JS/TS rest parameter (function f(a, ...rest) plus b is function f(a, b, ...rest)), a Go variadic one (func f(a int, xs ...int) plus b string is func f(a int, b string, xs ...int)), Java varargs (String... xs), the ... of C and C++, a C++ parameter pack (Ts... xs) and a C/C++ macro's GNU named variadic parameter (#define M(a, args...) plus b is #define M(a, b, args...)). On the old C++ form int a... outside a template, the new parameter goes before the ... (int g(int a, int b...)); int... and template functions keep it before the variadic parameter. A C++ int f(...) gets int f(int b, ...) at either position. In Ruby the new parameter goes before the kinds that must come after it, in Ruby's order: required, optional, *splat, required, keyword, **opts/**nil, &blk, ...; so b goes before keyword parameters, b = 1 before the splat, *r before keywords and k: 1 before **opts (def f(a, **opts, &blk) plus b is def f(a, b, **opts, &blk)). In a list written one per line the new parameter gets its own line above them. "start" goes after a TS this parameter or a Java receiver parameter, which must stay first (function f(this: Window, a) plus b is function f(this: Window, b, a)); in a list written one per line, above the first parameter and the comment lines right before it.

remove_parameter

file_path, target, parameter_name

Remove a parameter by name. One on a line of its own goes with that line and the comment at its end; comments on other lines stay (one after the parameter before it too), a last parameter with no comma after it takes the comma before it (except in Go before a line break, which needs it), and removing the only parameter leaves (). A Go name of several that share a type goes alone (func f(a, b int) minus a is func f(b int)), the other names keeping their comments; a type-only Go parameter, the receiver and named results are not parameters; a comment after the comma before a last Go name stays. A name bound by a JS/TS or Ruby destructuring parameter goes alone ({a: x, b} minus x is {b}; in an array or Ruby pattern a hole or _ keeps the positions, [a, b] minus a is [, b]), and the parameter goes when it was its only name; a key (a of {a: x}) is not a name. In a C file (.c, .h) removing the last parameter leaves (void), not () (unspecified parameters before C23), and removing the last named parameter before a ... is refused, since f(...) is invalid C before C23; C++ keeps () and allows f(...) (int f(int a, ...) minus a is int f(...)). In Python a bare * goes when only **kwargs would follow it (f(a, *, b) minus b is f(a), f(a, *, b, **kw) minus b is f(a, **kw)), and a / goes when the removed parameter was the only one before it (f(a, /, b) minus a is f(b)). Removing *args leaves a bare * when keyword-only parameters follow it (f(a, *args, b) minus args is f(a, *, b), and f(a, *args, b=1, **kw) gives f(a, *, b=1, **kw)); with nothing or only **kwargs after it, *args just goes (f(a, *args, **kw) gives f(a, **kw)).

Imports & includes

Tool

Parameters

Description

add_import

file_path, import_text

Add an import/from/#include/require line. Skips duplicates. Goes into the group of imports it belongs with, at its sorted place when the group is sorted, else after the group's last import, and after the file's last import when no group fits; a first import goes after the preamble with a blank line around it (see Import placement above). For Go, import "foo" and a spec alone ("foo", alias "foo") both work: the spec goes inside an existing import ( ... ) block, or is written as import "foo" without one.

remove_import

file_path, import_text

Remove a matching import line, compared after stripping surrounding whitespace: pass the exact line.

add_import_name

file_path, module, name

Add one name to an existing named-import statement: from <module> import a, b (Python) or import { a, b } from "<module>" (JS/TS, import type {...} and import D, {...} too). Idempotent. When the names are sorted the new one goes at its sorted place, else at the end. Sorted means plain, case-sensitive order (a single name always is), or else isort's and ruff's order by type: CONSTANT_CASE, then CamelCase, then the rest, each without case (from typing import TYPE_CHECKING, Any, cast plus Dict gives TYPE_CHECKING, Any, Dict, cast); names that fit both use plain order (Any, Dict plus TYPE_CHECKING gives Any, Dict, TYPE_CHECKING). A name sorts by the name it imports (b as bb as b, a TS type B as B). The list keeps its layout: names written one per line in parentheses or braces get the new one on a line of its own at their indentation (aligned under the ( or { when they are), above the next name and the comment lines right above it, or at the end after the last name's line and its comment, with the trailing-comma style kept (from x import (alpha, gamma) one per line plus beta gives alpha, beta, gamma, one per line); a comment heading the list stays first, and a next-line directive (see Import placement above) stays on the name below it. Inline lists, and a Python list without parentheses, stay inline, the new name going before the next one (from x import a, c plus b gives a, b, c; import { a, c } from "m" plus b gives { a, b, c }). For a default or namespace import (import Foo from ..., import * as ns from ...) use add_import with the full line.

remove_import_name

file_path, module, name

Remove one name from a multi-name named-import statement (Python and JS/TS). If the last named import is removed and no default/namespace binding remains, the whole line is removed; removing the last name beside a default binding (import D, { a } from "m") is refused, as it would leave empty braces: use remove_import, then add_import the default binding. In parentheses or braces, a name on a line of its own goes with that line and the comment at its end, comments on other lines stay, and a last name without a comma takes the comma before it.

Comments & docstrings

Tool

Parameters

Description

edit_leading_comment

file_path, target, op, comment=""

Edit the contiguous leading-comment block above a named symbol, which is above its decorators/annotations, a JS/TS export and a C++ template <...> header; a symbol that other code comes before on its line has none of its own and is refused. op="add" inserts (refused if the symbol already has a leading comment; the error quotes it, so a following op="replace" can repeat lines worth keeping, such as // eslint-disable-next-line or //go:noinline directives); op="replace" replaces (or inserts if none); op="remove" deletes. comment includes its markers, is re-indented to the symbol, and is required for add and replace (ignored for remove). Works for # / // / /* ... */ / Javadoc /** ... */ and a Ruby =begin document (alone or with # lines beside it, kept at column 0); a Ruby Sorbet sig and a TS function's overload signatures are part of the symbol, so the comment goes above them; a comment line indented deeper than the symbol ends the code above, so op="add" puts the new comment between the two; a /* ... */ that follows code on its line (int x = 1; /* n */) is not taken as a leading comment. In YAML and TOML the target is a key path (a YAML key with no value too) or a TOML [table] by its header's names (tool.uv); a key that does not start its line (inside a flow mapping or inline table) is refused, since the comment line above would belong to another key.

replace_docstring

file_path, target, new_docstring

Replace or insert a Python function/class docstring, or the module's for target="". Python-only. new_docstring must be one Python string literal with its quotes, for every target: bare text, an f-string, a bytes literal or an empty new_docstring is refused (the error says to write it with its quotes, as in '"""Summary."""') and the file is left unchanged. Inserting one into a body on its signature's line (def f(): return 1) is refused: rewrite the body with replace_function_body. A new module docstring goes before the first statement (a from __future__ import stays after it), right below a #! line and an encoding comment, and below a header comment set off by a blank line, which keeps its blank lines; a comment right above the first statement stays with it, below the docstring. One blank line follows the docstring (two before a def or class), and in a file with no statement it goes last. An f-string or bytes literal at the top of the module is no docstring and stays.

Dict/list editing (JSON, YAML, TOML, Python module-level dict/list literals, AND JS/TS object literals)

Values (content, value) are literal source text in the file's own syntax ('"2.0.0"', 42): quote strings where the format requires it.

Tool

Parameters

Description

replace_value

file_path, target, content

Replace the value of an existing key. Targets as for delete_key: JSON/YAML/TOML dotted path from the top level ("editor.fontSize" for a key with dots; keys inside YAML flow mappings and TOML inline tables too, which stay inline); Python DictName.keyExpr; JS/TS VarName.key (a shorthand { key } becomes key: <content>). A YAML key with no value (a:) gets one (a: 3, or a nested block below the key for content of several lines or a block sequence or mapping; a first line that is only |, >-, an &anchor or a !tag stays after the colon). In source files it also replaces the value of a variable, constant or field (Python X = 1 and class attributes, JS/TS const X = 1;, each name of let a = 1, b = 2 alone, and class fields A.x, Go var/const, C/C++ globals, C++ fields and static members, macros, Java fields, Ruby constants and locals, a heredoc's body too): only the value (int x(1); and int y{1}; keep their delimiters), and a declaration without one gets one (let x; becomes let x = 2;, X: int becomes X: int = 2, int g; becomes int g = 2;). Refused: a function, class or type (rewrite it with replace_function), a name that shares one value with others (a = b = 1, Go var c, d = f()), and one that cannot have a value of its own (extern, TS declare, an abstract field, Go var e, g int; for C struct fields and Go constants see the language table).

add_key

file_path, parent_target, key, value

Add a key-value pair to a dict/object/mapping/table. For config, parent_target is a dotted path from the top level (use "" for root; a TOML [table] by its header's names, a."b.c" for [a.'b.c']); YAML flow mappings (nested ones too) and keys with no value and TOML inline tables work too. For JSON and YAML key is one name, dots included. For Python it is the dict variable name; for JS/TS the name of a module-level const/let/var (or export const) object literal, as const / satisfies included. Both may continue into nested literals (CONFIG."db", CONFIG.server). JSON keys are quoted for you, YAML and TOML keys only when they need it (in TOML, dots make a dotted key), JS/TS keys only when they are not valid identifiers, Python keys are key expressions ('"timeout"'). The entry follows the container's layout (one per line, inline, trailing comma), and a key that already exists is refused (use replace_value).

delete_key

file_path, target

Delete a key-value pair. Targets: JSON/YAML/TOML dotted path from the top level ("editor.fontSize" for a key with dots; keys inside YAML flow mappings and TOML inline tables too, cut like JSON entries with their comma); Python DictName.keyExpr; JS/TS VarName.keyName on const/let/var or export const object literals (handles regular pairs, { key } shorthand, methods, and quoted "complex-key"). Python and JS/TS paths may continue into nested literals (CONFIG.server.port). For JSON, Python and JS/TS, the adjacent comma is also removed (an entry on its own line goes with that whole line, including its same-line comment), and deleting the only entry leaves {}; other YAML and TOML keys go with their lines. For an entry of an inline object literal passed as a function argument (foo({ x })), use delete_in_body on the enclosing function.

append_to_array

file_path, target, value

Append a literal value to a list/array/sequence. For Python, target is the list variable name; for config, a dotted path from the top level. The array keeps its layout: inline stays inline ([1, 2, 3]), one element per line gets a new line at the elements' indentation, and a trailing comma is kept; comments are not elements, and the new element's comma goes before a comment (see Layout-aware append_to_array above).

remove_from_array

file_path, target, value_match

Remove the first matching element from a list/array/sequence. value_match is compared with each element's source text after stripping whitespace. Comments are not elements: one on its own line goes with that line and the comment at its end, comments on other lines stay (one above it, or after the element before it, too), the trailing-comma style is kept (a last element with no comma after it takes the comma before it), and removing the only element leaves [] (a YAML block sequence becomes key: [], before a comment on the key's line).

Navigation & reading (read-only)

Tool

Parameters

Description

list_symbols

file_path

Formatted outline of all top-level functions, classes, methods and data with line numbers, one <kind> <name> (line N) per symbol, the name being its target (a symbol's line is that of its first decorator, export, Ruby Sorbet sig or first overload signature). JS/TS exported declarations are listed under their own names, an anonymous default export as default; generators and functions bound to a const/let/var as function, other const names as constant, let/var names as variable and class fields as field, TS abstract classes as class (with their abstract methods), and TS enums, type aliases, interfaces and namespaces as enum, type, interface and namespace. Nested classes are listed under their containers with their members (class A.B, method A.B.m), as are Python module variables and class attributes, Ruby constants and locals, C/C++ fields, macros, typedefs, globals, destructors and operators, Java fields and Go type aliases and variables (see the language table). Of a name a scope declares more than once, the target is listed, once. In C, C++, Java, JS and TS every entry is listed under the target that finds it at the listed line: overloads with their parameter types (method A.m(int) (line 2)), a C++ or Java method beside a field of its name as A.x() (in JS/TS the method is A.x and the field is not listed), a C++ definition outside its class by its qualified name (function A::f), and a tag or type that shares its name with another symbol after its keyword, so the line reads struct struct stat (line 1) or type type User (line 2); an entry no target reaches is left out. In Python and Ruby a name defined again is listed again, and the name targets the first. Names that a pattern binds (a, b = 1, 2, const {a} = o) are not listed, and a declaration of several names (int a, *b;) lists each. Go lists types with their methods, then the names of var and const declarations, then functions.

find_references

file_path, target

Syntactic search for all occurrences of an identifier (no scope awareness, so same-named unrelated identifiers show up too), matched per identifier rather than as a dotted path, returned as line N: <source line> in file order, by line and column.

read_symbol

file_path, target, depth="full"

Return source text of a single named symbol. depth controls how much: "full" returns the entire source (typically 10-20x fewer tokens than the whole file), from its first byte, so the later lines keep their file indentation: exactly what replace_function and delete_symbol take (with a Go type/var/const, a closure's key, a TS declare, the C typedef or declaration around a struct, a Ruby private, a TS abstract method's ;), ready to be edited and given back to replace_function as is; "interface" returns a class stub (header + fields + method sigs with ..., each member at its indentation relative to the class header and ending as written, with its ; or ,; a C/C++ class closes with };) or a function's signature; "signature" returns signature-only (a method with no body, such as a TS abstract or Java interface method or a C++ A() = default;, is its whole declaration, and a Go struct its header, type A struct). Both start where the full text starts, except that a function's or class's signature leaves out its decorators and a JS/TS export (handler: () =>, RunE: func() error, declare function f(a: number): void, extern "C" int f(int a), private def m(x), typedef struct N, or typedef struct without a tag), and a closure's interface is its signature. A TS overload group and a Python @overload group give the implementation's signature, a Ruby endless method its part before the =. For a Go type, "interface" is its declaration as "full" reads it plus its methods' signatures (type N int has no signature, but its interface lists its methods). Both refuse a variable, constant, field, type alias, macro or C/C++ forward declaration, saying what it is ('X' is a constant, which has no signature ...; read it with depth='full'). A declaration of several names (int a, *b;, Java int x, y;) reads whole for each of them, and a comment after the symbol on its last line is no part of it. For a config key path, "full" (the only depth config files take) returns the key's value (an empty string for a YAML key with no value).

read_imports

file_path

Return all import/include statements in the file.

Target format: Use the exact function name (e.g., get) or dotted Class.method path (e.g., LRUCache.get). In C, C++ and Java an overload takes its parameter types (A.m(int, String), A.m(), C f(void) being f()), a C++ definition outside its class its qualified name (A::f), and a name that a field shares with methods targets the field (the method is A.x()). A C/C++ tag or TS type alias or interface that shares its name with another symbol takes its keyword (struct stat, union u, class C, type User, interface Props), in front of a dotted path (type N.User). JS/TS exported declarations are targeted by their own names (export function add is add); an anonymous export default function/class is default (and its methods default.run), and const f = () => {...} is f. Decorated functions are fully supported — decorators are preserved when replacing bodies or signatures, and included when deleting or replacing the full function.

Tip: Call list_symbols first to discover exact target names before editing. This avoids guessing and makes subsequent edits much more reliable.

Which tool should I use?

A decision guide grouped by intent. Start at the top and pick the narrowest match.

Discovering what's in a file (do this first)

  • Don't know what symbols exist? → list_symbols

  • Need one specific function's full source? → read_symbol (depth="full", the default — 10-20x cheaper than reading the whole file)

  • Need a class's public API (methods + fields, no bodies)? → read_symbol(target, depth="interface")

  • Need just a function's signature? → read_symbol(target, depth="signature")

  • Need to see a file's imports/dependencies? → read_imports

  • Where is a symbol used? → find_references

Dotted targets descend into closures: Go stdioCmd.RunE (func_literal in struct field), TS app.handler (arrow function in object literal).

Adding new content

Intent

Tool

New top-level function, class, constant, or type alias

add_top_level (use position="top" to prepend after preamble)

New method in an existing class

add_method

New field/attribute/member in a class

add_field

New content before or after a top-level symbol

insert_sibling(position="before" | "after") ("before" goes above the symbol's leading comment)

New lines at the top of an existing function body

insert_in_body(at="top")

New lines at the bottom of an existing function body

insert_in_body(at="bottom")

New lines at a specific spot inside a function body (anchored to existing text)

insert_in_body(after=…) or insert_in_body(before=…)

New parameter on an existing function

add_parameter

New import or #include

add_import

New name in an existing from X import … or import { a, b } from "mod"

add_import_name

New comment above a symbol

edit_leading_comment(op="add")

New Python docstring on a function/class

replace_docstring

New key in a dict/object/mapping/table (any lang)

add_key

New item in a list/array/sequence (any lang)

append_to_array

Modifying existing content

Intent

Tool

Rewrite the full function (signature + body)

replace_function

Rewrite only the body, keep the signature

replace_function_body

Change one statement/block inside a large body

replace_in_body (scoped snippet match)

Change only the signature, keep the body

replace_signature

Change only the leading comment above a symbol

edit_leading_comment(op="replace")

Change only the Python docstring

replace_docstring

Change the value of an existing key (config, Python dict, JS/TS object)

replace_value

Removing content

Intent

Tool

Remove a function, method, or class

delete_symbol (consumes leading doc comment by default)

Remove one statement/line inside a function body (a route mount, a middleware call, a key of an inline object-literal argument)

delete_in_body

Remove a parameter from a function

remove_parameter

Remove an import or #include

remove_import

Remove one name from a multi-name named-import (Python or JS/TS)

remove_import_name

Remove a leading comment above a symbol

edit_leading_comment(op="remove")

Remove a key from a dict / config / JS-TS object literal

delete_key

Remove an item from a list/array

remove_from_array

Anti-patterns to avoid

  • Don't use replace_function or replace_function_body to change a few lines — use replace_in_body (scoped snippet match) or insert_in_body(at="top" \| "bottom") for appending/prepending. Rewriting the whole function is wasteful and error-prone.

  • Don't use replace_signature to add or remove one parameter — use add_parameter/remove_parameter.

  • Don't use replace_value to add a new key — use add_key. replace_value only updates existing keys, and add_key refuses a key that already exists.

  • Don't use add_import to add a name to an existing from X import … or named import — use add_import_name.

  • Don't guess at target names. Call list_symbols first. Names are case-sensitive and must match exactly.

Escape hatches

  • Deleting several related symbols? Call delete_symbol once per target. There is no delete_group tool: the extra calls cost less than a grouped primitive would.

  • Editing a sub-expression inside a method chain (dropping .references(...) from integer("x").notNull().references(...))? Use replace_in_body with the literal snippet when it is unique; for arbitrary expression surgery use your normal edit tool, since ast-editor does no pattern matching on expression trees.

  • Rewriting a whole file whose structure changes substantially? Use your normal write tool.

  • Valid code refused by the syntax check (very new language features, macro-heavy C/C++)? Make that edit with your normal edit tool.

Prerequisites

This MCP server uses uv to manage its Python environment and dependencies automatically. Install uv if you don't have it already:

macOS / Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Homebrew (macOS):

brew install uv

pip (any platform):

pip install uv

Verify the installation:

uv --version

For more options (Docker, Cargo, WinGet, etc.), see the official uv installation docs.

uv 0.9.17 or newer is required. This server's pyproject.toml sets a 7-day dependency cooldown ([tool.uv] exclude-newer = "7 days"). Older uv versions can't parse that relative duration. They print a warning and ignore the setting. A plain uv sync or uv run, including the uv --directory … run launch commands below, then discards the committed uv.lock, re-resolves to the newest compatible versions (even ones published in the last 7 days), and rewrites the file. Upgrade with uv self update, brew upgrade uv, or pip install -U uv, depending on how you installed it. Upgrading doesn't undo an earlier rewrite: restore the original uv.lock with git checkout HEAD -- uv.lock in this folder, or copy it again from the archive you downloaded.

The server depends on mcp, tree-sitter, and a tree-sitter grammar package for each supported language. uv handles all of these automatically on first run.

Installation

Note: Replace /absolute/path/to/ast-editor below with the absolute path to the ast-editor folder on your machine (the folder that contains its pyproject.toml).

Method 1: CLI Configuration (Claude Code, Codex, Gemini)

If your agent supports adding servers via CLI, run the following:

--scope user installs the server globally so it's available in every project on your machine. Drop it if you only want the server active in the current project.

# Claude Code / Codex
[claude|codex] mcp add ast-editor --scope user -- uv --directory /absolute/path/to/ast-editor run ast-editor-mcp

# Gemini CLI
gemini mcp add --transport stdio --scope user ast-editor -- uv --directory /absolute/path/to/ast-editor run ast-editor-mcp

Method 2: JSON Configuration

For tools that use a mcp_config.json or settings.json file, add the following block.

Important: Use the absolute path to uv for "command", not just "uv". GUI-based MCP clients (Claude Desktop, Cursor) don't always inherit your shell PATH, so a bare "uv" will fail with a "command not found" error. Get your absolute path with:

which uv
# e.g. /Users/you/.local/bin/uv  or  /opt/homebrew/bin/uv
{
  "mcpServers": {
    "ast-editor": {
      "command": "/absolute/path/to/uv",
      "args": [
        "--directory",
        "/absolute/path/to/ast-editor",
        "run",
        "ast-editor-mcp"
      ]
    }
  }
}

Agent

Configuration File Path

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS); %APPDATA%\Claude\claude_desktop_config.json (Windows)

Cursor

.cursor/mcp.json (Project) or ~/.cursor/mcp.json (Global)

Windsurf

Agent Panel → "..." → MCP Servers → View raw config

Antigravity

~/.gemini/config/mcp_config.json — see Antigravity IDE (uses uvx --from)

Gemini CLI

~/.gemini/settings.json (Global) or .gemini/settings.json (Project)

Antigravity IDE

The Antigravity IDE uses its own MCP config file and a uvx-based command — not the uv --directory … run form shown above. Add the server to ~/.gemini/config/mcp_config.json:

Important: Use the absolute path to uvx for "command", not a bare "uvx" — Antigravity doesn't inherit your shell PATH. Find it with which uvx (e.g. /opt/homebrew/bin/uvx).

Note: uvx ignores both this server's uv.lock and the [tool.uv] settings in its pyproject.toml. It re-resolves dependencies on each launch instead of using the locked versions, and the 7-day cooldown is passed explicitly as --exclude-newer "7 days" in args, so you get the newest compatible releases that are at least 7 days old. This requires uv 0.9.17 or newer: older versions reject the value (invalid value '7 days' for '--exclude-newer <EXCLUDE_NEWER>') and the server fails to start.

{
  "mcpServers": {
    "ast-editor": {
      "command": "/absolute/path/to/uvx",
      "args": [
        "--exclude-newer",
        "7 days",
        "--from",
        "/absolute/path/to/ast-editor",
        "ast-editor-mcp"
      ]
    }
  }
}

Using Standard Python (Fallback)

If you prefer not to use uv, run the following from the ast-editor folder (the one that contains its pyproject.toml), then point to the .venv executable it creates. python3 must be Python 3.10 or newer.

python3 -m venv .venv && source .venv/bin/activate && pip install .

This path doesn't use uv, so the [tool.uv] 7-day dependency cooldown doesn't apply. With pip 26.1 or newer, add --uploaded-prior-to P7D to the pip install command to apply the same cooldown.

{
  "mcpServers": {
    "ast-editor": {
      "command": "/absolute/path/to/ast-editor/.venv/bin/python",
      "args": ["-m", "ast_editor.server"]
    }
  }
}

Agent Configuration (Important)

Coding agents are heavily biased toward their default tools. You must explicitly instruct them to use ast-editor when editing code and config files. The agent prompt lives in AST-EDITOR.md — a standalone file you wire into your agent's system instructions. It is kept short, since it is loaded in every session: it covers how to pick and call each tool, and this README is the full reference. The server also sends a short, condensed version of this guidance (start with list_symbols, pick the narrowest tool, what to do when an edit is refused for a syntax error) as MCP server instructions, which some clients, Claude Code among them, pass to the model automatically alongside any prompt file. Wiring in the full prompt file is still recommended. After updating the server, copy the new AST-EDITOR.md over the one you installed (for example ~/.claude/AST-EDITOR.md), since an installed copy does not update itself.

Claude Code / Claude Desktop (via @-include)

Claude Code supports @filename includes in CLAUDE.md. Copy the prompt file into your global config directory and add one include line:

cp /absolute/path/to/ast-editor/AST-EDITOR.md ~/.claude/
echo '@AST-EDITOR.md' >> ~/.claude/CLAUDE.md

Or for a single project, place it next to the project's CLAUDE.md and add @AST-EDITOR.md there.

Other agents (Cursor, Codex CLI, Windsurf, Antigravity, Aider, Gemini CLI, etc.)

Copy the contents of AST-EDITOR.md into your agent's instruction file (AGENTS.md, .cursor/rules/*.mdc, .windsurfrules, .github/copilot-instructions.md, system prompt, etc.). Most non-Claude agents don't support @-include — paste the prompt body directly.

Agent

Instruction file

Any agent that reads AGENTS.md (Codex CLI, Windsurf, Zed, Cursor secondary)

AGENTS.md at repo root

Cursor

.cursor/rules/*.mdc (current) — legacy: .cursorrules

GitHub Copilot

.github/copilot-instructions.md

Windsurf

.windsurfrules or AGENTS.md

Antigravity

_agents/rules/

Aider / Gemini CLI / generic

Rules file or system prompt

Logging & Debugging

The server logs all tool invocations and errors to stderr (safe for stdio transport — does not interfere with JSON-RPC). Logs include timestamps and severity levels.

To inspect logs when running under Claude Desktop, check:

  • macOS: ~/Library/Logs/Claude/mcp*.log

  • Windows: %APPDATA%\Claude\logs\mcp*.log

For interactive testing, use the MCP Inspector, which provides a UI for exercising each tool with custom parameters and inspecting responses.

Available Tools

28 tools
add_fieldA

Add a field or member at the top of a class body: before the first member, after a Python class docstring or a Java enum's constants (adding the ; they need), and below the access label a C++ class opens with. It is indented like the class's members, with a blank line after it when a method follows. A function target is refused.

Use this when: adding a class attribute, field or member variable. Don't use this when: adding a method (add_method).

Example: class_target="LRUCache" content='version = "1.0"'

NameRequiredDescriptionDefault
contentYesField or member declaration as source text (e.g. 'version = "1.0"' or Go 'version int').
file_pathYesAbsolute path to the source or config file.
class_targetYesClass name as list_symbols lists it (a struct, interface or Go struct type too), dotted for nesting ('A.B').

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (mutation, non-destructive, closed world), and the description adds genuinely new behavior: exact insertion point per language, indentation matching, blank-line insertion when a method follows, Java enum semicolon handling, and that a function target is refused. It does not mention error modes beyond the function-target refusal or whether the edit is atomic, so it stops short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the placement behavior, then a crisp use/don't-use pair, then a compact example. Every sentence carries distinct information; nothing is redundant with the schema or annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, so return values need not be described. Placement, formatting side effects, target-type constraints and the sibling alternative are all covered, which is everything an agent needs to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds value the schema lacks: the sample invocation shows class_target='LRUCache' with dotted-nesting implied, and the refusal rule clarifies what class_target will accept. It does not clarify file_path or content syntax beyond the example.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Add a field or member') and scopes it precisely to a class body, immediately separating it from the sibling add_method. The placement rules across Python, Java and C++ make the operation unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: adding a class attribute, field or member variable' and 'Don't use this when: adding a method (add_method)', naming the sibling alternative directly. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_importA

Add an import line, skipping an exact duplicate. It goes into the group of imports it belongs with (same package, standard library or not, relative or not), at its sorted place when the group is sorted, else after the group's last import. A comment heading the group stays first (a next-line directive stays on its import), and so do Python from __future__ imports. With no fitting group it goes after the last import, and a first import after the preamble.

Use this when: importing something the file does not import yet. Don't use this when: adding a name to an existing import (add_import_name).

Example: import_text="from typing import Optional"

NameRequiredDescriptionDefault
file_pathYesAbsolute path to the source or config file.
import_textYesComplete import line in the file's language (e.g. 'import os', '#include <stdlib.h>'); in Go a bare spec ('"fmt"') works too.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare the safety profile (writes, non-destructive, not open-world); the description adds substantial context beyond that: deduplication of exact duplicates, group placement rules (same package / stdlib / relative), sorted-position vs. after-group fallback, comment headings and next-line directives preserved, `from __future__` pinned first, and the empty-file/preamble case. That is exactly the kind of placement and preservation detail the annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then placement semantics, then when/when-not, then an example. Every sentence about placement or preservation earns its place; the block is long but not padded. Minor deduction because the routing guidance is buried at the end rather than up front.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists and parameters are fully covered by the schema, so the description need only explain selection and placement behavior — which it does comprehensively, including the fallback cases. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and both parameters are fully documented (file_path, import_text with language-specific examples). The description's example ('from typing import Optional') largely restates what the schema already shows, so no meaningful meaning is added beyond structured fields. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Add an import line') plus a key behavioral qualifier ('skipping an exact duplicate'), and explicitly names the sibling it is not (add_import_name). An agent can distinguish it from add_import_name and remove_import without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: importing something the file does not import yet' and 'Don't use this when: adding a name to an existing import (add_import_name)' with the alternative named. Both the selection condition and the exclusion are stated outright.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_import_nameA

Add a name to an existing from <module> import ... (Python) or import { ... } from "<module>" (JS/TS) statement; a name already there is left as is. It goes at its sorted place when the names are sorted (plainly or as isort and ruff sort them), else at the end, in the list's layout: names one per line give it a line of its own, an inline list stays inline. Refused when the file has no such import.

Use this when: the module is already imported with named imports. Don't use this when: there is no such import yet, or for a default or namespace import (add_import with the full line).

Example (from typing import Any, cast becomes Any, Dict, cast): module="typing" name="Dict"

NameRequiredDescriptionDefault
nameYesOne imported name, e.g. 'Optional'.
moduleYesModule of the import as written, without quotes (e.g. 'typing' or './utils').
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare the generic mutating-but-non-destructive profile; the description adds real behavior: idempotency ('a name already there is left as is'), the exact insertion position (sorted place per isort/ruff, else end), layout preservation (one-per-line vs inline), and the failure mode ('Refused when the file has no such import'). That is precisely the context the annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then structured into when/when-not and a worked example, so an agent can stop reading once it has what it needs. The placement/layout paragraph is long but each clause changes behavior; the only marginal cost is the explicit example block.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-required-param mutation tool, the description covers preconditions, insertion semantics, failure behavior, and forwarding to the sibling tool; an output schema exists so return values need no explanation. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description goes further by grounding all three parameters in a concrete before/after example (`from typing import Any, cast` -> `Any, Dict, cast`) with module and name values, which demonstrates the 'as written, no quotes' convention in practice. It does not, however, say anything about file_path beyond what the schema already states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource: adds a single name to an existing named-import statement, with the exact syntax for both Python and JS/TS spelled out. It is clearly distinguished from the siblings add_import (full line / default / namespace imports) and remove_import_name (the inverse operation).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Has explicit 'Use this when' and 'Don't use this when' sections, and names the alternative tool (add_import with the full line) plus the precondition that the module is already imported with named imports. The refusal case (no such import) is stated as a routing signal, not left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_keyA

Add a key-value pair to a JSON object, YAML mapping, TOML table, Python dict literal or JS/TS object literal. The entry follows the container's layout: one key per line stays so, an inline object stays inline, and a trailing comma is kept. A YAML key with no value gets a nested mapping. A key the container already has is refused, naming the target to give replace_value.

Use this when: the key does not exist yet. Don't use this when: the key exists (replace_value), or you add a list item (append_to_array).

Example (TS, const CONFIG = { server: {...} }): parent_target="CONFIG.server" key="port" value="8080"

NameRequiredDescriptionDefault
keyYesKey to add. JSON, YAML and JS/TS: one name, quoted for you when needed. TOML: a dotted key, so quote a name with a dot yourself ('"example.com"'). Python: a key expression, so quote string keys ('"timeout"').
valueYesValue as source text in the file's syntax, strings quoted (e.g. '"2.0.0"' or '42').
file_pathYesAbsolute path to the source or config file.
parent_targetYesParent container: its key path from the top level in a config file ('' for the root; quote a key with dots, 'a."b.c"'), or a Python or JS/TS module-level variable's name, dotted into nested literals ('CONFIG.server').

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations (readOnlyHint=false, destructiveHint=false, openWorldHint=false) by disclosing preservation behavior (one key per line stays so, inline objects stay inline, trailing comma kept), a format-specific rule (YAML key with no value becomes a nested mapping), and error behavior (existing key is refused and the error names replace_value). These are exactly the traits an agent cannot infer from structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action, then behavior, then a Use/Don't-use block, then a compact example. Every sentence carries information; no filler or restatement of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, so return values need not be described, and annotations cover the safety profile. The description fills the remaining gaps: container layout preservation, format-specific YAML behavior, duplicate-key failure, and concrete parameter composition.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already carries the per-parameter meaning (dotted keys for TOML, key expressions for Python, source-text values), making 3 the baseline. The worked example showing parent_target="CONFIG.server" together with key/value adds real compositional meaning beyond the schema, earning a bump.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (add) and resource (a key-value pair) and enumerates the concrete container formats it applies to: JSON object, YAML mapping, TOML table, Python dict, JS/TS object literal. It also names the sibling tools it is not (replace_value, append_to_array), so an agent can route without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: the key does not exist yet' and 'Don't use this when: the key exists (replace_value), or you add a list item (append_to_array)' gives both the positive condition and two named alternatives. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_methodA

Add a method at the end of a class body, after its last member, spaced like the class's methods. In Go it goes after the type declaration. In a C++ class whose last method is followed by an access label, it goes right after that method, so it gets the same access. A one-line Python or Ruby class is refused, and so is a function target (use insert_in_body).

Use this when: adding a method to an existing class. Don't use this when: adding a field (add_field) or a top-level function (add_top_level).

Example: class_target="LRUCache" content='def clear(self):\n self.items.clear()'

NameRequiredDescriptionDefault
contentYesComplete method source, re-indented like the class's members, keeping its own relative indentation.
file_pathYesAbsolute path to the source or config file.
class_targetYesClass name as list_symbols lists it (a struct, interface or Go struct type too), dotted for nesting ('A.B').

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare safety hints (readOnly false, destructive false, closed world); the description adds substantial behavior: placement rules across Go/C++/Python/Ruby, refusal conditions for one-line classes and function targets, and indentation/spacing behavior. Nothing contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core behavior, then use/don't-use bullets and a compact example. Every sentence earns its place—language-specific rules are necessary precision, not padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema, annotations, and full schema coverage, the description covers everything an agent needs to call this correctly: placement per language, refusals, target restrictions, and an example. No material gaps remain.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so all three parameters are already well documented; the baseline is 3. The description doesn't add syntactic meaning to file_path or class_target beyond the schema, though the example clarifies content shape (full method source including def line).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (add a method to a class body) and precisely defines where it is placed. The language-specific clauses (Go after type declaration, C++ after access label, refusal of one-line Python/Ruby classes) make it unmistakable against siblings like add_field or add_top_level.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly provides 'Use this when' and 'Don't use this when' sections, naming the alternatives add_field and add_top_level. It also redirects function targets to insert_in_body, so the agent knows exactly which sibling to pick.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_parameterA

Add one parameter to a function's signature, keeping the body. "end" puts it after the last parameter but before those that must come after it (Python **kwargs, a JS/TS rest parameter, Go and Java variadics, C/C++ ..., Ruby **opts and &blk); "start" before the first, but after a TS this or Java receiver parameter. A list written one parameter per line gets it on a line of its own, a C (void) takes it in place of void, and a bare JS/TS arrow parameter gets parentheses. A name the function already has is refused.

Use this when: adding a parameter without retyping the signature. Don't use this when: rewriting the signature (replace_signature), or changing the body too (replace_function).

Example (def f(a, **kw) becomes def f(a, b, **kw)): target="f" parameter="b"

NameRequiredDescriptionDefault
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
positionNo'end' (default) after the last parameter, before any that must stay last; 'start' before the first. Other values are refused.end
file_pathYesAbsolute path to the source or config file.
parameterYesSource text of one parameter (e.g. 'default=None', 'const int* buf'); the comma is added for you.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnlyHint=false, destructiveHint=false) but say nothing about semantics. The description adds real behavioral detail the annotations cannot: language-specific placement rules for 'end'/'start', formatting side effects (one-param-per-line, C `(void)` replacement, arrow-function parenthesization), and the duplicate-name refusal. It stops short of stating that the file is rewritten in place or what the response contains, which keeps it at 4 rather than 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose, then when/when-not, then a worked example — a good ordering with no filler sentences. The long parenthetical enumerating every language's trailing constructs is justified but dense, slightly weighing down the opening sentence.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With full schema coverage, an output schema, and safety annotations already supplied, the description fills the remaining gaps: exact placement behavior, refusal cases, and formatting side effects. An agent has everything needed to call it correctly on the first try.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description goes further by explaining concretely what 'end' and 'start' mean per language ('before those that must come after it… Python **kwargs, a JS/TS rest parameter…') and by showing an example mapping target/parameter to a before/after signature. This adds meaning beyond the schema's abstract wording.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a precise verb + resource scope: 'Add one parameter to a function's signature, keeping the body.' That single clause distinguishes it from remove_parameter, replace_signature and replace_function_body without needing to open any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: adding a parameter without retyping the signature' and 'Don't use this when: rewriting the signature (replace_signature), or changing the body too (replace_function)' — naming both alternatives and the condition that selects them.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_top_levelA

Add top-level code (a function, class, constant or other statement) to a source file. "bottom" appends it at the end, inside a header's include guard; "top" puts it after the preamble, before the first declaration. It is set apart by a blank line, two around a Python def or class, but one-line code added at the bottom under stacked one-line code is stacked too. In Go, or a file indented with tabs, its indentation becomes tabs.

Use this when: adding a top-level symbol or statement. Don't use this when: placing it next to a symbol (insert_sibling), in a class (add_method, add_field), in a body (insert_in_body) or in a config file (add_key).

Example: content="MAX_CONNECTIONS = 10" position="top"

NameRequiredDescriptionDefault
contentYesTop-level code, inserted as written; read_symbol's text of a nested symbol is moved to column 0 as a whole.
positionNo'bottom' (default) at the end of the file; 'top' after the preamble (package clause, `#!` and magic comments, module docstring, directives such as 'use strict', imports and includes). Other values are refused.bottom
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false and destructiveHint=false, so safety is covered; the description adds real behavioral detail beyond that — placement inside a header's include guard for 'bottom', blank-line separation rules, and tab conversion in Go or tab-indented files. It stops short of stating what happens on conflicts (e.g., duplicate symbol names) or failure modes, which keeps it at 4 rather than 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the verb and resource, then positions, then use/don't-use, then an example — a sensible ordering. The blank-line and tab-indentation rules are somewhat dense, but each sentence describes observable behavior an agent would otherwise get wrong, so little is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema, full annotations, and 100% parameter coverage, the description need only cover placement semantics and sibling routing — and it does both, including language-specific formatting behavior. Nothing required to invoke the tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds placement nuance not fully in the schema — that 'bottom' lands inside an include guard and 'top' goes after the preamble but before the first declaration — plus a concrete example of content/position usage. The content parameter's column-0 behavior is left to the schema, which already documents it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Add top-level code ... to a source file') and enumerates what counts as top-level (function, class, constant, other statement). It also disambiguates from sibling tools by naming them, so an agent can distinguish it from insert_sibling or add_method without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' and 'Don't use this when' sections, with each exclusion routed to a named alternative (insert_sibling, add_method, add_field, insert_in_body, add_key). The condition that selects this tool versus each alternative is spelled out, leaving nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

append_to_arrayA

Append a value to a JSON, YAML or TOML array or a Python module-level list. The value follows the array's layout: an inline array stays inline ([1, 2] becomes [1, 2, 3]), one element per line gets a new line, and a trailing comma is kept. Comments are not elements: the comma goes before them.

Use this when: adding a list item. Don't use this when: adding a key-value pair (add_key).

Example: target="project.dependencies" value='"new-package"'

NameRequiredDescriptionDefault
valueYesValue as source text in the file's syntax, strings quoted (e.g. '"2.0.0"' or '42').
targetYesKey path from the top level to a JSON, YAML or TOML array ('project.dependencies'), or a Python module-level list's name.
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare the safety profile (readOnly=false, destructive=false, openWorld=false); the description adds genuinely non-obvious behavior — layout-preserving formatting, comma placement before comments, trailing-comma retention. It stops short of stating what happens when the target is missing or not an array, which matters for an in-place mutation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then formatting rules, then usage gates, then a compact example — no filler. The formatting paragraph is slightly dense but every clause conveys distinct behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations covering safety, an output schema covering return values, and full parameter documentation in the schema, the description supplies the remaining caller-critical knowledge: supported formats, formatting preservation, and the sibling to use instead. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema baseline already applies. The description still adds value by showing the concrete call shape (target="project.dependencies", value='"new-package"'), reinforcing that value is raw source text in the file's syntax rather than a serialized type.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource ('Append a value to a JSON, YAML or TOML array or a Python module-level list') and enumerates the supported formats. It explicitly sets itself apart from the closest sibling, add_key, so an agent can route correctly without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It uses an explicit 'Use this when / Don't use this when' structure and names the alternative tool (add_key) for the key-value case. The positive condition ('adding a list item') borders on restating the name, and no guidance is given relative to remove_from_array or insert_sibling, so it falls short of a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_in_bodyA
Destructive

Delete a snippet inside a function's body, matched as replace_in_body matches one. A deletion that empties its lines removes them whole; a comment after the snippet on its last line stays. One that would leave a Python body without a statement is refused (replace the snippet with pass).

Use this when: removing a statement, block or line in a body, or an entry of an object literal passed as an argument. Don't use this when: deleting a whole function or class (delete_symbol).

Example: target="RegisterRoutes" snippet='r.Mount("/kb", kbHandler)'

NameRequiredDescriptionDefault
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
snippetYesText in the target's body, matching one place: its exact text at token boundaries, or else whole lines with the same indentation added to or removed from each.
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, so the safety profile is known; the description earns credit by disclosing the non-obvious semantics beyond that: lines that empty are removed whole, a trailing comment on the last line survives, and a deletion that would empty a Python body is refused with a prescribed workaround. It stops short of describing what is returned or how other failure modes surface, so a 4 rather than a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense but every sentence carries content: behavior, exclusions, and an example. Front-loading the behavioral rules ahead of the use/don't-use rubric is slightly inverted for scanning, but nothing is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Three required parameters, all schema-documented at 100%, an output schema exists so return values need no prose, and annotations cover the destructive profile. The description supplies exactly the remaining gaps: matching semantics, edge-case behavior, and sibling routing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description goes beyond it with a concrete example pairing target="RegisterRoutes" with snippet='r.Mount("/kb", kbHandler)', showing the target/snippet relationship in practice. It adds no syntax detail for file_path and defers snippet matching rules to replace_in_body, so not a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Delete) plus a precise resource scope (a snippet inside a function's body), and immediately distinguishes itself from the broad-brush alternative by naming delete_symbol. An agent can tell it apart from replace_in_body and delete_symbol without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' enumerates the concrete cases (statement, block, line, object-literal entry as argument) and an explicit 'Don't use this when' routes whole-function/class removal to delete_symbol. That is a complete when/when-not with a named alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_keyA
Destructive

Delete a key-value pair from a config file, or from a Python or JS/TS module-level dict or object literal. The adjacent comma goes too where the syntax has one, and deleting the only entry leaves {}; YAML and TOML keys otherwise go with their lines.

Use this when: removing a whole entry. Don't use this when: removing a list item (remove_from_array), or an entry of an object literal passed as an argument (delete_in_body).

Example: target='CONFIG."timeout"'

NameRequiredDescriptionDefault
targetYesKey path from the top level in a config file ('dependencies.tree-sitter'; quote a key with dots, '"editor.fontSize"'), or a path into a Python or JS/TS module-level dict or object ('CONFIG."timeout"', 'CONFIG.server.port').
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and openWorldHint=false, so safety is covered; the description goes further and discloses the concrete side effects of deletion — adjacent comma removal, `{}` left behind for the last entry, line removal for YAML/TOML. It stops short of saying what happens if the target key does not exist (error vs. no-op), which is the one behavioral detail still missing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then syntax consequences, then the use/don't-use routing block, then a single example. Every sentence carries distinct information and the sectioning makes it scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists and annotations cover the safety profile, so return values and risk need not be explained. Parameters are fully documented. The only remaining gap is failure semantics for a missing key, which matters for a destructive edit but is not stated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the `target` parameter already documents the dotted-path and quoted-key syntax. The description's example (`target='CONFIG."timeout"'`) illustrates the same format but adds no new constraint or edge case beyond the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (delete a key-value pair) and immediately bounds the scope to config files and Python/JS/TS module-level dicts/object literals. It explicitly names the siblings it is not (remove_from_array, delete_in_body), so an agent can route correctly without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Uses explicit 'Use this when' / 'Don't use this when' sections with two named alternatives and the exact condition that selects each. This is the strongest form of routing guidance and leaves nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_symbolA
Destructive

Delete a whole function, method, class or other symbol, with its decorators, a JS/TS export and a C++ template <...> header. The blank lines that set it apart go too, so the file keeps its spacing. A symbol that shares a line with other code is cut out of the line. One name of a declaration of several (int a, b;, struct S {...} s;) is refused: give replace_function the declaration without it. Refused in JSON, YAML and TOML files (use delete_key).

Use this when: removing a symbol along with its doc comment. Don't use this when: removing a config key (delete_key), an import (remove_import) or lines inside a body (delete_in_body).

Example: target="LRUCache.old_method"

NameRequiredDescriptionDefault
targetYesSymbol name as list_symbols lists it, dotted for nesting (e.g. 'LRUCache.old_method', 'A.m(int)').
file_pathYesAbsolute path to the source or config file.
include_leading_commentsNoTrue (default) also deletes the comment block above the symbol, or before it on its first line; false keeps it.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, but the description goes well beyond that: it discloses exactly what is removed (decorators, export keyword, template header, blank lines, optionally the doc comment), what is refused (multi-name declarations, JSON/YAML/TOML files), and how shared-line symbols are handled. This is the level of destruction detail an agent needs before calling a destructive edit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and its scope, then structured into when/when-not and an example. Every sentence carries information, though the opening paragraph is dense enough that a slightly tighter split would read faster.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive, file-type-restricted edit tool, the description covers scope, refusals, alternatives and target addressing. An output schema exists, so return values need no explanation, and nothing an agent needs in order to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both required parameters are already documented, including the dotted 'LRUCache.old_method' form and the include_leading_comments default. The description still adds real semantics by specifying that a single name from a multi-name declaration is refused and must be handled via replace_function — a constraint on the target parameter not present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Delete a whole function, method, class or other symbol') and enumerates precisely what the deletion encompasses: decorators, a JS/TS export, a C++ template header, and surrounding blank lines. An agent can distinguish this from replace_function or delete_key without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Includes explicit 'Use this when' and 'Don't use this when' sections that name three concrete alternatives (delete_key, remove_import, delete_in_body) and the condition selecting each. It also routes the multi-name-declaration case to a specific sibling, so there is no ambiguity left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_leading_commentA
Destructive

Add, replace or remove the comment block directly above a symbol, its doc comment. The block sits above the symbol's decorators, annotations, export and C++ template <...> header, and a new comment goes there, re-indented to the symbol. A comment indented deeper than the symbol ends the code above and is not part of the block. A symbol that other code comes before on its line is refused. In YAML and TOML the target is a key path or a TOML [table]; JSON has no comments and is refused.

Use this when: adding, rewriting or deleting a symbol's doc comment. Don't use this when: writing a Python docstring (replace_docstring) or a comment inside a body (insert_in_body, replace_in_body).

Example: target="LRUCache.get", op="add", comment="# Return the cached value."

NameRequiredDescriptionDefault
opYes'add' inserts a comment, refused when the symbol has one (the error quotes it: use 'replace' with a block that repeats lines to keep, such as lint directives); 'replace' replaces the block, or adds one; 'remove' deletes it. Other values are refused.
targetYesSymbol name as list_symbols lists it, dotted for nesting ('LRUCache.get'), or a YAML/TOML key path from the top level or TOML [table] name ('tool.uv').
commentNoComment text with its markers ('# note', '// note', '/** ... */'); required for 'add' and 'replace', ignored for 'remove'.
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say destructiveHint=true/readOnly=false, but the description adds the real behavioral context: indentation is rewritten to match the symbol, a comment indented deeper than the symbol terminates the code above, symbols with preceding code on their line are refused, JSON has no comments and is refused, and YAML/TOML targets are key paths or [table] names. This is exactly the mutation-risk detail the annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The scoping facts (where the block goes, indentation, refusals) come first, then the use/don't-use routing, then a compact example. Every clause carries a distinct rule; there is no filler despite the length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values need no explanation, and the description still covers all four parameters' behavior indirectly plus the language-specific edge cases (YAML/TOML/JSON) and the refusal conditions. For a destructive, file-mutating tool, an agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline of 3 is met by the schema alone; the description adds value by giving a worked example (`target="LRUCache.get"`, `op="add"`) that shows the dotted-symbol targeting convention in use. It does not restate `op` refusal rules or `comment` requirements, which live in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific triple of operations ('Add, replace or remove') on a precisely scoped resource ('the comment block directly above a symbol, its doc comment'), with the boundary of what counts as the block spelled out (sits above decorators, annotations, `export`, C++ `template <...>` headers). An agent can immediately distinguish it from `insert_in_body`, `replace_in_body` or `replace_docstring`.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' and 'Don't use this when' clauses, and the exclusions name the exact sibling tools (`replace_docstring`, `insert_in_body`, `replace_in_body`) plus the condition (Python docstring, comment inside a body) that routes the agent away. Nothing left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_referencesA
Read-only

List each occurrence of an identifier in a source file as line N: <source line>, in file order. Read-only and syntactic, with no scope analysis, so unrelated identifiers of the same name show up too. C++ destructors and operators go by their list_symbols names (~A, operator==).

Use this when: surveying a name's uses in a file before a rename. Don't use this when: you need references across files or by scope (use a language server).

Example: target="LRUCache"

NameRequiredDescriptionDefault
targetYesIdentifier to find, a plain name, not a dotted path (e.g. 'LRUCache').
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare a safe read-only profile, and the description adds substantive context beyond them: the analysis is syntactic with no scope analysis, so false-positive matches occur, plus how C++ destructors and operators are named. This materially changes how an agent should interpret results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then structured when/when-not blocks and a short example — efficient overall. The trailing example largely duplicates the schema's own example, so it is mildly redundant but not wasteful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations, a 100%-covered two-param schema, and an output schema, the description supplies exactly the missing pieces: output format, the syntactic-analysis caveat, and routing to a language server when scope matters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both parameters are already documented, including the 'plain name, not a dotted path' constraint and the LRUCache example. The description's example repeats that guidance rather than adding new meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (list each occurrence of an identifier in a source file) and even fixes the output shape (`line N: <source line>`, file order). It is clearly distinguishable from siblings like list_symbols or read_symbol.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: surveying a name's uses in a file before a rename' and 'Don't use this when: you need references across files or by scope (use a language server)' — both the trigger and the alternative are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

insert_in_bodyA

Insert new_snippet inside a function's body. Set exactly one of at, after or before. at="top" goes after a Python docstring, at="bottom" at body level after the last statement, and in an empty body one level deeper than the function; at is refused for a body on its signature's line (use replace_function_body).

An anchor is matched as replace_in_body matches a snippet and must be unique. One that ends its line (after) or starts it (before) gets new_snippet on lines of its own; after a line that opens a block (if x:, if (x) {), new lines go inside the block. A mid-line anchor gets new_snippet byte for byte.

Use this when: adding lines to a body. Don't use this when: replacing the whole body (replace_function_body), changing existing lines (replace_in_body), or adding a top-level symbol (add_top_level).

Example (both written without indentation): target="handle" new_snippet='auth_check(request)' before='validate(request)'

NameRequiredDescriptionDefault
atNo'top' or 'bottom' of the body; '' (default) leaves this mode unused. Other values are refused.
afterNoAnchor in the body to insert after; '' (default) leaves this mode unused.
beforeNoAnchor in the body to insert before; '' (default) leaves this mode unused.
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
file_pathYesAbsolute path to the source or config file.
new_snippetYesText to insert. With at, re-indented as a block to the body's indentation; with after or before, indented like the anchor (as written when already at the file's indentation there).

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: it discloses refusal cases ("`at` is refused for a body on its signature's line"), the uniqueness constraint on anchors, and the empty-body / docstring positioning rules. It does not cover failure behavior for a missing target or unparsable file, so it falls just short of exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and the one-of-three rule, then when/when-not, then a compact example. Dense but every clause carries information; only the parenthetical language-specific hints slightly interrupt the flow.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and all six parameters are documented. For a mutation tool with real edge cases (empty body, signature-line body, block-opening anchors), the description supplies the positioning, indentation, and refusal semantics an agent needs to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not carry: what `at="top"` means relative to a Python docstring, where `at="bottom"` lands in an empty body, and how indentation is applied for anchored versus `at` insertion. Anchor matching semantics (uniqueness, line-opening versus mid-line) are additional and useful.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Insert new_snippet inside a function's body") and immediately pins the scope with the mutual-exclusion rule for `at`/`after`/`before`. It is clearly distinguishable from replace_in_body and replace_function_body, which it names.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit "Use this when: adding lines to a body" and "Don't use this when:" enumerating three alternatives with the condition that selects each (replace_function_body, replace_in_body, add_top_level). Nothing about tool selection is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

insert_siblingA

Insert code next to a named symbol, at its level. "before" goes above the symbol's leading comment, decorators and export; "after" below the symbol (after the ; that ends a C/C++ class). The code is spaced like the gap it goes into: one-line code stacked without blank lines stays stacked, and next to a function or class it gets at least one blank line, two around a top-level Python def or class. A comment indented deeper than the target ends the code above, so "before" goes below it. "before" is refused when other code comes before the target on its line, "after" when code follows it. Refused in JSON, YAML and TOML files (use add_key).

Use this when: placing code relative to a symbol, such as a helper above its caller. Don't use this when: appending at the end of the file (add_top_level), or inserting into a body (insert_in_body) or a class (add_method, add_field).

Example: target="LRUCache" content="CACHE_SIZE = 100" position="before"

NameRequiredDescriptionDefault
targetYesSymbol name as list_symbols lists it, dotted for nesting (e.g. 'LRUCache', 'A.m').
contentYesCode to insert, re-indented to the target's level (in tabs where the file indents with them); read_symbol's text goes in as a whole.
positionYes'before' or 'after' the target; other values are refused.
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (which only say this is a non-read-only, non-destructive local write), the description discloses exact placement semantics (above leading comments/decorators/export vs. below the terminating ';'), how blank-line spacing is derived from the surrounding gap, the deeper-indented-comment edge case, and two refusal conditions (code already on the line before/after the target). It also states format-level refusals (JSON/YAML/TOML) not captured by the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then rules, then when/when-not, then example — a sound structure. The spacing paragraph is dense and could be tightened, but every sentence carries operative information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations covering safety, an output schema covering returns, and full schema coverage on all four parameters, the description only needs to supply placement semantics and refusals — which it does thoroughly. An agent has everything needed to call this correctly on the first attempt.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description adds real value by clarifying what 'before'/'after' mean positionally, which the schema only reduces to a string enum-like value. The example then demonstrates target/content/position concretely. It does not restate the dotted-nesting or re-indentation behavior already covered in the schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb (Insert), resource (code), and placement rule (next to a named symbol, at its level). It is immediately distinguishable from add_top_level, insert_in_body, add_method, and add_field, all of which are named as alternatives later.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' and 'Don't use this when' blocks name the correct condition and route each wrong-condition case to a specific sibling (add_top_level, insert_in_body, add_method, add_field, add_key). There is also a worked example, so nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_symbolsA
Read-only

List the symbols of a source file, one per line as <kind> <name> (line N), class members indented under their class (method A.B.m). Each name is the target the other tools take: overloads with their parameter types (A.m(int)), a C++ definition outside its class by its qualified name (A::f), and a struct or TS type that shares its name with another symbol after its keyword (struct struct stat targets struct stat). Kinds include function, method, class, struct, enum, interface, namespace, type, macro, field, variable and constant; a Go var x = func(...) {...}, listed as a variable, also takes the body, parameter and signature tools. A name with a prototype or forward declaration is listed once, at its definition. Read-only.

Use this when: before editing a file, to get exact target names. Don't use this when: you already know the target (read_symbol reads one symbol).

Example: file_path="/abs/path/to/module.py"

NameRequiredDescriptionDefault
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false and openWorldHint=false, so the trailing 'Read-only.' is largely redundant. The description still earns credit for behavioral detail the annotations cannot carry: prototype/forward declarations are listed once at the definition, and Go `var x = func(...)` entries also work with body/parameter/signature tools — non-obvious rules that affect how an agent reuses the returned names.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core behavior and output format, then usage rules, then an example — a sensible ordering. The long enumeration of kinds and edge cases (overloads, C++ qualified names, `struct struct stat`) is dense but each item disambiguates a real target-name case; only the redundant 'Read-only.' line fails to earn its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter listing tool with an output schema and full annotation coverage, nothing an agent needs is missing: output line format, target-name conventions per language, dedup rules, and the routing decision against read_symbol are all present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter and schema description coverage is 100%, so the schema already fully documents `file_path` as an absolute path. The description's example (`file_path="/abs/path/to/module.py"`) merely restates that format rather than adding new semantics, so this sits at the baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the symbols of a source file') and immediately specifies the exact output shape (`<kind> <name> (line N)`, indented members). It also pre-empts confusion with the mutation siblings by explaining that each listed name is the exact target string the other tools consume.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: before editing a file, to get exact target names' and 'Don't use this when: you already know the target (read_symbol reads one symbol).' The alternative sibling is named with the condition that selects it, so no inference is required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_importsA
Read-only

Return the import statements of a source file (imports, includes, Ruby requires), without reading the rest. Read-only.

Use this when: checking a file's dependencies, as before add_import. Don't use this when: changing imports (add_import, remove_import).

Example: file_path="/abs/path/to/module.py"

NameRequiredDescriptionDefault
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, so the 'Read-only' line is redundant, but the description adds genuine behavioral context: it returns only the import section 'without reading the rest' of the file, implying a cheaper partial read. That is useful beyond the annotation set, though nothing is said about edge cases like files with no imports.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well front-loaded with purpose first, then usage routing, then example. The standalone 'Read-only.' sentence is largely waste given the annotations, but overall it is tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values needn't be described, the single parameter is fully documented, and the read-only nature is covered by both annotations and text. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single parameter is already documented as an absolute path, so the schema does the heavy lifting. The example is essentially a restatement of that guidance, adding minimal semantic value beyond the structured field.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return the import statements of a source file') and clarifies scope by naming the supported forms (imports, includes, Ruby requires). It is immediately distinguishable from siblings like add_import and remove_import.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly gives a 'Use this when' condition (checking dependencies, before add_import) and a 'Don't use this when' exclusion routing to add_import/remove_import. No inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_symbolA
Read-only

Return the source of one symbol (function, class, method, variable) or the value of a config key, without reading the whole file. Read-only. "full" is the text replace_function and delete_symbol take: decorators, a JS/TS export, a C++ template <...> header and code that only wraps the symbol (a Go type, a closure's key, a C typedef) included, a C/C++ class ending with its ;. It starts at the symbol's first byte, the later lines at their file indentation. "interface" gives a class's header, fields and method signatures (bodies as ...), indented as in the class, or a function's signature; "signature" the signature or class header alone, after decorators. Both refuse a variable, constant, field, type alias or macro, saying what it is. A name with a prototype or forward declaration reads as its definition.

Use this when: reading one symbol; pick the narrowest depth. Don't use this when: you need the file's outline (list_symbols) or its imports (read_imports).

Example: target="LRUCache", depth="interface"

NameRequiredDescriptionDefault
depthNo'full' (default) the whole symbol; 'interface' a class skeleton or function signature; 'signature' the signature or class header. Config files take only 'full'. Other values are refused.full
targetYesSymbol name as list_symbols lists it, dotted for nesting ('LRUCache.get', 'A.m(int)'), or a config key path from the top level ('project.version'; quote a key with dots, '"editor.fontSize"').
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only/non-destructive, so the description's real value is the disclosure of output semantics that no structured field carries: exactly what 'full' text includes (decorators, JS/TS export, C++ template header, Go type wrappers, C typedefs, class trailing ';'), where the text starts and how later lines are indented, and that 'interface'/'signature' refuse variables, constants, fields, type aliases and macros with an explanatory error. Prototype/forward-declaration resolution to the definition is also a behavioral trait worth knowing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose, then the depth semantics, then a clearly labeled use/don't-use block and an example — good structure. The middle paragraph is unusually dense (a single sentence enumerating Go types, typedefs, template headers, closure keys), which costs some readability but every clause carries real information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and full parameter coverage, the description need only cover call semantics — and it does: depth choice, target naming, config-file restriction to 'full', and how symbols with prototypes resolve. Nothing an agent needs to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description nonetheless adds meaning the schema does not, notably the concrete shape of each depth's text and the refusal behavior of the narrower depths. The target-format detail (dotted nesting, quoted dotted config keys) duplicates the schema rather than extending it, which keeps it from a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return the source of one symbol ... or the value of a config key') plus the key benefit ('without reading the whole file'), which immediately separates it from file-level readers. The 'Use this when / Don't use this when' block names list_symbols and read_imports as the alternatives, so the agent can route without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit two-sided guidance: 'Use this when: reading one symbol; pick the narrowest depth' and 'Don't use this when: you need the file's outline (list_symbols) or its imports (read_imports)'. The alternatives are named with the exact condition that selects them, and the depth-selection heuristic is stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_from_arrayA
Destructive

Remove the first element matching value_match from a JSON, YAML or TOML array or a Python module-level list. An element on a line of its own goes with that line and its comment; other comments stay. The trailing-comma style is kept, and removing the only element leaves [].

Use this when: removing one list item. Don't use this when: removing a whole key (delete_key).

Example: target="project.dependencies" value_match='"old-package"'

NameRequiredDescriptionDefault
targetYesKey path from the top level to a JSON, YAML or TOML array ('project.dependencies'), or a Python module-level list's name.
file_pathYesAbsolute path to the source or config file.
value_matchYesElement to remove, compared with each element's source text, whitespace stripped (e.g. '"old-package"').

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructiveHint=true and readOnlyHint=false, and the description goes well beyond them: only the first match is removed, the element's own line and comment are removed while other comments stay, trailing-comma style is preserved, and removing the sole element yields `[]`. The one gap is behavior on no-match (no-op, error, or silent success), which matters for a destructive edit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core semantics, then a matching-detail paragraph, then structured Use/Don't-use lines and a compact example. Every sentence carries actionable information; nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and the formatting/matching rules are unusually complete. The missing piece is failure behavior when value_match matches nothing and whether matching is case- or whitespace-sensitive beyond the stated whitespace-stripped comparison.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, and the description largely restates the documented parameters via the example (target, value_match). It does add one non-obvious semantic — that only the first matching element is removed rather than all matches — which is why it holds at the baseline rather than dropping.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb+resource: 'Remove the first element matching value_match from a JSON, YAML or TOML array or a Python module-level list.' It also names the sibling it must not be confused with (delete_key), so an agent can select it without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Contains explicit 'Use this when: removing one list item' and 'Don't use this when: removing a whole key (delete_key)' clauses, giving both the positive trigger and the exclusion with the named alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_importA
Destructive

Remove an import line, matched by its text with surrounding whitespace stripped.

Use this when: removing an unused import. Don't use this when: removing one name from a multi-name import (remove_import_name).

Example: import_text="import os"

NameRequiredDescriptionDefault
file_pathYesAbsolute path to the source or config file.
import_textYesExact import line to remove (e.g. 'import os').

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, so the agent knows this mutates the file. The description adds the whitespace-stripping match rule, which is genuine behavioral context, but it says nothing about what happens when the import is not found, whether the file must exist, or whether the file is rewritten/backed up. With annotations covering the safety profile, this is adequate but not rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose statement first, then when/when-not, then a one-line example — everything is front-loaded and every sentence carries routing or matching information. No filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation, parameters are fully covered, and the alternative tool is named. The remaining gap is failure behavior (no matching line found) for a destructive, file-mutating operation, which an agent would benefit from knowing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both parameters are already documented, but the description adds non-obvious semantics the schema lacks: matching is by text with surrounding whitespace stripped. The example import_text="import os" also clarifies the expected string form, which goes beyond 'Exact import line to remove' in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Remove) and resource (an import line) plus the matching rule (text with surrounding whitespace stripped). It also names the sibling it is not (remove_import_name), so an agent can distinguish the two without inspecting either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: removing an unused import' and 'Don't use this when: removing one name from a multi-name import (remove_import_name)' gives both the trigger condition and the named alternative. That is exactly the routing information needed among the 28 sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_import_nameA
Destructive

Remove one name from a from <module> import ... (Python) or import { ... } from "<module>" (JS/TS) statement. A name on a line of its own goes with that line and its comment. Removing the only name removes the statement, but is refused when a default import would be left with empty braces (import D, { a } from "m"): use remove_import, then add_import.

Use this when: dropping one name from a multi-name import. Don't use this when: removing the whole import line (remove_import).

Example: module="typing" name="List"

NameRequiredDescriptionDefault
nameYesOne imported name, e.g. 'Optional'.
moduleYesModule of the import as written, without quotes (e.g. 'typing' or './utils').
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the destructiveHint=true annotation by disclosing concrete edit semantics: a name on its own line takes the line and its comment, removing the only name removes the statement, and the default-import empty-braces case is refused with a prescribed workaround (remove_import then add_import). This is exactly the failure-mode context an agent needs before invoking a destructive edit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded action sentence, then structured Use/Don't-use/Example blocks with no filler. The denser braces-refusal sentence is behaviorally necessary rather than padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values need no explanation; parameters are fully covered in the schema; and the description supplies the edge cases and refusal behavior that structured fields cannot express.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and each parameter already documents its format, so baseline is 3. The description's example (module="typing", name="List") illustrates the pairing but largely restates what the schema already says about module and name.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource ('Remove one name from a from-import / import {...} statement') and covers both Python and JS/TS syntax. It is immediately distinguishable from sibling remove_import (whole statement) and add_import_name.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: dropping one name from a multi-name import' and 'Don't use this when: removing the whole import line (remove_import)', naming the alternative tool and the condition that selects it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_parameterA
Destructive

Remove a parameter by name from a function's signature, keeping the body. A parameter on a line of its own goes with that line and its comment. Removing the only one leaves (), or (void) in C. In Python a * or / left invalid goes too, and a *args with keyword-only parameters after it leaves a bare * (def f(a, *args, b) minus args is def f(a, *, b)). One Go name of a, b int, or one name that a destructuring parameter binds, goes alone. Refused: a name the function lacks, and in C the last named parameter before ....

Use this when: dropping one parameter without retyping the signature. Don't use this when: rewriting the signature (replace_signature).

Example: target="LRUCache.get" parameter_name="default"

NameRequiredDescriptionDefault
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
file_pathYesAbsolute path to the source or config file.
parameter_nameYesBare name of the parameter (e.g. 'default'), without its type or default value.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations (destructiveHint=true, readOnlyHint=false) only signal that this mutates; the description adds substantial context beyond that: comments travel with a line-own parameter, `()`/`(void)` results, Python `*`/`/` cleanup, `*args` leaving a bare `*`, Go `a, b int` and destructuring singletons, and explicit refusal cases. That is exactly the edge-case behavior an agent cannot infer from annotations or schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then behavior, then when/when-not, then an example — a sensible progression with no filler sentences. It is dense with parenthetical edge cases, which earns its place but could be tightened slightly; the example partially restates the schema's own field descriptions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive mutation tool, the agent knows the effect on the signature and body, the formatting-preservation rules, and the conditions under which the call is refused. An output schema exists, so return-value detail is unnecessary — nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description goes further by clarifying what a 'name' means operationally — one name of a Go `a, b int` group, or a name bound by a destructuring parameter, is removed alone. The worked example (target="LRUCache.get", parameter_name="default") also reinforces the dotted target format.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource: 'Remove a parameter by name from a function's signature, keeping the body.' The scope qualifier ('keeping the body') immediately distinguishes it from replace_signature and replace_function, so an agent can route without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when: dropping one parameter without retyping the signature' and 'Don't use this when: rewriting the signature (replace_signature)', naming the sibling alternative. Both the positive trigger and the exclusion are stated, not implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

replace_docstringA
Destructive

Replace or add the docstring of a Python function, method or class, or of the module with target "". new_docstring must be one string literal with its quotes; anything else (bare text, an f-string, bytes, an empty value) is refused. A new docstring goes first in the body; the module's goes below a #! line and encoding or header comments. A new docstring for a body on its signature's line is refused (use replace_function_body); so is any non-Python file.

Use this when: adding or updating a Python docstring. Don't use this when: editing a # comment above the symbol (edit_leading_comment).

Example: target="LRUCache.get" new_docstring='"""Return the cached value, or None."""'

NameRequiredDescriptionDefault
targetYesPython function, method or class name, dotted for nesting (e.g. 'LRUCache.get'), or '' for the module.
file_pathYesAbsolute path to the source or config file.
new_docstringYesOne Python string literal with its quotes, usually triple-quoted.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructiveHint=true and readOnlyHint=false, and the description goes well beyond that: it enumerates refusal conditions (bare text, f-strings, bytes, empty value, non-Python files, signature-line bodies), states where the new docstring is placed in the body, and specifies module-level placement below `#!` and encoding/header comments. These are non-obvious mutation side effects an agent cannot get from the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The placement and refusal rules are front-loaded in the first paragraph, followed by the when/when-not block and a compact example – well ordered and dense. The 'Use this when: adding or updating a Python docstring' line is a light restatement of what the first paragraph already established, a small redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a three-required-parameter mutating edit tool with an output schema available, everything an agent needs to call it correctly is present: valid input form, placement semantics, refused cases, and escalation paths to sibling tools. No gaps remain that would cause a malformed or misrouted call.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real constraints the schema does not: new_docstring must include its own quotes, f-strings and bytes are refused, and target="" means the module. The worked example anchors both arguments concretely; it stops short of describing any return or error payload shape, but the output schema covers that.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (replace/add) and a specific resource (Python docstring of a function, method, class, or module) and pins down the target addressing scheme. It actively distinguishes itself from siblings by naming replace_function_body and edit_leading_comment, so an agent can route without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Has explicit 'Use this when: adding or updating a Python docstring' and 'Don't use this when: editing a `#` comment above the symbol (edit_leading_comment)', naming the alternative tool. It also names replace_function_body as the fallback for the signature-line-body case, giving three concrete routing rules.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

replace_functionA
Destructive

Replace a whole symbol (decorators, signature and body) with new content. The replaced span is what read_symbol returns: decorators, a JS/TS export, a C++ template <...> header and code that only wraps the symbol are part of it, so keep them in content. read_symbol's text, edited or not, goes in as it is, and may become several members; content written from column 0 is re-indented to the symbol's level. A symbol that shares a line with other code has only its own text replaced. For one name of int a, b; the content must keep the others. Refused in JSON, YAML and TOML files (use replace_value).

Use this when: rewriting a symbol top to bottom (name, parameters and body). Don't use this when: only the body changes (replace_function_body), only the signature (replace_signature), or a few lines (replace_in_body).

Example: target="LRUCache.get" content='def get(self, key, default=None):\n return self.items.get(key, default)'

NameRequiredDescriptionDefault
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
contentYesComplete new source of the symbol, decorators included, in read_symbol's shape or written from column 0.
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructiveHint=true/openWorldHint=false, and the description adds real behavioral context on top: exactly what span gets replaced, re-indentation from column 0, shared-line symbols being narrowed, multi-member expansion, and the multi-declarator `int a, b;` constraint. This is the kind of edge-case disclosure a mutating tool needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then edge cases, then when/when-not, then an example — a sensible ordering. It runs long and a few clauses (e.g., restating that decorators stay in content) overlap with the schema, keeping it just short of ideal density.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations, a full-coverage schema, and an output schema present, everything an agent needs is covered: span semantics, formatting normalization, format exclusions, and sibling routing. Nothing material is left to inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is high; the description still adds meaning by saying content must retain decorators/exports/template headers and may be supplied in read_symbol's exact shape, plus a concrete target/content example. It does not add much beyond the schema on `file_path`, but that parameter is self-evident.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a precise verb+resource+scope: 'Replace a whole symbol (decorators, signature and body) with new content.' That phrasing immediately distinguishes it from partial-edit siblings like replace_function_body and replace_signature, so no schema inspection is needed to tell them apart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' (rewriting a symbol top to bottom) and 'Don't use this when' with three named alternatives mapped to concrete conditions (body-only, signature-only, few lines). It also routes JSON/YAML/TOML writers to replace_value, covering a whole class of misuse.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

replace_function_bodyA
Destructive

Replace only the body of a function, keeping its signature and decorators. A body on its signature's line is rebuilt over lines of its own, and an empty body gets content one level deeper than the function. A Ruby method's rescue, else and ensure clauses stay and only the code before them is replaced, unless content has clauses of its own. Refused for a function without a block body: a Ruby endless method, or an arrow function whose body is an expression (use replace_function).

Use this when: changing the implementation but not the interface. Don't use this when: the parameters or return type change too (replace_function), or only a few lines change (replace_in_body).

Example: target="LRUCache.get" content='if key in self.items:\n return self.items[key]\nreturn None'

NameRequiredDescriptionDefault
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
contentYesNew body statements without the signature or braces, re-indented as a block to the body's indentation.
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare destructiveHint=true/readOnlyHint=false, but the description goes far beyond: it discloses single-line body reconstruction, indentation depth for empty bodies, preservation of Ruby rescue/else/ensure clauses, and explicit refusal conditions (endless methods, expression-bodied arrow functions). This is exactly the behavioral context a caller needs before a destructive edit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded purpose followed by well-labeled when/when-not and an example, with no filler. It is dense and multi-clause but each sentence (clauses, refusals, example) earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive edit tool, it covers scope, edge cases, refusal behavior, alternatives, and an example; an output schema exists so return values need no explanation. Complete for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the worked example clarifies the dotted nesting syntax for target and the escaped-newline literal form for content, adding practical meaning beyond the schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Replace only the body of a function') plus the scope boundary ('keeping its signature and decorators'). The body-on-line, empty-body, and Ruby clause cases make the exact operation unambiguous versus replace_function and replace_in_body.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Has explicit 'Use this when' and 'Don't use this when' blocks that name both alternatives (replace_function, replace_in_body) and the exact conditions selecting each. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

replace_in_bodyA
Destructive

Replace a snippet inside a function's body; the match is scoped to that body, so text elsewhere in the file never matches. A snippet found nowhere is refused; one found in several places is refused with their line numbers: add context.

Use this when: changing one statement or block in a long body. Don't use this when: replacing the whole body (replace_function_body), or editing a sub-expression no unique snippet can locate (your normal edit tool).

Example (a block written without its indentation): target="handle" old_snippet="if retry:\n wait()" new_snippet="if retry:\n backoff()\n wait()"

NameRequiredDescriptionDefault
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
file_pathYesAbsolute path to the source or config file.
new_snippetYesReplacement text; its lines move with the match's indentation, keeping their relative indentation, and text already at the file's indentation goes in as written. '' deletes the snippet.
old_snippetYesText in the target's body, matching one place: its exact text at token boundaries, or else whole lines with the same indentation added to or removed from each.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, so the safety profile is covered. The description adds real behavioral content beyond that: ambiguous matches are refused with line numbers, zero matches are refused, and new-snippet lines follow the match's indentation. It doesn't say what the refusal looks like (error vs. status), which keeps it off a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded purpose, then clearly labeled use/don't-use blocks and one worked example. Slightly long because of the multi-line example, but each block carries information an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values needn't be explained, and all four params are documented in-schema. The description closes the main remaining gaps (scoping, uniqueness, refusal conditions, indentation). Only the shape of a refusal response is left implicit.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description goes further by explaining the uniqueness requirement for old_snippet with a concrete example, including the indentation-relaxed writing style, which is the non-obvious part agents get wrong.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('replace a snippet inside a function's body') plus the scoping constraint that distinguishes it from a plain text replace. It also names the sibling it is not (replace_function_body) so an agent can route without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' / 'Don't use this when' sections with two named alternatives: replace_function_body for whole-body replacement, and the normal edit tool for sub-expressions no unique snippet can locate. The selection condition is fully spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

replace_signatureA
Destructive

Replace the signature of a function, or the header of a class, keeping its body. Existing decorators, Java annotations and a C++ template <...> header stay unless new_signature has its own. Code that read_symbol's signature starts with (a JS/TS const f =, a closure's key, a TS declare, a Ruby private) is replaced when new_signature starts with it, else kept. A method without a body keeps its ;. Refused for a symbol with no signature, such as a variable.

Use this when: changing a function's parameters, return type or name. Don't use this when: the body changes too (replace_function), or one parameter is added or removed (add_parameter, remove_parameter).

Example: target="LRUCache.get" new_signature="def get(self, key, default=None):"

NameRequiredDescriptionDefault
targetYesFunction or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)').
file_pathYesAbsolute path to the source or config file.
new_signatureYesEverything from the start of the definition up to its body, as read_symbol(depth='signature') returns it (e.g. 'def get(self, key):', JS 'get(key, d)' without the '{'); indentation on its first line is ignored.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and non-read-only, so the description's job is to explain the blast radius — and it does: the body is preserved, decorators/Java annotations/`template<...>` headers survive unless overridden, prefix tokens are conditionally replaced, and the operation is refused for symbols with no signature. That is materially more than the annotations convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then scoped by when/when-not and anchored by an example — a well-organized shape. The decorator/template preservation sentence is dense but relevant to correctness; nothing is redundant filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described. The description covers the mutation's side effects, preservation rules, and refusal cases, which is everything an agent needs to invoke this destructive edit safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents all three parameters. The description still adds value by clarifying how new_signature interacts with the existing signature (prefix kept vs. replaced) and by giving a concrete target/new_signature example, though it does not restate the dotted-nesting target format.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('replace the signature of a function, or the header of a class, keeping its body'), which is precise enough to distinguish it from replace_function and replace_function_body without opening either schema. The refusal condition ('symbol with no signature, such as a variable') further narrows the scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' (parameter/return type/name changes) and 'Don't use this when' clauses name the exact alternatives — replace_function for body changes, add_parameter/remove_parameter for single-parameter edits. The routing decision is fully determined.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

replace_valueA
Destructive

Replace the value of an existing config key, dict or object entry, or of a variable, constant, field or macro in a source file (X = 1, const X = 1;, int x = 1;, #define N 3). Only the value changes; a declaration without one gets one (let x; becomes let x = 2;, YAML a: becomes a: 2). Refused: a function, class or type (use replace_function), a name sharing one value with others (a = b = 1) and a declaration that cannot have a value (extern, TS declare). Array elements are not addressable.

Use this when: updating a value that exists. Don't use this when: the key is new (add_key), or you edit an array (append_to_array, remove_from_array).

Example: target="project.version" content='"2.0.0"'

NameRequiredDescriptionDefault
targetYesKey path from the top level in a config file ('project.version'; quote a key with dots, '"editor.fontSize"'; keys in YAML flow mappings and TOML inline tables count, 'a.x'), a path into a Python or JS/TS module-level dict or object ('CONFIG."timeout"', 'CONFIG.server.port'), or a variable, constant, field or macro name ('MAX', 'A.x').
contentYesValue as source text in the file's syntax, strings quoted (e.g. '"2.0.0"' or '42').
file_pathYesAbsolute path to the source or config file.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations (destructiveHint=true, readOnlyHint=false) by disclosing exactly what mutates: only the value changes, and a declaration lacking a value gains one (`let x;` -> `let x = 2;`, YAML `a:` -> `a: 2`). It also lists three hard refusal conditions (chained `a = b = 1`, `extern`, TS `declare`) and states array elements are not addressable. Output schema exists, so return format need not be repeated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then structured into use/don't-use blocks and an example. Despite the length, every sentence contributes either routing, a refusal boundary, or a semantic detail; there is no padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-parameter destructive write tool with an output schema and full annotation coverage, the description supplies everything an agent needs: scope, refusal cases, sibling routing, and a worked example. No material gap remains.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description adds value by showing a concrete target/content pair (target="project.version", content='"2.0.0"'), which conveys that content must be quoted source text in the file's syntax — a common failure point. It stops short of explaining the target path grammar in depth, which is left to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Replace) and resource (value of an existing config key, dict/object entry, variable, constant, field or macro), then enumerates concrete syntax forms (`X = 1`, `const X = 1;`, `#define N 3`). It also names what it explicitly is not (replace_function) and what is out of scope (array elements), so an agent can place it among the 29 siblings immediately.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Has explicit 'Use this when: updating a value that exists' and 'Don't use this when: the key is new (add_key), or you edit an array (append_to_array, remove_from_array)' sections naming the exact alternatives. The refusal list adds further routing (function/class/type -> replace_function). Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 28 tool updatesv3.2.0
    • Changedadd_field3 fields changed
      • addedInput schema / properties / class_target / description
        Added value: +"Class name as list_symbols lists it (a struct, interface or Go struct type too), dotted for nesting ('A.B')."
      • addedInput schema / properties / content / description
        Added value: +"Field or member declaration as source text (e.g. 'version = \"1.0\"' or Go 'version int')."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
    • Changedadd_import2 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / import_text / description
        Added value: +"Complete import line in the file's language (e.g. 'import os', '#include <stdlib.h>'); in Go a bare spec ('\"fmt\"') works too."
    • Changedadd_import_name3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / module / description
        Added value: +"Module of the import as written, without quotes (e.g. 'typing' or './utils')."
      • addedInput schema / properties / name / description
        Added value: +"One imported name, e.g. 'Optional'."
    • Changedadd_key4 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / key / description
        Added value: +"Key to add. JSON, YAML and JS/TS: one name, quoted for you when needed. TOML: a dotted key, so quote a name with a dot yourself ('\"example.com\"'). Python: a key expression, so quote string keys ('\"timeout\"')."
      • addedInput schema / properties / parent_target / description
        Added value: +"Parent container: its key path from the top level in a config file ('' for the root; quote a key with dots, 'a.\"b.c\"'), or a Python or JS/TS module-level variable's name, dotted into nested literals ('CONFIG.server')."
      • addedInput schema / properties / value / description
        Added value: +"Value as source text in the file's syntax, strings quoted (e.g. '\"2.0.0\"' or '42')."
    • Changedadd_method3 fields changed
      • addedInput schema / properties / class_target / description
        Added value: +"Class name as list_symbols lists it (a struct, interface or Go struct type too), dotted for nesting ('A.B')."
      • addedInput schema / properties / content / description
        Added value: +"Complete method source, re-indented like the class's members, keeping its own relative indentation."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
    • Changedadd_parameter4 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / parameter / description
        Added value: +"Source text of one parameter (e.g. 'default=None', 'const int* buf'); the comma is added for you."
      • addedInput schema / properties / position / description
        Added value: +"'end' (default) after the last parameter, before any that must stay last; 'start' before the first. Other values are refused."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changedadd_top_level3 fields changed
      • addedInput schema / properties / content / description
        Added value: +"Top-level code, inserted as written; read_symbol's text of a nested symbol is moved to column 0 as a whole."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / position / description
        Added value: +"'bottom' (default) at the end of the file; 'top' after the preamble (package clause, `#!` and magic comments, module docstring, directives such as 'use strict', imports and includes). Other values are refused."
    • Changedappend_to_array3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Key path from the top level to a JSON, YAML or TOML array ('project.dependencies'), or a Python module-level list's name."
      • addedInput schema / properties / value / description
        Added value: +"Value as source text in the file's syntax, strings quoted (e.g. '\"2.0.0\"' or '42')."
    • Changeddelete_in_body3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / snippet / description
        Added value: +"Text in the target's body, matching one place: its exact text at token boundaries, or else whole lines with the same indentation added to or removed from each."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changeddelete_key2 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Key path from the top level in a config file ('dependencies.tree-sitter'; quote a key with dots, '\"editor.fontSize\"'), or a path into a Python or JS/TS module-level dict or object ('CONFIG.\"timeout\"', 'CONFIG.server.port')."
    • Changeddelete_symbol3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / include_leading_comments / description
        Added value: +"True (default) also deletes the comment block above the symbol, or before it on its first line; false keeps it."
      • addedInput schema / properties / target / description
        Added value: +"Symbol name as list_symbols lists it, dotted for nesting (e.g. 'LRUCache.old_method', 'A.m(int)')."
    • Changededit_leading_comment4 fields changed
      • addedInput schema / properties / comment / description
        Added value: +"Comment text with its markers ('# note', '// note', '/** ... */'); required for 'add' and 'replace', ignored for 'remove'."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / op / description
        Added value: +"'add' inserts a comment, refused when the symbol has one (the error quotes it: use 'replace' with a block that repeats lines to keep, such as lint directives); 'replace' replaces the block, or adds one; 'remove' deletes it. Other values are refused."
      • addedInput schema / properties / target / description
        Added value: +"Symbol name as list_symbols lists it, dotted for nesting ('LRUCache.get'), or a YAML/TOML key path from the top level or TOML [table] name ('tool.uv')."
    • Changedfind_references2 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Identifier to find, a plain name, not a dotted path (e.g. 'LRUCache')."
    • Changedinsert_in_body6 fields changed
      • addedInput schema / properties / after / description
        Added value: +"Anchor in the body to insert after; '' (default) leaves this mode unused."
      • addedInput schema / properties / at / description
        Added value: +"'top' or 'bottom' of the body; '' (default) leaves this mode unused. Other values are refused."
      • addedInput schema / properties / before / description
        Added value: +"Anchor in the body to insert before; '' (default) leaves this mode unused."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / new_snippet / description
        Added value: +"Text to insert. With at, re-indented as a block to the body's indentation; with after or before, indented like the anchor (as written when already at the file's indentation there)."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changedinsert_sibling4 fields changed
      • addedInput schema / properties / content / description
        Added value: +"Code to insert, re-indented to the target's level (in tabs where the file indents with them); read_symbol's text goes in as a whole."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / position / description
        Added value: +"'before' or 'after' the target; other values are refused."
      • addedInput schema / properties / target / description
        Added value: +"Symbol name as list_symbols lists it, dotted for nesting (e.g. 'LRUCache', 'A.m')."
    • Changedlist_symbols1 field changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
    • Changedread_imports1 field changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
    • Changedread_symbol3 fields changed
      • addedInput schema / properties / depth / description
        Added value: +"'full' (default) the whole symbol; 'interface' a class skeleton or function signature; 'signature' the signature or class header. Config files take only 'full'. Other values are refused."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Symbol name as list_symbols lists it, dotted for nesting ('LRUCache.get', 'A.m(int)'), or a config key path from the top level ('project.version'; quote a key with dots, '\"editor.fontSize\"')."
    • Changedremove_from_array3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Key path from the top level to a JSON, YAML or TOML array ('project.dependencies'), or a Python module-level list's name."
      • addedInput schema / properties / value_match / description
        Added value: +"Element to remove, compared with each element's source text, whitespace stripped (e.g. '\"old-package\"')."
    • Changedremove_import2 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / import_text / description
        Added value: +"Exact import line to remove (e.g. 'import os')."
    • Changedremove_import_name3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / module / description
        Added value: +"Module of the import as written, without quotes (e.g. 'typing' or './utils')."
      • addedInput schema / properties / name / description
        Added value: +"One imported name, e.g. 'Optional'."
    • Changedremove_parameter3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / parameter_name / description
        Added value: +"Bare name of the parameter (e.g. 'default'), without its type or default value."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changedreplace_docstring3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / new_docstring / description
        Added value: +"One Python string literal with its quotes, usually triple-quoted."
      • addedInput schema / properties / target / description
        Added value: +"Python function, method or class name, dotted for nesting (e.g. 'LRUCache.get'), or '' for the module."
    • Changedreplace_function3 fields changed
      • addedInput schema / properties / content / description
        Added value: +"Complete new source of the symbol, decorators included, in read_symbol's shape or written from column 0."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changedreplace_function_body3 fields changed
      • addedInput schema / properties / content / description
        Added value: +"New body statements without the signature or braces, re-indented as a block to the body's indentation."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changedreplace_in_body4 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / new_snippet / description
        Added value: +"Replacement text; its lines move with the match's indentation, keeping their relative indentation, and text already at the file's indentation goes in as written. '' deletes the snippet."
      • addedInput schema / properties / old_snippet / description
        Added value: +"Text in the target's body, matching one place: its exact text at token boundaries, or else whole lines with the same indentation added to or removed from each."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changedreplace_signature3 fields changed
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / new_signature / description
        Added value: +"Everything from the start of the definition up to its body, as read_symbol(depth='signature') returns it (e.g. 'def get(self, key):', JS 'get(key, d)' without the '{'); indentation on its first line is ignored."
      • addedInput schema / properties / target / description
        Added value: +"Function or method name as list_symbols lists it, dotted for nesting ('LRUCache.get'); overloads take parameter types ('A.m(int)')."
    • Changedreplace_value3 fields changed
      • addedInput schema / properties / content / description
        Added value: +"Value as source text in the file's syntax, strings quoted (e.g. '\"2.0.0\"' or '42')."
      • addedInput schema / properties / file_path / description
        Added value: +"Absolute path to the source or config file."
      • addedInput schema / properties / target / description
        Added value: +"Key path from the top level in a config file ('project.version'; quote a key with dots, '\"editor.fontSize\"'; keys in YAML flow mappings and TOML inline tables count, 'a.x'), a path into a Python or JS/TS module-level dict or object ('CONFIG.\"timeout\"', 'CONFIG.server.port'), or a variable, constant, field or macro name ('MAX', 'A.x')."
  2. 17 tool updatesv2.10.0
    • Removedadd_comment_before
    • Changedadd_top_level1 field changed
      • addedInput schema / properties / position
        Added value: +{
        +  "default": "bottom",
        +  "title": "Position",
        +  "type": "string"
        +}
    • Removedappend_to_body
    • Addeddelete_in_body
    • Changeddelete_symbol1 field changed
      • addedInput schema / properties / include_leading_comments
        Added value: +{
        +  "default": true,
        +  "title": "Include Leading Comments",
        +  "type": "boolean"
        +}
    • Addededit_leading_comment
    • Removedget_signature
    • Removedinsert_after
    • Removedinsert_before
    • Addedinsert_in_body
    • Addedinsert_sibling
    • Removedprepend_to_body
    • Removedread_interface
    • Changedread_symbol1 field changed
      • addedInput schema / properties / depth
        Added value: +{
        +  "default": "full",
        +  "title": "Depth",
        +  "type": "string"
        +}
    • Removedremove_leading_comment
    • Addedreplace_in_body
    • Removedreplace_leading_comment
  3. 32 tool updatesv1.2.0
    • First observedadd_comment_before
    • First observedadd_field
    • First observedadd_import
    • First observedadd_import_name
    • First observedadd_key
    • First observedadd_method
    • First observedadd_parameter
    • First observedadd_top_level
    • First observedappend_to_array
    • First observedappend_to_body
    • First observeddelete_key
    • First observeddelete_symbol
    • First observedfind_references
    • First observedget_signature
    • First observedinsert_after
    • First observedinsert_before
    • First observedlist_symbols
    • First observedprepend_to_body
    • First observedread_imports
    • First observedread_interface
    • First observedread_symbol
    • First observedremove_from_array
    • First observedremove_import
    • First observedremove_import_name
    • First observedremove_leading_comment
    • First observedremove_parameter
    • First observedreplace_docstring
    • First observedreplace_function
    • First observedreplace_function_body
    • First observedreplace_leading_comment
    • First observedreplace_signature
    • First observedreplace_value

TDQS

A4.3/5.0

Scored across 28 tools

Disambiguation4/5

Most tools target distinct operations, and the descriptions provide explicit “Use this when / Don’t use this when” guidance to separate close neighbors like replace_function, replace_function_body, and replace_in_body. The remaining ambiguity comes from the large number of fine-grained edit tools, where an agent must carefully choose between body, signature, parameter, sibling, and top-level variants.

Naming Consistency4/5

Names are consistently snake_case and verb-first, following predictable patterns such as add_import, remove_import_name, replace_signature, and list_symbols. The minor inconsistency is the mixed use of delete and remove for deletion operations, though the names remain readable and mostly predictable.

Tool Count2/5

With 28 tools, the surface is heavy and requires agents to select among many highly granular operations. The domain is complex, but several tools could plausibly be consolidated or grouped under operation-based interfaces to reduce selection burden.

Completeness4/5

The server covers a strong lifecycle for imports, symbols, bodies, signatures, parameters, comments, docstrings, and config keys/arrays. Notable gaps remain around higher-level refactors such as rename-across-file or moving symbols, but agents can work around these by combining find_references and copy/delete operations.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers