From 76881c9e173f3a15d0145b0d98d3da1f4e0cc48c Mon Sep 17 00:00:00 2001 From: ysyneu Date: Fri, 29 May 2026 11:37:42 +0800 Subject: [PATCH 1/2] feat: adopt typed timestamps from flashduty-sdk Bump flashduty-sdk to the Timestamp/TimestampMilli release. mcp marshals SDK results straight through MarshalResult -> sdk.Marshal, so tool output now renders RFC3339 (JSON and TOON) with no source change. Add a regression test pinning that behavior. --- go.mod | 2 +- go.sum | 4 +- pkg/flashduty/marshal_time_test.go | 93 ++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 pkg/flashduty/marshal_time_test.go diff --git a/go.mod b/go.mod index 8d85804..27c0dbf 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/bluele/gcache v0.0.2 - github.com/flashcatcloud/flashduty-sdk v0.8.1-0.20260510070250-0340ac6a5a33 + github.com/flashcatcloud/flashduty-sdk v0.9.1-0.20260529032514-a227261bdb9a github.com/google/go-github/v72 v72.0.0 github.com/josephburnett/jd v1.9.2 github.com/mark3labs/mcp-go v0.52.0 diff --git a/go.sum b/go.sum index 1731372..7fa9b67 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/flashcatcloud/flashduty-sdk v0.8.1-0.20260510070250-0340ac6a5a33 h1:HnB++VulEnF+oIsNwKK8QBv4CCdG+ztdFKLScI4bXlc= -github.com/flashcatcloud/flashduty-sdk v0.8.1-0.20260510070250-0340ac6a5a33/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY= +github.com/flashcatcloud/flashduty-sdk v0.9.1-0.20260529032514-a227261bdb9a h1:QdRXdFRI4rsUqmtMbMFwsB6vxQLLYyBhKPo9PU5uQ5Q= +github.com/flashcatcloud/flashduty-sdk v0.9.1-0.20260529032514-a227261bdb9a/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= diff --git a/pkg/flashduty/marshal_time_test.go b/pkg/flashduty/marshal_time_test.go new file mode 100644 index 0000000..4ab67ec --- /dev/null +++ b/pkg/flashduty/marshal_time_test.go @@ -0,0 +1,93 @@ +package flashduty + +import ( + "strconv" + "strings" + "testing" + "time" + + sdk "github.com/flashcatcloud/flashduty-sdk" + "github.com/mark3labs/mcp-go/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// timeFixture is a small struct exercising both SDK timestamp types the way an +// SDK response object would carry them. +type timeFixture struct { + CreatedAt sdk.Timestamp `json:"created_at"` + UpdatedAt sdk.TimestampMilli `json:"updated_at"` +} + +// resultText pulls the single text payload out of an MCP CallToolResult. +func resultText(t *testing.T, res *mcp.CallToolResult) string { + t.Helper() + require.NotNil(t, res) + require.False(t, res.IsError, "tool result reported an error: %+v", res.Content) + require.Len(t, res.Content, 1) + tc, ok := res.Content[0].(mcp.TextContent) + require.True(t, ok, "expected TextContent, got %T", res.Content[0]) + return tc.Text +} + +// TestMarshalResultRendersTimestampsAsRFC3339 proves the transparent win from +// the typed-timestamp SDK: mcp marshals SDK results straight through and gets +// RFC3339 strings out, never raw epoch integers — with no mcp-side code change. +func TestMarshalResultRendersTimestampsAsRFC3339(t *testing.T) { + t.Parallel() + + // A concrete instant well clear of the epoch so the year is unambiguous. + secs := int64(1748487600) // 2025-ish; exact day depends on TZ, year does not. + wantYear := strconv.Itoa(time.Unix(secs, 0).Year()) + + fixture := timeFixture{ + CreatedAt: sdk.Timestamp(secs), + UpdatedAt: sdk.TimestampMilli(secs * 1000), + } + + formats := []struct { + name string + format OutputFormat + // JSON wraps the RFC3339 value in quotes; TOON leaves it bare. The + // year + 'T' separator are present in both. + text string + }{ + {name: "default-json", format: GetOutputFormat()}, + {name: "json", format: OutputFormatJSON}, + {name: "toon", format: OutputFormatTOON}, + } + + for _, f := range formats { + f := f + t.Run(f.name, func(t *testing.T) { + t.Parallel() + + out := resultText(t, MarshalResultWithFormat(fixture, f.format)) + + // RFC3339 shape: contains the date/time 'T' separator and the year. + assert.Contains(t, out, "T", "expected RFC3339 'T' separator in %q", out) + assert.Contains(t, out, wantYear, "expected RFC3339 year in %q", out) + + // Negative assertion: the raw epoch integers must NOT appear. + assert.NotContains(t, out, strconv.FormatInt(secs, 10), + "raw epoch-seconds leaked into output %q", out) + assert.NotContains(t, out, strconv.FormatInt(secs*1000, 10), + "raw epoch-millis leaked into output %q", out) + }) + } +} + +// TestMarshalResultUsesGlobalFormat covers the no-arg MarshalResult path so the +// default-format wrapper is exercised too. +func TestMarshalResultUsesGlobalFormat(t *testing.T) { + secs := int64(1748487600) + wantYear := strconv.Itoa(time.Unix(secs, 0).Year()) + + fixture := timeFixture{CreatedAt: sdk.Timestamp(secs)} + + out := resultText(t, MarshalResult(fixture)) + assert.True(t, strings.Contains(out, wantYear) && strings.Contains(out, "T"), + "expected RFC3339 timestamp in default-format output %q", out) + assert.NotContains(t, out, strconv.FormatInt(secs, 10), + "raw epoch-seconds leaked into default-format output %q", out) +} From 58cfa8d58672ad9e85aeeee8a0a4df3d0578013f Mon Sep 17 00:00:00 2001 From: ysyneu Date: Fri, 29 May 2026 11:59:49 +0800 Subject: [PATCH 2/2] chore: pin flashduty-sdk v0.9.1 (released tag) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 27c0dbf..ff1b712 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/bluele/gcache v0.0.2 - github.com/flashcatcloud/flashduty-sdk v0.9.1-0.20260529032514-a227261bdb9a + github.com/flashcatcloud/flashduty-sdk v0.9.1 github.com/google/go-github/v72 v72.0.0 github.com/josephburnett/jd v1.9.2 github.com/mark3labs/mcp-go v0.52.0 diff --git a/go.sum b/go.sum index 7fa9b67..3094466 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/flashcatcloud/flashduty-sdk v0.9.1-0.20260529032514-a227261bdb9a h1:QdRXdFRI4rsUqmtMbMFwsB6vxQLLYyBhKPo9PU5uQ5Q= -github.com/flashcatcloud/flashduty-sdk v0.9.1-0.20260529032514-a227261bdb9a/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY= +github.com/flashcatcloud/flashduty-sdk v0.9.1 h1:vDTkSjAJJD6Ex5r7S+VCxPi4yxSFNw1bU/SfoRCvk+k= +github.com/flashcatcloud/flashduty-sdk v0.9.1/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=