feat: surface MCP tool annotations to host code #110
No reviewers
Labels
No labels
bug
commercial
documentation
duplicate
enhancement
feature
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jasoncouture/llama-shears!110
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/tool-attribute-updates"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
[McpServerTool]annotations on every internal tool —ReadOnly/Destructive/Idempotent/OpenWorldpopulated per the spec so clients can reason about side effects.ToolDescriptorAnnotationstoToolDescriptorand propagates them from the MCP client (tool.ProtocolTool.Annotations), so host code, UI, and future per-call confirmation gates can inspect destructive/idempotent/open-world/read-only without re-deriving from the schema.Test plan
dotnet buildcleandotnet test— 566 / 566 passingfile_read,file_write,shell_run) advertises the expected annotations to a connected MCP clientPull request overview
Surfaces MCP tool annotation metadata (read-only / destructive / idempotent / open-world) into host-visible abstractions by introducing
ToolDescriptorAnnotations, plumbing it through the MCP client mapping, and annotating built-in MCP server tools accordingly.Changes:
ToolDescriptorAnnotationsand include it onToolDescriptorso hosts/UIs can reason about tool side effects.ToolAnnotationsfromMcpClientTool.ProtocolTool.AnnotationsintoToolDescriptor.Annotations.[McpServerTool]annotation flags on many built-in API tools and update affected unit tests/docs.Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
ToolDescriptorconstruction to include annotations.ToolDescriptorconstruction to include annotations.ToolDescriptorconstruction to include annotations.Annotationsto the publicToolDescriptorrecord.ToolDescriptorAnnotations.[McpServerTool]flags for TODO tools.[McpServerTool]flags for skill tools.[McpServerTool]flags for memory store tool.[McpServerTool]flags for memory search tool.[McpServerTool]flags for memory index tool.[McpServerTool]flags for file write tool.[McpServerTool]flags for regex-replace tool.[McpServerTool]flags for file read tool.[McpServerTool]flags for file move tool.[McpServerTool]flags for file list tool.[McpServerTool]flags for grep tool.[McpServerTool]flags for file delete tool.[McpServerTool]flags for file append tool.[McpServerTool]flags for cron trigger tool.[McpServerTool]flags for cron schedule tool.[McpServerTool]flags for cron list tool.[McpServerTool]flags for cron edit tool.[McpServerTool]flags for cron cancel tool.ToolDescriptorAnnotations.ToolDescriptorparameter/property.ToolDescriptorAnnotationsto provider namespace index.ToolDescriptorAnnotationsto root API index.Comments suppressed due to low confidence (5)
src/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/ReadFileTool.cs:33
file_readallows absolute paths "anywhere on disk the host can reach" (see parameter description and implementation), which is not a closed-domain tool. MarkingOpenWorld = falseseems inconsistent with the meaning documented for OpenWorld and could cause UIs/confirmation gates to under-warn. Either setOpenWorld = truehere, or restrict the implementation to workspace-only reads soOpenWorld = falseis accurate.src/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/ListFilesTool.cs:33
file_listhonors absolute paths as-is (outside the workspace), so it can enumerate arbitrary host directories. SettingOpenWorld = falseunderstates the side-effect/scope profile for consumers relying on the hint. Consider settingOpenWorld = trueor constraining listing to workspace-only so the hint matches behavior.src/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/WriteFileTool.cs:36
file_writeis markedIdempotent = true, but repeated calls with identical arguments are not generally no-ops: withoverwrite=falsethe second call fails once the file exists, and withoverwrite=trueit still rewrites the file (timestamps/metadata change). Consider settingIdempotent = false(or adjusting semantics so identical calls become true no-ops).src/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/DeleteFileTool.cs:33
file_deleteis markedIdempotent = true, but after a successful delete a second identical call returns an error (Path not found). That makes the idempotent hint misleading for clients attempting safe retries. Consider settingIdempotent = falseor changing the tool to treat missing paths as a successful no-op (Deleted=false, no error) so it is truly idempotent.src/LlamaShears.Api/Tools/ModelContextProtocol/Cron/CancelCronTool.cs:24
cron_cancelis markedIdempotent = true, but after a successful cancel a second identical call returns an error (job no longer exists). That makes the hint inaccurate for retry/confirmation behavior. Consider settingIdempotent = falseor making cancel idempotent by returningCancelled=falsewithout an error when the job is already absent.💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@ -17,3 +17,3 @@[McpServerTool(Name = "todo_add")][McpServerTool(Name = "todo_add", Destructive = false, OpenWorld = false)][Description("Appends a batch of items to the agent's TODO list. All items in the batch share the same 'done' flag; for a mixed batch, call twice. Pass an array of one to add a single item. Returns the full list as a JSON object (state, itemCount, items[]). The whole batch is rejected if any item is empty, contains newlines, or exceeds 200 characters.")]PR description says "Sets the right MCP [McpServerTool] annotations on every internal tool", but
shell_runstill uses[McpServerTool(Name = "shell_run")]without any of the ReadOnly/Destructive/Idempotent/OpenWorld flags. Either update the PR description to scope outshell_run, or add the appropriate annotations for it as well (it appears to be OpenWorld=true and Destructive=true by default).@ -117,0 +128,4 @@destructive,idempotent,openWorld,readOnly);When
readOnlyHintis true butdestructiveHintis absent, this mapping will still defaultDestructiveto true, producing a contradictory combination (ReadOnly=true, Destructive=true) that consumers may not special-case. Consider normalizing annotations (e.g., if ReadOnly is true, force Destructive=false and possibly Idempotent=true) so the propagated hints are internally consistent.@ -16,3 +17,4 @@string Description,ImmutableArray<ToolParameter> Parameters,ToolDescriptorAnnotations Annotations,JsonElement Schema = default);ToolDescriptoris a public abstraction and adding a new required positional parameter (Annotations) is a source-breaking change for any downstream callers constructingToolDescriptor. If you want this to be non-breaking, consider makingAnnotationsoptional (nullable with a documented default, or a default value) and/or adding a secondary constructor/factory so existing call sites continue to compile while still surfacing annotations when available.