Skip to content

feat: Add MCP server#876

Open
BenjaminMichaelis wants to merge 13 commits intomainfrom
bmichaelis/MCPServer
Open

feat: Add MCP server#876
BenjaminMichaelis wants to merge 13 commits intomainfrom
bmichaelis/MCPServer

Conversation

@BenjaminMichaelis
Copy link
Copy Markdown
Member

@BenjaminMichaelis BenjaminMichaelis commented Feb 25, 2026

Fixes #761

Comment thread EssentialCSharp.Web.Tests/McpTests.cs Fixed
Comment thread EssentialCSharp.Web.Tests/McpTests.cs Fixed
Comment thread EssentialCSharp.Chat.Shared/Services/AIChatService.cs Fixed
Comment thread EssentialCSharp.Web.Tests/McpTests.cs Fixed
Comment thread EssentialCSharp.Web/Controllers/McpTokenController.cs Fixed
Comment thread EssentialCSharp.Web/Controllers/McpTokenController.cs Fixed
Comment thread EssentialCSharp.Chat.Shared/Services/AIChatService.cs Fixed
@BenjaminMichaelis BenjaminMichaelis self-assigned this Apr 25, 2026
Comment thread EssentialCSharp.Web/Tools/BookSearchTool.cs Fixed
Comment thread EssentialCSharp.Web/Tools/BookGuidelinesTool.cs Fixed
Comment thread EssentialCSharp.Web/Tools/BookGuidelinesTool.cs Fixed
Comment thread EssentialCSharp.Web/Tools/BookGuidelinesTool.cs Fixed
Comment thread EssentialCSharp.Web/Tools/BookGuidelinesTool.cs Fixed
Comment thread EssentialCSharp.Web/Tools/BookContentTool.cs Fixed
BenjaminMichaelis added a commit that referenced this pull request Apr 25, 2026
- BookSearchTool: collapse else-if(!semanticAvailable)+else into single
  else with ternary to remove constant-condition CodeQL finding
- BookGuidelinesTool: use pattern match (is int / is GuidelineType) to
  extract nullable locals before lambda capture, removing nullable
  dereference warnings at lines 38 and 41

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@BenjaminMichaelis BenjaminMichaelis changed the title feat: Add MCP server with JWT authentication feat: Add MCP server Apr 25, 2026
BenjaminMichaelis added a commit that referenced this pull request Apr 26, 2026
…elect

- Remove GetBookMetadata() MCP tool and trim BookMetadata class to SiteUrl only
- Fix PR #876 feedback: refactor foreach+clone into codeLines.Select(line => line.CloneNode(deep:true))

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BenjaminMichaelis and others added 10 commits April 25, 2026 21:46
- Add McpTokenService with JWT token generation/validation
- Add McpTokenController for token management UI
- Add MCP Access page to account management
- Configure MCP server with BookSearchTool
- Update ModelContextProtocol packages to 0.9.0-preview.2
- Add Microsoft.AspNetCore.Authentication.JwtBearer
- Fix MCP client API: use concrete McpClient type
- Add toolCallDepth guard to prevent infinite recursion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add IGuidelinesService + GuidelinesService (singleton) for guidelines.json
- Add BookListingTool: GetListingSourceCode, SearchListingsByCode
- Add BookGuidelinesTool: GetCSharpGuidelines, GetGuidelinesByTopic
- Add BookContentTool: GetSectionContent, GetListingWithContext,
  GetNavigationContext, GetChapterSummary
- Extend BookSearchTool: GetChapterSections, GetDirectContentUrl,
  GetBookMetadata, LookupConcept, CheckTopicCoverage,
  FindBookHelpForDiagnostic, FindRelatedSections
