Skip to content

Commit 352610d

Browse files
fix(projects): preserve contracts with concrete output envelopes
Restore exact advertised inputs and retain deferred runtime validation. Use concrete method-tagged outputs and keep legacy handler content unchanged. Add empty-page, sequential pagination, backend-error and schema compatibility coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 3ba9a03 commit 352610d

11 files changed

Lines changed: 1827 additions & 4553 deletions

‎README.md‎

Lines changed: 45 additions & 45 deletions
Large diffs are not rendered by default.

‎pkg/github/__toolsnaps__/projects_get.snap‎

Lines changed: 320 additions & 348 deletions
Large diffs are not rendered by default.

‎pkg/github/__toolsnaps__/projects_get_typed.snap‎

Lines changed: 0 additions & 851 deletions
This file was deleted.

‎pkg/github/__toolsnaps__/projects_list.snap‎

Lines changed: 492 additions & 522 deletions
Large diffs are not rendered by default.

‎pkg/github/__toolsnaps__/projects_list_typed.snap‎

Lines changed: 0 additions & 1050 deletions
This file was deleted.

‎pkg/github/__toolsnaps__/projects_write.snap‎

Lines changed: 433 additions & 569 deletions
Large diffs are not rendered by default.

‎pkg/github/__toolsnaps__/projects_write_typed.snap‎

Lines changed: 0 additions & 1001 deletions
This file was deleted.

