fix(mcp): surface actionable errors through the MCP boundary - #166
Merged
Conversation
…tion ModelContextProtocol 1.4.1 replaces the message of any non-McpException with "An error occurred invoking '…'", so the root service's guidance (most importantly "provide an absolute path" for roots-unsupported clients) and the library's AnalysisException/ParsingException messages never reached clients (audit §3 #4 High residual). - WorkspaceRootService: all four throw sites now McpException. - ProjGraphTools: RunAnalysisAsync wraps every analysis call, converting ProjGraphException to McpException with the same message. - New McpTransportTests: first true e2e tests over a real stdio transport (audit §4.7 #18) launching the self-contained server apphost; the relative-path/no-roots pin failed with the stripped generic message before this fix and passes after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xt choice The DbContext branch silently analyzed the first context in the file (FindContextClass FirstOrDefault) when several existed, and both branches let a mistyped contextName fall through to analysis where it surfaced as a stripped generic error (audit §4.4 #6 + §5.4). Both now resolve against the discovered candidate list via a shared helper and fail with the candidates named. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prepending the >50-files warning ahead of the YAML front-matter breaks strict Mermaid parsers (audit §4.4 #5 residual); get_project_graph already appended. Both now share AppendWarningComments, and the cached resource stores the appended form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- RunAnalysisAsync preserves the inner ProjGraphException so server logs keep the original type and stack trace. - PreparePathAsync rejects blank paths with McpException instead of ArgumentException (the last stripped-message path on the hottest parameter of all four tools); null/empty pins updated accordingly. - Transport test apphost probe asserts the bin root exists and prefers the newest apphost across stale RID leftovers. - Warning test pins that the cached resource matches the returned (appended) diagram, as the plan promised. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves the ProjGraph MCP server’s client-facing error quality by ensuring actionable guidance survives the MCP SDK’s tool-invocation boundary, while also fixing Mermaid warning placement and making ERD selection deterministic when multiple EF candidates exist. It adds the first true end-to-end MCP transport test that exercises the real stdio boundary where error-message stripping occurs.
Changes:
- Convert key MCP-facing failures to
McpException(workspace-roots resolution + analysis-layerProjGraphExceptionwrapping) so messages are not stripped by the MCP SDK. - Improve
get_erdbehavior for multi-DbContext / ModelSnapshot selection by validating/forcing explicit candidate selection and surfacing candidate lists on errors. - Add an end-to-end stdio transport integration test suite and update existing integration tests to assert
McpExceptionsemantics; append class-diagram warnings after Mermaid content.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/ProjGraph.Tests.Integration.Mcp/ProjGraph.Tests.Integration.Mcp.csproj | Adds MCP client SDK package reference for true transport-level tests. |
| tests/ProjGraph.Tests.Integration.Mcp/McpWarningsTests.cs | Pins warning placement after Mermaid output and verifies cached resource matches returned diagram. |
| tests/ProjGraph.Tests.Integration.Mcp/McpTransportTests.cs | New real stdio MCP client/server E2E tests validating tool listing + absolute-path guidance surfacing. |
| tests/ProjGraph.Tests.Integration.Mcp/McpRootsTests.cs | Updates assertions to expect McpException so guidance survives the SDK boundary. |
| tests/ProjGraph.Tests.Integration.Mcp/McpIntegrationTests.cs | Updates null/empty path assertions to McpException for MCP-client-visible guidance. |
| tests/ProjGraph.Tests.Integration.Mcp/McpErdTests.cs | Adds pins for multi-context candidate selection + typo validation + whitespace-path guidance and updates exception expectations. |
| tests/ProjGraph.Tests.Integration.Mcp/Helpers/McpTestHelper.cs | Allows injecting a shared DiagramResourceCache for cache/output consistency tests. |
| src/ProjGraph.Mcp/WorkspaceRootService.cs | Converts all client-relevant failure paths to McpException with actionable messages. |
| src/ProjGraph.Mcp/ProjGraphTools.cs | Adds RunAnalysisAsync wrapper for ProjGraphException, validates ERD candidate selection, and appends class-diagram warnings after Mermaid output. |
| docs/superpowers/specs/2026-07-16-mcp-error-surfacing-design.md | Records the approved design for error surfacing, ERD candidate handling, and warning placement. |
| docs/superpowers/plans/2026-07-16-mcp-error-surfacing.md | Captures the implementation plan and constraints for delivering the design. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the audit's §3 #4 High residual and the two MCP Medium residuals (§4.4 #5/#6) plus the §5.4 snapshot-name Low, and adds the repo's first true end-to-end MCP test over a real stdio transport (§4.7 #18).
Spec:
docs/superpowers/specs/2026-07-16-mcp-error-surfacing-design.md· Plan:docs/superpowers/plans/2026-07-16-mcp-error-surfacing.mdWhy
ModelContextProtocol 1.4.1 replaces the message of every non-
McpExceptionthrown by a tool with a generic "An error occurred invoking '…'". Every piece of guidance the server produced through a BCL or library exception was therefore invisible to clients — including the exact scenario that made the original finding High: a relative path from a roots-unsupported client never saw "Please provide an absolute path."What
WorkspaceRootService: all four throw sites (roots-unsupported, not-found, ambiguous match, wildcard) now throwMcpExceptionwith the same guidance.RunAnalysisAsyncwraps every library analysis call, convertingProjGraphException(base ofAnalysisException/ParsingException) toMcpExceptionwith the same message and the original as inner exception. Blankpatharguments also get an actionableMcpExceptioninstead ofArgumentException.get_erdmulti-DbContext: with several DbContexts and nocontextName, the tool errors listing the candidates instead of silently analyzing the first (plausible-but-wrong output is the worst failure mode for LLM callers). A mistypedcontextNamenow errors with the candidate list on both the DbContext and snapshot branches (the snapshot branch held the list but never validated against it).get_class_diagram: the >50-files warning is appended after the diagram via the sharedAppendWarningCommentsinstead of prepended ahead of the YAML front-matter (which strict Mermaid parsers reject); the cached resource stores the appended form.Tests
McpTransportTests: launches the real self-contained server apphost overStdioClientTransportwith a realMcpClient— the first tests to cross the SDK layer where the stripping happens. The relative-path/no-roots pin failed with the literal stripped message before the fix and passes after; verified red→green live.contextNametypos, warning placement + cached-resource agreement, blank-path rejection.McpException— that flip is the point of the change.Full suite 878/878 green on Linux;
dotnet format --verify-no-changesclean; no EF golden moved. Independent code review: zero Critical/Important findings; all six Minor findings addressed in the final commit.🤖 Generated with Claude Code