Skip to content

fix(tools): omit tool annotations from JSON when unset - #911

Open
syf2211 wants to merge 2 commits into
mark3labs:mainfrom
syf2211:fix/710-omit-tool-annotations
Open

fix(tools): omit tool annotations from JSON when unset#911
syf2211 wants to merge 2 commits into
mark3labs:mainfrom
syf2211:fix/710-omit-tool-annotations

Conversation

@syf2211

@syf2211 syf2211 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Omit the annotations field from tool JSON when no annotation values are set, and add WithoutDefaultAnnotations() so callers can opt out of NewTool's default hint pointers.

Motivation

Tool.MarshalJSON always included annotations, even for tools with no annotation metadata. Clients could not distinguish "explicitly annotated" from "no annotation data available" (see #710).

Changes

  • Add ToolAnnotation.HasAny() to detect whether any annotation field is set
  • Gate annotations in Tool.MarshalJSON on HasAny()
  • Add WithoutDefaultAnnotations() ToolOption to clear NewTool default hints
  • Add regression tests for marshal behavior

Tests

go test ./mcp/ ./server/ -count=1

All tests pass.

Notes

  • NewTool() behavior is unchanged: default hint pointers are still serialized for backward compatibility
  • Use NewTool(name, WithoutDefaultAnnotations(), ...) to omit annotations from tools/list output when you have no annotation metadata

Summary by CodeRabbit

  • New Features
    • Tool metadata now omits the annotations field in JSON unless annotation hint values are explicitly set, resulting in cleaner output.
    • Added a tool option to start with no default annotation hints, so you can selectively add only the annotation hints you need.
  • Tests
    • Added coverage to verify correct annotations JSON inclusion/omission behavior and helper detection of set annotation values.

Add ToolAnnotation.HasAny() and only include the annotations field in
Tool.MarshalJSON when at least one annotation value is present.

Introduce WithoutDefaultAnnotations() so callers can opt out of the
default hint pointers initialized by NewTool and omit annotations from
tools/list output when they have no annotation metadata.

Fixes mark3labs#710
@mark-iii-labs-huly

Copy link
Copy Markdown

Connected to Huly®: MCP_G-475

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 165f3a75-5050-44c9-99e2-70c15ad30752

📥 Commits

Reviewing files that changed from the base of the PR and between 5a69eeb and 8816266.

📒 Files selected for processing (1)
  • mcp/tools_additional_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • mcp/tools_additional_test.go

Walkthrough

ToolAnnotation now exposes HasAny(), Tool.MarshalJSON omits annotations unless any annotation data is set, and WithoutDefaultAnnotations() clears the default annotations. Tests cover JSON output and HasAny() behavior.

Tool Annotations Marshaling

Layer / File(s) Summary
HasAny, conditional marshaling, and WithoutDefaultAnnotations
mcp/tools.go
ToolAnnotation.HasAny() checks for non-empty Title or non-nil hint pointers; MarshalJSON uses it to conditionally emit annotations; WithoutDefaultAnnotations() resets t.Annotations to an empty ToolAnnotation{}.
Marshaling and HasAny tests
mcp/tools_additional_test.go
TestToolAnnotationsMarshalJSON asserts annotations presence/absence for default, cleared, explicit, and raw-schema cases, plus ToolAnnotation.HasAny() results for zero-value and populated annotations.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related issues

Possibly related PRs

  • mark3labs/mcp-go#158: Introduced ToolAnnotation and the default annotation wiring that this PR now makes conditionally omittable.
  • mark3labs/mcp-go#165: Changed Tool.MarshalJSON to include annotations, which this PR now gates on HasAny().
  • mark3labs/mcp-go#260: Related to ToolAnnotation hint-field marshaling behavior that HasAny() inspects.

Suggested reviewers

  • robert-jackson-glean
  • ezynda3
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: omitting tool annotations from JSON when unset.
Description check ✅ Passed The description covers summary, motivation, changes, tests, and notes; only optional template sections are omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
mcp/tools_additional_test.go (1)

297-362: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Convert this new test matrix to a table-driven test.

These cases are already exercising a small annotation-state matrix, so a tests := []struct{ ... } table will be easier to extend and keeps the file aligned with the repo’s Go test convention. As per coding guidelines, implement table-driven tests with tests := []struct{ name, ... }.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/tools_additional_test.go` around lines 297 - 362, Refactor
TestToolAnnotationsMarshalJSON into a table-driven test using a tests :=
[]struct{ name, ... } matrix so each case is easier to extend and follows the
repo’s Go test convention. Keep the existing coverage for NewTool,
WithoutDefaultAnnotations, NewToolWithRawSchema, and ToolAnnotation.HasAny by
moving the current subtest logic into table entries and iterating over them with
t.Run. Preserve the same assertions and JSON checks while centralizing the
shared marshal/unmarshal setup.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcp/tools.go`:
- Around line 961-964: WithoutDefaultAnnotations currently resets the entire
ToolAnnotation on Tool, which clears explicitly set metadata like the title and
can make MarshalJSON omit annotations entirely. Update
WithoutDefaultAnnotations() so it only removes the default hint pointer fields
while preserving any user-supplied annotations such as the title set by
WithTitleAnnotation, and keep the behavior localized to the ToolAnnotation
handling in that option.

---

Nitpick comments:
In `@mcp/tools_additional_test.go`:
- Around line 297-362: Refactor TestToolAnnotationsMarshalJSON into a
table-driven test using a tests := []struct{ name, ... } matrix so each case is
easier to extend and follows the repo’s Go test convention. Keep the existing
coverage for NewTool, WithoutDefaultAnnotations, NewToolWithRawSchema, and
ToolAnnotation.HasAny by moving the current subtest logic into table entries and
iterating over them with t.Run. Preserve the same assertions and JSON checks
while centralizing the shared marshal/unmarshal setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 981223fb-7d0e-4fe6-a329-2e8dba79bf97

📥 Commits

Reviewing files that changed from the base of the PR and between b6e6224 and 5a69eeb.

📒 Files selected for processing (2)
  • mcp/tools.go
  • mcp/tools_additional_test.go

Comment thread mcp/tools.go
Comment on lines +961 to +964
func WithoutDefaultAnnotations() ToolOption {
return func(t *Tool) {
t.Annotations = ToolAnnotation{}
}

@coderabbitai coderabbitai Bot Jun 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don't clear an explicitly set annotation title here.

This resets the whole ToolAnnotation, so NewTool("test", WithTitleAnnotation("My Tool"), WithoutDefaultAnnotations()) drops the user-supplied title and MarshalJSON omits annotations entirely. WithoutDefaultAnnotations() should only strip the default hint pointers, not unrelated explicit metadata.

Suggested fix
 func WithoutDefaultAnnotations() ToolOption {
 	return func(t *Tool) {
-		t.Annotations = ToolAnnotation{}
+		title := t.Annotations.Title
+		t.Annotations = ToolAnnotation{
+			Title: title,
+		}
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func WithoutDefaultAnnotations() ToolOption {
return func(t *Tool) {
t.Annotations = ToolAnnotation{}
}
func WithoutDefaultAnnotations() ToolOption {
return func(t *Tool) {
title := t.Annotations.Title
t.Annotations = ToolAnnotation{
Title: title,
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/tools.go` around lines 961 - 964, WithoutDefaultAnnotations currently
resets the entire ToolAnnotation on Tool, which clears explicitly set metadata
like the title and can make MarshalJSON omit annotations entirely. Update
WithoutDefaultAnnotations() so it only removes the default hint pointer fields
while preserving any user-supplied annotations such as the title set by
WithTitleAnnotation, and keep the behavior localized to the ToolAnnotation
handling in that option.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@coderabbitai was this fixed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ezynda3 No—the current implementation still resets the entire ToolAnnotation, so it clears a title supplied by an earlier option. For example:

NewTool("test",
    WithTitleAnnotation("My Tool"),
    WithoutDefaultAnnotations(),
)

will omit annotations rather than retain {"title":"My Tool"}. WithoutDefaultAnnotations() should preserve t.Annotations.Title while clearing only the default hint pointers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@syf2211 pleas have a look

Address review feedback on PR mark3labs#911 by using a shared test matrix for
annotation JSON marshaling cases.
@ezynda3 ezynda3 added the status: needs submitter response Waiting for feedback from issue opener label Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: needs submitter response Waiting for feedback from issue opener

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants