From 1dd71d6cf7b5d5ba91465b5e096706dc8664a806 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Jun 2026 20:53:08 +0000 Subject: [PATCH] MCP get_message: clamp max_chars above 4000 to cap Values above maxBodyChars (4000) previously fell back to the default (2000). Now they clamp to the cap like limitArg does for list limits. Zero or negative values still use the default. Co-authored-by: endolith --- internal/mcp/handlers.go | 4 +++- internal/mcp/server.go | 2 +- internal/mcp/server_test.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/internal/mcp/handlers.go b/internal/mcp/handlers.go index 186fe495..db865b4d 100644 --- a/internal/mcp/handlers.go +++ b/internal/mcp/handlers.go @@ -837,8 +837,10 @@ func (h *handlers) getMessage(ctx context.Context, req mcp.CallToolRequest) (*mc } maxChars := intArg(args, "max_chars", defaultBodyChars) - if maxChars <= 0 || maxChars > maxBodyChars { + if maxChars <= 0 { maxChars = defaultBodyChars + } else if maxChars > maxBodyChars { + maxChars = maxBodyChars } fullBody := msg.BodyText diff --git a/internal/mcp/server.go b/internal/mcp/server.go index a30de679..e20252db 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -275,7 +275,7 @@ func getMessageTool() mcp.Tool { mcp.Description("Byte offset from the start of body_text to center the window on (e.g. char_offset from search_in_message). Takes precedence over offset."), ), mcp.WithNumber("max_chars", - mcp.Description("Maximum body_text bytes to return (default 2000, max 4000). Out-of-range values are silently replaced with the default."), + mcp.Description("Maximum body_text bytes to return (default 2000, max 4000). Values above 4000 are clamped to 4000; zero or negative values use the default."), ), ) } diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 20cba26b..74075d65 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -844,6 +844,42 @@ func TestGetMessage(t *testing.T) { assertpkg.Equal(t, 0, msg.Offset, "starts at body start") }) + t.Run("max_chars above cap clamps to 4000", func(t *testing.T) { + assert := assertpkg.New(t) + longBody := strings.Repeat("x", 5000) + eng2 := &querytest.MockEngine{ + Messages: map[int64]*query.MessageDetail{ + 54: testutil.NewMessageDetail(54).WithBodyText(longBody).BuildPtr(), + }, + } + h2 := newTestHandlers(eng2) + msg := runTool[getMessageResp](t, "get_message", h2.getMessage, map[string]any{ + "id": float64(54), + "max_chars": float64(5000), + }) + assert.Equal(4000, msg.BodyReturned, "body_returned") + assert.Len(msg.BodyText, 4000, "clamped body_text") + assert.True(msg.HasMore, "has_more") + }) + + t.Run("max_chars zero uses default", func(t *testing.T) { + assert := assertpkg.New(t) + longBody := strings.Repeat("x", 5000) + eng2 := &querytest.MockEngine{ + Messages: map[int64]*query.MessageDetail{ + 55: testutil.NewMessageDetail(55).WithBodyText(longBody).BuildPtr(), + }, + } + h2 := newTestHandlers(eng2) + msg := runTool[getMessageResp](t, "get_message", h2.getMessage, map[string]any{ + "id": float64(55), + "max_chars": float64(0), + }) + assert.Equal(2000, msg.BodyReturned, "body_returned") + assert.Len(msg.BodyText, 2000, "default body_text") + assert.True(msg.HasMore, "has_more") + }) + errorCases := []struct { name string args map[string]any