From 4ac0c890a36bd67f3cdc95f0947608928f03e376 Mon Sep 17 00:00:00 2001 From: Tiago Vilas Boas Date: Thu, 10 Sep 2026 22:39:13 +0000 Subject: [PATCH] fix(annotations): add OpenWorldHint to default UGC issue/PR/discussion tools Clients that require an explicit boolean (same class of gap as ReadOnlyHint) cannot treat omitted OpenWorldHint as the spec default of true. Granular issue/PR tools already declare OpenWorldHint: true; the always-on counterparts that return or publish issue, PR, review, and discussion user-generated content did not. Scope is limited to that default-on UGC surface and does not change search tools, matching the #1100 decision to postpone a broad OSS rollout. Co-authored-by: Tiago Vilas Boas --- .../add_comment_to_pending_review.snap | 1 + .../__toolsnaps__/add_issue_comment.snap | 1 + .../add_reply_to_pull_request_comment.snap | 1 + .../discussion_comment_write.snap | 1 + pkg/github/__toolsnaps__/get_discussion.snap | 1 + .../get_discussion_comments.snap | 1 + pkg/github/__toolsnaps__/issue_read.snap | 1 + pkg/github/__toolsnaps__/issue_write.snap | 1 + .../__toolsnaps__/pull_request_read.snap | 1 + .../pull_request_review_write.snap | 1 + pkg/github/discussions.go | 11 ++++++---- pkg/github/discussions_test.go | 8 ++++++++ pkg/github/issues.go | 15 ++++++++------ pkg/github/issues_test.go | 9 +++++++++ pkg/github/pullrequests.go | 20 +++++++++++-------- pkg/github/pullrequests_test.go | 12 +++++++++++ 16 files changed, 67 insertions(+), 18 deletions(-) diff --git a/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap b/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap index 4a6e6ca9ba..d7dc9434a9 100644 --- a/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap +++ b/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": false, "title": "Add review comment to the requester's latest pending pull request review" }, diff --git a/pkg/github/__toolsnaps__/add_issue_comment.snap b/pkg/github/__toolsnaps__/add_issue_comment.snap index a68494d448..51cd1f5597 100644 --- a/pkg/github/__toolsnaps__/add_issue_comment.snap +++ b/pkg/github/__toolsnaps__/add_issue_comment.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": false, "title": "Add comment to issue or pull request" }, diff --git a/pkg/github/__toolsnaps__/add_reply_to_pull_request_comment.snap b/pkg/github/__toolsnaps__/add_reply_to_pull_request_comment.snap index a86ea3d4b5..513f69f86a 100644 --- a/pkg/github/__toolsnaps__/add_reply_to_pull_request_comment.snap +++ b/pkg/github/__toolsnaps__/add_reply_to_pull_request_comment.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": false, "title": "Add reply to pull request comment" }, diff --git a/pkg/github/__toolsnaps__/discussion_comment_write.snap b/pkg/github/__toolsnaps__/discussion_comment_write.snap index eb730b48e7..cc26e51e7c 100644 --- a/pkg/github/__toolsnaps__/discussion_comment_write.snap +++ b/pkg/github/__toolsnaps__/discussion_comment_write.snap @@ -2,6 +2,7 @@ "annotations": { "destructiveHint": true, "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": false, "title": "Manage discussion comments" }, diff --git a/pkg/github/__toolsnaps__/get_discussion.snap b/pkg/github/__toolsnaps__/get_discussion.snap index 3f538d4e97..c854fd315b 100644 --- a/pkg/github/__toolsnaps__/get_discussion.snap +++ b/pkg/github/__toolsnaps__/get_discussion.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": true, "title": "Get discussion" }, diff --git a/pkg/github/__toolsnaps__/get_discussion_comments.snap b/pkg/github/__toolsnaps__/get_discussion_comments.snap index a2705c9bf1..a4a3356f62 100644 --- a/pkg/github/__toolsnaps__/get_discussion_comments.snap +++ b/pkg/github/__toolsnaps__/get_discussion_comments.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": true, "title": "Get discussion comments" }, diff --git a/pkg/github/__toolsnaps__/issue_read.snap b/pkg/github/__toolsnaps__/issue_read.snap index faf6085a5e..7ca0117686 100644 --- a/pkg/github/__toolsnaps__/issue_read.snap +++ b/pkg/github/__toolsnaps__/issue_read.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": true, "title": "Get issue details" }, diff --git a/pkg/github/__toolsnaps__/issue_write.snap b/pkg/github/__toolsnaps__/issue_write.snap index 20cc730f51..62ffe23107 100644 --- a/pkg/github/__toolsnaps__/issue_write.snap +++ b/pkg/github/__toolsnaps__/issue_write.snap @@ -10,6 +10,7 @@ }, "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": false, "title": "Create or update issue/pull request" }, diff --git a/pkg/github/__toolsnaps__/pull_request_read.snap b/pkg/github/__toolsnaps__/pull_request_read.snap index d518c7cad9..c84cc387c2 100644 --- a/pkg/github/__toolsnaps__/pull_request_read.snap +++ b/pkg/github/__toolsnaps__/pull_request_read.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": true, "title": "Get details for a single pull request" }, diff --git a/pkg/github/__toolsnaps__/pull_request_review_write.snap b/pkg/github/__toolsnaps__/pull_request_review_write.snap index 74ef808559..ff5f4cc902 100644 --- a/pkg/github/__toolsnaps__/pull_request_review_write.snap +++ b/pkg/github/__toolsnaps__/pull_request_review_write.snap @@ -1,6 +1,7 @@ { "annotations": { "idempotentHint": false, + "openWorldHint": true, "readOnlyHint": false, "title": "Write operations (create, submit, delete) on pull request reviews" }, diff --git a/pkg/github/discussions.go b/pkg/github/discussions.go index 4b40569c39..5a57f15a3b 100644 --- a/pkg/github/discussions.go +++ b/pkg/github/discussions.go @@ -290,8 +290,9 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { Name: "get_discussion", Description: t("TOOL_GET_DISCUSSION_DESCRIPTION", "Get a specific discussion by ID"), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_GET_DISCUSSION_USER_TITLE", "Get discussion"), - ReadOnlyHint: true, + Title: t("TOOL_GET_DISCUSSION_USER_TITLE", "Get discussion"), + ReadOnlyHint: true, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: &jsonschema.Schema{ Type: "object", @@ -398,8 +399,9 @@ func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.Serve Name: "get_discussion_comments", Description: t("TOOL_GET_DISCUSSION_COMMENTS_DESCRIPTION", "Get comments from a discussion"), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_GET_DISCUSSION_COMMENTS_USER_TITLE", "Get discussion comments"), - ReadOnlyHint: true, + Title: t("TOOL_GET_DISCUSSION_COMMENTS_USER_TITLE", "Get discussion comments"), + ReadOnlyHint: true, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: WithCursorPagination(&jsonschema.Schema{ Type: "object", @@ -598,6 +600,7 @@ Supports adding top-level comments, replying to existing comments, updating comm Title: t("TOOL_DISCUSSION_COMMENT_WRITE_USER_TITLE", "Manage discussion comments"), ReadOnlyHint: false, DestructiveHint: jsonschema.Ptr(true), + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: &jsonschema.Schema{ Type: "object", diff --git a/pkg/github/discussions_test.go b/pkg/github/discussions_test.go index 111372a9ef..709b919a23 100644 --- a/pkg/github/discussions_test.go +++ b/pkg/github/discussions_test.go @@ -501,6 +501,9 @@ func Test_GetDiscussion(t *testing.T) { assert.Equal(t, "get_discussion", tool.Name) assert.NotEmpty(t, tool.Description) + assert.True(t, tool.Annotations.ReadOnlyHint, "get_discussion should be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "get_discussion returns untrusted discussion UGC") schema, ok := tool.InputSchema.(*jsonschema.Schema) require.True(t, ok, "InputSchema should be *jsonschema.Schema") assert.Contains(t, schema.Properties, "owner") @@ -666,6 +669,9 @@ func Test_GetDiscussionComments(t *testing.T) { assert.Equal(t, "get_discussion_comments", tool.Name) assert.NotEmpty(t, tool.Description) + assert.True(t, tool.Annotations.ReadOnlyHint, "get_discussion_comments should be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "get_discussion_comments returns untrusted comment UGC") schema, ok := tool.InputSchema.(*jsonschema.Schema) require.True(t, ok, "InputSchema should be *jsonschema.Schema") assert.Contains(t, schema.Properties, "owner") @@ -966,6 +972,8 @@ func Test_DiscussionCommentWrite(t *testing.T) { assert.False(t, tool.Annotations.ReadOnlyHint, "discussion_comment_write should not be read-only") require.NotNil(t, tool.Annotations.DestructiveHint) assert.True(t, *tool.Annotations.DestructiveHint, "discussion_comment_write should be destructive") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "discussion_comment_write publishes discussion comment UGC") schema, ok := tool.InputSchema.(*jsonschema.Schema) require.True(t, ok, "InputSchema should be *jsonschema.Schema") assert.Contains(t, schema.Properties, "method") diff --git a/pkg/github/issues.go b/pkg/github/issues.go index cc8bc599a1..f70e5bdc8b 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -827,8 +827,9 @@ func IssueRead(t translations.TranslationHelperFunc) inventory.ServerTool { Name: "issue_read", Description: t("TOOL_ISSUE_READ_DESCRIPTION", "Get information about a specific issue in a GitHub repository."), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_ISSUE_READ_USER_TITLE", "Get issue details"), - ReadOnlyHint: true, + Title: t("TOOL_ISSUE_READ_USER_TITLE", "Get issue details"), + ReadOnlyHint: true, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: schema, }, @@ -1369,8 +1370,9 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool Name: "add_issue_comment", Description: t("TOOL_ADD_ISSUE_COMMENT_DESCRIPTION", "Add a comment and/or reaction to a specific issue or issue comment in a GitHub repository. Use this tool with pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add or react to review comments. At least one of body or reaction is required."), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_ADD_ISSUE_COMMENT_USER_TITLE", "Add comment to issue or pull request"), - ReadOnlyHint: false, + Title: t("TOOL_ADD_ISSUE_COMMENT_USER_TITLE", "Add comment to issue or pull request"), + ReadOnlyHint: false, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: &jsonschema.Schema{ Type: "object", @@ -2410,8 +2412,9 @@ func IssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool { Name: "issue_write", Description: t("TOOL_ISSUE_WRITE_DESCRIPTION", "Create a new or update an existing issue in a GitHub repository."), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_ISSUE_WRITE_USER_TITLE", "Create or update issue/pull request"), - ReadOnlyHint: false, + Title: t("TOOL_ISSUE_WRITE_USER_TITLE", "Create or update issue/pull request"), + ReadOnlyHint: false, + OpenWorldHint: jsonschema.Ptr(true), }, Meta: mcp.Meta{ "ui": map[string]any{ diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 83bedc5b54..f118eb7af8 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -132,6 +132,9 @@ func Test_GetIssue(t *testing.T) { assert.Equal(t, "issue_read", tool.Name) assert.NotEmpty(t, tool.Description) + assert.True(t, tool.Annotations.ReadOnlyHint, "issue_read should be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "issue_read returns untrusted issue/comment UGC") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo") @@ -1837,6 +1840,9 @@ func Test_CreateIssue(t *testing.T) { assert.Equal(t, "issue_write", tool.Name) assert.NotEmpty(t, tool.Description) + assert.False(t, tool.Annotations.ReadOnlyHint, "issue_write should not be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "issue_write publishes issue UGC") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo") @@ -6227,6 +6233,9 @@ func TestAddIssueCommentSchema(t *testing.T) { assert.Equal(t, "add_issue_comment", tool.Name) assert.NotEmpty(t, tool.Description) + assert.False(t, tool.Annotations.ReadOnlyHint, "add_issue_comment should not be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "add_issue_comment publishes comment UGC") schema := tool.InputSchema.(*jsonschema.Schema) assert.Contains(t, schema.Properties, "owner") assert.Contains(t, schema.Properties, "repo") diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 5cee8b3231..624defe046 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -73,8 +73,9 @@ Possible options: Name: "pull_request_read", Description: t("TOOL_PULL_REQUEST_READ_DESCRIPTION", "Get information on a specific pull request in GitHub repository."), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_GET_PULL_REQUEST_USER_TITLE", "Get details for a single pull request"), - ReadOnlyHint: true, + Title: t("TOOL_GET_PULL_REQUEST_USER_TITLE", "Get details for a single pull request"), + ReadOnlyHint: true, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: schema, }, @@ -1214,8 +1215,9 @@ func AddReplyToPullRequestComment(t translations.TranslationHelperFunc) inventor Name: "add_reply_to_pull_request_comment", Description: t("TOOL_ADD_REPLY_TO_PULL_REQUEST_COMMENT_DESCRIPTION", "Add a reply and/or reaction to an existing pull request comment. This can create a new comment linked as a reply to the specified comment, add an emoji reaction to the specified comment, or do both. At least one of body or reaction is required."), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_ADD_REPLY_TO_PULL_REQUEST_COMMENT_USER_TITLE", "Add reply to pull request comment"), - ReadOnlyHint: false, + Title: t("TOOL_ADD_REPLY_TO_PULL_REQUEST_COMMENT_USER_TITLE", "Add reply to pull request comment"), + ReadOnlyHint: false, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: schema, }, @@ -1864,8 +1866,9 @@ Available methods: - unresolve_thread: Unresolve a previously resolved review thread. Requires only "threadId" parameter. The owner, repo, and pullNumber parameters are not used for this method. Unresolving an already-unresolved thread is a no-op. `), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_PULL_REQUEST_REVIEW_WRITE_USER_TITLE", "Write operations (create, submit, delete) on pull request reviews"), - ReadOnlyHint: false, + Title: t("TOOL_PULL_REQUEST_REVIEW_WRITE_USER_TITLE", "Write operations (create, submit, delete) on pull request reviews"), + ReadOnlyHint: false, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: schema, }, @@ -2393,8 +2396,9 @@ func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.S Name: "add_comment_to_pending_review", Description: t("TOOL_ADD_COMMENT_TO_PENDING_REVIEW_DESCRIPTION", "Add review comment to the requester's latest pending pull request review. A pending review needs to already exist to call this (check with the user if not sure)."), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_ADD_COMMENT_TO_PENDING_REVIEW_USER_TITLE", "Add review comment to the requester's latest pending pull request review"), - ReadOnlyHint: false, + Title: t("TOOL_ADD_COMMENT_TO_PENDING_REVIEW_USER_TITLE", "Add review comment to the requester's latest pending pull request review"), + ReadOnlyHint: false, + OpenWorldHint: jsonschema.Ptr(true), }, InputSchema: schema, }, diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index c0e392aea6..6f4a0471af 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -27,6 +27,9 @@ func Test_GetPullRequest(t *testing.T) { assert.Equal(t, "pull_request_read", tool.Name) assert.NotEmpty(t, tool.Description) + assert.True(t, tool.Annotations.ReadOnlyHint, "pull_request_read should be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "pull_request_read returns untrusted PR/review UGC") schema := tool.InputSchema.(*jsonschema.Schema) assert.Contains(t, schema.Properties, "method") assert.Contains(t, schema.Properties, "owner") @@ -3195,6 +3198,9 @@ func TestCreateAndSubmitPullRequestReview(t *testing.T) { assert.Equal(t, "pull_request_review_write", tool.Name) assert.NotEmpty(t, tool.Description) + assert.False(t, tool.Annotations.ReadOnlyHint, "pull_request_review_write should not be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "pull_request_review_write publishes review UGC") schema := tool.InputSchema.(*jsonschema.Schema) assert.Contains(t, schema.Properties, "method") assert.Contains(t, schema.Properties, "owner") @@ -3646,6 +3652,9 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { assert.Equal(t, "add_comment_to_pending_review", tool.Name) assert.NotEmpty(t, tool.Description) + assert.False(t, tool.Annotations.ReadOnlyHint, "add_comment_to_pending_review should not be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "add_comment_to_pending_review publishes review comment UGC") schema := tool.InputSchema.(*jsonschema.Schema) assert.Contains(t, schema.Properties, "owner") assert.Contains(t, schema.Properties, "repo") @@ -4334,6 +4343,9 @@ func TestAddReplyToPullRequestComment(t *testing.T) { assert.Equal(t, "add_reply_to_pull_request_comment", tool.Name) assert.NotEmpty(t, tool.Description) + assert.False(t, tool.Annotations.ReadOnlyHint, "add_reply_to_pull_request_comment should not be read-only") + require.NotNil(t, tool.Annotations.OpenWorldHint) + assert.True(t, *tool.Annotations.OpenWorldHint, "add_reply_to_pull_request_comment publishes reply UGC") schema := tool.InputSchema.(*jsonschema.Schema) assert.Contains(t, schema.Properties, "owner") assert.Contains(t, schema.Properties, "repo")