plumb
OfficialServer Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| prompts | {} |
| resources | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| workspace_symbolsA | Search for symbols (functions, types, variables, constants) by name or substring across the entire workspace — instant, uses the LSP index. Pass uri to restrict the search to that one document instead. Returns names, kinds, and source locations. |
| get_definitionA | Returns the SOURCE LOCATION (file path + line number) of where a symbol is defined. PREFER a name (uri + symbol_name) — plumb resolves the exact identifier position for you, avoiding off-by-one errors; a raw file position (uri + line + character) is the fallback and, when it lands off an identifier, is snapped to the enclosing symbol. Use when you need to navigate to the implementation of a symbol. For documentation or type signatures at the same position, use explain_symbol instead. |
| explain_symbolA | Returns DOCUMENTATION and type information (LSP hover content: function signature, doc comment, often in Markdown) for the symbol at the given position or by name. PREFER a name (uri + symbol_name) — plumb resolves the exact identifier position for you, avoiding off-by-one errors; a raw file position (uri + line + character) is the fallback and, when it lands off an identifier, is snapped to the enclosing symbol. Use when you need to understand what a symbol is without navigating to its source. For the file location of where the symbol is defined, use get_definition instead. |
| file_outlineA | Return a token-cheap skeleton of a file: every function, type, method, class, and constant as its signature line with the body collapsed, nested by containment, with byte-precise 1-based line ranges. Use it to understand a large file's shape in one call without reading it — a 2000-line file becomes a few hundred tokens. Symbols come from the language server when available, falling back to the tree-sitter topology index when the server is cold or does not cover the file (source is annotated). Set include_docs=false to omit leading doc-comment lines. |
| find_referencesA | Find all references to a symbol across the entire workspace. Returns file path, line number, and the source line at each reference site. PREFER a name (uri + symbol_name) — plumb resolves the exact identifier position for you, avoiding off-by-one errors; a raw file position (uri + line + character) is the fallback and, when it lands off an identifier, is snapped to the enclosing symbol. |
| call_hierarchyA | Show the call hierarchy for a symbol: who calls it (incoming) and what it calls (outgoing). PREFER a name (uri + symbol_name) — plumb resolves the exact identifier position for you, avoiding off-by-one errors; a raw file position (uri + line + character) is the fallback and, when it lands off an identifier, is snapped to the enclosing symbol. Useful for understanding control flow and assessing the impact of changes. When the language server provides no call hierarchy for the file (e.g. zls for Zig), falls back to the topology call graph, annotated source=topology (approximate). |
| type_hierarchyA | Show the type hierarchy for a type: its supertypes (interfaces it implements, embedded types) and subtypes (types that implement or embed it). PREFER a name (uri + symbol_name) — plumb resolves the exact identifier position for you, avoiding off-by-one errors; a raw file position (uri + line + character) is the fallback and, when it lands off an identifier, is snapped to the enclosing symbol. Useful for understanding inheritance and polymorphism. |
| diagnosticsA | Return LSP errors, warnings, and hints for one file, several files, or the whole workspace. Pass uris (a list of file:// URIs) to check specific files — omit or pass [] to query all files. A single call with multiple URIs replaces multiple single-file calls. Results are pushed by the language server as it analyses code; they may be empty if the server has not yet sent any diagnostics — a report taken while the server is still warming is labelled INCOMPLETE, so a clean result then is not proof the code compiles. |
| read_fileA | Read the text contents of a file (absolute path, file:// URI, or workspace-relative path). Use start_line/end_line to stream a slice of a large file. Each line is prefixed with a 1-based line number + tab (cat -n style) for exact range math; this gutter is display-only — strip the leading '\t' before reusing a line as an edit_file/find_replace old_string. Binary files are rejected; output is capped at 200 KiB (use line ranges on large files). The header carries the file's mtime (RFC3339Nano) and SHA-256 — pass them back as expected_mtime/expected_sha on edit_file for optimistic-concurrency checks. Pass pattern to search WITHIN the file instead of windowing: it returns each matching line with its 1-based line number (and optional context_lines), so an over-cap file stays searchable in one tool — literal text by default (smart-case: case-insensitive when all lowercase), Go RE2 regex when use_regex; output is bounded by max_matches (default 200) and labelled when truncated. Combine pattern with start_line/end_line to restrict the search to a line window; pattern with limit is rejected. |
| read_symbolA | Read the source body of a named symbol (function, method, type) in one call. Accepts plain name or dotted ReceiverType.MethodName form. Returns all matches when the name is ambiguous. Each body line carries a display-only 1-based file line-number gutter ('\t', cat -n style) — strip it before reusing a line as an edit_file old_string. Falls back to a tree-sitter parse when the language server is cold or absent. |
| read_multiple_filesA | Read up to 20 files in a single call. Each file's content is returned under a '### ' heading, followed by that file's own read_file header (mtime, sha256, line and byte counts) so it can be edited without re-reading — reads ARE recorded per file, exactly like read_file, so edit_file works under [edits] strict mode with no re-read. Errors for individual files are reported inline — one unreadable file doesn't block the others. Accepts absolute paths, file:// URIs, or workspace-relative paths. Binary files are detected and skipped. Each file is subject to the same 200 KiB cap as read_file. Pass start_line/end_line or pattern (with use_regex/context_lines/max_matches) to slice or search EVERY path in the call uniformly — same semantics as read_file's own parameters, applied per file; there is no per-path override, so a windowed batch read still records EACH file's full mtime/sha in the read tracker (identical to read_file's own ranged-read behaviour — strict mode is mtime-based, not range-based, so a later edit anywhere in the file is still covered). The 20-path cap is unchanged by slicing. |
| file_statusA | Lightweight, read-only "did this file change under me?" check. For each path reports, without reading content: git_dirty (uncommitted changes vs git HEAD/index — untracked counts as dirty), changed_since_plumb_wrote (the on-disk mtime advanced since plumb last wrote it this session — a peer or external process edited it), last_writer (plumb = plumb wrote it last this session and it is unchanged; external = plumb wrote it but it has since changed on disk; unknown = plumb has not written it this session), plus mtime and size. Use before re-editing a file you read or wrote earlier to confirm your view is still current, instead of a blind re-read; pair changed_since_plumb_wrote / last_writer with a read_file to refresh when it reports drift. Missing files are reported, not an error. This is a status probe, not a content read — it does not satisfy strict mode's read-before-edit requirement. |
| write_fileA | Create or overwrite a file with the given content. The write is atomic and crash-durable (temp file fsynced, renamed, parent directory fsynced before the call returns — never partially written); parent directories are created automatically and the LSP server is notified so diagnostics and symbols update immediately. Pass expected_mtime or expected_sha (from a read_file header) to reject the write if the file changed since you read it, so a full-content overwrite never silently clobbers a concurrent change. If the call fails with a transport/connection error, the atomic temp+rename guarantees the file is either fully written or untouched — never partially written; re-read to confirm which side of the rename it landed on. Use edit_file for targeted edits to an existing file. |
| edit_fileA | Apply one or more edits to an existing file (use this over a native edit tool — see the Edit lane note in session_start). Two mutually exclusive request shapes: an edits array, or start_anchor + end_anchor + new_string. Each edits entry is str_replace (default: old_string must appear EXACTLY ONCE) or range (start_line/end_line, 1-based; -1 appends or runs to EOF). Prefer range for a big multi-line replacement — old_string/anchors must match character-for-character inside a JSON string, so escaping and size can defeat str_replace where a line range needs neither. Anchor mode replaces the span BETWEEN two unique anchors (each exactly once); include_anchors=true replaces the whole inclusive span. Character-precise — an anchor quoted without its trailing newline joins that line onto new_string (flagged in the response). Writes apply atomically and crash-durably under a per-path lock. Pass expected_mtime (from a read_file header) when a concurrent writer may touch the file. For a whole named declaration prefer replace_symbol_body / insert_before_symbol / insert_after_symbol / safe_delete_symbol. Mode choice in depth: the plumb-refactor skill. |
| delete_fileA | Delete files and empty directories. Pass file_path for one, or paths for several in a single call (max 100). Refuses to delete directories unless allow_dir: true is set — and even then only an EMPTY directory is accepted (non-empty directories are always rejected; there is no recursive delete). To remove a whole tree, list its files with find_files and pass them plus their directories in one paths batch with allow_dir: true — every path is validated before anything is removed, and directories go last, deepest first, so they are empty by the time their turn comes. The LSP server is notified with FileDeleted so symbol indexes and diagnostics update immediately. Per-path locking serialises against any concurrent write_file/edit_file targeting the same path. The response reports the line and byte count removed (bytes only for a binary or oversized file). |
| rename_fileA | Move (rename) a file. Parent directories of |
| copy_fileA | Copy a file to a new path, preserving file permissions. Parent directories of |
| transaction_applyA | Apply str_replace edits across multiple files atomically. Every operation is validated against the on-disk content first; if any old_string is missing or ambiguous, NO files are written. If writes start succeeding but one fails partway, the already-written files are rolled back to their pre-transaction content. Per-path locks prevent interleaving with other write tools. Use for refactors that must land as one unit. Up to 50 operations per call; the response lists each file with a unified diff unless show_write_diff is off. |
| undo_editA | Revert plumb's most recent write to a file — the safe alternative to |
| search_in_filesA | Exact scan of current file contents — literal text by default, regex when use_regex=true. Use search_in_files when you need every occurrence, exact verification, audits, or safe replacement prep. Unlike shell grep/rg, results are confined to the active project (no .git/, node_modules/, build artefacts, or anything else .gitignore excludes), binary files are skipped (null-byte sniff of the first 8 KB), files larger than max_file_bytes (50 MiB default) are skipped before opening, globs with a literal directory prefix (e.g. "src/**/*.go") prune sibling directories from the walk. Smart-case (case-insensitive when the pattern is all lowercase), supports context lines and glob file filters. |
| find_filesA | Workspace-scoped file/directory finder and directory lister. Unlike shell find/fd/ls, results are confined to the active project (no .git/, node_modules/, build output, or anything else .gitignore excludes), every call is recorded in the project's stats, and the pattern semantics are consistent across hosts. pattern is optional — omit it to list everything. Supports glob and regex patterns, extension and type (file/dir/any) filters, depth limits (max_depth=1 lists one level, like ls), sort_by name/size/modified, and include_details for a per-entry [FILE]/[DIR]/[LINK] marker, size, and modified time. |
| gitA | Run git through one tiered, policy-gated tool (no shell, no agent-supplied command line). Read subcommands (status, log, diff, show, blame, shortlog, branch/tag/stash listing) always run. Write (add, commit, switch, mv, branch/tag create, stash push/pop) needs [git] allow_writes (default on). Destructive (reset, clean, checkout, restore, rebase, revert, cherry-pick, branch/tag delete, stash drop) needs allow_destructive AND confirm:true. Network (push, fetch, pull) needs allow_push AND confirm:true; force-pushing a protected branch or using an ad-hoc URL/remote is always refused. add and commit are typed: add stages with -A semantics; commit takes message, plus an optional files list for a path-limited commit. Every other subcommand uses args. Cross-session guard refuses a write/destructive/network op if a DIFFERENT session moved this repo's HEAD/branch since observed (override with confirm:true). expected_head pins the exact HEAD commit those ops must be at. Full tier table, the cross-session guard, commit attribution, and the narrower plumb tool to prefer over a destructive git call: the plumb-git skill. |
| git_initA | Initialise a new git repository at the given path (git init). The directory is created if it does not exist. Set init_plumb: true to also create a .plumb/ workspace marker with a blank context.md, so plumb attaches to the project automatically on the next session. |
| run_taskA | Run a stored per-language task command — build, lint, test, e2e, verify, or a project-defined slot — configured in [tasks.]. It executes only the command the user saved for this workspace's language (no shell, no agent-supplied command line); the optional target fills a {target} placeholder with one shell-safe argument, and the shipped test defaults carry one so scoping needs no config edit. Commands run from the workspace root, or from [tasks.] working_dir when the module lives in a subdirectory. A project-supplied (.plumb/config.toml) command must be trusted first (run |
| mutation_testA | Mutation-test your own assertions: apply an explicit mutant, prove it still COMPILES, run a scoped test set, classify the result, and restore the file — the check that tells a real assertion from a vacuous one. Takes explicit mutants only (file_path + exact-once old_string/new_string, like edit_file); it does not generate them. Three outcomes: KILLED (mutant compiled and a test failed — the assertion is real), SURVIVED (mutant compiled and every test still passed — the assertion is VACUOUS, the finding that matters), and INVALID (the mutant did not apply, did not compile, could not be started, or timed out — it proves nothing and is NEVER reported as a kill; that false kill is why the compile gate exists). Scope the run with test_target, which fills the stored test command's {target} placeholder (topology_affected says which tests to name) — the shipped go/python/rust test defaults carry one, so scoping works out of the box. Commands are the stored, trust-gated [tasks.] slots run_task uses; you cannot pass a command line. Restoration is guaranteed on every exit path (pass, fail, compile error, timeout, panic, cancellation): the pre-mutation bytes are snapshotted in memory, rewritten under the same per-path lock, and SHA-256-verified before the run is reported clean. It REFUSES to touch a file with uncommitted changes (untracked included), no override — a clean file means |
| run_commandA | Run a named command from the workspace's [[command]] allow-list (build/test/lint/scripts) without leaving plumb. It runs only the exact fixed argv the user configured (no shell, no agent-supplied command line); the optional target fills a single {target} placeholder with one shell-safe argument. A command from a project's .plumb/config.toml must be trusted first (run |
| execute_shell_commandA | Run an ad-hoc shell command in the workspace via sh -c (pipes/redirects/globs work), for verifying an edit compiles or tests pass without leaving plumb. DISABLED by default: enable it with [commands] allow_shell = true in your global config, or in a project's .plumb/config.toml plus |
| agent_configA | Read and (when enabled) write a small allowlist of plumb config keys on the user's behalf — task commands ([tasks.]), log level, theme, topology excludes, quality analysers. op=describe lists exactly what you may write (always available); op=set writes a batch to the project's .plumb/config.toml, validated and applied all-or-nothing, tagged provenance=agent and one-step revertible (plumb config unset). Writing is OFF unless the user enabled [agent_config_writes]; safety-critical keys (git tiers, workspace roots, strict mode, API keys, the enable knob itself) are never writable. Use it to set up a repo's build/test commands from what you can read in the project. |
| file_diffA | Returns a unified diff between two arbitrary files. Works outside git — for tracked files use the git tool's diff subcommand instead, which understands refs and the index. Use context_lines to control surrounding context and ignore_whitespace to skip formatting-only changes. |
| find_replaceA | Grep-equivalent: find text across files with optional replacement. Search and replace text across files in a directory tree. Defaults to dry_run=true so you can preview the diff before committing. Set dry_run=false to write changes. When the [edits].show_write_diff config flag is on (the default), the response appends a per-file unified diff — in both preview and applied modes — for up to the first 20 changed files, with a "+N more file(s)" summary beyond that. Set show_write_diff=false to suppress it. Skips binary files (detected via null-byte sniff of the first 8 KB). Skips files larger than max_file_bytes (50 MiB default). Honours .gitignore. Use 'glob' to limit which files to touch (e.g. ".go", "**/.md"); a glob with a literal directory prefix (e.g. "src/**/*.go") prunes sibling directories from the walk entirely. Files are processed in parallel; output is sorted by path. For identifier refactors use rename_symbol (scope- and type-aware); find_replace is for plain-text edits (doc strings, license headers, hostnames, version strings, non-code files). |
| daemon_infoA | Returns metadata about the current MCP session and daemon process: session name (e.g. swift-falcon), session ID, daemon version, the source commit the binary was built from (with a dirty marker, or an explicit unknown), Go runtime, OS/arch, start timestamp, and uptime, plus the MCP protocol revision negotiated with this client (and, on a mismatch, the revision it offered and the capabilities it advertised), plus live config-store state (generation, last reload time, and whether a restart is needed for a pending restart-bound change), and — when available — this connection's workspace-pin provenance (how, when, and from where the pin was last set). It also reports this session's total tool-call count and its slowest calls (per-call durations from recorded stats). Use this to identify which session you are operating in or to verify the daemon state. |
| rename_sessionA | Renames the current MCP session. Pass the new name as the |
| workspace_sessionsA | Returns same-workspace session awareness: who else is actively connected to this project and what files they recently edited. you — this session's name. active_sessions — sessions on this workspace right now (name, client, how long since their last tool call). A single entry whose is_self field is true means you are the only active session — your view of the workspace is authoritative. Multiple entries mean concurrent agents are working here; treat any file a peer recently touched as potentially changed. recent_writes — the last N write operations (write_file, edit_file, rename_file, git commit, …) by all sessions on this workspace. The file path (when available), session name, operation, and age are shown. Only operations that could modify the workspace are listed: read-only git subcommands (status, log, diff, …) and dry-run previews never appear. A call that failed or was refused is kept but marked '[failed — no change applied]' — evidence the peer is working in that file even though nothing landed on disk. A successful git commit is attributed in full: its line carries the session name, the commit's short SHA and subject, and the repository, so a peer's commit is traceable to the session that authored it. When [collab] peer_awareness is on and the topology index has the file, each entry is annotated with its enclosing package/symbol (best-effort, source=topology). Use this before editing a file that another session may have recently modified: if it appears in recent_writes, re-read it first. Parameters: recent_limit — max recent-write entries to return (default 10, max 50). Workspace boundary: workspace_sessions is scoped to the caller's pinned workspace; it never reveals sessions from a different project. |
| share_intentA | Broadcast what you are working on to other agents active on this workspace RIGHT NOW, so they can steer around your in-progress work instead of colliding with it (e.g. "refactoring the rate limiter — avoid internal/tools/ratelimit*"). This is ADVISORY and a CLAIM, not a lock: it never blocks anyone's write, and what you say you are doing is not the same as what the daemon observes you did (that is workspace_sessions' recent_writes). Peers see your intent in workspace_sessions, and a peer whose write touches a path matching your path_globs gets a bounded advisory hint labelled as an unverified claim. You have at most ONE live intent — calling this again replaces it. The intent expires after ttl_minutes (default from [collab] intent_ttl_minutes) and is cleared automatically when your session ends. Delivery is by polling and hint injection only; plumb does not push to another agent. Requires [collab] intents = true; otherwise the call is refused. Strictly per-workspace; the body is secret-scrubbed before storage. Parameters: body — what you are doing (required, free text). path_globs — optional workspace-relative globs for the area you are working on (e.g. ["internal/tools/ratelimit*"]); drives peer write hints. ttl_minutes — optional expiry override; defaults to [collab] intent_ttl_minutes. |
| leave_noteA | Send a message to another agent — a named peer session, or "next" (whoever attaches to this workspace next). Send half of plumb's mailbox; check_messages is the receive half. Full etiquette — addressing, delivery, the exchange cap, cross-project rules: the plumb-chat skill. Every message belongs to a thread: omit conversation_id to start one (the reply carries its id), or quote an id you were given to reply into that thread (to may then be omitted). A thread is capped at [collab] max_exchanges messages; once spent, replies are refused. Delivery is by polling only, exactly once — via the next tool call, check_messages, or session_start. A peer idle on its human has not seen the message; silence is not refusal, so do not re-send. Messages are bound to the exact SESSION when it is connected; a disconnected peer, or "next", is delivered by name instead. Cross-project sends need the recipient project's opt-in. Requires [collab] mailbox = true; the body is secret-scrubbed. Parameters: body (required); to (peer session name or "next" — omitted means "next" on a new thread, the other participant on a reply); conversation_id (reply into an existing thread). |
| check_messagesA | Read messages other agents have sent you, optionally waiting for one to arrive. Receive half of plumb's mailbox; leave_note is the send half. Full etiquette — addressing, delivery, the exchange cap, cross-project rules: the plumb-chat skill. Omit wait_seconds (or 0) to return immediately with whatever is waiting. A positive wait_seconds BLOCKS until a message arrives or the wait expires — hand your turn to a peer instead of polling. Capped by [collab] max_wait_seconds, kept below the client's own call timeout. Each message is delivered exactly ONCE, to whichever path sees it first — this tool, the block appended to any tool result, or session_start. Re-calling will not redeliver it. Every message carries a conversation_id; quote it in leave_note to reply in thread. Also reports your OWN unread mail — any message you sent that nobody has read yet, with its age, since plumb does not push and cannot otherwise tell "read, no answer yet" from "never read". Listing is a read; it never consumes the message on the recipient's behalf. Requires [collab] mailbox = true. Parameters: wait_seconds — block up to this long for a message (default 0, no wait). |
| share_findingsA | Hand off what you have just learned to other agents on this workspace as a durable, searchable memory — RIGHT NOW, instead of waiting for the idle summary to fire when your session ends. Use it after you have mapped a subsystem, pinned down a gotcha, or worked out how something fits together, so a peer working in parallel can pick it up immediately. The finding is written through plumb's generated-memory pipeline: it is secret-scrubbed before storage, stamped with your session and the date as its provenance, and indexed for search. Peers discover it through the ordinary channels — search_memories, workspace_search, relevant_memories, memory hint injection, and the next session_start. This is AGENT-GENERATED content: it is labelled lower-confidence than a user-written memory and never displaces one in a capped hint slot. It counts against the same [memory] generated_memory_keep retention as an idle episodic summary. Nothing here is an LLM summary — you supply the text. Requires [collab] knowledge_handoff = true; otherwise the call is refused. Strictly per-workspace. Parameters: summary — a one- or two-line headline of the finding (required). description — optional longer detail appended below the summary. paths — optional workspace-relative globs the finding is about (e.g. ["internal/tools/ratelimit*"]); stored as frontmatter so relevant_memories and hint injection route it to those files. |
| session_startA | Bootstrap tool — call this first at the start of every session. Returns one-shot orientation: workspace path, language, current git branch, first 200 lines of .plumb/context.md, all saved memory names/descriptions, top-5 most-used tools, 5 most recently-modified files, 3 most recent commits, the live git tool policy (whether commits/destructive/push are enabled), and any active LSP errors/warnings. If no workspace is resolved yet, pass an absolute |
| rename_symbolA | Rename a symbol throughout the workspace using LSP semantic refactoring. The language server identifies every reference across all files and applies a precise edit set atomically. Safer than text find-and-replace: it understands scope, shadowing, and types, so it won't rename unrelated identifiers that share the name. Prefer symbol_name to identify the symbol; plumb resolves it through the document-symbol tree and queries the language server at the exact identifier position. Raw line/character remains supported and recovers from narrow "no identifier" misses by snapping once to the enclosing symbol. Runs in dry_run mode by default; set dry_run=false to apply. The response appends a per-file unified diff (a preview in dry-run, the applied change otherwise), capped at 20 files, unless show_write_diff is disabled. If the language server cannot compute the rename (an error, or an empty edit set — common with sourcekit-lsp before the build graph resolves), pass structural_fallback=true to attempt a best-effort identifier-boundary text rename via find_replace (still dry_run by default). The fallback is NOT scope-aware — it renames every whole-word occurrence in same-extension files — so review the preview before applying. |
| insert_before_symbolA | Insert text immediately before a symbol's declaration. Useful for adding a new function/method before an existing one, or prepending a doc comment. Locates the symbol via the LSP document symbol tree (no manual line counting). Provide the full text to insert in 'content' — include trailing newline if appropriate. Set include_doc_comment=true to insert before any existing leading doc comment instead of between the comment and the symbol — useful when adding a new function (with its own doc comment) above a function that already has one. The response includes a unified diff of the change — a preview in dry-run, the applied change otherwise — unless show_write_diff is disabled. Works even when the language server is cold or cannot parse the file: it then locates the symbol via a fresh tree-sitter parse (line-granular range, annotated in the output). |
| insert_after_symbolA | Insert text immediately after a symbol's declaration. Useful for adding a new method to a struct (insert after an existing one), or appending a related helper. Provide the full text to insert in 'content' — include leading newline if appropriate. The response includes a unified diff of the change — a preview in dry-run, the applied change otherwise — unless show_write_diff is disabled. Works even when the language server is cold or cannot parse the file: it then locates the symbol via a fresh tree-sitter parse (line-granular range, annotated in the output). |
| replace_symbol_bodyA | Replace the entire declaration of a symbol with new content. The replacement spans the symbol's full Range as reported by the LSP — for a function, this is from 'func' keyword through the closing '}'. Provide the complete new declaration (signature + body) in 'content'. Set include_doc_comment=true to also cover any contiguous doc comment above the symbol — gopls and most LSP servers report the symbol range starting at the declaration keyword, so without this flag the old doc comment is left orphaned. With it on, your 'content' must include the new doc comment too (or the symbol will have none). Use rename_symbol if you only want to change the symbol's name. Use this tool when changing logic, signature, or both — addressed by name_path, no line/character coordinates to compute like edit_file's range mode. The response includes a unified diff of the change — a preview in dry-run, the applied change otherwise — unless show_write_diff is disabled. Works even when the language server is cold or cannot parse the file: it then locates the symbol via a fresh tree-sitter parse (line-granular range, annotated in the output). |
| safe_delete_symbolA | Delete a symbol's declaration only if it has no remaining references. Calls LSP textDocument/references first. If any reference outside the declaration itself is found, the deletion is rejected with the list of referencing locations so the caller can decide what to do. This prevents accidental deletion of code that's still in use. Set include_doc_comment=true to also delete any contiguous doc comment above the symbol — otherwise the comment is left orphaned, pointing at whatever ends up next in the file. The response includes a unified diff of the deletion — a preview in dry-run, the applied change otherwise — unless show_write_diff is disabled. |
| move_symbolA | Move a top-level declaration (function, method, type, const, or var) from one file to another within the SAME directory/package, atomically. The symbol's full source — declaration plus, by default, its leading doc comment (include_doc_comment) — moves from source_uri to destination_uri in one all-or-nothing operation: if the destination write fails the source is rolled back. Locates the symbol via the LSP document-symbol tree, falling back to tree-sitter when the language server is cold. Scope (v1, conservative): source and destination must be in the SAME directory — plumb does not rewrite references or imports, so a move that would change a symbol's package or import path is REFUSED. destination_uri must already exist unless create_destination=true. Also refuses an ambiguous symbol (disambiguate with name_path), a path outside the workspace, or (Go) mismatched build constraints between source and destination. Dry-run by default (dry_run=true): previews the unified diff without writing; set dry_run=false to apply. Undo is per-file — reverting a move takes two undo_edit calls. Scope rationale: the plumb-refactor skill. |
| list_memoriesA | List memories saved for a workspace. Memories are markdown notes stored in /.plumb/memories/.md. They persist project-specific context — conventions, architectural decisions, gotchas — across MCP conversations. Each memory may have YAML frontmatter (name, description) used as a one-line summary in the listing. If 'workspace' is omitted, the daemon's currently-resolved workspace is used. |
| read_memoryA | Read a saved memory by name from a workspace's .plumb/memories/ directory. Returns the full markdown content (including any frontmatter). Use list_memories first to discover what memories exist. |
| write_memoryA | Write or overwrite a memory in a workspace's .plumb/memories/ directory. The memory is a markdown file at /.plumb/memories/.md. If 'description' or 'paths' is provided, frontmatter is prepended automatically — list_memories will surface the description, and relevant_memories / hint injection use paths globs to attach the memory to files. Memory names must match [A-Za-z0-9_-]+. Choose specific names that describe the memory's topic (e.g. 'auth-architecture', 'test-conventions', 'gotchas-cache-invalidation'). |
| delete_memoryA | Delete a memory by name from a workspace's .plumb/memories/ directory. Use only when explicitly asked, or when the memory has clearly become obsolete (e.g. it describes code that no longer exists). |
| search_memoriesA | Search saved memories for a workspace. When the FTS5 memory index is available and fresh, returns ranked hits (by relevance, with a bonus for user-authored memories) annotated source=memory-fts. Otherwise falls back to a deterministic grep over the markdown files, returning each match with the memory name and line. Smart-case (case-insensitive if 'pattern' is all lowercase) unless 'case_sensitive' is set; 'use_regex' forces the grep path. 'mode' (auto|fts|grep) overrides the choice; default auto. Memory-only corpus with a deterministic grep fallback — for ranked discovery across code, docs, AND memories in one call, use workspace_search instead. Useful when you don't know which memory contains a piece of context — much faster than reading every memory. |
| relevant_memoriesA | Return memories whose frontmatter 'paths:' globs match the given file. Memories can be auto-attached to specific parts of a project by adding a 'paths:' field to their frontmatter (e.g. 'paths: internal/auth/**, cmd/server/*.go'). This tool surfaces only the memories relevant to a given file — much smaller than list_memories when many memories exist. Call this when starting work on a file to discover context the LLM should load before editing. |
| topology_statusA | Report the health and statistics of the topology index for this workspace: indexer state, indexed/skipped file counts (with the recorded reason for each skipped file, most recent first), total nodes and edges, database size, last sync time, indexed languages, and the most recent indexing error if any. Returns a clear message when topology indexing is disabled. |
| topology_searchA | Ranked FTS5 search over the topology index. Narrow a broad query first: kinds, language, limit, include_snippets=false. Finds symbols, functions, types, classes, and other named entities by name, tokenised identifier (camelCase/snake_case), qualified name, signature, or docstring. Results include kind, file path, line range, match field, score, and optional snippet. Source is 'topology' (approximate; use search_in_files for exact filesystem matches). Code-structure corpus only — for ranked discovery that also spans docs and memories, use workspace_search (this tool is one of its backends). Returns a clear message when the index is disabled or empty. |
| topology_exploreA | Bounded BFS neighbourhood around a named symbol in the topology index. NARROW IT FIRST on a large file or an unfamiliar language: include_source="none" returns names only (the default, "signatures", is several times larger), and depth=1 with max_nodes=15 answers "what touches this?" in a fraction of the default budget (depth 2, 50 nodes, 30000 bytes) — raise them once you know what you are looking for. Returns the centre node, neighbour nodes, and connecting edges up to depth/max_nodes/max_bytes. Reports truncation when limits are hit. Source is 'topology' (approximate — use LSP semantic tools for authoritative reference and definition lookups). Returns an error when topology is disabled or the symbol is not in the index. |
| topology_impactA | Bidirectional BFS blast-radius analysis around a named symbol. Returns two sections: 'depends on' (outward — what the symbol depends on) and 'depended on by' (inward — what depends on this symbol). Primary use: assess blast radius before a refactor. Source is 'topology' (approximate); the topology call graph is intra-file, so for a function/method the inward section is augmented with a 'cross-file callers' block resolved via the language server (source=lsp) when one is available. Returns a clear message when topology is disabled or the symbol is not in the index. |
| topology_affectedA | After you change code, ask this which tests to run instead of running the whole suite. Given changed files or symbols, it answers with PACKAGES to run — one row each with the test count and why the package is implicated, plus the individual test names in the package the change landed in. Where the workspace's test runner takes a positional path (go, python), each row leads with a ready target to hand straight to run_task(slot:"test"), expressed relative to [tasks.].working_dir so it works from the directory that command runs in. Where the runner scopes by name or by a project-specific flag (rust, typescript, swift, zig), the directory is named and no command is guessed. A package is reached either by containing the change, or by importing a package that does (cross-package import edges). Within a reached package every test is counted, because co-location cannot tell which of them exercise the change: that is the recall bias, and it is deliberate — a missed test is worse than an extra. Results are heuristic; verify before relying. max_results bounds the number of PACKAGES, and the changed package is always listed first so a cap cannot drop it. Returns a clear message when topology is disabled. |
| topology_routesA | Pattern-matches entry-point-shaped symbol NAMES and signatures: Go handler funcs (http.HandleFunc, r.GET/POST, mux.Handle), Cobra cmd.Run/RunE, Python decorators (@app.route, @router.get, FastAPI path decorators), and Swift/Vapor idioms (RouteCollection.boot, configure(_:Application), ParsableCommand.run). It does NOT parse route registrations or call sites, so it cannot recover a path-to-handler binding (e.g. "/api/x" -> handlerFn) — it only finds functions whose name or signature looks like a known entry-point idiom. Results are candidates, not confirmed routes: each carries a confidence annotation reflecting the pattern's typical accuracy, not a resolved binding. Returns a clear message when no candidates match or topology is disabled. |
| structural_queryA | Run a curated structural check over the topology index — find symbols by SHAPE, not name. Complements topology_search (find by name) and search_in_files (find by text) with audits useful for review and refactor prep. Named queries (no raw tree-sitter queries are exposed): "undocumented-exports" (exported functions/methods/types/constants with no doc comment), "long-functions" (functions over min_lines, default 80), "unused-context" (Go functions taking context.Context whose body never references it). Results are approximate (source=topology) and confidence-labelled where the check is heuristic. Returns a clear message when the index is disabled or empty. |
| workspace_searchA | Ranked discovery across the workspace's indexed corpora: code symbols, doc sections (Markdown/HTML), and project memories. Use workspace_search when you have a conceptual question ("where is daemon locking handled?") and want likely starting points. Approximate by design and never a proof of absence — the exact lane is search_in_files (literal or regex over current file contents). Results are FTS5-ranked within each corpus and interleaved; every hit is labelled with corpus, source, field, score, and why it matched, and the header reports per-corpus index freshness (exact_match=false always). |
| minimal_diff_reviewA | Reviews a diff for signs of over-building — findings NEVER block a write, they are hints. Deterministic, no LLM: it flags a single-use abstraction, a thin forwarding wrapper, a new dependency with a well-known stdlib equivalent, a possible duplicate helper, and a logic change with no accompanying test change. Evidence is asymmetric: a check stays silent unless it can point at concrete evidence and (where defensible) a smaller alternative, so silence is NOT proof a change is minimal. Findings are labelled by confidence: high = proven from the diff text; low = leans on the topology index, which is approximate (its call graph is intra-file — unlike find_references' exact cross-file lookup) and may be a few edits stale. Reviews the working-tree diff vs base_ref (default HEAD); pass |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| orient | Get oriented — load workspace context, memories, and diagnostics in one shot. |
| whats-broken | Show all current LSP errors and warnings, then triage and suggest fixes. |
| recent-changes | Summarise recent git commits, show what changed, and flag any new diagnostics. |
| selftest | Run a self-test of every plumb tool against a disposable sandbox, then report PASS/FAIL/SKIP. |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/plumbkit/plumb'
If you have feedback or need assistance with the MCP directory API, please join our Discord server