Skip to content

Commit 5ae51e4

Browse files
fix: restore Destructive = false on all MCP tool annotations
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>
1 parent df8d307 commit 5ae51e4

4 files changed

Lines changed: 21 additions & 21 deletions

File tree

EssentialCSharp.Web/Tools/BookContentTool.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.ComponentModel;
1+
using System.ComponentModel;
22
using System.Globalization;
33
using System.Text;
44
using System.Text.RegularExpressions;
@@ -33,7 +33,7 @@ public BookContentTool(
3333
_searchService = serviceProvider.GetService<AISearchService>();
3434
}
3535

36-
[McpServerTool(Title = "Get Section Content", ReadOnly = true, Idempotent = true, OpenWorld = false),
36+
[McpServerTool(Title = "Get Section Content", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
3737
Description("Retrieve the prose content of a specific book section identified by its slug/key (e.g., 'hello-world', 'creating-editing-compiling-and-running-c-source-code'). Returns the section text with code examples preserved. Use GetChapterSections to discover available slugs.")]
3838
public async Task<string> GetSectionContent(
3939
[Description("The section slug/key (e.g., 'hello-world'). Use GetChapterSections to get valid slugs.")] string sectionKey,
@@ -145,7 +145,7 @@ public async Task<string> GetSectionContent(
145145
return body.Length == 0 ? $"No content found after section heading '{mapping.RawHeading}'." : header.Append(body).ToString();
146146
}
147147

148-
[McpServerTool(Title = "Get Listing With Context", ReadOnly = true, Idempotent = true, OpenWorld = false),
148+
[McpServerTool(Title = "Get Listing With Context", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
149149
Description("Retrieve a specific book listing's source code together with the semantic book content that explains it. Combines code from GetListingSourceCode with related explanatory text found via search. Ideal for understanding what a listing demonstrates.")]
150150
public async Task<string> GetListingWithContext(
151151
[Description("The chapter number of the listing.")] int chapter,
@@ -191,7 +191,7 @@ public async Task<string> GetListingWithContext(
191191
return sb.ToString();
192192
}
193193

194-
[McpServerTool(Title = "Get Navigation Context", ReadOnly = true, Idempotent = true, OpenWorld = false),
194+
[McpServerTool(Title = "Get Navigation Context", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
195195
Description("Get the navigation context for a book section: its breadcrumb path, the previous and next sections, its parent section, and its sibling sections. Useful for understanding where a section sits in the book's structure.")]
196196
public string GetNavigationContext(
197197
[Description("The section slug/key (e.g., 'hello-world'). Use GetChapterSections to get valid slugs.")] string sectionKey)
@@ -325,7 +325,7 @@ public string GetNavigationContext(
325325
return sb.ToString();
326326
}
327327

328-
[McpServerTool(Title = "Get Chapter Summary", ReadOnly = true, Idempotent = true, OpenWorld = false),
328+
[McpServerTool(Title = "Get Chapter Summary", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
329329
Description("Get a structural overview of a book chapter: its top-level section headings in reading order, and the coding guidelines associated with that chapter. Useful for understanding what a chapter covers before diving in.")]
330330
public string GetChapterSummary(
331331
[Description("The chapter number (e.g., 5 for Chapter 5).")] int chapter)

EssentialCSharp.Web/Tools/BookGuidelinesTool.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.ComponentModel;
1+
using System.ComponentModel;
22
using System.Globalization;
33
using System.Text;
44
using EssentialCSharp.Web.Services;
@@ -16,7 +16,7 @@ public BookGuidelinesTool(IGuidelinesService guidelinesService)
1616
_guidelinesService = guidelinesService;
1717
}
1818

19-
[McpServerTool(Title = "Get C# Guidelines", ReadOnly = true, Idempotent = true, OpenWorld = false),
19+
[McpServerTool(Title = "Get C# Guidelines", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
2020
Description("Retrieve C# coding guidelines from the Essential C# book. Optionally filter by keyword, chapter number, or guideline type (do/consider/avoid/donot). The book contains guidelines covering naming conventions, error handling, LINQ, async/await, generics, and many other topics. Each guideline includes its chapter and subsection context.")]
2121
public string GetCSharpGuidelines(
2222
[Description("Optional keyword to filter guidelines by (searched in guideline text and subsection name).")] string? keyword = null,
@@ -67,7 +67,7 @@ public string GetCSharpGuidelines(
6767
return sb.ToString();
6868
}
6969

70-
[McpServerTool(Title = "Get Guidelines By Topic", ReadOnly = true, Idempotent = true, OpenWorld = false),
70+
[McpServerTool(Title = "Get Guidelines By Topic", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
7171
Description("Search C# coding guidelines from the Essential C# book by topic or concept. More discoverable than filtering by chapter — finds all guidelines related to exceptions, naming, async, LINQ, generics, interfaces, and more. Results are ordered by relevance to the topic.")]
7272
public string GetGuidelinesByTopic(
7373
[Description("The topic or concept to search guidelines for (e.g., 'exception handling', 'naming', 'async', 'LINQ', 'generics', 'interface').")] string topic,

EssentialCSharp.Web/Tools/BookListingTool.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.ComponentModel;
1+
using System.ComponentModel;
22
using System.Globalization;
33
using System.Text;
44
using EssentialCSharp.Web.Services;
@@ -18,7 +18,7 @@ public BookListingTool(IListingSourceCodeService listingService, ISiteMappingSer
1818
_siteMappingService = siteMappingService;
1919
}
2020

21-
[McpServerTool(Title = "Get Listing Source Code", ReadOnly = true, Idempotent = true, OpenWorld = false),
21+
[McpServerTool(Title = "Get Listing Source Code", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
2222
Description("Retrieve the complete source code for a specific numbered listing from the Essential C# book. Example: chapter=5, listing=3 retrieves Listing 5.3. Returns the code and its file type.")]
2323
public async Task<string> GetListingSourceCode(
2424
[Description("The chapter number containing the listing (e.g., 5 for Chapter 5).")] int chapter,
@@ -35,7 +35,7 @@ public async Task<string> GetListingSourceCode(
3535
return $"## Listing {response.ChapterNumber}.{response.ListingNumber}\n\n```{langHint}\n{response.Content}\n```";
3636
}
3737

38-
[McpServerTool(Title = "Search Listings By Code", ReadOnly = true, Idempotent = true, OpenWorld = false),
38+
[McpServerTool(Title = "Search Listings By Code", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
3939
Description("Search all code listings in the Essential C# book for a specific code pattern, keyword, or identifier. Searches actual C# source code (not prose). Useful for finding examples of Task.WhenAll, yield return, IDisposable, pattern matching, and similar code constructs.")]
4040
public async Task<string> SearchListingsByCode(
4141
[Description("The code pattern or keyword to search for in listing source code (case-insensitive substring match).")] string pattern,

EssentialCSharp.Web/Tools/BookSearchTool.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.ComponentModel;
1+
using System.ComponentModel;
22
using System.Globalization;
33
using System.Text;
44
using System.Text.RegularExpressions;
@@ -35,7 +35,7 @@ public BookSearchTool(IServiceProvider serviceProvider, ISiteMappingService site
3535
_guidelinesService = guidelinesService;
3636
}
3737

38-
[McpServerTool(Title = "Search Book Content", ReadOnly = true, Idempotent = true, OpenWorld = false),
38+
[McpServerTool(Title = "Search Book Content", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
3939
Description("Search the Essential C# book content using semantic vector search. Returns relevant text chunks with chapter and heading context. Use this to find information about C# programming concepts covered in the book.")]
4040
public async Task<string> SearchBookContent(
4141
[Description("The search query describing the C# concept or topic to find in the book.")] string query,
@@ -87,7 +87,7 @@ public async Task<string> SearchBookContent(
8787
return sb.ToString();
8888
}
8989

90-
[McpServerTool(Title = "Get Chapter List", ReadOnly = true, Idempotent = true, OpenWorld = false),
90+
[McpServerTool(Title = "Get Chapter List", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
9191
Description("Get the table of contents for the Essential C# book, listing all chapters and their sections with navigation links.")]
9292
public string GetChapterList()
9393
{
@@ -118,7 +118,7 @@ public string GetChapterList()
118118
return sb.ToString();
119119
}
120120

121-
[McpServerTool(Title = "Get Chapter Sections", ReadOnly = true, Idempotent = true, OpenWorld = false),
121+
[McpServerTool(Title = "Get Chapter Sections", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
122122
Description("Get all sections and subsections in a specific chapter of the Essential C# book, in reading order. Returns each section's heading, slug, anchor link, and indent level. Use the returned slugs with other tools like GetSectionContent or GetNavigationContext.")]
123123
public string GetChapterSections(
124124
[Description("The chapter number (e.g., 5 for Chapter 5).")] int chapter)
@@ -150,7 +150,7 @@ public string GetChapterSections(
150150
return sb.ToString();
151151
}
152152

153-
[McpServerTool(Title = "Get Direct Content URL", ReadOnly = true, Idempotent = true, OpenWorld = false),
153+
[McpServerTool(Title = "Get Direct Content URL", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
154154
Description("Get the canonical deep-link URL for a specific book section or subsection. Returns a clickable URL that navigates directly to the section. Use this to include precise references in responses.")]
155155
public string GetDirectContentUrl(
156156
[Description("The section slug/key (e.g., 'hello-world'). Use GetChapterSections or GetChapterList to find valid slugs.")] string sectionKey)
@@ -175,7 +175,7 @@ public string GetDirectContentUrl(
175175
$"URL: {url}";
176176
}
177177

178-
[McpServerTool(Title = "Get Book Metadata", ReadOnly = true, Idempotent = true, OpenWorld = false),
178+
[McpServerTool(Title = "Get Book Metadata", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
179179
Description("Get citation-quality metadata for the Essential C# book: title, authors, edition, C# version covered, ISBN, publisher, and website URL. Use this when generating citations or when asked which edition or C# version the book covers.")]
180180
public string GetBookMetadata()
181181
{
@@ -192,7 +192,7 @@ public string GetBookMetadata()
192192
""";
193193
}
194194

195-
[McpServerTool(Title = "Lookup Concept", ReadOnly = true, Idempotent = true, OpenWorld = false),
195+
[McpServerTool(Title = "Lookup Concept", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
196196
Description("Find all sections in the Essential C# book that cover a specific C# concept. Combines section heading search with semantic vector search (when available) to give broad coverage. Returns section slugs, chapter numbers, and direct links.")]
197197
public async Task<string> LookupConcept(
198198
[Description("The C# concept, feature, or topic to find in the book (e.g., 'LINQ', 'async/await', 'pattern matching', 'generics').")] string concept,
@@ -267,7 +267,7 @@ public async Task<string> LookupConcept(
267267
return sb.ToString();
268268
}
269269

270-
[McpServerTool(Title = "Check Topic Coverage", ReadOnly = true, Idempotent = true, OpenWorld = false),
270+
[McpServerTool(Title = "Check Topic Coverage", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
271271
Description("Determine whether and how thoroughly the Essential C# book covers a given topic. Returns a coverage assessment: 'Comprehensive', 'Mentioned', or 'Not found in headings'. Use this before citing the book to calibrate confidence.")]
272272
public async Task<string> CheckTopicCoverage(
273273
[Description("The C# topic, feature, or concept to check (e.g., 'source generators', 'records', 'LINQ').")] string topic,
@@ -337,7 +337,7 @@ public async Task<string> CheckTopicCoverage(
337337
return sb.ToString();
338338
}
339339

340-
[McpServerTool(Title = "Find Book Help For Diagnostic", ReadOnly = true, Idempotent = true, OpenWorld = false),
340+
[McpServerTool(Title = "Find Book Help For Diagnostic", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
341341
Description("Find Essential C# book sections, content, and coding guidelines that help explain a C# compiler error, warning, or runtime exception. Accepts a CS diagnostic code (e.g., 'CS8600') or a plain description (e.g., 'null reference exception', 'cannot implicitly convert'). Returns relevant sections, explanatory prose, and related guidelines.")]
342342
public async Task<string> FindBookHelpForDiagnostic(
343343
[Description("A C# compiler diagnostic code (e.g., 'CS8600', 'CS0029') or a plain error description (e.g., 'null reference exception', 'async method lacks await').")] string diagnostic,
@@ -437,7 +437,7 @@ public async Task<string> FindBookHelpForDiagnostic(
437437
return sb.ToString();
438438
}
439439

440-
[McpServerTool(Title = "Find Related Sections", ReadOnly = true, Idempotent = true, OpenWorld = false),
440+
[McpServerTool(Title = "Find Related Sections", ReadOnly = true, Destructive = false, Idempotent = true, OpenWorld = false),
441441
Description("Find other sections in the Essential C# book that are semantically related to a given section. Uses the section heading as a search query to discover thematically connected content across the entire book. Requires AI services to be configured.")]
442442
public async Task<string> FindRelatedSections(
443443
[Description("The section slug/key to find related content for (e.g., 'async-await'). Use GetChapterSections to get valid slugs.")] string sectionKey,

0 commit comments

Comments
 (0)