- Register all new tools in Program.cs MCP block

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove Destructive = false from all 17 MCP tool annotations (default per spec)
- Fix FindBookHelpForDiagnostic description to accurately reflect guidelines output
- Add 500-char max length validation to SearchBookContent, LookupConcept, CheckTopicCoverage, FindBookHelpForDiagnostic
- Add missing empty check to SearchBookContent
- Make GetSectionContent async (File.ReadAllTextAsync + doc.LoadHtml, removes TOCTOU File.Exists)
- Add path canonicalization via Path.GetRelativePath (robust cross-platform, handles drive-root edge case)
- Add .html file extension allowlist check on resolved path
- Add AnchorId validation via [GeneratedRegex] before XPath interpolation
- Separate IOException catches: FileNotFoundException/DirectoryNotFoundException show 'not generated yet', UnauthorizedAccessException and IOException show generic messages (no ex.Message leak)
- Make BookContentTool a partial class for GeneratedRegex support

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MCP spec (and C# SDK) default destructiveHint to true.
Explicitly setting Destructive = false is required so clients
know these read-only book search tools are non-destructive.

Note: per spec, destructiveHint is 'only meaningful when
readOnlyHint == false', but the SDK still emits the value
on the wire so explicit false is the safe, correct choice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- BookSearchTool: collapse else-if(!semanticAvailable)+else into single
  else with ternary to remove constant-condition CodeQL finding
- BookGuidelinesTool: use pattern match (is int / is GuidelineType) to
  extract nullable locals before lambda capture, removing nullable
  dereference warnings at lines 38 and 41

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…all sites

The helper class only had one method (NormalizeExtension) used in 3 places.
The VB.NET/F# cases were dead code — all 1,396 real book listings are .cs.
Simplified to a ternary inline at each call site; non-cs extensions pass through unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…elect

- Remove GetBookMetadata() MCP tool and trim BookMetadata class to SiteUrl only
- Fix PR #876 feedback: refactor foreach+clone into codeLines.Select(line => line.CloneNode(deep:true))

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…h tools

- AISearchService.ExecuteVectorSearch gains optional top param (default 5, clamped to 1-10)
- SearchBookContent, LookupConcept, FindRelatedSections expose maxResults to MCP clients
- Raises default from 3→5 for better coverage of broad C# concepts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@BenjaminMichaelis BenjaminMichaelis marked this pull request as ready for review April 28, 2026 19:13
Copilot AI review requested due to automatic review settings April 28, 2026 19:13
Copy link
Copy Markdown

@danOIntellitect danOIntellitect left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me generally, biggest thing I noticed is the usage of stringbuilders in the mcp tool themselves, this is leading to lots of replication of string patterns which could be refactored in a couple different ways.

  1. Use structuredcontent which allows you to just return a C# object instead. For some of these outputs this would look nice because you have basically done a pseudo-markdown approach.
    [McpServerTool(UseStructuredContent = true)]

  2. If you still want the string output for token reasons (which is a good reason) then I would still look at wrapping the output in classes and putting the stringbuilder logic there, and then just at the end doing a object.ToMcpString() or something similar. I view mcp tools very similar to ASP.NET controllers where I want very little processing or formatting at that level, they should handle input validation, authentication, authorization, etc. Not the output itself

Comment thread EssentialCSharp.Web/Program.cs Outdated
Comment thread EssentialCSharp.Web/Tools/BookGuidelinesTool.cs Outdated
Comment thread EssentialCSharp.Web/Tools/BookGuidelinesTool.cs Outdated
Comment thread EssentialCSharp.Web/Views/McpSetup/Index.cshtml Outdated
Comment thread EssentialCSharp.Web/Tools/BookListingTool.cs
Comment thread EssentialCSharp.Web/Tools/BookSearchTool.cs Outdated
Comment thread EssentialCSharp.Web/Tools/BookContentTool.cs Outdated
BenjaminMichaelis added a commit that referenced this pull request Apr 28, 2026
- Issue 1: Replace anonymous rate-limit body with typed McpRateLimitErrorEnvelope records;
  use JsonSerializer.SerializeAsync instead of WriteAsync(Serialize(...))
- Issue 2: Strip prose default values from all tool Description attributes
- Issue 3: Merge GetGuidelinesByTopic into GetCSharpGuidelines via optional 'topic' param;
  delete redundant GetGuidelinesByTopic tool
- Issue 4: Make /mcp-setup page dynamic — inject IEnumerable<McpServerTool> into
  McpSetupController; replace 330+ lines of static HTML cards with Razor @foreach loop
- Issue 5: Add listing search pattern minimum validation (>=2 alphanumeric chars or
  recognized C# operator)
- Issues 6+7: Extract duplicated FormatGuidelineType/FormatType to shared
  GuidelineTypeExtensions.ToDisplayString() extension method; delete private copies

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread EssentialCSharp.Web/Program.cs Outdated
Comment on lines +522 to +530

private sealed record McpRateLimitErrorEnvelope(
[property: System.Text.Json.Serialization.JsonPropertyName("jsonrpc")] string JsonRpc,
[property: System.Text.Json.Serialization.JsonPropertyName("id")] object? Id,
[property: System.Text.Json.Serialization.JsonPropertyName("error")] McpRateLimitErrorDetail Error);

private sealed record McpRateLimitErrorDetail(
[property: System.Text.Json.Serialization.JsonPropertyName("code")] int Code,
[property: System.Text.Json.Serialization.JsonPropertyName("message")] string Message);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are already types defined in the modelcontextprotocol sdk for these, JsonRpcError and JsonRpcErrorDetail

- Issue 1: Replace anonymous rate-limit body with typed McpRateLimitErrorEnvelope records;
  use JsonSerializer.SerializeAsync instead of WriteAsync(Serialize(...))
- Issue 2: Strip prose default values from all tool Description attributes
- Issue 3: Merge GetGuidelinesByTopic into GetCSharpGuidelines via optional 'topic' param;
  delete redundant GetGuidelinesByTopic tool
- Issue 4: Make /mcp-setup page dynamic — inject IEnumerable<McpServerTool> into
  McpSetupController; replace 330+ lines of static HTML cards with Razor @foreach loop
- Issue 5: Add listing search pattern minimum validation (>=2 alphanumeric chars or
  recognized C# operator)
- Issues 6+7: Extract duplicated FormatGuidelineType/FormatType to shared
  GuidelineTypeExtensions.ToDisplayString() extension method; delete private copies

Fix 3 subagent-found bugs: trimmedPattern in search, Razor HTML escaping, whitespace-only type guard

- BookListingTool.cs: use trimmedPattern (not pattern) in Contains() search call
- Index.cshtml: fix Razor HTML escaping of optional badge via @if block
- BookGuidelinesTool.cs: use IsNullOrWhiteSpace guard for type param
- BookGuidelinesTool.cs: fix description to say substring match not exact text

Use MCP SDK JsonRpcError/JsonRpcErrorDetail instead of custom records

Replace McpRateLimitErrorEnvelope and McpRateLimitErrorDetail private
records with the SDK's built-in ModelContextProtocol.Protocol types,
as suggested in PR review.
Comment on lines +257 to +260
var request = new HttpRequestMessage(HttpMethod.Post, "/mcp")
{
Content = new StringContent(payload, Encoding.UTF8, "application/json")
};
Comment on lines +303 to +309
foreach (JsonElement tool in tools.EnumerateArray())
{
if (string.Equals(tool.GetProperty("name").GetString(), toolName, StringComparison.Ordinal))
{
return tool;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create MCP server for EC# information and guidelines?

2 participants