TerseSharp 0.15.2
See the version list below for details.
dotnet tool install --global TerseSharp --version 0.15.2
dotnet new tool-manifest
dotnet tool install --local TerseSharp --version 0.15.2
#tool dotnet:?package=TerseSharp&version=0.15.2
nuke :add-package TerseSharp --version 0.15.2
TerseSharp
Your agent stops reading whole C# files.
A Roslyn-powered MCP server that lets a coding agent navigate,
read, edit and refactor a .NET solution semantically — no Read, no Grep, no line-number
Edit, no shelling out. 82 tools. One install. No IDE, no licence, no network.
The same four questions, before and after
| Question | Built-in tools | TerseSharp |
|---|---|---|
| What's on this 2,000-line type? | Read → ~6,000 tokens |
get_type_outline → ~450 |
| Who calls this method? | Grep + follow-up reads → ~4,000 |
find_usages → ~200 |
| Rename it across the solution | ~5,000 tokens, misses the interface | rename_symbol → ~150, correct |
| Why is the build red? | ~8,000 tokens of MSBuild spew | build → ~600 |
Roslyn already knows all four answers. TerseSharp hands them over in the shape the agent needs: a signature list instead of a file, real call sites instead of string matches, a solution-wide rename instead of a regex sweep, deduplicated diagnostics instead of build logs.
🎨 XAML that knows about your C#
TerseSharp holds the XAML tree and the Roslyn compilation in one process — so it answers the two
questions no text tool can. WPF · Avalonia (.axaml) · WinUI · MAUI, dialect detected from the
markup namespace.
Does this binding actually bind? WPF has no compile-time binding check at all — a typo fails
silently to debug output. xaml_bindings validate=true resolves the data context from x:DataType
or d:DataContext, maps the XAML prefix through its clr-namespace:, and walks every path segment
against the real symbol:
BoundView.xaml:7 EXACT TextBlock.Text {Binding Symbol} OK Symbol on OrderViewModel
BoundView.xaml:9 EXACT TextBlock.Text {Binding Symbl} ERROR no member 'Symbl'; nearest 'Symbol'
BoundView.xaml:10 EXACT TextBlock.Text {Binding Sel.Symbol} OK Sel.Symbol on OrderViewModel
Where does this resource come from? One call instead of reading App.xaml and every merged
dictionary in order:
xaml_resolve AccentBrush
Views/OrderView.xaml:5 SolidColorBrush scope=local
Views/Themes/Dark.xaml:4 SolidColorBrush scope=theme
Plus: rename_symbol rewrites XAML too — rename a code-behind handler and the Click="…" follows;
rename a bound property and {Binding …} follows, but only where an x:Class or x:DataType proves
it. xaml_set_property edits an attribute in place without reformatting the file.
xaml_codebehind, xaml_outline, xaml_names, xaml_resources, xaml_validate, xaml_find.
Install
No IDE, no licence, no Node, no Python, no language server, no API key, no network.
dotnet tool install -g TerseSharp
Register it with your agent — TerseSharp writes the config itself, you don't hand-edit JSON:
terse install # detect installed clients and register with all of them
terse install --client claude-code # or pick one: claude-code | cursor | vscode | windsurf
terse install --skill # also install the agent skill
terse install --guard # also install the hook that BLOCKS Read/Grep/Edit on C#, XAML and .resx
terse doctor # verify SDK, MSBuild, workspace load, client registration
🔒 Make it stick. The most expensive failure mode is an agent that has TerseSharp installed and
reaches for Read/Grep anyway — every token the server saves on a call the agent never makes is
zero. terse install --guard registers terse guard as a Claude Code PreToolUse hook that
denies the built-in and names the tool to use instead:
| Denied | Read/Write/Edit/MultiEdit on .cs, .razor, .cshtml, .razor.css, .razor.js, .csproj, .props, .targets, .sln/.slnx, .xaml, .axaml, .resx, .resw · Glob/Grep scoped to them · a shell text read or listing on them (grep, rg, cat, head, tail, sed, awk, findstr, type, find, fd, ls, dir, tree, wc, nl, plus the PowerShell forms Get-ChildItem, gci, Get-Content, gc, Select-String, sls) · dotnet build, dotnet test, msbuild, vstest, dotnet format, dotnet clean — anywhere in a compound command. A denial names the matching tool family: resx_* for a resource file, razor_* for Razor markup, and for a XAML glob or shell walk it names xaml_find, xaml_resolve and xaml_styles before find_files |
| Names a tool that can do it | Write/Edit on a .cs path that does not exist yet names write_text(path, content, force=true) — no symbol tool creates a file, and a denial with no legal move is what produces a silent fallback. A relative path the hook cannot resolve is offered creation only as the "if it does not exist yet" case, never as an unconditional overwrite |
| Says freshness is handled | every .cs write denial adds that a file created or edited through write_text is picked up automatically, with no reload |
| Allowed | plain .css, .js, .csv, .csx — matching is by file extension plus the .razor.css/.razor.js pair, not substring, so an ordinary stylesheet stays editable |
| Denied | dotnet format, dotnet clean — format, cleanup fix=…, cleanup verify=true and clean replace them |
| Allowed | dotnet restore, pack, publish, run, tool — no TerseSharp tool replaces these, and a denial that names no alternative is a wall |
| Never blocks on failure | malformed hook input allows the call, so a guard fault cannot wedge a session |
Pair it with --skill: the skill teaches the swaps, the guard enforces them.
🎮 Unity: works on Unity game code too — Unity generates a real .sln with
Assembly-CSharp.csproj, so outlines, find_usages, symbol-addressed edits and compile-gated rename
across your MonoBehaviours all work. Open the project in the editor once so the project files exist.
Scene graph, inspector values and play-mode state are out of scope: TerseSharp answers questions about
your C# code, not the editor.
With no arguments the server walks up from the current directory, finds your .sln / .slnx /
.slnf / .csproj, and loads it.
Claude Code reads ~/.claude.json, or $CLAUDE_CONFIG_DIR/.claude.json when that variable is set —
terse install and terse doctor follow it, --skill lands in $CLAUDE_CONFIG_DIR/skills (else
~/.claude/skills), and doctor prints the config path it read.
Prefer to configure it by hand:
{
"mcpServers": {
"terse-sharp": {
"command": "terse",
"args": ["serve", "--workspace", "C:/path/to/YourApp.slnx"]
}
}
}
What each tool replaces
| Instead of | Use | Why |
|---|---|---|
Read a .cs file |
get_file_outline |
types + members + line ranges, no bodies |
Read to see one method |
get_symbol_source |
that member only |
Grep a type or member name |
search_symbols |
declarations only; CamelHump (OSvc → OrderService) |
Grep to find callers |
find_usages |
real references, one line per file with a src/test marker; containers=true also names the member each usage sits in |
Edit a .cs file |
replace_symbol_body |
addressed by symbol id, immune to line drift |
Read a .razor / .cshtml file |
razor_outline |
directives, component tree and @code members, each component resolved to its type |
Edit a .razor file |
razor_set_attribute |
element-addressed, and the Razor generator re-runs so a broken edit is rolled back |
| find-and-replace a name | rename_symbol |
solution-wide, incl. interfaces, overrides, doc crefs |
Read a .resx file |
resx_get |
keys and values per culture; a missing translation prints MISSING |
Grep a resource key |
resx_find · resx_usages |
across every family, or every C#/XAML/Razor site that names it |
Edit a .resx file |
resx_set · resx_remove · resx_rename |
schema header, ordering, indentation, line endings and BOM preserved |
dotnet build |
build |
deduplicated diagnostics, no MSBuild spew; a clean build is one line |
dotnet test |
run_tests |
a green run is one line; a failure carries its message, expected/actual and one source frame |
dotnet format |
format, cleanup fix=all, cleanup verify=true |
compile-gated code fixes and a one-line verdict, never raw CLI output |
dotnet clean |
clean |
freed-byte counters, also removes obj, releases the workspace's file locks |
The 83 tools
Every response is one record per line, with an explicit truncated/total and an EXACT or
HEURISTIC tag. Paths are workspace-relative.
- Workspace —
load_workspace,workspace_status,list_workspaces,unload_workspace,list_projects - Navigation —
search_symbols,get_symbol,get_file_outline,get_type_outline,get_symbol_source,find_usages,find_implementations,explore_symbol,impact_of - .NET semantics grep cannot reach —
find_registrations(DI: open generics, factories,Add*extensions),list_endpoints(ASP.NET CoreMap*) - Analyze & clean —
analyze,format,cleanup,clean,get_diagnostics - Edit —
replace_symbol_body,replace_symbol,add_member,delete_symbol,rename_symbol - Refactor —
extract_interface,move_type_to_file,move_type_to_namespace,change_signature,undo_last_change - Projects & solutions —
solution_projects,solution_add_project,solution_remove_project,project_create,project_properties,project_set_property,project_add_reference,project_remove_reference,package_list,package_add,package_remove - XAML —
xaml_outline,xaml_names,xaml_resources,xaml_resolve,xaml_styles,xaml_bindings,xaml_validate,xaml_find,xaml_codebehind,xaml_localization,xaml_set_property,xaml_add_element,xaml_remove_element—xaml_resolvereports every declaration of a resource key across the workspace with its scope, andxaml_bindings validate=truechecks each binding path against thex:DataTypeord:DataContexttype resolved through Roslyn - Localization (
.resx/.resw) —resx_files,resx_get,resx_find,resx_usages,resx_set,resx_remove,resx_rename,resx_validate—resx_getprints every key with its value per culture andMISSINGwhere a translation is absent,resx_validatereports missing translations, placeholder mismatches, duplicate names, orphans, empty values and a stale designer, and the writers rewrite only the<data>element they address, keeping the schema header, ordering, indentation, line endings and byte order mark intact - Razor / Blazor —
razor_outline,razor_component,razor_find,razor_bindings,razor_codebehind,razor_validate,razor_set_attribute,razor_add_element,razor_remove_element,razor_set_directive— components resolve through the Razor source generator, sorazor_outlinenames the real type of every<Card />,razor_componentreports the parameters of a component from source or a referenced package, andrazor_validatereports the faults the compiler does not: an unknown[Parameter], a duplicate@pageroute, a@bindwith no setter, an unregistered@inject - Files —
read_text,write_text,edit_text,find_files,search_text,search_regex - Build & test —
build,run_tests,rerun_failed,list_tests
Analysis without a licence
analyze runs the compiler plus every analyzer your projects already reference - CA rules,
StyleCop, SonarAnalyzer, Roslynator, anything in your PackageReference list - down to info and
hidden severity, which a normal build hides. It also reports dead code in the same list -
unreferenced private members as TERSE001, plus the compiler's unused-field and unreachable-code
hints - so one call covers everything. cleanup removes unused using directives, sorts what
remains System-first and reformats to your .editorconfig. cleanup fix=style|analyzers|all also applies the code fixes of every analyzer the project references - the in-process equivalent of dotnet format style and dotnet format analyzers - compile-gated, rolled back if it breaks the build, and reporting UNFIXED <id> for anything no fixer covers. format verify=true and cleanup verify=true replace --verify-no-changes with a one-line verdict, path= takes a file, a directory or a glob, and generated code is never rewritten. clean replaces dotnet clean: it deletes bin and obj and reports projects=, files= and freedBytes= instead of MSBuild output, releasing the workspace's own file locks first when they block the delete; it is not covered by undo_last_change. All Roslyn: no IDE, no external tool,
no licence, no network.
Every response is one record per line, with an explicit truncated/total and an EXACT
(Roslyn-resolved) or HEURISTIC (text/index) tag.
Freshness — the workspace follows the disk
A loaded workspace used to be a snapshot: a file created with write_text, an edit from your IDE or a
git checkout never reached the Roslyn solution, so the next replace_symbol answered from stale
state with an EXACT tag. It now tracks the tree.
A
FileSystemWatcherper workspace nominates changed paths. It is a hint, not a source of truth: state changes only after a content comparison, so a dropped, duplicated or out-of-order OS event can delay a refresh but never corrupt one, and the server's own writes are no-ops.Sync is lazy — events accumulate and are drained by the next call that needs semantics, so a
git checkoutstorm costs one reload, not one per file.read_text,write_text,edit_text,find_files,search_textandsearch_regexanswer from disk and skip it.A targeted stamp check compares one file's
(LastWriteTimeUtc, Length)before answering about it, which is why correctness survives a dropped event and--no-watch.Doubt is a rebuild — a changed
.csproj/.props/.targets/.sln/global.json/.editorconfig, a.csadded or removed under a project's directory, a watcher buffer overflow or an over-cap pending set reload the solution rather than guess. A.csoutside every project directory belongs to no project and is ignored. A call already in flight keeps answering from the snapshot it started with.Four generation counters —
Code,Project,Xaml,Resx— so a.csedit does not invalidate the XAML graph and a reload bumpsCodeandProjectonly. They carry across a reload; compare them for inequality rather than ordering, because unloading and loading a workspace afresh starts them again.Undo knows it was overtaken — an external change to a file an undo snapshot covers drops that snapshot and every one above it, and
undo_last_changesays so instead of silently reverting someone else's work.Repeat questions are answered from an index, not a rescan —
xaml_resolve,xaml_validate,xaml_styles,xaml_localization,xaml_find, theresx_*tools,find_registrationsandlist_endpointsused to walk and re-parse the whole tree on every call. They now share one index per workspace, built once per generation and reused until that counter moves, so the second call reads no file at all. When a generation moves, only the files whose(LastWriteTimeUtc, Length)changed are re-parsed — a one-file edit in a 200-file tree costs one parse, not 200. When the watcher is off or degraded, the index verifies by stamp sweep before answering. Parsed documents sit behind a bounded LRU (128 documents or 32 MB) because anXDocumentcosts 5-10× its file, soxaml_findand the XAML sweep insidefind_usages— the two that need every document rather than its index record — still re-parse beyond that cap.
workspace_status reports watch=active gen=c12/p1/x3/r0/rz2 pending=0 lastSyncMs=8 gaps=0 and
index=xaml(hit=12 miss=1 files=9) resx(hit=4 miss=1 families=2) code(hit=0 miss=0 calls=-) razor(hit=3 miss=1 files=10) documents=9/128 parses=9.
load_workspace(reload: true) forces a reload; --no-watch (or TERSE_WATCH=0) turns the watcher
off for constrained containers, and terse doctor reports whether this platform supports file
watching at all.
Safety
- Symbol-addressed edits — no
old_stringecho, no line numbers to drift. dryRunon every mutation returns the unified diff and writes nothing.- Compile-gated — an edit that introduces a new compile error is rolled back and the error
returned. Pre-existing errors never block an edit.
allowErrors: trueopts out. - Short symbol references — an outline prints
OrderService.Submit(Order)rather than a 200-character documentation id, and every tool that takes asymbolIdaccepts that name back. A member a short name cannot address unambiguously — a constructor, operator, indexer, generic or explicit interface implementation — keeps its documentation id, so every reference an outline prints resolves.ids=fullprints ids for everything; an ambiguous name lists the candidates rather than guessing. - Truncation that steers — a truncated listing says which parameter narrows it.
- Diff-only responses — mutations return the diff, a changed-line count and
errors=N (+D) warnings=N (+D), never the file. AdryRunthat would be rolled back says so and names the errors it would introduce. - Workspace containment — paths compare by whole segment, so root
C:\repodoes not containC:\repoEvil. --read-onlymakes every mutating tool refuse and touch nothing.
Parallel worktrees
Run several agents at once across several git worktrees of one repo, and across unrelated repos —
one server holding many workspaces (LRU, default 4), or many processes, or both. Every answer names
its worktree and branch, and an ambiguous request returns AMBIGUOUS_WORKSPACE listing the
candidates instead of guessing — answering from the wrong checkout is the one failure an agent
cannot detect.
Links
License
MIT Licensed. See LICENSE.
Built on Roslyn and the MCP C# SDK.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.33.0 | 0 | 8/12/2026 |
| 0.32.0 | 26 | 8/11/2026 |
| 0.31.0 | 36 | 8/11/2026 |
| 0.30.0 | 54 | 8/10/2026 |
| 0.29.0 | 48 | 8/10/2026 |
| 0.28.0 | 59 | 8/8/2026 |
| 0.27.0 | 55 | 8/8/2026 |
| 0.26.0 | 58 | 8/8/2026 |
| 0.25.0 | 57 | 8/8/2026 |
| 0.24.0 | 53 | 8/7/2026 |
| 0.23.0 | 60 | 8/7/2026 |
| 0.22.0 | 58 | 8/6/2026 |
| 0.21.0 | 58 | 8/6/2026 |
| 0.20.0 | 56 | 8/6/2026 |
| 0.19.0 | 71 | 8/5/2026 |
| 0.18.0 | 97 | 8/4/2026 |
| 0.17.1 | 98 | 8/3/2026 |
| 0.17.0 | 102 | 8/1/2026 |
| 0.16.0 | 90 | 8/1/2026 |
| 0.15.2 | 99 | 8/1/2026 |