From 6325391d192f6afd6c2c5056a4723642ddf267a3 Mon Sep 17 00:00:00 2001 From: Adam Linart Date: Mon, 22 Jun 2026 16:45:56 +0200 Subject: [PATCH 1/2] Add schema generation type override for twapi.Date for tasks get and list responses --- internal/twprojects/tasks.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/twprojects/tasks.go b/internal/twprojects/tasks.go index b13830a..ff5e58f 100644 --- a/internal/twprojects/tasks.go +++ b/internal/twprojects/tasks.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "reflect" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" @@ -37,12 +38,26 @@ func init() { var err error // generate the output schemas only once - taskGetOutputSchema, err = jsonschema.For[projects.TaskGetResponse](&jsonschema.ForOptions{}) + taskGetOutputSchema, err = jsonschema.For[projects.TaskGetResponse](&jsonschema.ForOptions{ + TypeSchemas: map[reflect.Type]*jsonschema.Schema{ + reflect.TypeFor[twapi.Date](): { + Types: []string{"null", "string"}, + Description: "Null or date-only date string", + }, + }, + }) if err != nil { panic(fmt.Sprintf("failed to generate JSON schema for TaskGetResponse: %v", err)) } helpers.WithMetaWebLinkSchema(taskGetOutputSchema) - taskListOutputSchema, err = jsonschema.For[projects.TaskListResponse](&jsonschema.ForOptions{}) + taskListOutputSchema, err = jsonschema.For[projects.TaskListResponse](&jsonschema.ForOptions{ + TypeSchemas: map[reflect.Type]*jsonschema.Schema{ + reflect.TypeFor[twapi.Date](): { + Types: []string{"null", "string"}, + Description: "Null or date-only date string", + }, + }, + }) if err != nil { panic(fmt.Sprintf("failed to generate JSON schema for TaskListResponse: %v", err)) } From 8e83c3de1205248bd798efde9487750ddebeb868 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Justo Date: Mon, 22 Jun 2026 12:47:53 -0300 Subject: [PATCH 2/2] Extract twapi.Date schema override into helpers.WithDateTypeSchema The twapi.Date JSON-schema override was inlined in the task get/list output schemas, but the same Task struct (with its twapi.Date startDate/ dueDate fields) is also sideloaded into the calendar event list and search responses, which were generating the wrong schema for those nested dates. Extract the override into a reusable helpers.WithDateTypeSchema option and apply it to all four response schemas: task get/list, calendar event list, and search. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/helpers/schema_date.go | 37 ++++++++++++++++++++++++++ internal/twprojects/calendar_events.go | 4 ++- internal/twprojects/search.go | 2 +- internal/twprojects/tasks.go | 19 ++----------- 4 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 internal/helpers/schema_date.go diff --git a/internal/helpers/schema_date.go b/internal/helpers/schema_date.go new file mode 100644 index 0000000..4c28aad --- /dev/null +++ b/internal/helpers/schema_date.go @@ -0,0 +1,37 @@ +package helpers + +import ( + "reflect" + + "github.com/google/jsonschema-go/jsonschema" + twapi "github.com/teamwork/twapi-go-sdk" +) + +// WithDateTypeSchema registers a JSON-schema override for the twapi.Date type +// on the given generation options. twapi.Date is defined as `type Date +// time.Time`, so the reflection-based generator would otherwise emit a useless +// object schema for time.Time's unexported fields. This override forces it to a +// nullable, date-only string. +// +// Use it whenever generating an output schema from a response type that carries +// (or sideloads) twapi.Date fields: +// +// schema, err = jsonschema.For[Response](helpers.WithDateTypeSchema(&jsonschema.ForOptions{})) +// +// Any other options already set on opts (including pre-existing TypeSchemas +// entries) are preserved. The options value is modified in place and also +// returned for convenient chaining. +func WithDateTypeSchema(opts *jsonschema.ForOptions) *jsonschema.ForOptions { + if opts == nil { + opts = &jsonschema.ForOptions{} + } + if opts.TypeSchemas == nil { + opts.TypeSchemas = make(map[reflect.Type]*jsonschema.Schema) + } + opts.TypeSchemas[reflect.TypeFor[twapi.Date]()] = &jsonschema.Schema{ + Types: []string{"null", "string"}, + Format: "date", + Description: "Null or date-only date string", + } + return opts +} diff --git a/internal/twprojects/calendar_events.go b/internal/twprojects/calendar_events.go index cdce2a8..ee6119d 100644 --- a/internal/twprojects/calendar_events.go +++ b/internal/twprojects/calendar_events.go @@ -29,7 +29,9 @@ func init() { var err error // generate the output schema only once - calendarEventListOutputSchema, err = jsonschema.For[projects.CalendarEventListResponse](&jsonschema.ForOptions{}) + calendarEventListOutputSchema, err = jsonschema.For[projects.CalendarEventListResponse]( + helpers.WithDateTypeSchema(&jsonschema.ForOptions{}), + ) if err != nil { panic(fmt.Sprintf("failed to generate JSON schema for CalendarEventListResponse: %v", err)) } diff --git a/internal/twprojects/search.go b/internal/twprojects/search.go index 968a05a..f90c14a 100644 --- a/internal/twprojects/search.go +++ b/internal/twprojects/search.go @@ -28,7 +28,7 @@ var ( func init() { var err error - searchOutputSchema, err = jsonschema.For[projects.SearchResponse](&jsonschema.ForOptions{}) + searchOutputSchema, err = jsonschema.For[projects.SearchResponse](helpers.WithDateTypeSchema(&jsonschema.ForOptions{})) if err != nil { panic(fmt.Sprintf("failed to generate JSON schema for SearchResponse: %v", err)) } diff --git a/internal/twprojects/tasks.go b/internal/twprojects/tasks.go index ff5e58f..db357da 100644 --- a/internal/twprojects/tasks.go +++ b/internal/twprojects/tasks.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "net/http" - "reflect" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" @@ -38,26 +37,12 @@ func init() { var err error // generate the output schemas only once - taskGetOutputSchema, err = jsonschema.For[projects.TaskGetResponse](&jsonschema.ForOptions{ - TypeSchemas: map[reflect.Type]*jsonschema.Schema{ - reflect.TypeFor[twapi.Date](): { - Types: []string{"null", "string"}, - Description: "Null or date-only date string", - }, - }, - }) + taskGetOutputSchema, err = jsonschema.For[projects.TaskGetResponse](helpers.WithDateTypeSchema(&jsonschema.ForOptions{})) if err != nil { panic(fmt.Sprintf("failed to generate JSON schema for TaskGetResponse: %v", err)) } helpers.WithMetaWebLinkSchema(taskGetOutputSchema) - taskListOutputSchema, err = jsonschema.For[projects.TaskListResponse](&jsonschema.ForOptions{ - TypeSchemas: map[reflect.Type]*jsonschema.Schema{ - reflect.TypeFor[twapi.Date](): { - Types: []string{"null", "string"}, - Description: "Null or date-only date string", - }, - }, - }) + taskListOutputSchema, err = jsonschema.For[projects.TaskListResponse](helpers.WithDateTypeSchema(&jsonschema.ForOptions{})) if err != nil { panic(fmt.Sprintf("failed to generate JSON schema for TaskListResponse: %v", err)) }