A solution-aware MCP server for .NET — nine Roslyn-powered tools that give Claude Code structured, symbol-level access to your DbContexts, entities, migrations, relationships, DI registrations, aggregate health analysis, and custom JSON-defined analyzer rules. Published on npm.
Status: v0.2.0 published to npm. 9 tools live. Looking for early adopters and real-world feedback from teams running multi-context EF Core projects. Issues and PRs welcome.
When Claude Code works on .NET projects, it reads files one at a time and infers structure. For larger solutions, this is slow and lossy.
This MCP server gives Claude structured, Roslyn-backed access to solution-level information. Questions like "what DbContexts exist", "what does this migration actually do", or "how are User and Order related" become single tool calls instead of multi-file searches.
Two-layer bridge MCP design:
- TypeScript MCP layer (~270 lines): Handles MCP protocol, spawns subprocess, returns structured responses
- .NET CLI layer (~2,000 lines C#): Uses the Roslyn Workspace API to load solutions and analyze code symbolically
Claude Code (MCP client)
│ stdio
▼
TypeScript MCP Server (src/index.ts)
│ spawns
▼
.NET CLI (cli/DotnetContextMcp.Cli) — published binary or dotnet run
│ uses
▼
Roslyn (Microsoft.CodeAnalysis) — loads .sln, analyzes symbols/syntax
│ reads
▼
Your .NET solution (DbContexts, entities, migrations)
| Tool | Description |
|---|---|
echo |
Connectivity test |
list_dbcontexts |
Discover all DbContext classes in the solution |
list_entities |
List DbSet properties and entity metadata (optionally filtered by DbContext) |
list_migrations |
Migration history with timestamps and owning context |
analyze_migration |
Detailed Up/Down operations for a specific migration (13 operation types) |
find_relationships |
Entity relationships: navigation properties, foreign keys, cardinality (OneToMany, ManyToOne, OneToOne, ManyToMany) |
find_dbcontext_dependencies |
Analyze DbContext dependency injection registrations across the solution: registration method (AddDbContext, AddDbContextPool, AddDbContextFactory), provider (SqlServer, Npgsql, Sqlite, etc.), connection string source, lifetime, and location (file + line). |
analyze_solution_health |
Comprehensive EF Core health report for the solution. Composes all other analyzers into an aggregate view: DbContext + entity + migration + registration + relationship counts. Detects 5 issue categories (multi-context registration, hardcoded connection strings, unregistered DbContexts, missing migrations, many-to-many complexity). Returns health score (0-100), grade (A-F), and actionable recommendations. |
run_custom_analyzers |
Discovers and runs custom JSON-defined analyzer rules from .dotnet-context-mcp/plugins/*.json or referenced from dotnet-context-mcp.config.json. Supports 3 rule types: name-regex, entity-count, operation-forbidden. Returns issues with severity, message, and affected items. |
You can define your own rules with JSON. Drop plugin files into
{solution-root}/.dotnet-context-mcp/plugins/*.json and they'll be
auto-discovered by run_custom_analyzers and (optionally)
analyze_solution_health.
Example plugin:
{
"name": "Team Naming Conventions",
"version": "1.0.0",
"rules": [
{
"id": "TEAM001",
"target": "dbcontext",
"check": "name-regex",
"pattern": "^[A-Z][a-zA-Z]*DbContext$",
"severity": "warning",
"message": "DbContext '{name}' must be PascalCase ending in 'DbContext'"
},
{
"id": "TEAM002",
"target": "dbcontext",
"check": "entity-count",
"operator": "less-than-or-equal",
"value": 30,
"severity": "info",
"message": "DbContext '{name}' has {count} entities. Consider splitting at 30+."
},
{
"id": "TEAM003",
"target": "migration-operation",
"check": "operation-forbidden",
"operationType": "DropTable",
"severity": "error",
"message": "DropTable in '{migrationName}' requires DBA approval"
}
]
}See full plugin documentation in the docs site (coming soon).
| Platform | RID | Status |
|---|---|---|
| Windows x64 | win-x64 |
Tested |
| Linux x64 | linux-x64 |
Binary built, untested in production |
| macOS x64 (Intel) | osx-x64 |
Binary built, untested in production |
| macOS ARM64 (Apple Silicon) | osx-arm64 |
Binary built, untested in production |
Current limitation: Microsoft.Build.Locator requires a .NET 8 SDK to be installed on the target machine to locate MSBuild. Truly hermetic operation (no SDK required) is deferred to a future phase.
claude mcp add dotnet-context-mcp -s user -- npx -y dotnet-context-mcp@latestThe -s user flag makes this MCP server available globally across all your projects. For project-specific install, omit the flag.
Restart Claude Code (/exit, then claude) to load the new MCP server.
Verify the server is connected:
claude mcp list
# Should show: dotnet-context-mcp ✔ ConnectedThe first tool call may take 5-15 seconds (npm fetches the package and downloads the platform-specific binary). Subsequent calls are 3-4 seconds.
Instead of manually adding dotnet-context-mcp to your MCP client's config, use one of:
- VS Code / Cursor extension — dotnet-context-mcp on Open VSX (auto-detects clients, adds config, provides sidebar tree view)
- CLI command —
npx dotnet-context-mcp@latest init-client(interactive setup, supports all MCP clients)
Both take under a minute.
Pre-built binaries are automatically downloaded for:
- Windows x64
- Linux x64
- macOS Intel (x64)
- macOS Apple Silicon (arm64)
- Node.js 18+
- Claude Code 2.x
- .NET 8 SDK installed on your machine (required for Roslyn's MSBuildLocator)
tarcommand (built-in on macOS/Linux; available on Windows 10+)
git clone https://github.com/sayinbrahim/dotnet-context-mcp.git
cd dotnet-context-mcp
npm install
npm run build:all # TypeScript + Windows binary
claude mcp add dotnet-context-mcp -- node /absolute/path/to/build/index.jsSee CONTRIBUTING.md for development setup details.
After installation, just ask Claude Code in plain language:
"List all DbContexts in this solution: C:\path\to\MySolution.sln"
Claude calls list_dbcontexts and returns each DbContext's name, namespace, project, and file path.
"Analyze the AddOrderRelations migration and tell me if it's safe to deploy"
Claude calls analyze_migration, inspects the Up/Down operations (CreateTable, AddForeignKey, AlterColumn, etc.), and gives you an informed answer — not just that a migration exists, but what it actually does.
"What's the relationship between User and Order in TestDbContext?"
Claude calls find_relationships and returns the navigation graph: cardinality (OneToMany/ManyToOne), the foreign key column, and whether the relationship is required.
The server supports two transports, selected by CLI flag when launching build/index.js. Tool registration is shared — both transports run the same 11 tools, only the connection layer differs.
node build/index.js
# or explicitly:
node build/index.js --transport stdioThis is the existing, unchanged behavior: the server is spawned as a subprocess and speaks MCP over stdin/stdout. This is what claude mcp add and the CLI passthrough subcommands use, and it's the only transport in stable use today.
node build/index.js --transport http --port 3000Implements the MCP 2026-07-28 spec's stateless Streamable HTTP transport, exposing a single POST /mcp JSON-RPC endpoint. This is prep work for a future hosted deployment (Cloudflare Workers / Azure Container Apps) — it is not yet meant for production use. No auth, localhost only.
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "echo", "arguments": { "message": "hello http" } }
}'Architecture: the transport is stateless — no session ID is issued or tracked. Because the SDK forbids reusing a stateless transport instance across requests, a fresh WebStandardStreamableHTTPServerTransport is created and connected to the shared McpServer instance for each incoming request, then disconnected once the response is written. Tool definitions live in exactly one place (src/index.ts); only the per-request transport object is throwaway.
Known limitation: because the same McpServer instance is (re)connected per request, two requests arriving concurrently can race — the second may see "Already connected to a transport" if it arrives before the first request's transport has disconnected. This is safe for local, single-client development and testing but is not safe for concurrent production traffic. A hosted deployment will need a transport/session pool (one McpServer+transport pair per in-flight request, or an equivalent) before this can serve real concurrent load — tracked as follow-up work, not solved here.
By default, the HTTP transport runs without authentication. For beta deployments, enable API key auth via CLI flag or environment variable:
dotnet-context-mcp --transport http --port 3000 --api-key YOUR_SECRET_KEYOr via env var (useful for Docker):
DOTNET_CONTEXT_API_KEY=YOUR_SECRET_KEY dotnet-context-mcp --transport http --port 3000Clients must send the key in the X-API-Key header:
curl -X POST http://localhost:3000/mcp \
-H "X-API-Key: YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Do not deploy without authentication on a public network.
Run dotnet-context-mcp as an HTTP server in a container:
docker pull 4322sayin/dotnet-context-mcp:alpha
docker run -p 3000:3000 4322sayin/dotnet-context-mcp:alphaThe container listens on port 3000. Configure via:
PORTenv var (default3000)
Note: this is the HTTP transport, useful for hosted deployment or dev servers. For local Claude Code / Cursor use, prefer the npm install (stdio transport).
The image is glibc-based (node:22-slim) because the bundled .NET CLI is a self-contained linux-x64 build linked against glibc — it will not run on musl-based images like node:22-alpine.
- Cold start: First call takes 3–4 seconds (self-contained binary warm-up). Previously 15–20s with
dotnet run. - .NET SDK required:
MSBuildLocatormust find a .NET 8 SDK on the target machine. Hermetic operation is a future phase. - .NET 10 .slnx format: MSBuildWorkspace requires classic
.sln..slnx(.NET 10 XML format) is not yet supported. - Non-ASCII paths: Solution paths with non-ASCII characters may fail. Use ASCII-only paths for now.
- Phase 1-5: MCP scaffold + Roslyn (list_dbcontexts)
- Phase 6: list_entities
- Phase 7: list_migrations
- Phase 8: analyze_migration (v0.1.1)
- Phase 9: find_relationships (v0.1.2)
- Phase 10: find_dbcontext_dependencies (v0.1.4)
- Phase 11: analyze_solution_health (v0.1.5)
- Phase 12: custom analyzer plugin system (v0.2.0)
- Phase 13: VS Code extension installer (v0.1.0 on Open VSX, VS Code Marketplace pending)
See CONTRIBUTING.md for development setup, testing, and PR guidelines.
- GitHub Issues for bugs
- Discussions for questions
MIT — see LICENSE for details.
Built by Halil İbrahim Sayın — Senior .NET Developer at Enerjisa, Ankara.
Contact: LinkedIn