From 61eccf1cde08d27531ba31c2a8018ac9c5c614fe Mon Sep 17 00:00:00 2001 From: Bryan Thompson Date: Fri, 26 Dec 2025 09:26:10 -0600 Subject: [PATCH] feat: Add tool annotations for improved LLM tool understanding (#347) Add readOnlyHint and destructiveHint annotations to all 13 tools to help LLMs better understand tool behavior and make safer decisions. Changes: - Added readOnlyHint: true to read-only tools (6 tools): check_login_status, get_login_qrcode, list_feeds, search_feeds, get_feed_detail, user_profile - Added destructiveHint: true to destructive tools (7 tools): delete_cookies, publish_content, publish_with_video, like_feed, favorite_feed, post_comment_to_feed, reply_comment_in_feed - Added title annotations for human-readable display - Fixed tool count from 12 to 13 in log statement This improves tool safety metadata for MCP clients. Co-authored-by: triepod-ai Co-authored-by: Claude --- mcp_server.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/mcp_server.go b/mcp_server.go index 7ee4214..b894a46 100644 --- a/mcp_server.go +++ b/mcp_server.go @@ -10,6 +10,9 @@ import ( "github.com/sirupsen/logrus" ) +// Helper functions for annotation pointers +func boolPtr(b bool) *bool { return &b } + // MCP 工具参数结构体定义 // PublishContentArgs 发布内容的参数 @@ -148,6 +151,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "check_login_status", Description: "检查小红书登录状态", + Annotations: &mcp.ToolAnnotations{ + Title: "Check Login Status", + ReadOnlyHint: true, + }, }, withPanicRecovery("check_login_status", func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { result := appServer.handleCheckLoginStatus(ctx) @@ -160,6 +167,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "get_login_qrcode", Description: "获取登录二维码(返回 Base64 图片和超时时间)", + Annotations: &mcp.ToolAnnotations{ + Title: "Get Login QR Code", + ReadOnlyHint: true, + }, }, withPanicRecovery("get_login_qrcode", func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { result := appServer.handleGetLoginQrcode(ctx) @@ -172,6 +183,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "delete_cookies", Description: "删除 cookies 文件,重置登录状态。删除后需要重新登录。", + Annotations: &mcp.ToolAnnotations{ + Title: "Delete Cookies", + DestructiveHint: boolPtr(true), + }, }, withPanicRecovery("delete_cookies", func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { result := appServer.handleDeleteCookies(ctx) @@ -184,6 +199,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "publish_content", Description: "发布小红书图文内容", + Annotations: &mcp.ToolAnnotations{ + Title: "Publish Content", + DestructiveHint: boolPtr(true), + }, }, withPanicRecovery("publish_content", func(ctx context.Context, req *mcp.CallToolRequest, args PublishContentArgs) (*mcp.CallToolResult, any, error) { // 转换参数格式到现有的 handler @@ -203,6 +222,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "list_feeds", Description: "获取首页 Feeds 列表", + Annotations: &mcp.ToolAnnotations{ + Title: "List Feeds", + ReadOnlyHint: true, + }, }, withPanicRecovery("list_feeds", func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { result := appServer.handleListFeeds(ctx) @@ -215,6 +238,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "search_feeds", Description: "搜索小红书内容(需要已登录)", + Annotations: &mcp.ToolAnnotations{ + Title: "Search Feeds", + ReadOnlyHint: true, + }, }, withPanicRecovery("search_feeds", func(ctx context.Context, req *mcp.CallToolRequest, args SearchFeedsArgs) (*mcp.CallToolResult, any, error) { result := appServer.handleSearchFeeds(ctx, args) @@ -227,6 +254,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "get_feed_detail", Description: "获取小红书笔记详情,返回笔记内容、图片、作者信息、互动数据(点赞/收藏/分享数)及评论列表。默认返回前10条一级评论,如需更多评论请设置load_all_comments=true", + Annotations: &mcp.ToolAnnotations{ + Title: "Get Feed Detail", + ReadOnlyHint: true, + }, }, withPanicRecovery("get_feed_detail", func(ctx context.Context, req *mcp.CallToolRequest, args FeedDetailArgs) (*mcp.CallToolResult, any, error) { argsMap := map[string]interface{}{ @@ -268,6 +299,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "user_profile", Description: "获取指定的小红书用户主页,返回用户基本信息,关注、粉丝、获赞量及其笔记内容", + Annotations: &mcp.ToolAnnotations{ + Title: "User Profile", + ReadOnlyHint: true, + }, }, withPanicRecovery("user_profile", func(ctx context.Context, req *mcp.CallToolRequest, args UserProfileArgs) (*mcp.CallToolResult, any, error) { argsMap := map[string]interface{}{ @@ -284,6 +319,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "post_comment_to_feed", Description: "发表评论到小红书笔记", + Annotations: &mcp.ToolAnnotations{ + Title: "Post Comment", + DestructiveHint: boolPtr(true), + }, }, withPanicRecovery("post_comment_to_feed", func(ctx context.Context, req *mcp.CallToolRequest, args PostCommentArgs) (*mcp.CallToolResult, any, error) { argsMap := map[string]interface{}{ @@ -301,6 +340,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "reply_comment_in_feed", Description: "回复小红书笔记下的指定评论", + Annotations: &mcp.ToolAnnotations{ + Title: "Reply Comment", + DestructiveHint: boolPtr(true), + }, }, func(ctx context.Context, req *mcp.CallToolRequest, args ReplyCommentArgs) (*mcp.CallToolResult, any, error) { if args.CommentID == "" && args.UserID == "" { @@ -327,6 +370,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "publish_with_video", Description: "发布小红书视频内容(仅支持本地单个视频文件)", + Annotations: &mcp.ToolAnnotations{ + Title: "Publish Video", + DestructiveHint: boolPtr(true), + }, }, withPanicRecovery("publish_with_video", func(ctx context.Context, req *mcp.CallToolRequest, args PublishVideoArgs) (*mcp.CallToolResult, any, error) { argsMap := map[string]interface{}{ @@ -345,6 +392,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "like_feed", Description: "为指定笔记点赞或取消点赞(如已点赞将跳过点赞,如未点赞将跳过取消点赞)", + Annotations: &mcp.ToolAnnotations{ + Title: "Like Feed", + DestructiveHint: boolPtr(true), + }, }, withPanicRecovery("like_feed", func(ctx context.Context, req *mcp.CallToolRequest, args LikeFeedArgs) (*mcp.CallToolResult, any, error) { argsMap := map[string]interface{}{ @@ -362,6 +413,10 @@ func registerTools(server *mcp.Server, appServer *AppServer) { &mcp.Tool{ Name: "favorite_feed", Description: "收藏指定笔记或取消收藏(如已收藏将跳过收藏,如未收藏将跳过取消收藏)", + Annotations: &mcp.ToolAnnotations{ + Title: "Favorite Feed", + DestructiveHint: boolPtr(true), + }, }, withPanicRecovery("favorite_feed", func(ctx context.Context, req *mcp.CallToolRequest, args FavoriteFeedArgs) (*mcp.CallToolResult, any, error) { argsMap := map[string]interface{}{ @@ -374,7 +429,7 @@ func registerTools(server *mcp.Server, appServer *AppServer) { }), ) - logrus.Infof("Registered %d MCP tools", 12) + logrus.Infof("Registered %d MCP tools", 13) } // convertToMCPResult 将自定义的 MCPToolResult 转换为官方 SDK 的格式