How it works
A single local process, loaded with the same MSBuild workspace an IDE uses. This page explains what it loads, what it writes, how it decides that a change is safe, and what does — and does not — leave your machine.
The process model
Your MCP client starts the server as a child process and speaks MCP over stdin/stdout. Nothing is hosted: there is no listening port except the optional loopback dashboard. The process does four things:
- Loads the solution with Roslyn's
MSBuildWorkspace, so tools see the project graph, target frameworks, references and compiler diagnostics the way an IDE does. - Keeps derived data in
<workspace>/.visualMCP/visualmcp.db— indexes, edit history, the board, cached reports. The source files on disk remain the only truth. - Serves an optional web view on a loopback port: read-only pages over the store, plus an interactive board.
- Writes its log to a file — stdout belongs to the protocol.
At startup the server opens the environment in the background, so the first tool call does not pay for MSBuild evaluation. A client's discovery probe is detected and skips the load entirely.
One process, one workspace
The folder your client opened is the workspace: it is
fixed at startup, discovery and the store belong to it, and
load_solution accepts only a solution beneath it. Another
repository is another server, which the client already starts. The
store is the workspace's one database, keyed on its primary solution —
and two sessions on the same workspace share it, with one background
watcher elected through a lease.
The workspace is a snapshot: the server keeps it in sync with its own
writes, and after large changes made outside it — an IDE, a branch
switch — call load_solution again to refresh the model.
The solution store
.visualMCP/visualmcp.db is a SQLite database with one rule
for what goes in: only derived, reconstructible data,
always stamped with the hash or timestamp of its inputs. Activity
(every tool call, every file written — with the previous text,
compressed), the board, the symbol index, the metacode index, the
full-text index, the resolved NuGet graph and the decompilation
artifacts each have their own tables.
- Indexes are snapshots. They are built incrementally from content hashes; build or refresh them after substantial edits.
- The store is a cache, not a backup. Add
.visualMCP/to.gitignore. Deleting it costs a rebuild, not data — the board being the deliberate exception.
The dashboard
The server hosts a web view of itself at
http://127.0.0.1:4677: a home page with process facts and
the latest calls, the interactive board, one page per store table with
filtering and CSV/JSON export, the schema, the live tool catalogue and
the log tail. Only the board writes, through the same service the
board_* tools call, from a same-origin POST — so another
site cannot drive it. VISUALMCP_DASHBOARD=off disables it.
The safety model
Tools fall into four classes. Knowing which one you are calling is the whole safety story.
| Class | Examples | Behaviour |
|---|---|---|
| Read-only | find_symbol, solution_audit, inspect_code, profile_* | Reads the solution, the store or a process. Writes nothing. |
| Preview-first | simplify_code, apply_code_fix, razor_edit, most refactorings | Returns the proposed change and a line diff. Nothing is written until dryRun=false. |
| Write-through | apply_rename, change_signature, safe_delete | Performs the change; the tool's schema states the default. |
| Destructive, confirmed | git_reset with mode=hard, docker_prune | Refuses unless confirm=true is passed. |
Three further mechanisms make writes auditable and reversible:
- The compiler gate. Risky refactorings compile the project after the change and refuse it if it would add errors; the file is left untouched.
- The edit history. Every write preserves the file's BOM and line endings and records the previous text.
edit_historylists each write;revert_edit(id)restores the file exactly. - The
expectguard.razor_edittakes the text the target lines must currently hold and refuses the splice if the file no longer matches.
Privacy, stated precisely
The server is local and has no remote endpoint. It does not upload
source, telemetry or usage data. Network access happens only when a
specific job requires it: a NuGet restore during a build, an explicit
download_file, the GitHub CLI, the update check, the
symbol server you configure, and the one-time Ghidra download used by
native binary analysis.
What does reach a remote party is the conversation
your client sends to its model provider — including tool results such
as source returned by read_file or read_symbol.
That is identical to any other MCP tool or to pasting code into the
chat; VisualMCP only makes sure the server side of the conversation
stays local.