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