{
  "markdown": "# Roslyn-Backed MCP Server\n\n[![CI](https://github.com/darylmcd/Roslyn-Backed-MCP/actions/workflows/ci.yml/badge.svg)](https://github.com/darylmcd/Roslyn-Backed-MCP/actions/workflows/ci.yml)\n[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-listed-blue)](https://registry.modelcontextprotocol.io/)\n\nLocal-first MCP (Model Context Protocol) server for semantic C# analysis, navigation, validation, and refactoring on real `.sln` / `.slnx` / `.csproj` workspaces. It uses Roslyn and `MSBuildWorkspace`, runs over stdio, and does not require Visual Studio.\n\n> **Correct package ID:** `Darylmcd.RoslynMcp` &nbsp;·&nbsp; **CLI:** `roslynmcp` &nbsp;·&nbsp; **Plugin:** `roslyn-mcp@roslyn-mcp-marketplace`\n\n## What It Does\n\n- Loads real C# solutions and projects with session-scoped `workspaceId`s.\n- Exposes semantic navigation, diagnostics, build/test helpers, and preview/apply refactoring workflows over MCP.\n- Ships as a .NET global tool, a Claude Code plugin, and a source-buildable stdio host.\n- Publishes the authoritative live surface through `server_info` and `roslyn://server/catalog`.\n\n## Why Roslyn-Backed MCP\n\n- **No Visual Studio dependency** — runs anywhere the .NET SDK runs (Windows, macOS, Linux, containers, CI).\n- **Production ops discipline** — repeatable CI mirror (`just ci`), release verification scripts, and documented update paths for the global tool and release-pinned Claude Code plugin.\n- **Safe install defaults** — no `${user_config.*}` placeholder substitution that breaks prompt-skipping install flows; the server starts with compiled-in defaults and accepts literal overrides via project-scope `.mcp.json`.\n- **Authoritative live surface** — every release publishes `server_info` + `roslyn://server/catalog` so clients can discover the exact tool/resource/prompt set and support tier (stable vs experimental) without guessing.\n- **Preview → apply discipline** — refactoring tools issue preview tokens with TTLs and a verify step before mutating the workspace, so agents can dry-run multi-file edits.\n- **Three install paths** — pick one: global tool (`dotnet tool install`), zero-install via `dnx` (.NET 10), or the Claude Code plugin.\n\n## Quick Start\n\n### Prerequisites\n\n- [.NET 10 SDK](https://dotnet.microsoft.com/download) — source builds use the `10.0.400` floor in [`global.json`](global.json) (`rollForward: latestFeature`); published install paths retain their own package/runtime requirements\n\n### Option A — Install As A Global Tool\n\n```bash\ndotnet tool install -g Darylmcd.RoslynMcp\n```\n\n- Package ID: `Darylmcd.RoslynMcp`\n- CLI command: `roslynmcp`\n- Updates: `dotnet tool update -g Darylmcd.RoslynMcp`\n\n> **Configure a filesystem boundary before first use.** The global tool ships a binary, not a\n> config — you write your own `.mcp.json`, and an unset boundary is **fail-closed**: every\n> path-taking tool (`workspace_load`, edits, symbol lookups) rejects its input, and solution\n> discovery returns nothing. Set `ROSLYNMCP_SANCTIONED_ROOTS` in your client config:\n>\n> ```json\n> {\n>   \"mcpServers\": {\n>     \"roslyn\": {\n>       \"type\": \"stdio\",\n>       \"command\": \"roslynmcp\",\n>       \"env\": { \"ROSLYNMCP_SANCTIONED_ROOTS\": \".\" }\n>     }\n>   }\n> }\n> ```\n>\n> `.` resolves against the server process's working directory, which your MCP client chooses —\n> use an absolute path if you want the boundary pinned regardless of how the server is launched.\n> See [Configuration](#configuration). The Claude Code plugin and Desktop extension ship this\n> default already; only hand-written configs need it.\n\n### Option B — Zero-Install Via `dnx` (.NET 10)\n\n`dnx` is the .NET SDK's `npx`-equivalent: it resolves a tool package from NuGet on demand, without installing a global shim. Requires **.NET 10 SDK 10.0.100 or later** (`dnx` ships with the SDK).\n\nOne-shot smoke test:\n\n```bash\ndnx Darylmcd.RoslynMcp\n```\n\nThe process should start and then appear to hang — that's expected; it's an MCP server waiting for protocol messages on stdin. `dnx` is noninteractive unless `--interactive` is explicitly requested, so MCP hosts need no consent flag.\n\n`.mcp.json` snippet:\n\n```json\n{\n  \"mcpServers\": {\n    \"roslyn\": {\n      \"type\": \"stdio\",\n      \"command\": \"dnx\",\n      \"args\": [\n        \"Darylmcd.RoslynMcp\",\n        \"--source\",\n        \"https://api.nuget.org/v3/index.json\"\n      ]\n    }\n  }\n}\n```\n\nTrade-offs vs. the global tool:\n\n- ✅ No PATH pollution; no manual install step.\n- ✅ Each cold start resolves to the latest version unless pinned (no `dotnet tool update` step).\n- ⚠️ Cold-start cost on first invocation while the package downloads.\n- ⚠️ For reproducible setups, pin the version in the package token: `Darylmcd.RoslynMcp@<version>`.\n\nA copy-paste config also lives at [`docs/mcp-json-examples/dnx.mcp.json`](docs/mcp-json-examples/dnx.mcp.json).\n\n### Option C — Claude Code Plugin\n\n```text\n/plugin marketplace add darylmcd/Roslyn-Backed-MCP\n/plugin install roslyn-mcp@roslyn-mcp-marketplace\n```\n\nThe plugin bundles 32 skills and safety hooks, then launches the exact release-matched `Darylmcd.RoslynMcp` package through `dnx`; it does not require a global `roslynmcp` shim. The first launch requires NuGet access unless the package is already cached. For packaging, reinstall, and local plugin-dev details, see [docs/setup.md](docs/setup.md) and [docs/reinstall.md](docs/reinstall.md).\n\n### Build And Run From Source\n\n```bash\ndotnet build RoslynMcp.slnx --nologo\ndotnet test RoslynMcp.slnx --nologo\ndotnet run --project src/RoslynMcp.Host.Stdio\n```\n\n### Per-Client Config\n\nThe JSON shape is the same across MCP clients — only the file path differs. Drop one of the [`docs/mcp-json-examples/`](docs/mcp-json-examples/) snippets into the right location for your client:\n\n| Client | Config file | Notes |\n|--------|-------------|-------|\n| Claude Code | `.mcp.json` (repo root) | Project-scope; pairs naturally with the Claude Code Plugin path above. |\n| Cursor | `.cursor/mcp.json` (repo root) or `~/.cursor/mcp.json` (global) | Project-scope wins over global. |\n| VS Code (MCP-aware) | `.vscode/mcp.json` (repo root) | Workspace-scope; restart the MCP host after editing. |\n| Claude Desktop | `claude_desktop_config.json` (per-OS app-data dir) | Global only; no project-scope config. |\n\nMinimal config (works with **Option A** — global tool):\n\n```json\n{\n  \"mcpServers\": {\n    \"roslyn\": {\n      \"type\": \"stdio\",\n      \"command\": \"roslynmcp\"\n    }\n  }\n}\n```\n\nFor the **Option B** (`dnx`) form, use the snippet from the previous section or copy [`docs/mcp-json-examples/dnx.mcp.json`](docs/mcp-json-examples/dnx.mcp.json).\n\nFor NDJSON framing, handshake order, and minimal Python/C# client examples, see [docs/stdio-client-integration.md](docs/stdio-client-integration.md).\n\n### Health Check\n\nAfter installing and wiring up `.mcp.json`, paste this single prompt into your MCP client to verify the server is reachable and report its surface:\n\n> Call `server_info` and read the `roslyn://server/catalog` resource. Report back:\n> 1. The server name and version.\n> 2. The total tool / resource / prompt counts and their stable-vs-experimental split.\n> 3. Whether any workspaces are currently loaded (and their IDs if so).\n> 4. Any warnings or degraded-state flags.\n\nIf both calls succeed and the version matches what you installed, the install is healthy.\n\n### MCP Registry\n\nThe server is published to the official [MCP Registry](https://registry.modelcontextprotocol.io/) under the name **`io.github.darylmcd/roslyn-mcp`**. MCP-Registry-aware clients — and the downstream catalogs that mirror the registry (the GitHub MCP Registry, the VS Code and Visual Studio MCP galleries, and aggregators) — can discover and install the server by name.\n\nManifest: [`.claude-plugin/server.json`](.claude-plugin/server.json) — name `io.github.darylmcd/roslyn-mcp`, NuGet package `Darylmcd.RoslynMcp`, runtime `dnx`. Every release tag republishes it automatically via the `publish-nuget` workflow using GitHub OIDC.\n\nYou can also install directly without a registry-aware client via the [Global Tool](#option-a--install-as-a-global-tool) or [Claude Code Plugin](#option-c--claude-code-plugin) paths above.\n\n## Configuration\n\nThe server starts with built-in operational defaults. File-path access is the exception: configure\n`ROSLYNMCP_SANCTIONED_ROOTS` explicitly (usually `.` in a project-scope `.mcp.json`). Multiple roots\nuse the platform path separator (`;` on Windows, `:` on macOS/Linux). An empty root list fails\nclosed; see [Setup](docs/setup.md#configure-the-filesystem-boundary). Other `ROSLYNMCP_*` values\nremain optional literal `env` overrides.\n\n| Variable | Default | Purpose |\n|----------|---------|---------|\n| `ROSLYNMCP_SANCTIONED_ROOTS` | empty (deny path access) | Server-owned path-validation and solution-discovery boundary |\n| `ROSLYNMCP_PATH_VALIDATION_FAIL_OPEN` | `false` | Temporary compatibility escape hatch for the **empty**-boundary case only: allows path access when no roots are configured. It never bypasses a non-empty boundary. Prefer configuring roots |\n| `ROSLYNMCP_ALLOW_ROOT_EXPANSION` | `false` | Allows a request with `expandSanctionedRoots=true` to reach sibling worktrees under each sanctioned root's immediate parent; both opt-ins are required |\n| `ROSLYNMCP_MAX_WORKSPACES` | `8` | Concurrent workspace cap |\n| `ROSLYNMCP_BUILD_TIMEOUT_SECONDS` | `300` | Build timeout |\n| `ROSLYNMCP_TEST_TIMEOUT_SECONDS` | `600` | Test timeout |\n| `ROSLYNMCP_PREVIEW_TTL_MINUTES` | `5` | Preview-token TTL |\n| `ROSLYNMCP_REQUEST_TIMEOUT_SECONDS` | `120` | Per-request timeout |\n| `ROSLYNMCP_TOOL_TIERS` | `stable,experimental` | Registered MCP surface tiers; set `stable` to expose the closed stable-only workflow (currently 94 callable tools) to clients that eagerly load discovery definitions; previews whose apply route is experimental are omitted, and experimental requires the stable baseline |\n| `ROSLYNMCP_OBSERVABILITY_SINK` | `disabled` | Operator-side diagnostics: `disabled`, structured unexpected failures on `stderr`, or the full enabled `ILogger` stream as bounded JSON lines with `file` |\n\nCopy-ready examples live in [docs/mcp-json-examples/README.md](docs/mcp-json-examples/README.md). The full runtime/config surface is documented in [ai_docs/runtime.md](ai_docs/runtime.md).\n\nFor log destinations, verbosity controls, correlation identifiers, and health probes, see the\n[stdio observability contract](docs/stdio-client-integration.md#operator-observability).\n\n## Security\n\nLoading a solution or project executes MSBuild evaluation. Treat workspaces as trusted code unless you run the server inside a sandbox, container, or VM.\n\n- Only load repos you trust.\n- Use isolation for untrusted workspaces.\n- Path validation is defense in depth, not a substitute for trusting the loaded project graph.\n\n**The filesystem boundary is server-owned.** `ROSLYNMCP_SANCTIONED_ROOTS` is configured by you, the\noperator — not by the connecting client. A client's MCP Roots can only *narrow* that boundary; they\ncan never widen it or act as the sole authority. This is deliberate: the control exists to constrain\nthe **agent**, so a model that is confused or prompt-injected into reading outside your project\ncannot do so, even if it asks. Sibling-worktree widening needs two independent opt-ins — the server\noperator setting `ROSLYNMCP_ALLOW_ROOT_EXPANSION=true` *and* the request setting\n`expandSanctionedRoots=true` — so request input alone never widens access.\n\nPaths are canonicalized component-by-component, resolving every symlink and junction in the ancestor\nchain before comparison, so a file under a linked ancestor cannot present an in-boundary logical path\nwhile pointing outside it.\n\nSee [SECURITY.md](SECURITY.md) for disclosure policy.\n\n## Upgrading From 2.x\n\nPath validation is now bounded by a server-owned root list instead of the client's (deprecated)\n`roots/list` capability. Two things to do before upgrading:\n\n1. **Set `ROSLYNMCP_SANCTIONED_ROOTS`.** An unset boundary is fail-closed — every path-taking tool\n   rejects its input. `.` is the normal project-scoped value; see [Configuration](#configuration) for\n   the delimiter and [Option A](#option-a--install-as-a-global-tool) for a copy-ready snippet. If you\n   need to defer, `ROSLYNMCP_PATH_VALIDATION_FAIL_OPEN=true` restores the old unbounded behavior as a\n   temporary measure. From this release the server warns at startup and reports\n   `server_info.pathBoundary` when the boundary is missing, so the state is visible before your first\n   call rather than after it.\n2. **Stop relying on `roots/list` for discovery.** Query-anchored solution discovery no longer calls\n   it and scans only configured roots. Pass a file-path argument, configure a root containing exactly\n   one solution, call `workspace_load` explicitly, or pass a `workspaceId`.\n\nIf you install via the Claude Code plugin or the Desktop extension, both ship the default — but\nupdate **both** layers together. A binary-only update leaves a stale config with no boundary set,\nwhich is the fail-closed case above. Rationale and full detail:\n[ADR 0002](docs/decisions/0002-configured-sanctioned-root-boundary.md).\n\n## Live Surface\n\nThe current release exposes **174 tools** (113 stable / 61 experimental), **14 resources** (9 stable / 5 experimental), and **20 prompts** (all experimental).\n\nUse the running server for the authoritative live catalog and support tiers:\n\n- `server_info` for a human-readable summary\n- `roslyn://server/catalog` for the machine-readable contract\n- `roslyn://server/resource-templates` for resource URI templates\n\nStable families include workspace/session management, semantic navigation, diagnostics, build/test helpers, and preview/apply refactoring flows. Experimental families include broader project mutation, scaffolding, orchestration, direct text-edit helpers, and prompts.\n\n## Repository Layout\n\n- `src/RoslynMcp.Host.Stdio/` — stdio host, tool/resource/prompt wiring, logging\n- `src/RoslynMcp.Core/` — DTOs, contracts, abstractions, preview-store types\n- `src/RoslynMcp.Roslyn/` — Roslyn workspace, analysis, diagnostics, refactoring, execution services\n- `tests/RoslynMcp.Tests/` — integration and regression coverage\n- `skills/` — bundled Claude Code skill definitions\n- `hooks/` — Claude Code safety hooks\n\n## Docs\n\n- [docs/setup.md](docs/setup.md) — packaging, Docker, tool install, plugin install, CI artifacts\n- [docs/compatibility.md](docs/compatibility.md) — MCP client compatibility matrix (Claude Code, Cursor, VS Code, Claude Desktop)\n- [docs/stdio-client-integration.md](docs/stdio-client-integration.md) — custom MCP client integration\n- [docs/product-contract.md](docs/product-contract.md) — stable vs experimental surface contract\n- [docs/release-policy.md](docs/release-policy.md) — release gates and compatibility rules\n- [AGENTS.md](AGENTS.md) — bootstrap entry point for AI agents working in this repo\n- [ai_docs/README.md](ai_docs/README.md) — canonical AI-doc routing index\n\n## Filing Surface-Test Findings\n\nIf you find a bug or behaviour gap while running [`/mcp-server-surface-test`](skills/mcp-server-surface-test/README.md) against your own C# repo, share it back via the [Surface-test finding](https://github.com/darylmcd/Roslyn-Backed-MCP/issues/new?template=mcp-server-surface-test-finding.yml) issue template. The shipped skill renders findings into a copy-paste body block by default; pass `--auto-file` and the skill calls `gh issue create` for you.\n\nP0 / `area: security` findings are refused for public filing — see [SECURITY.md](SECURITY.md) for the private-disclosure path.\n\n## Support\n\n- Bugs and feature requests: [GitHub Issues](https://github.com/darylmcd/Roslyn-Backed-MCP/issues)\n- Contribution guidelines: [CONTRIBUTING.md](CONTRIBUTING.md)\n- Security disclosures: [SECURITY.md](SECURITY.md)\n",
  "bytes": 15782,
  "sha": "69cc0f590b16628f8ecfdc6bd118b0a7efdfe370f4ac5accca78c30d6d9eec90",
  "repo_slug": "darylmcd/roslyn-backed-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_darylmcd_roslyn_mcp_e85d0603/readme"
}