Serena.DotNet
A .NET / C#-focused MCP (Model Context Protocol) coding agent inspired by oraios/serena. Ships as a self-contained .NET global tool with 38 tools for navigating, understanding, and editing C# code via Roslyn LSP integration. Features a human-readable on-disk symbol cache, outline-first responses for large symbol bodies, lazy LSP startup, and BOM-preserving file edits.
Ask AI about Serena.DotNet
Powered by Claude Β· Grounded in docs
I know everything about Serena.DotNet. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
Serena.DotNet
A code-aware MCP (Model Context Protocol) coding agent for C# / .NET, with Roslyn LSP integration. Targets .NET 10. Inspired by and indebted to oraios/serena.
Distributed as the global tool Serena.DotNet (executable serena-dotnet).
Status: 0.1.0 β public preview. API surface is stable enough for daily use; expect minor tool-schema tweaks before 1.0.
Origin
I built this because I wanted the symbol-aware MCP experience of oraios/serena without the Python/uv/uvx install dance on every dev machine. On Windows-heavy .NET shops, dotnet tool install -g is already the established way developers ship CLI tooling β one binary, one PATH entry, predictable updates, no per-project venv. This project is a from-scratch reimplementation of the agent-facing tool surface in .NET, optimized for C# codebases as the primary target. It is not a fork β no Python code was translated β but the tool naming, the symbol-aware approach, the project-activation model, and the memory system all come from oraios/serena's design.
Acknowledgements
oraios/serena by Oraios AI is the original. Their design β symbol-level tools, project-based workflow, memory system, language-server backend abstraction β is the foundation this project builds on. If you work in Python, Java, Rust, Go, TypeScript, or any of the 40+ languages they support, use theirs. They've put years of work into a polished, multi-language product. This project exists to serve a narrower audience β C# developers who already have the .NET SDK installed and want a single-binary install β and may be the right fit for that case.
Installation
dotnet tool install -g Serena.DotNet
Update an existing install:
dotnet tool update -g Serena.DotNet
No Python, no uv, no virtualenv. If you have the .NET 10 SDK, you already have everything you need.
How Serena.DotNet differs from oraios/serena
| Dimension | oraios/serena (Python) | Serena.DotNet (this project) |
|---|---|---|
| Install | uv tool install ... serena-agent@latest --prerelease=allow | dotnet tool install -g Serena.DotNet |
| Prerequisites | uv (which itself needs to be installed first) | .NET 10 SDK (most .NET devs already have it) |
| Runtime | Python 3.13 | .NET 10 native binary |
| Language scope | 40+ languages via pluggable LSP backends; also a paid JetBrains backend | C# / .NET first-class via the official Microsoft.CodeAnalysis.LanguageServer (Roslyn). Other languages possible but not the focus. |
| Symbol cache format | Internal | Human-readable JSON at .serena/cache/<lang>/symbols.json β you can cat it, grep it, diff it, or hand-edit it. Fingerprinted by file size + LastWriteTimeUtc. |
| Cache build | Built on demand during the session | Explicit, one-shot serena-dotnet project index . builds the whole repo upfront so the first find_symbol returns instantly with zero LSP traffic. |
| LSP startup | Server starts during normal flow | Lazy β Roslyn doesn't spin up at all if every query can be answered from the symbol cache. Saves multiple GB of RAM and minutes of warmup on idle sessions. |
find_symbol on huge results | Returns matches with bodies; if the response is large, the MCP client decides to spill it to a sidecar file and the agent loses navigability | Outline-first: every match's name_path, kind, file:line-range, and body_chars is emitted inline as a header before the bodies. Bodies past a per-body cap are replaced with <body omitted: N chars; re-call find_symbol with relative_path="X", name_path="Y", include_body=true to fetch> so the agent always knows what's there and how to ask for it. |
| Overload discovery | Standard symbol tools | Dedicated match_overloads: true parameter on find_symbol returns every symbol with the same leaf name regardless of parent path β designed for "show me all IsFullText(...) overloads in one call". |
| No-match recovery | Returns "no symbols found" | When find_symbol doesn't match exactly but a leaf name does match elsewhere, returns a "did you mean" list of qualified candidates with line ranges β using the cache it already loaded, no extra LSP calls. |
get_symbols_overview on .NET files | Returns top-level outline | Auto-descends through pure container kinds (Namespace, Module, Package). C# files almost always wrap their types in a single namespace, so depth=0 returns the actual class/method outline rather than the namespace span. Opt out with auto_descend_containers: false. |
| File-write encoding | Writes via Python open() defaults | BOM-preserving: edit tools sniff the file's first 3 bytes and emit a UTF-8 BOM only if the original had one. csproj/JSON/yaml files stay BOM-free; files that originally had a BOM keep it. New files are written without a BOM. |
| Per-tool timeout | Configurable | SERENA_TOOL_TIMEOUT_SECONDS enforces a hard ceiling; on expiry the Roslyn process tree is force-killed and rebuilt so a stuck LSP can't pin a CPU forever. |
| Warming/Loading/Ready telemetry | Tools may return empty mid-load | Pre-flight readiness gate: find_symbol/find_referencing_symbols throw a structured language_server_warming response with workspace state and uptime instead of pretending the symbol doesn't exist. Tool-side advice tells the agent whether to poll, scope down, or restart. |
| Maturity / scope | Years of work, 23k+ stars, 155+ contributors, 40+ languages, multi-backend | Single-author, C#-focused, 0.1.0. Use oraios/serena for breadth; use this when the .NET-specific tuning matters to you. |
CLI
serena-dotnet serve # Start the MCP server over stdio
serena-dotnet config show # Print current config
serena-dotnet config init # Write default ~/.serena/config.yml
serena-dotnet register <name> <path> # Register a project
serena-dotnet list-projects # Show registered projects
serena-dotnet setup # Initialize .serena/ in current dir
serena-dotnet doctor # Check LSP prerequisites
serena-dotnet project index . # Build the symbol cache for this repo
serena-dotnet version # Show version info
MCP Client Configuration
Minimal stdio entry for VS Code (.vscode/mcp.json) or any MCP client:
{
"servers": {
"serena-dotnet": {
"type": "stdio",
"command": "serena-dotnet",
"args": ["serve"],
"env": {
"SERENA_TOOL_TIMEOUT_SECONDS": "600",
"SERENA_LSP_REQUEST_TIMEOUT_SECONDS": "600",
"SERENA_LSP_PARALLELISM": "2"
}
}
}
}
The MCP client launches serena-dotnet serve with the workspace folder as its working directory, and Serena auto-activates whatever repo is at that path β so the same config works in every workspace, no per-repo edits. The env vars are optional but recommended for large solutions where Roslyn warmup or per-request work can exceed the lower defaults.
Tools (38)
| Category | Tools |
|---|---|
| File | read_file, create_text_file, list_dir, find_file, search_for_pattern |
| Symbol (read) | find_symbol, get_symbols_overview, find_referencing_symbols |
| Symbol (edit) | replace_symbol_body, insert_before_symbol, insert_after_symbol, rename_symbol, safe_delete_symbol |
| Line edit | insert_at_line, replace_lines, delete_lines, replace_content |
| Memory | read_memory, write_memory, list_memories, delete_memory, rename_memory, edit_memory |
| Project | activate_project, get_current_config, set_active_solution, clear_active_solution, remove_project, list_queryable_projects, query_project |
| Language server | warm_language_server, get_language_server_status, restart_language_server, kill_language_server |
| Workflow | check_onboarding_performed, onboarding, initial_instructions, execute_shell_command |
Symbol-tool ergonomics (v1.0.34)
find_symbolreturns outline-first wheninclude_body=true: a compact name-path/line-range index for every match precedes the bodies, so even when the MCP client spills the response to a sidecar file, the inline header still tells the agent where everything is. Bodies that exceed the per-body cap are replaced with a<body omitted: N chars; re-call ... include_body=true>hint that includes the exact arguments to fetch them.find_symbolwithmatch_overloads: truereturns every symbol whose leaf name matches the pattern's last segment, regardless of parent path β purpose-built for enumerating method overloads in a single call.- When
find_symbolfinds no exact match, it auto-suggests symbols with the same leaf name as a "did you mean" list (using the cache it already loaded β no extra LSP calls). get_symbols_overviewauto-descends past pure container kinds (Namespace, Module, Package). C# files almost always have a single wrapping namespace, so depth=0 now returns the actual class/method outline instead of the namespace span. Passauto_descend_containers: falseto opt out.
File-write behavior
All edit tools (replace_content, create_text_file, insert_at_line, replace_lines, delete_lines, and the symbol-edit tools) preserve the target file's existing UTF-8 BOM state. Files without a BOM stay BOM-free; files with one keep theirs. Newly created files are written without a BOM.
Performance on Large Repos
For repos with thousands of source files (e.g. monorepos with hundreds of projects), find_symbol is cache-first by default. Build the cache once per repo:
serena-dotnet project index .
After that, every find_symbol call:
- Serves results from the on-disk symbol cache (no LSP traffic).
- Runs a debounced parallel fingerprint check on cached files.
- Auto-reindexes up to 10 stale files inline so subsequent calls stay fresh.
If more than 10 cached files have changed since the last index (e.g. after a git pull), the refresher logs a warning and skips inline reindex β re-run serena-dotnet project index . to refresh the bulk.
The cache lives under .serena/cache/<lang>/symbols.json in the repo and is fingerprinted by file size + LastWriteTimeUtc.
Tunable env vars
| Variable | Default | Range | Purpose |
|---|---|---|---|
SERENA_TOOL_TIMEOUT_SECONDS | 90 | 5β3600 | Per-tool-call timeout (Roslyn force-restart on expiry) |
SERENA_LSP_REQUEST_TIMEOUT_SECONDS | 60 | 5β1800 | Per-LSP-request timeout (raise for very slow uncached requests on huge solutions) |
SERENA_LSP_PARALLELISM | 2 | 1β16 | Concurrent documentSymbol requests (higher floods Roslyn) |
SERENA_SEARCH_PARALLELISM | min(CPU, 16) | 1β32 | Parallel file scan in search_for_pattern |
SERENA_MAX_INLINE_BODY_BYTES | 8192 | 512β1048576 | Per-body inline cap for find_symbol include_body=true outline-first response |
Building from source
dotnet build
dotnet test
dotnet pack -c Release src/Serena.Cli/Serena.Cli.csproj -o nupkg
dotnet tool update -g Serena.DotNet --add-source ./nupkg
See BUILD.md for additional notes.
License
MIT