‎pkg/github/projects.go‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func derefString(s *githubv4.String) string {
284284

285285
// ProjectsList returns the tool and handler for listing GitHub Projects resources.
286286
func ProjectsList(t translations.TranslationHelperFunc) inventory.ServerTool {
287-
tool := NewTool[ProjectsListInput, *ProjectsListOutput](
287+
tool := NewToolWithSchemaOptions[ProjectsListInput, *ProjectsListOutput](
288288
ToolsetMetadataProjects,
289289
mcp.Tool{
290290
Name: "projects_list",
@@ -359,6 +359,7 @@ Use this tool to list projects for a user or organization, or list project field
359359
},
360360
},
361361
scopes.RequireAll(scopes.ReadProject),
362+
projectsSchemaOptions(),
362363
projectsTypedHandler[ProjectsListInput](func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
363364
method, err := RequiredParam[string](args, "method")
364365
if err != nil {
@@ -446,12 +447,12 @@ Use this tool to list projects for a user or organization, or list project field
446447
}, decodeProjectsListOutput),
447448
normalizeProjectsRouting("list"),
448449
)
449-
return projectsInputSchema(tool)
450+
return tool
450451
}
451452

452453
// ProjectsGet returns the tool and handler for getting GitHub Projects resources.
453454
func ProjectsGet(t translations.TranslationHelperFunc) inventory.ServerTool {
454-
tool := NewTool[ProjectsGetInput, *ProjectsGetOutput](
455+
tool := NewToolWithSchemaOptions[ProjectsGetInput, *ProjectsGetOutput](
455456
ToolsetMetadataProjects,
456457
mcp.Tool{
457458
Name: "projects_get",
@@ -525,6 +526,7 @@ Use this tool to get details about individual projects, project fields, project
525526
},
526527
},
527528
scopes.RequireAll(scopes.ReadProject),
529+
projectsSchemaOptions(),
528530
projectsTypedHandler[ProjectsGetInput](func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
529531
method, err := RequiredParam[string](args, "method")
530532
if err != nil {
@@ -649,7 +651,7 @@ Use this tool to get details about individual projects, project fields, project
649651
}, decodeProjectsGetOutput),
650652
normalizeProjectsRouting("get"),
651653
)
652-
return projectsInputSchema(tool)
654+
return tool
653655
}
654656

655657
func updateProjectItemsItemSchema() *jsonschema.Schema {
@@ -730,7 +732,7 @@ func projectUpdatedFieldSchema() *jsonschema.Schema {
730732

731733
// ProjectsWrite returns the tool and handler for modifying GitHub Projects resources.
732734
func ProjectsWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
733-
tool := NewTool[ProjectsWriteInput, *ProjectsWriteOutput](
735+
tool := NewToolWithSchemaOptions[ProjectsWriteInput, *ProjectsWriteOutput](
734736
ToolsetMetadataProjects,
735737
mcp.Tool{
736738
Name: "projects_write",
@@ -895,6 +897,7 @@ func ProjectsWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
895897
},
896898
},
897899
scopes.RequireAll(scopes.Project),
900+
projectsSchemaOptions(),
898901
projectsTypedHandler[ProjectsWriteInput](func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
899902
method, err := RequiredParam[string](args, "method")
900903
if err != nil {
@@ -1044,7 +1047,7 @@ func ProjectsWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
10441047
}, decodeProjectsWriteOutput),
10451048
normalizeProjectsRouting("write"),
10461049
)
1047-
return projectsInputSchema(tool)
1050+
return tool
10481051
}
10491052

10501053
// Helper functions for consolidated projects tools

‎pkg/github/projects_test.go‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func Test_ProjectsList(t *testing.T) {
3636
assert.Contains(t, inputSchema.Properties, "project_number")
3737
assert.Contains(t, inputSchema.Properties, "query")
3838
assert.Contains(t, inputSchema.Properties, "fields")
39-
assert.Contains(t, inputSchema.Properties["method"].AnyOf[0].Enum, projectsMethodListProjectViews)
40-
assert.Empty(t, inputSchema.Required, "required arguments are checked in legacy order by the handler")
39+
assert.Contains(t, inputSchema.Properties["method"].Enum, projectsMethodListProjectViews)
40+
assert.ElementsMatch(t, []string{"method", "owner"}, inputSchema.Required)
4141
}
4242

4343
func Test_ProjectsList_ListProjects(t *testing.T) {
@@ -635,10 +635,10 @@ func Test_ProjectsGet(t *testing.T) {
635635
assert.Contains(t, inputSchema.Properties, "owner_type")
636636
assert.Contains(t, inputSchema.Properties, "project_number")
637637
assert.Contains(t, inputSchema.Properties, "view_id")
638-
assert.Contains(t, inputSchema.Properties["method"].AnyOf[0].Enum, projectsMethodGetProjectView)
638+
assert.Contains(t, inputSchema.Properties["method"].Enum, projectsMethodGetProjectView)
639639
assert.Contains(t, inputSchema.Properties, "field_id")
640640
assert.Contains(t, inputSchema.Properties, "item_id")
641-
assert.Empty(t, inputSchema.Required, "required arguments are checked in legacy order by the handler")
641+
assert.ElementsMatch(t, []string{"method"}, inputSchema.Required)
642642
}
643643

644644
func Test_ProjectsGet_GetProject(t *testing.T) {
@@ -931,10 +931,10 @@ func Test_ProjectsWrite(t *testing.T) {
931931
assert.Contains(t, inputSchema.Properties, "filter")
932932
assert.Contains(t, inputSchema.Properties, "visible_fields")
933933
assert.Contains(t, inputSchema.Properties, "visible_field_names")
934-
assert.Contains(t, inputSchema.Properties["method"].AnyOf[0].Enum, projectsMethodCreateProjectView)
935-
assert.Contains(t, inputSchema.Properties["method"].AnyOf[0].Enum, projectsMethodUpdateProjectView)
936-
assert.Contains(t, inputSchema.Properties["method"].AnyOf[0].Enum, projectsMethodDeleteProjectView)
937-
assert.Empty(t, inputSchema.Required, "required arguments are checked in legacy order by the handler")
934+
assert.Contains(t, inputSchema.Properties["method"].Enum, projectsMethodCreateProjectView)
935+
assert.Contains(t, inputSchema.Properties["method"].Enum, projectsMethodUpdateProjectView)
936+
assert.Contains(t, inputSchema.Properties["method"].Enum, projectsMethodDeleteProjectView)
937+
assert.ElementsMatch(t, []string{"method", "owner"}, inputSchema.Required)
938938

939939
// Verify DestructiveHint is set
940940
assert.NotNil(t, toolDef.Tool.Annotations)
@@ -945,7 +945,7 @@ func Test_ProjectsWrite(t *testing.T) {
945945
func Test_ProjectsWrite_UpdateProjectItemsSchema(t *testing.T) {
946946
inputSchema := ProjectsWrite(translations.NullTranslationHelper).Tool.InputSchema.(*jsonschema.Schema)
947947
assert.Contains(t, inputSchema.Properties["items"].Description, "prefer it over calling 'update_project_item' in a loop")
948-
itemSchema := inputSchema.Properties["items"].AnyOf[0].Items
948+
itemSchema := inputSchema.Properties["items"].Items
949949

950950
assert.Equal(t, "object", itemSchema.Type)
951951
assert.Empty(t, itemSchema.Properties, "item references should be modeled by oneOf, not flattened properties")
@@ -977,7 +977,7 @@ func Test_ProjectsWrite_UpdateProjectItemsSchema(t *testing.T) {
977977
assert.NotNil(t, variant.AdditionalProperties.Not, "variant must reject additional properties")
978978
}
979979

980-
fieldSchema := inputSchema.Properties["updated_field"].AnyOf[0]
980+
fieldSchema := inputSchema.Properties["updated_field"]
981981
assert.Equal(t, "object", fieldSchema.Type)
982982
assert.Contains(t, fieldSchema.Description, "one top-level field/value applies to every item")
983983
require.Len(t, fieldSchema.OneOf, 2)

0 commit comments

Comments
 (0)