Skip to content

Repository files navigation

Developer Project Assistant

PR Validate

A local Model Context Protocol server, built in C#/.NET, that lets an AI client (Claude Code, Claude Desktop, VS Code, Visual Studio, etc.) inspect and operate on a local .NET solution through a controlled set of tools — list projects, read and search source, run builds and tests, check for outdated NuGet packages, and get structured build/test failure diagnostics.

It communicates over stdio using the official MCP C# SDK, and is packaged as a .NET tool so it can be installed once and invoked by command name.

See docs/implementation-plan.md for the full design, phase-by-phase implementation history, and architecture notes.

Status

Functional and tested against real .NET solutions, including this repo's own. Packaged as a .NET tool and published to NuGet.org via trusted publishing (OIDC) whenever a v* tag is pushed — see release.yml.

Requirements

  • .NET 10 SDK on the machine running the server (it shells out to the dotnet CLI for build/test/package operations).

Installation

Install as a global .NET tool from NuGet.org:

dotnet tool install --global DeveloperProjectAssistant.McpServer

Or build and install from source instead:

git clone https://github.com/kl00t/developer-project-assistant.git
cd developer-project-assistant
dotnet pack src/DeveloperProjectAssistant.McpServer -c Release
dotnet tool install --global --add-source src/DeveloperProjectAssistant.McpServer/bin/Release --version <version-in-csproj> DeveloperProjectAssistant.McpServer

Either way this installs a developer-project-assistant command on your PATH. See src/DeveloperProjectAssistant.McpServer/README.md for more on packaging, including a gotcha with prerelease versions and dotnet tool install.

Tools

Every tool operates relative to a single project root, fixed when the server starts (see Configuration below) — there is no per-call path to point it at a different repo.

Tool What it does Key parameters
ping Checks the server is running.
get_server_info Returns the server version and configured project root.
list_projects Lists the projects in the .slnx/.sln solution. solutionRelativePath (only needed if there's more than one solution file)
read_file Reads a source file, truncating beyond a byte limit. path, maxBytes (default 512 KB)
search_symbols Syntactic search for classes/methods by name — no build required. query, kind (Class/Method/Any), exactMatch, caseSensitive, maxResults
run_build Runs dotnet build; returns structured diagnostics by default. projectOrSolutionRelativePath, configuration, includeRawOutput, timeoutSeconds
run_tests Runs dotnet test; returns pass/fail/skipped counts and structured failures. same as run_build
report_outdated_packages Runs dotnet list package --outdated. Needs network access. projectOrSolutionRelativePath, includeRawOutput, timeoutSeconds
summarize_last_failure Re-parses the most recent run_build/run_tests output into structured diagnostics, without re-running it.

search_symbols' kind: Class matches class declarations only, not record/struct types (a syntactic-search limitation, not a bug — see the implementation plan for details).

Example: run_build

Request:

{ "name": "run_build", "arguments": { "configuration": "Release" } }

Response (compact by default — pass "includeRawOutput": true to also get the full stdout/stderr):

{
  "success": false,
  "exitCode": 1,
  "diagnostics": [
    {
      "filePath": "src/Broken.cs",
      "line": 7,
      "column": 41,
      "severity": "error",
      "code": "CS1525",
      "message": "Invalid expression term ';'",
      "projectPath": "src/Broken.csproj"
    }
  ],
  "timedOut": false,
  "outputTruncated": false
}

Example: run_tests

Response:

{
  "success": false,
  "exitCode": 1,
  "trxFound": true,
  "total": 2,
  "passed": 1,
  "failed": 1,
  "skipped": 0,
  "failures": [
    {
      "testName": "SampleLib.Tests.CalculatorTests.AlwaysFails",
      "errorMessage": "Assert.Equal() Failure: Values differ\r\nExpected: 999\r\nActual:   5",
      "stackTrace": "   at SampleLib.Tests.CalculatorTests.AlwaysFails() in ..."
    }
  ]
}

Configuration

The project root is fixed at launch — one server instance inspects one solution, so a repo that wants this tool available adds its own config pointing at itself.

Setup for Claude Code

Option A — project-scoped .mcp.json (recommended): add a .mcp.json at the root of the solution you want the server to inspect:

// Running from an installed tool:
{
  "mcpServers": {
    "developer-project-assistant": {
      "command": "developer-project-assistant",
      "args": ["--project-root", "${workspaceFolder}"]
    }
  }
}
// Running from source during development:
{
  "mcpServers": {
    "developer-project-assistant": {
      "command": "dotnet",
      "args": [
        "run", "--project", "<path-to>/src/DeveloperProjectAssistant.McpServer",
        "--", "--project-root", "${workspaceFolder}"
      ]
    }
  }
}

Option B — the claude mcp add CLI:

claude mcp add developer-project-assistant --scope project -- developer-project-assistant --project-root "<path-to-solution>"

Flag names (--scope, argument separators) have changed across Claude Code versions — run claude mcp add --help to confirm current syntax.

Verifying the connection: either option writes to (or is equivalent to) .mcp.json at the solution root. After adding it, restart Claude Code (or run /mcp if available in your version) and confirm developer-project-assistant shows as connected, then ask it to "list the projects in this solution" to exercise list_projects end-to-end.

See docs/implementation-plan.md §6 for the same setup written up in more detail, plus notes on other MCP clients (VS Code, Visual Studio).

Security notes

  • Trust boundary: run_build and run_tests execute whatever build/test code exists in the target solution — equivalent in risk to running dotnet build/dotnet test yourself locally. Only point this server at solutions you trust; this is not sandboxed.
  • Path confinement: every file-reading and search tool resolves paths through a guard that refuses anything outside the configured project root, including symlink escapes (best-effort) and .. traversal.
  • Process limits: every shelled-out dotnet invocation has a timeout (default 5 minutes, configurable per call) and an output-size cap, so a hung or noisy child process can't consume the session indefinitely.
  • No secret handling: the server never accepts or forwards credentials through tool arguments. Anything the build needs (NuGet feed auth, etc.) must already be available in the ambient environment the server process runs in.
  • Errors are generic to the client, detailed in logs: the underlying MCP SDK doesn't forward exception messages to the calling client — every unhandled error becomes the same generic "An error occurred" result. Real error detail (rejected paths, malformed solution files, etc.) is logged to stderr instead, for local troubleshooting.

See docs/implementation-plan.md §7 for the full security notes as originally scoped.

Development

dotnet build
dotnet test

License

MIT

About

Local MCP server (C#/.NET) for AI clients to inspect a .NET solution: list projects, read/search source, run build/tests, check outdated NuGet packages.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages