From 2be83a06726fb586a3802da79ba11c11cad43f1c Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Fri, 9 Jan 2026 13:33:42 +0000 Subject: [PATCH 1/4] get it working --- internal/ghmcp/server.go | 5 +++ pkg/inventory/builder.go | 14 ++++++- pkg/inventory/registry.go | 10 +++++ pkg/inventory/registry_test.go | 71 ++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 165886606a..164ad95845 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -235,6 +235,11 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) { fmt.Fprintf(os.Stderr, "Warning: unrecognized toolsets ignored: %s\n", strings.Join(unrecognized, ", ")) } + // Check for unrecognized tools and error out (unlike toolsets which just warn) + if unrecognized := inventory.UnrecognizedTools(); len(unrecognized) > 0 { + return nil, fmt.Errorf("unrecognized tools: %s", strings.Join(unrecognized, ", ")) + } + // Register GitHub tools/resources/prompts from the inventory. // In dynamic mode with no explicit toolsets, this is a no-op since enabledToolsets // is empty - users enable toolsets at runtime via the dynamic tools below (but can diff --git a/pkg/inventory/builder.go b/pkg/inventory/builder.go index 0400c2a24a..b2a49c1e3a 100644 --- a/pkg/inventory/builder.go +++ b/pkg/inventory/builder.go @@ -145,9 +145,17 @@ func (b *Builder) Build() *Inventory { // Process toolsets and pre-compute metadata in a single pass r.enabledToolsets, r.unrecognizedToolsets, r.toolsetIDs, r.toolsetIDSet, r.defaultToolsetIDs, r.toolsetDescriptions = b.processToolsets() - // Process additional tools (resolve aliases) + // Build set of valid tool names for validation + validToolNames := make(map[string]bool, len(b.tools)) + for i := range b.tools { + validToolNames[b.tools[i].Tool.Name] = true + } + + // Process additional tools (resolve aliases and track unrecognized) + // Note: input is expected to be pre-cleaned (trimmed, deduped) via CleanTools if len(b.additionalTools) > 0 { r.additionalTools = make(map[string]bool, len(b.additionalTools)) + var unrecognizedTools []string for _, name := range b.additionalTools { // Always include the original name - this handles the case where // the tool exists but is controlled by a feature flag that's OFF. @@ -157,8 +165,12 @@ func (b *Builder) Build() *Inventory { // the new consolidated tool is available. if canonical, isAlias := b.deprecatedAliases[name]; isAlias { r.additionalTools[canonical] = true + } else if !validToolNames[name] { + // Not a valid tool and not a deprecated alias - track as unrecognized + unrecognizedTools = append(unrecognizedTools, name) } } + r.unrecognizedTools = unrecognizedTools } return r diff --git a/pkg/inventory/registry.go b/pkg/inventory/registry.go index f3691e38ab..7313d58b32 100644 --- a/pkg/inventory/registry.go +++ b/pkg/inventory/registry.go @@ -58,6 +58,8 @@ type Inventory struct { filters []ToolFilter // unrecognizedToolsets holds toolset IDs that were requested but don't match any registered toolsets unrecognizedToolsets []string + // unrecognizedTools holds tool names that were requested via WithTools but don't exist + unrecognizedTools []string } // UnrecognizedToolsets returns toolset IDs that were passed to WithToolsets but don't @@ -66,6 +68,13 @@ func (r *Inventory) UnrecognizedToolsets() []string { return r.unrecognizedToolsets } +// UnrecognizedTools returns tool names that were passed to WithTools but don't +// match any registered tools (and are not deprecated aliases). This is used to +// error out when invalid tool names are specified. +func (r *Inventory) UnrecognizedTools() []string { + return r.unrecognizedTools +} + // MCP method constants for use with ForMCPRequest. const ( MCPMethodInitialize = "initialize" @@ -112,6 +121,7 @@ func (r *Inventory) ForMCPRequest(method string, itemName string) *Inventory { featureChecker: r.featureChecker, filters: r.filters, // shared, not modified unrecognizedToolsets: r.unrecognizedToolsets, + unrecognizedTools: r.unrecognizedTools, } // Helper to clear all item types diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 2c32628733..bb1db15a90 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -270,6 +270,77 @@ func TestUnrecognizedToolsets(t *testing.T) { } } +func TestUnrecognizedTools(t *testing.T) { + tools := []ServerTool{ + mockTool("tool1", "toolset1", true), + mockTool("tool2", "toolset2", true), + } + + deprecatedAliases := map[string]string{ + "old_tool": "tool1", + } + + tests := []struct { + name string + withTools []string + expectedUnrecognized []string + }{ + { + name: "all valid", + withTools: []string{"tool1", "tool2"}, + expectedUnrecognized: nil, + }, + { + name: "one invalid", + withTools: []string{"tool1", "blabla"}, + expectedUnrecognized: []string{"blabla"}, + }, + { + name: "multiple invalid", + withTools: []string{"invalid1", "tool1", "invalid2"}, + expectedUnrecognized: []string{"invalid1", "invalid2"}, + }, + { + name: "deprecated alias is valid", + withTools: []string{"old_tool"}, + expectedUnrecognized: nil, + }, + { + name: "mixed valid and deprecated alias", + withTools: []string{"old_tool", "tool2"}, + expectedUnrecognized: nil, + }, + { + name: "empty input", + withTools: []string{}, + expectedUnrecognized: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inv := NewBuilder(). + SetTools(tools). + WithDeprecatedAliases(deprecatedAliases). + WithToolsets([]string{"all"}). + WithTools(tt.withTools). + Build() + unrecognized := inv.UnrecognizedTools() + + if len(unrecognized) != len(tt.expectedUnrecognized) { + t.Fatalf("Expected %d unrecognized, got %d: %v", + len(tt.expectedUnrecognized), len(unrecognized), unrecognized) + } + + for i, expected := range tt.expectedUnrecognized { + if unrecognized[i] != expected { + t.Errorf("Expected unrecognized[%d] = %q, got %q", i, expected, unrecognized[i]) + } + } + }) + } +} + func TestWithTools(t *testing.T) { tools := []ServerTool{ mockTool("tool1", "toolset1", true), From bf9d3821a3b824a6def540348f1e34ec787763ed Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Fri, 9 Jan 2026 13:39:48 +0000 Subject: [PATCH 2/4] clean up approach by moving cleantools inside builder, this simplifies remote server too --- internal/ghmcp/server.go | 2 +- pkg/inventory/builder.go | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 164ad95845..b1bad9fa4c 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -221,7 +221,7 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) { WithDeprecatedAliases(github.DeprecatedToolAliases). WithReadOnly(cfg.ReadOnly). WithToolsets(enabledToolsets). - WithTools(github.CleanTools(cfg.EnabledTools)). + WithTools(cfg.EnabledTools). WithFeatureChecker(createFeatureChecker(cfg.EnabledFeatures)) // Apply token scope filtering if scopes are known (for PAT filtering) diff --git a/pkg/inventory/builder.go b/pkg/inventory/builder.go index b2a49c1e3a..9d135218e6 100644 --- a/pkg/inventory/builder.go +++ b/pkg/inventory/builder.go @@ -101,6 +101,7 @@ func (b *Builder) WithToolsets(toolsetIDs []string) *Builder { // WithTools specifies additional tools that bypass toolset filtering. // These tools are additive - they will be included even if their toolset is not enabled. // Read-only filtering still applies to these tools. +// Input is cleaned (trimmed, deduplicated) during Build(). // Deprecated tool aliases are automatically resolved to their canonical names during Build(). // Returns self for chaining. func (b *Builder) WithTools(toolNames []string) *Builder { @@ -127,6 +128,24 @@ func (b *Builder) WithFilter(filter ToolFilter) *Builder { return b } +// cleanTools trims whitespace and removes duplicates from tool names. +// Empty strings after trimming are excluded. +func cleanTools(tools []string) []string { + seen := make(map[string]bool) + var cleaned []string + for _, name := range tools { + trimmed := strings.TrimSpace(name) + if trimmed == "" { + continue + } + if !seen[trimmed] { + seen[trimmed] = true + cleaned = append(cleaned, trimmed) + } + } + return cleaned +} + // Build creates the final Inventory with all configuration applied. // This processes toolset filtering, tool name resolution, and sets up // the inventory for use. The returned Inventory is ready for use with @@ -151,12 +170,13 @@ func (b *Builder) Build() *Inventory { validToolNames[b.tools[i].Tool.Name] = true } - // Process additional tools (resolve aliases and track unrecognized) - // Note: input is expected to be pre-cleaned (trimmed, deduped) via CleanTools + // Process additional tools (clean, resolve aliases, and track unrecognized) if len(b.additionalTools) > 0 { - r.additionalTools = make(map[string]bool, len(b.additionalTools)) + cleanedTools := cleanTools(b.additionalTools) + + r.additionalTools = make(map[string]bool, len(cleanedTools)) var unrecognizedTools []string - for _, name := range b.additionalTools { + for _, name := range cleanedTools { // Always include the original name - this handles the case where // the tool exists but is controlled by a feature flag that's OFF. r.additionalTools[name] = true From 8e269d442471cf720d57951e4f2d4e56d43b094d Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Fri, 9 Jan 2026 14:24:22 +0000 Subject: [PATCH 3/4] add tests for trimming and deduplication --- pkg/inventory/registry_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index bb1db15a90..2bb0c7d658 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -315,6 +315,36 @@ func TestUnrecognizedTools(t *testing.T) { withTools: []string{}, expectedUnrecognized: nil, }, + { + name: "whitespace trimmed from valid tool", + withTools: []string{" tool1 ", " tool2 "}, + expectedUnrecognized: nil, + }, + { + name: "whitespace trimmed from invalid tool", + withTools: []string{" invalid_tool "}, + expectedUnrecognized: []string{"invalid_tool"}, + }, + { + name: "duplicate tools deduplicated", + withTools: []string{"tool1", "tool1"}, + expectedUnrecognized: nil, + }, + { + name: "duplicate invalid tools deduplicated", + withTools: []string{"blabla", "blabla"}, + expectedUnrecognized: []string{"blabla"}, + }, + { + name: "mixed whitespace and duplicates", + withTools: []string{" tool1 ", "tool1", " tool1 "}, + expectedUnrecognized: nil, + }, + { + name: "empty strings ignored", + withTools: []string{"", "tool1", " ", ""}, + expectedUnrecognized: nil, + }, } for _, tt := range tests { From 739f39af65a30a31c8d8dc3d2cdc4d6e18d6b1bf Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Mon, 12 Jan 2026 16:14:50 +0000 Subject: [PATCH 4/4] error out in the builder if there are unrecognized tools --- cmd/github-mcp-server/generate_docs.go | 6 +- cmd/github-mcp-server/list_scopes.go | 5 +- internal/ghmcp/server.go | 10 +- pkg/github/__toolsnaps__/actions_get.snap | 14 +- .../__toolsnaps__/actions_run_trigger.snap | 12 +- .../add_comment_to_pending_review.snap | 18 +- .../__toolsnaps__/add_issue_comment.snap | 14 +- .../__toolsnaps__/add_project_item.snap | 16 +- .../__toolsnaps__/cancel_workflow_run.snap | 12 +- pkg/github/__toolsnaps__/create_branch.snap | 12 +- pkg/github/__toolsnaps__/create_gist.snap | 10 +- .../__toolsnaps__/create_or_update_file.snap | 18 +- .../__toolsnaps__/create_pull_request.snap | 16 +- .../__toolsnaps__/create_repository.snap | 8 +- pkg/github/__toolsnaps__/delete_file.snap | 16 +- .../__toolsnaps__/delete_project_item.snap | 14 +- .../delete_workflow_run_logs.snap | 12 +- .../__toolsnaps__/dismiss_notification.snap | 10 +- .../download_workflow_run_artifact.snap | 12 +- pkg/github/__toolsnaps__/fork_repository.snap | 10 +- .../get_code_scanning_alert.snap | 12 +- pkg/github/__toolsnaps__/get_commit.snap | 12 +- .../__toolsnaps__/get_dependabot_alert.snap | 12 +- pkg/github/__toolsnaps__/get_discussion.snap | 12 +- .../get_discussion_comments.snap | 12 +- .../__toolsnaps__/get_file_contents.snap | 10 +- pkg/github/__toolsnaps__/get_gist.snap | 8 +- .../get_global_security_advisory.snap | 8 +- pkg/github/__toolsnaps__/get_job_logs.snap | 10 +- pkg/github/__toolsnaps__/get_label.snap | 12 +- .../__toolsnaps__/get_latest_release.snap | 10 +- .../get_notification_details.snap | 8 +- pkg/github/__toolsnaps__/get_project.snap | 12 +- .../__toolsnaps__/get_project_field.snap | 14 +- .../__toolsnaps__/get_project_item.snap | 18 +- .../__toolsnaps__/get_release_by_tag.snap | 12 +- .../__toolsnaps__/get_repository_tree.snap | 10 +- .../get_secret_scanning_alert.snap | 12 +- pkg/github/__toolsnaps__/get_tag.snap | 12 +- .../__toolsnaps__/get_team_members.snap | 10 +- .../__toolsnaps__/get_workflow_run.snap | 12 +- .../__toolsnaps__/get_workflow_run_logs.snap | 12 +- .../__toolsnaps__/get_workflow_run_usage.snap | 12 +- pkg/github/__toolsnaps__/issue_read.snap | 14 +- pkg/github/__toolsnaps__/issue_write.snap | 20 +- pkg/github/__toolsnaps__/label_write.snap | 14 +- pkg/github/__toolsnaps__/list_branches.snap | 10 +- .../list_code_scanning_alerts.snap | 10 +- pkg/github/__toolsnaps__/list_commits.snap | 10 +- .../__toolsnaps__/list_dependabot_alerts.snap | 10 +- .../list_discussion_categories.snap | 8 +- .../__toolsnaps__/list_discussions.snap | 8 +- .../list_global_security_advisories.snap | 4 +- .../__toolsnaps__/list_issue_types.snap | 8 +- pkg/github/__toolsnaps__/list_issues.snap | 14 +- pkg/github/__toolsnaps__/list_label.snap | 10 +- ...st_org_repository_security_advisories.snap | 8 +- .../__toolsnaps__/list_project_fields.snap | 12 +- .../__toolsnaps__/list_project_items.snap | 16 +- pkg/github/__toolsnaps__/list_projects.snap | 10 +- .../__toolsnaps__/list_pull_requests.snap | 10 +- pkg/github/__toolsnaps__/list_releases.snap | 10 +- .../list_repository_security_advisories.snap | 10 +- .../list_secret_scanning_alerts.snap | 10 +- pkg/github/__toolsnaps__/list_tags.snap | 10 +- .../__toolsnaps__/list_workflow_jobs.snap | 12 +- .../list_workflow_run_artifacts.snap | 12 +- .../__toolsnaps__/list_workflow_runs.snap | 12 +- pkg/github/__toolsnaps__/list_workflows.snap | 10 +- .../manage_notification_subscription.snap | 10 +- ..._repository_notification_subscription.snap | 12 +- .../__toolsnaps__/merge_pull_request.snap | 12 +- pkg/github/__toolsnaps__/projects_get.snap | 18 +- pkg/github/__toolsnaps__/projects_list.snap | 16 +- pkg/github/__toolsnaps__/projects_write.snap | 14 +- .../__toolsnaps__/pull_request_read.snap | 14 +- .../pull_request_review_write.snap | 14 +- pkg/github/__toolsnaps__/push_files.snap | 30 +- .../__toolsnaps__/request_copilot_review.snap | 12 +- .../__toolsnaps__/rerun_failed_jobs.snap | 12 +- .../__toolsnaps__/rerun_workflow_run.snap | 12 +- pkg/github/__toolsnaps__/run_workflow.snap | 14 +- pkg/github/__toolsnaps__/search_code.snap | 8 +- pkg/github/__toolsnaps__/search_issues.snap | 8 +- pkg/github/__toolsnaps__/search_orgs.snap | 8 +- .../__toolsnaps__/search_pull_requests.snap | 8 +- .../__toolsnaps__/search_repositories.snap | 8 +- pkg/github/__toolsnaps__/search_users.snap | 8 +- pkg/github/__toolsnaps__/star_repository.snap | 10 +- pkg/github/__toolsnaps__/sub_issue_write.snap | 16 +- .../__toolsnaps__/unstar_repository.snap | 10 +- pkg/github/__toolsnaps__/update_gist.snap | 12 +- .../__toolsnaps__/update_project_item.snap | 16 +- .../__toolsnaps__/update_pull_request.snap | 16 +- .../update_pull_request_branch.snap | 12 +- pkg/github/dynamic_tools_test.go | 15 +- pkg/github/scope_filter_test.go | 3 +- pkg/github/tools.go | 9 +- pkg/github/toolset_icons_test.go | 6 +- pkg/inventory/builder.go | 15 +- pkg/inventory/registry.go | 10 - pkg/inventory/registry_test.go | 331 +++++++++--------- 102 files changed, 756 insertions(+), 752 deletions(-) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index a458c04b69..78fd6c40a9 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -51,7 +51,8 @@ func generateReadmeDocs(readmePath string) error { t, _ := translations.TranslationHelper() // (not available to regular users) while including tools with FeatureFlagDisable. - r := github.NewInventory(t).WithToolsets([]string{"all"}).Build() + // Build() can only fail if WithTools specifies invalid tools - not used here + r, _ := github.NewInventory(t).WithToolsets([]string{"all"}).Build() // Generate toolsets documentation toolsetsDoc := generateToolsetsDoc(r) @@ -341,7 +342,8 @@ func generateRemoteToolsetsDoc() string { t, _ := translations.TranslationHelper() // Build inventory - stateless - r := github.NewInventory(t).Build() + // Build() can only fail if WithTools specifies invalid tools - not used here + r, _ := github.NewInventory(t).Build() // Generate table header (icon is combined with Name column) buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n") diff --git a/cmd/github-mcp-server/list_scopes.go b/cmd/github-mcp-server/list_scopes.go index 2d1817500b..d8b8bf3922 100644 --- a/cmd/github-mcp-server/list_scopes.go +++ b/cmd/github-mcp-server/list_scopes.go @@ -121,7 +121,10 @@ func runListScopes() error { inventoryBuilder = inventoryBuilder.WithTools(enabledTools) } - inv := inventoryBuilder.Build() + inv, err := inventoryBuilder.Build() + if err != nil { + return fmt.Errorf("failed to build inventory: %w", err) + } // Collect all tools and their scopes output := collectToolScopes(inv, readOnly) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index b1bad9fa4c..ad146ce06f 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -229,17 +229,15 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) { inventoryBuilder = inventoryBuilder.WithFilter(github.CreateToolScopeFilter(cfg.TokenScopes)) } - inventory := inventoryBuilder.Build() + inventory, err := inventoryBuilder.Build() + if err != nil { + return nil, fmt.Errorf("failed to build inventory: %w", err) + } if unrecognized := inventory.UnrecognizedToolsets(); len(unrecognized) > 0 { fmt.Fprintf(os.Stderr, "Warning: unrecognized toolsets ignored: %s\n", strings.Join(unrecognized, ", ")) } - // Check for unrecognized tools and error out (unlike toolsets which just warn) - if unrecognized := inventory.UnrecognizedTools(); len(unrecognized) > 0 { - return nil, fmt.Errorf("unrecognized tools: %s", strings.Join(unrecognized, ", ")) - } - // Register GitHub tools/resources/prompts from the inventory. // In dynamic mode with no explicit toolsets, this is a no-op since enabledToolsets // is empty - users enable toolsets at runtime via the dynamic tools below (but can diff --git a/pkg/github/__toolsnaps__/actions_get.snap b/pkg/github/__toolsnaps__/actions_get.snap index b5f3b85bd3..e779926f9a 100644 --- a/pkg/github/__toolsnaps__/actions_get.snap +++ b/pkg/github/__toolsnaps__/actions_get.snap @@ -6,12 +6,6 @@ "description": "Get details about specific GitHub Actions resources.\nUse this tool to get details about individual workflows, workflow runs, jobs, and artifacts by their unique IDs.\n", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo", - "resource_id" - ], "properties": { "method": { "type": "string", @@ -37,7 +31,13 @@ "type": "string", "description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' method.\n- Provide a workflow run ID for 'get_workflow_run', 'get_workflow_run_usage', and 'get_workflow_run_logs_url' methods.\n- Provide an artifact ID for 'download_workflow_run_artifact' method.\n- Provide a job ID for 'get_workflow_job' method.\n" } - } + }, + "required": [ + "method", + "owner", + "repo", + "resource_id" + ] }, "name": "actions_get" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/actions_run_trigger.snap b/pkg/github/__toolsnaps__/actions_run_trigger.snap index 4e16f89581..279ef357b2 100644 --- a/pkg/github/__toolsnaps__/actions_run_trigger.snap +++ b/pkg/github/__toolsnaps__/actions_run_trigger.snap @@ -6,11 +6,6 @@ "description": "Trigger GitHub Actions workflow operations, including running, re-running, cancelling workflow runs, and deleting workflow run logs.", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo" - ], "properties": { "inputs": { "type": "object", @@ -47,7 +42,12 @@ "type": "string", "description": "The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method." } - } + }, + "required": [ + "method", + "owner", + "repo" + ] }, "name": "actions_run_trigger" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap b/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap index 78795c0961..a3fcdca3c3 100644 --- a/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap +++ b/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap @@ -5,14 +5,6 @@ "description": "Add review comment to the requester's latest pending pull request review. A pending review needs to already exist to call this (check with the user if not sure).", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "pullNumber", - "path", - "body", - "subjectType" - ], "properties": { "body": { "type": "string", @@ -66,7 +58,15 @@ "LINE" ] } - } + }, + "required": [ + "owner", + "repo", + "pullNumber", + "path", + "body", + "subjectType" + ] }, "name": "add_comment_to_pending_review" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/add_issue_comment.snap b/pkg/github/__toolsnaps__/add_issue_comment.snap index fb2a9e7b39..51703c1522 100644 --- a/pkg/github/__toolsnaps__/add_issue_comment.snap +++ b/pkg/github/__toolsnaps__/add_issue_comment.snap @@ -5,12 +5,6 @@ "description": "Add a comment to a specific issue in a GitHub repository. Use this tool to add comments to pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add review comments.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "issue_number", - "body" - ], "properties": { "body": { "type": "string", @@ -28,7 +22,13 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "issue_number", + "body" + ] }, "name": "add_issue_comment" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/add_project_item.snap b/pkg/github/__toolsnaps__/add_project_item.snap index 08f4953706..715748eea6 100644 --- a/pkg/github/__toolsnaps__/add_project_item.snap +++ b/pkg/github/__toolsnaps__/add_project_item.snap @@ -5,13 +5,6 @@ "description": "Add a specific Project item for a user or org", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner", - "project_number", - "item_type", - "item_id" - ], "properties": { "item_id": { "type": "number", @@ -41,7 +34,14 @@ "type": "number", "description": "The project's number." } - } + }, + "required": [ + "owner_type", + "owner", + "project_number", + "item_type", + "item_id" + ] }, "name": "add_project_item" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/cancel_workflow_run.snap b/pkg/github/__toolsnaps__/cancel_workflow_run.snap index 83eb31a7f9..a8e75bcc21 100644 --- a/pkg/github/__toolsnaps__/cancel_workflow_run.snap +++ b/pkg/github/__toolsnaps__/cancel_workflow_run.snap @@ -5,11 +5,6 @@ "description": "Cancel a workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -23,7 +18,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "cancel_workflow_run" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/create_branch.snap b/pkg/github/__toolsnaps__/create_branch.snap index 675a2de9c0..223b2d44e0 100644 --- a/pkg/github/__toolsnaps__/create_branch.snap +++ b/pkg/github/__toolsnaps__/create_branch.snap @@ -5,11 +5,6 @@ "description": "Create a new branch in a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "branch" - ], "properties": { "branch": { "type": "string", @@ -27,7 +22,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "branch" + ] }, "name": "create_branch" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/create_gist.snap b/pkg/github/__toolsnaps__/create_gist.snap index 465206ab43..be166f5526 100644 --- a/pkg/github/__toolsnaps__/create_gist.snap +++ b/pkg/github/__toolsnaps__/create_gist.snap @@ -5,10 +5,6 @@ "description": "Create a new gist", "inputSchema": { "type": "object", - "required": [ - "filename", - "content" - ], "properties": { "content": { "type": "string", @@ -27,7 +23,11 @@ "description": "Whether the gist is public", "default": false } - } + }, + "required": [ + "filename", + "content" + ] }, "name": "create_gist" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/create_or_update_file.snap b/pkg/github/__toolsnaps__/create_or_update_file.snap index 2d9ae1144f..edfa73cb4f 100644 --- a/pkg/github/__toolsnaps__/create_or_update_file.snap +++ b/pkg/github/__toolsnaps__/create_or_update_file.snap @@ -5,14 +5,6 @@ "description": "Create or update a single file in a GitHub repository. \nIf updating, you should provide the SHA of the file you want to update. Use this tool to create or update a file in a GitHub repository remotely; do not use it for local file operations.\n\nIn order to obtain the SHA of original file version before updating, use the following git command:\ngit ls-tree HEAD \u003cpath to file\u003e\n\nIf the SHA is not provided, the tool will attempt to acquire it by fetching the current file contents from the repository, which may lead to rewriting latest committed changes if the file has changed since last retrieval.\n", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "path", - "content", - "message", - "branch" - ], "properties": { "branch": { "type": "string", @@ -42,7 +34,15 @@ "type": "string", "description": "The blob SHA of the file being replaced." } - } + }, + "required": [ + "owner", + "repo", + "path", + "content", + "message", + "branch" + ] }, "name": "create_or_update_file" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/create_pull_request.snap b/pkg/github/__toolsnaps__/create_pull_request.snap index 80f0b98633..c32272b7a1 100644 --- a/pkg/github/__toolsnaps__/create_pull_request.snap +++ b/pkg/github/__toolsnaps__/create_pull_request.snap @@ -5,13 +5,6 @@ "description": "Create a new pull request in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "title", - "head", - "base" - ], "properties": { "base": { "type": "string", @@ -45,7 +38,14 @@ "type": "string", "description": "PR title" } - } + }, + "required": [ + "owner", + "repo", + "title", + "head", + "base" + ] }, "name": "create_pull_request" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/create_repository.snap b/pkg/github/__toolsnaps__/create_repository.snap index 290767c667..298d3ca163 100644 --- a/pkg/github/__toolsnaps__/create_repository.snap +++ b/pkg/github/__toolsnaps__/create_repository.snap @@ -5,9 +5,6 @@ "description": "Create a new GitHub repository in your account or specified organization", "inputSchema": { "type": "object", - "required": [ - "name" - ], "properties": { "autoInit": { "type": "boolean", @@ -29,7 +26,10 @@ "type": "boolean", "description": "Whether repo should be private" } - } + }, + "required": [ + "name" + ] }, "name": "create_repository" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/delete_file.snap b/pkg/github/__toolsnaps__/delete_file.snap index b985154e86..ca1dbcd36a 100644 --- a/pkg/github/__toolsnaps__/delete_file.snap +++ b/pkg/github/__toolsnaps__/delete_file.snap @@ -6,13 +6,6 @@ "description": "Delete a file from a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "path", - "message", - "branch" - ], "properties": { "branch": { "type": "string", @@ -34,7 +27,14 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "path", + "message", + "branch" + ] }, "name": "delete_file" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/delete_project_item.snap b/pkg/github/__toolsnaps__/delete_project_item.snap index 430c83cc86..f4bf66c72e 100644 --- a/pkg/github/__toolsnaps__/delete_project_item.snap +++ b/pkg/github/__toolsnaps__/delete_project_item.snap @@ -6,12 +6,6 @@ "description": "Delete a specific Project item for a user or org", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner", - "project_number", - "item_id" - ], "properties": { "item_id": { "type": "number", @@ -33,7 +27,13 @@ "type": "number", "description": "The project's number." } - } + }, + "required": [ + "owner_type", + "owner", + "project_number", + "item_id" + ] }, "name": "delete_project_item" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap b/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap index fc9a5cd46c..21530058b9 100644 --- a/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap +++ b/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap @@ -6,11 +6,6 @@ "description": "Delete logs for a workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -24,7 +19,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "delete_workflow_run_logs" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/dismiss_notification.snap b/pkg/github/__toolsnaps__/dismiss_notification.snap index b0125ba536..88f7c2bba8 100644 --- a/pkg/github/__toolsnaps__/dismiss_notification.snap +++ b/pkg/github/__toolsnaps__/dismiss_notification.snap @@ -5,10 +5,6 @@ "description": "Dismiss a notification by marking it as read or done", "inputSchema": { "type": "object", - "required": [ - "threadID", - "state" - ], "properties": { "state": { "type": "string", @@ -22,7 +18,11 @@ "type": "string", "description": "The ID of the notification thread" } - } + }, + "required": [ + "threadID", + "state" + ] }, "name": "dismiss_notification" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap b/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap index c4d89872ce..b402b422d1 100644 --- a/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap +++ b/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap @@ -6,11 +6,6 @@ "description": "Get download URL for a workflow run artifact", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "artifact_id" - ], "properties": { "artifact_id": { "type": "number", @@ -24,7 +19,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "artifact_id" + ] }, "name": "download_workflow_run_artifact" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/fork_repository.snap b/pkg/github/__toolsnaps__/fork_repository.snap index 18525a4f72..fcef4f0298 100644 --- a/pkg/github/__toolsnaps__/fork_repository.snap +++ b/pkg/github/__toolsnaps__/fork_repository.snap @@ -5,10 +5,6 @@ "description": "Fork a GitHub repository to your account or specified organization", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "organization": { "type": "string", @@ -22,7 +18,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "fork_repository", "icons": [ diff --git a/pkg/github/__toolsnaps__/get_code_scanning_alert.snap b/pkg/github/__toolsnaps__/get_code_scanning_alert.snap index 9e46b960a3..2b15edc7a1 100644 --- a/pkg/github/__toolsnaps__/get_code_scanning_alert.snap +++ b/pkg/github/__toolsnaps__/get_code_scanning_alert.snap @@ -6,11 +6,6 @@ "description": "Get details of a specific code scanning alert in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "alertNumber" - ], "properties": { "alertNumber": { "type": "number", @@ -24,7 +19,12 @@ "type": "string", "description": "The name of the repository." } - } + }, + "required": [ + "owner", + "repo", + "alertNumber" + ] }, "name": "get_code_scanning_alert" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_commit.snap b/pkg/github/__toolsnaps__/get_commit.snap index c6b96d5ed9..b498473f6e 100644 --- a/pkg/github/__toolsnaps__/get_commit.snap +++ b/pkg/github/__toolsnaps__/get_commit.snap @@ -6,11 +6,6 @@ "description": "Get details for a commit from a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "sha" - ], "properties": { "include_diff": { "type": "boolean", @@ -40,7 +35,12 @@ "type": "string", "description": "Commit SHA, branch name, or tag name" } - } + }, + "required": [ + "owner", + "repo", + "sha" + ] }, "name": "get_commit" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_dependabot_alert.snap b/pkg/github/__toolsnaps__/get_dependabot_alert.snap index a517809e2b..fcd64eb88c 100644 --- a/pkg/github/__toolsnaps__/get_dependabot_alert.snap +++ b/pkg/github/__toolsnaps__/get_dependabot_alert.snap @@ -6,11 +6,6 @@ "description": "Get details of a specific dependabot alert in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "alertNumber" - ], "properties": { "alertNumber": { "type": "number", @@ -24,7 +19,12 @@ "type": "string", "description": "The name of the repository." } - } + }, + "required": [ + "owner", + "repo", + "alertNumber" + ] }, "name": "get_dependabot_alert" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_discussion.snap b/pkg/github/__toolsnaps__/get_discussion.snap index feef0f0575..24d1c74d96 100644 --- a/pkg/github/__toolsnaps__/get_discussion.snap +++ b/pkg/github/__toolsnaps__/get_discussion.snap @@ -6,11 +6,6 @@ "description": "Get a specific discussion by ID", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "discussionNumber" - ], "properties": { "discussionNumber": { "type": "number", @@ -24,7 +19,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "discussionNumber" + ] }, "name": "get_discussion" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_discussion_comments.snap b/pkg/github/__toolsnaps__/get_discussion_comments.snap index 3af5edc8ce..67eaeefecc 100644 --- a/pkg/github/__toolsnaps__/get_discussion_comments.snap +++ b/pkg/github/__toolsnaps__/get_discussion_comments.snap @@ -6,11 +6,6 @@ "description": "Get comments from a discussion", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "discussionNumber" - ], "properties": { "after": { "type": "string", @@ -34,7 +29,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "discussionNumber" + ] }, "name": "get_discussion_comments" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_file_contents.snap b/pkg/github/__toolsnaps__/get_file_contents.snap index 638452fe7b..16aeeb82c1 100644 --- a/pkg/github/__toolsnaps__/get_file_contents.snap +++ b/pkg/github/__toolsnaps__/get_file_contents.snap @@ -6,10 +6,6 @@ "description": "Get the contents of a file or directory from a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -32,7 +28,11 @@ "type": "string", "description": "Accepts optional commit SHA. If specified, it will be used instead of ref" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "get_file_contents" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_gist.snap b/pkg/github/__toolsnaps__/get_gist.snap index 4d26618221..99d67037fc 100644 --- a/pkg/github/__toolsnaps__/get_gist.snap +++ b/pkg/github/__toolsnaps__/get_gist.snap @@ -6,15 +6,15 @@ "description": "Get gist content of a particular gist, by gist ID", "inputSchema": { "type": "object", - "required": [ - "gist_id" - ], "properties": { "gist_id": { "type": "string", "description": "The ID of the gist" } - } + }, + "required": [ + "gist_id" + ] }, "name": "get_gist" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_global_security_advisory.snap b/pkg/github/__toolsnaps__/get_global_security_advisory.snap index 18c30425a8..580995d7cc 100644 --- a/pkg/github/__toolsnaps__/get_global_security_advisory.snap +++ b/pkg/github/__toolsnaps__/get_global_security_advisory.snap @@ -6,15 +6,15 @@ "description": "Get a global security advisory", "inputSchema": { "type": "object", - "required": [ - "ghsaId" - ], "properties": { "ghsaId": { "type": "string", "description": "GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx)." } - } + }, + "required": [ + "ghsaId" + ] }, "name": "get_global_security_advisory" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_job_logs.snap b/pkg/github/__toolsnaps__/get_job_logs.snap index 8b2319527d..5288429606 100644 --- a/pkg/github/__toolsnaps__/get_job_logs.snap +++ b/pkg/github/__toolsnaps__/get_job_logs.snap @@ -6,10 +6,6 @@ "description": "Download logs for a specific workflow job or efficiently get all failed job logs for a workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "failed_only": { "type": "boolean", @@ -40,7 +36,11 @@ "description": "Number of lines to return from the end of the log", "default": 500 } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "get_job_logs" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_label.snap b/pkg/github/__toolsnaps__/get_label.snap index 8541044d03..1c8bb25b5c 100644 --- a/pkg/github/__toolsnaps__/get_label.snap +++ b/pkg/github/__toolsnaps__/get_label.snap @@ -6,11 +6,6 @@ "description": "Get a specific label from a repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "name" - ], "properties": { "name": { "type": "string", @@ -24,7 +19,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "name" + ] }, "name": "get_label" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_latest_release.snap b/pkg/github/__toolsnaps__/get_latest_release.snap index 23b551a0f6..7c3c7ecf6e 100644 --- a/pkg/github/__toolsnaps__/get_latest_release.snap +++ b/pkg/github/__toolsnaps__/get_latest_release.snap @@ -6,10 +6,6 @@ "description": "Get the latest release in a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -19,7 +15,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "get_latest_release" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_notification_details.snap b/pkg/github/__toolsnaps__/get_notification_details.snap index de197f2b14..a826d28f22 100644 --- a/pkg/github/__toolsnaps__/get_notification_details.snap +++ b/pkg/github/__toolsnaps__/get_notification_details.snap @@ -6,15 +6,15 @@ "description": "Get detailed information for a specific GitHub notification, always call this tool when the user asks for details about a specific notification, if you don't know the ID list notifications first.", "inputSchema": { "type": "object", - "required": [ - "notificationID" - ], "properties": { "notificationID": { "type": "string", "description": "The ID of the notification" } - } + }, + "required": [ + "notificationID" + ] }, "name": "get_notification_details" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_project.snap b/pkg/github/__toolsnaps__/get_project.snap index 8194b7358e..9d115a0a88 100644 --- a/pkg/github/__toolsnaps__/get_project.snap +++ b/pkg/github/__toolsnaps__/get_project.snap @@ -6,11 +6,6 @@ "description": "Get Project for a user or org", "inputSchema": { "type": "object", - "required": [ - "project_number", - "owner_type", - "owner" - ], "properties": { "owner": { "type": "string", @@ -28,7 +23,12 @@ "type": "number", "description": "The project's number" } - } + }, + "required": [ + "project_number", + "owner_type", + "owner" + ] }, "name": "get_project" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_project_field.snap b/pkg/github/__toolsnaps__/get_project_field.snap index 0df557a032..82649ed343 100644 --- a/pkg/github/__toolsnaps__/get_project_field.snap +++ b/pkg/github/__toolsnaps__/get_project_field.snap @@ -6,12 +6,6 @@ "description": "Get Project field for a user or org", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner", - "project_number", - "field_id" - ], "properties": { "field_id": { "type": "number", @@ -33,7 +27,13 @@ "type": "number", "description": "The project's number." } - } + }, + "required": [ + "owner_type", + "owner", + "project_number", + "field_id" + ] }, "name": "get_project_field" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_project_item.snap b/pkg/github/__toolsnaps__/get_project_item.snap index d77c49c1ef..b2d69263a0 100644 --- a/pkg/github/__toolsnaps__/get_project_item.snap +++ b/pkg/github/__toolsnaps__/get_project_item.snap @@ -6,19 +6,13 @@ "description": "Get a specific Project item for a user or org", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner", - "project_number", - "item_id" - ], "properties": { "fields": { "type": "array", - "description": "Specific list of field IDs to include in the response (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included.", "items": { "type": "string" - } + }, + "description": "Specific list of field IDs to include in the response (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included." }, "item_id": { "type": "number", @@ -40,7 +34,13 @@ "type": "number", "description": "The project's number." } - } + }, + "required": [ + "owner_type", + "owner", + "project_number", + "item_id" + ] }, "name": "get_project_item" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_release_by_tag.snap b/pkg/github/__toolsnaps__/get_release_by_tag.snap index 77f19488c8..3623dcc1c5 100644 --- a/pkg/github/__toolsnaps__/get_release_by_tag.snap +++ b/pkg/github/__toolsnaps__/get_release_by_tag.snap @@ -6,11 +6,6 @@ "description": "Get a specific release by its tag name in a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "tag" - ], "properties": { "owner": { "type": "string", @@ -24,7 +19,12 @@ "type": "string", "description": "Tag name (e.g., 'v1.0.0')" } - } + }, + "required": [ + "owner", + "repo", + "tag" + ] }, "name": "get_release_by_tag" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_repository_tree.snap b/pkg/github/__toolsnaps__/get_repository_tree.snap index 8824628839..28479c3304 100644 --- a/pkg/github/__toolsnaps__/get_repository_tree.snap +++ b/pkg/github/__toolsnaps__/get_repository_tree.snap @@ -6,10 +6,6 @@ "description": "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -32,7 +28,11 @@ "type": "string", "description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "get_repository_tree" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap b/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap index 4d55011da3..b44173f65f 100644 --- a/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap +++ b/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap @@ -6,11 +6,6 @@ "description": "Get details of a specific secret scanning alert in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "alertNumber" - ], "properties": { "alertNumber": { "type": "number", @@ -24,7 +19,12 @@ "type": "string", "description": "The name of the repository." } - } + }, + "required": [ + "owner", + "repo", + "alertNumber" + ] }, "name": "get_secret_scanning_alert" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_tag.snap b/pkg/github/__toolsnaps__/get_tag.snap index e33f5c2e4b..c6dd80a7bf 100644 --- a/pkg/github/__toolsnaps__/get_tag.snap +++ b/pkg/github/__toolsnaps__/get_tag.snap @@ -6,11 +6,6 @@ "description": "Get details about a specific git tag in a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "tag" - ], "properties": { "owner": { "type": "string", @@ -24,7 +19,12 @@ "type": "string", "description": "Tag name" } - } + }, + "required": [ + "owner", + "repo", + "tag" + ] }, "name": "get_tag" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_team_members.snap b/pkg/github/__toolsnaps__/get_team_members.snap index 5b7f090fe1..4a7a5e2a8e 100644 --- a/pkg/github/__toolsnaps__/get_team_members.snap +++ b/pkg/github/__toolsnaps__/get_team_members.snap @@ -6,10 +6,6 @@ "description": "Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials", "inputSchema": { "type": "object", - "required": [ - "org", - "team_slug" - ], "properties": { "org": { "type": "string", @@ -19,7 +15,11 @@ "type": "string", "description": "Team slug" } - } + }, + "required": [ + "org", + "team_slug" + ] }, "name": "get_team_members" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_workflow_run.snap b/pkg/github/__toolsnaps__/get_workflow_run.snap index 37921ffadf..b7a3404fc1 100644 --- a/pkg/github/__toolsnaps__/get_workflow_run.snap +++ b/pkg/github/__toolsnaps__/get_workflow_run.snap @@ -6,11 +6,6 @@ "description": "Get details of a specific workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -24,7 +19,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "get_workflow_run" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_workflow_run_logs.snap b/pkg/github/__toolsnaps__/get_workflow_run_logs.snap index 77fb619b72..824d85c495 100644 --- a/pkg/github/__toolsnaps__/get_workflow_run_logs.snap +++ b/pkg/github/__toolsnaps__/get_workflow_run_logs.snap @@ -6,11 +6,6 @@ "description": "Download logs for a specific workflow run (EXPENSIVE: downloads ALL logs as ZIP. Consider using get_job_logs with failed_only=true for debugging failed jobs)", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -24,7 +19,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "get_workflow_run_logs" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/get_workflow_run_usage.snap b/pkg/github/__toolsnaps__/get_workflow_run_usage.snap index c9fe49f96f..5d52735b28 100644 --- a/pkg/github/__toolsnaps__/get_workflow_run_usage.snap +++ b/pkg/github/__toolsnaps__/get_workflow_run_usage.snap @@ -6,11 +6,6 @@ "description": "Get usage metrics for a workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -24,7 +19,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "get_workflow_run_usage" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/issue_read.snap b/pkg/github/__toolsnaps__/issue_read.snap index c6a9e7306f..57be8aec7b 100644 --- a/pkg/github/__toolsnaps__/issue_read.snap +++ b/pkg/github/__toolsnaps__/issue_read.snap @@ -6,12 +6,6 @@ "description": "Get information about a specific issue in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo", - "issue_number" - ], "properties": { "issue_number": { "type": "number", @@ -46,7 +40,13 @@ "type": "string", "description": "The name of the repository" } - } + }, + "required": [ + "method", + "owner", + "repo", + "issue_number" + ] }, "name": "issue_read" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/issue_write.snap b/pkg/github/__toolsnaps__/issue_write.snap index 8c6634a02a..2340133800 100644 --- a/pkg/github/__toolsnaps__/issue_write.snap +++ b/pkg/github/__toolsnaps__/issue_write.snap @@ -5,18 +5,13 @@ "description": "Create a new or update an existing issue in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo" - ], "properties": { "assignees": { "type": "array", - "description": "Usernames to assign to this issue", "items": { "type": "string" - } + }, + "description": "Usernames to assign to this issue" }, "body": { "type": "string", @@ -32,10 +27,10 @@ }, "labels": { "type": "array", - "description": "Labels to apply to this issue", "items": { "type": "string" - } + }, + "description": "Labels to apply to this issue" }, "method": { "type": "string", @@ -82,7 +77,12 @@ "type": "string", "description": "Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter." } - } + }, + "required": [ + "method", + "owner", + "repo" + ] }, "name": "issue_write" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/label_write.snap b/pkg/github/__toolsnaps__/label_write.snap index 879817442a..aec42eef2a 100644 --- a/pkg/github/__toolsnaps__/label_write.snap +++ b/pkg/github/__toolsnaps__/label_write.snap @@ -5,12 +5,6 @@ "description": "Perform write operations on repository labels. To set labels on issues, use the 'update_issue' tool.", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo", - "name" - ], "properties": { "color": { "type": "string", @@ -45,7 +39,13 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "method", + "owner", + "repo", + "name" + ] }, "name": "label_write" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_branches.snap b/pkg/github/__toolsnaps__/list_branches.snap index b589c9b7e1..f6dd524290 100644 --- a/pkg/github/__toolsnaps__/list_branches.snap +++ b/pkg/github/__toolsnaps__/list_branches.snap @@ -6,10 +6,6 @@ "description": "List branches in a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -30,7 +26,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_branches" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap b/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap index 6f2a4e3427..3249887cc5 100644 --- a/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap +++ b/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap @@ -6,10 +6,6 @@ "description": "List code scanning alerts in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -51,7 +47,11 @@ "type": "string", "description": "The name of the tool used for code scanning." } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_code_scanning_alerts" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_commits.snap b/pkg/github/__toolsnaps__/list_commits.snap index bd67602ed7..6edfa0a842 100644 --- a/pkg/github/__toolsnaps__/list_commits.snap +++ b/pkg/github/__toolsnaps__/list_commits.snap @@ -6,10 +6,6 @@ "description": "Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "author": { "type": "string", @@ -38,7 +34,11 @@ "type": "string", "description": "Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA." } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_commits" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap index d96d3972c5..dd24a41f80 100644 --- a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap +++ b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap @@ -6,10 +6,6 @@ "description": "List dependabot alerts in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -40,7 +36,11 @@ "auto_dismissed" ] } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_dependabot_alerts" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_discussion_categories.snap b/pkg/github/__toolsnaps__/list_discussion_categories.snap index 888ebbdcad..92adedaeca 100644 --- a/pkg/github/__toolsnaps__/list_discussion_categories.snap +++ b/pkg/github/__toolsnaps__/list_discussion_categories.snap @@ -6,9 +6,6 @@ "description": "List discussion categories with their id and name, for a repository or organisation.", "inputSchema": { "type": "object", - "required": [ - "owner" - ], "properties": { "owner": { "type": "string", @@ -18,7 +15,10 @@ "type": "string", "description": "Repository name. If not provided, discussion categories will be queried at the organisation level." } - } + }, + "required": [ + "owner" + ] }, "name": "list_discussion_categories" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_discussions.snap b/pkg/github/__toolsnaps__/list_discussions.snap index 95a8bebf56..87cae6d5e7 100644 --- a/pkg/github/__toolsnaps__/list_discussions.snap +++ b/pkg/github/__toolsnaps__/list_discussions.snap @@ -6,9 +6,6 @@ "description": "List discussions for a repository or organisation.", "inputSchema": { "type": "object", - "required": [ - "owner" - ], "properties": { "after": { "type": "string", @@ -48,7 +45,10 @@ "type": "string", "description": "Repository name. If not provided, discussions will be queried at the organisation level." } - } + }, + "required": [ + "owner" + ] }, "name": "list_discussions" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_global_security_advisories.snap b/pkg/github/__toolsnaps__/list_global_security_advisories.snap index fd9fa78c58..279d186334 100644 --- a/pkg/github/__toolsnaps__/list_global_security_advisories.snap +++ b/pkg/github/__toolsnaps__/list_global_security_advisories.snap @@ -17,10 +17,10 @@ }, "cwes": { "type": "array", - "description": "Filter by Common Weakness Enumeration IDs (e.g. [\"79\", \"284\", \"22\"]).", "items": { "type": "string" - } + }, + "description": "Filter by Common Weakness Enumeration IDs (e.g. [\"79\", \"284\", \"22\"])." }, "ecosystem": { "type": "string", diff --git a/pkg/github/__toolsnaps__/list_issue_types.snap b/pkg/github/__toolsnaps__/list_issue_types.snap index b17dcc54f9..ad05688097 100644 --- a/pkg/github/__toolsnaps__/list_issue_types.snap +++ b/pkg/github/__toolsnaps__/list_issue_types.snap @@ -6,15 +6,15 @@ "description": "List supported issue types for repository owner (organization).", "inputSchema": { "type": "object", - "required": [ - "owner" - ], "properties": { "owner": { "type": "string", "description": "The organization owner of the repository" } - } + }, + "required": [ + "owner" + ] }, "name": "list_issue_types" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_issues.snap b/pkg/github/__toolsnaps__/list_issues.snap index 9d6b555865..fe1c662526 100644 --- a/pkg/github/__toolsnaps__/list_issues.snap +++ b/pkg/github/__toolsnaps__/list_issues.snap @@ -6,10 +6,6 @@ "description": "List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "after": { "type": "string", @@ -25,10 +21,10 @@ }, "labels": { "type": "array", - "description": "Filter by labels", "items": { "type": "string" - } + }, + "description": "Filter by labels" }, "orderBy": { "type": "string", @@ -65,7 +61,11 @@ "CLOSED" ] } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_issues" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_label.snap b/pkg/github/__toolsnaps__/list_label.snap index 0b4f3b20c7..051af59581 100644 --- a/pkg/github/__toolsnaps__/list_label.snap +++ b/pkg/github/__toolsnaps__/list_label.snap @@ -6,10 +6,6 @@ "description": "List labels from a repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -19,7 +15,11 @@ "type": "string", "description": "Repository name - required for all operations" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_label" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap b/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap index 5f8823659b..a13023fbd0 100644 --- a/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap +++ b/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap @@ -6,9 +6,6 @@ "description": "List repository security advisories for a GitHub organization.", "inputSchema": { "type": "object", - "required": [ - "org" - ], "properties": { "direction": { "type": "string", @@ -41,7 +38,10 @@ "closed" ] } - } + }, + "required": [ + "org" + ] }, "name": "list_org_repository_security_advisories" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_project_fields.snap b/pkg/github/__toolsnaps__/list_project_fields.snap index 6bef185079..da3b815305 100644 --- a/pkg/github/__toolsnaps__/list_project_fields.snap +++ b/pkg/github/__toolsnaps__/list_project_fields.snap @@ -6,11 +6,6 @@ "description": "List Project fields for a user or org", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner", - "project_number" - ], "properties": { "after": { "type": "string", @@ -40,7 +35,12 @@ "type": "number", "description": "The project's number." } - } + }, + "required": [ + "owner_type", + "owner", + "project_number" + ] }, "name": "list_project_fields" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_project_items.snap b/pkg/github/__toolsnaps__/list_project_items.snap index bceb5d9eb0..839937d3a4 100644 --- a/pkg/github/__toolsnaps__/list_project_items.snap +++ b/pkg/github/__toolsnaps__/list_project_items.snap @@ -6,11 +6,6 @@ "description": "Search project items with advanced filtering", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner", - "project_number" - ], "properties": { "after": { "type": "string", @@ -22,10 +17,10 @@ }, "fields": { "type": "array", - "description": "Field IDs to include (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned.", "items": { "type": "string" - } + }, + "description": "Field IDs to include (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned." }, "owner": { "type": "string", @@ -51,7 +46,12 @@ "type": "string", "description": "Query string for advanced filtering of project items using GitHub's project filtering syntax." } - } + }, + "required": [ + "owner_type", + "owner", + "project_number" + ] }, "name": "list_project_items" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_projects.snap b/pkg/github/__toolsnaps__/list_projects.snap index f48e262173..03862bcd55 100644 --- a/pkg/github/__toolsnaps__/list_projects.snap +++ b/pkg/github/__toolsnaps__/list_projects.snap @@ -6,10 +6,6 @@ "description": "List Projects for a user or organization", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner" - ], "properties": { "after": { "type": "string", @@ -39,7 +35,11 @@ "type": "string", "description": "Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: \"roadmap is:open\", \"is:open feature planning\"." } - } + }, + "required": [ + "owner_type", + "owner" + ] }, "name": "list_projects" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_pull_requests.snap b/pkg/github/__toolsnaps__/list_pull_requests.snap index ae90c3fe05..8d60dc99a4 100644 --- a/pkg/github/__toolsnaps__/list_pull_requests.snap +++ b/pkg/github/__toolsnaps__/list_pull_requests.snap @@ -6,10 +6,6 @@ "description": "List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "base": { "type": "string", @@ -65,7 +61,11 @@ "all" ] } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_pull_requests" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_releases.snap b/pkg/github/__toolsnaps__/list_releases.snap index 98d4ce66fc..6e81858877 100644 --- a/pkg/github/__toolsnaps__/list_releases.snap +++ b/pkg/github/__toolsnaps__/list_releases.snap @@ -6,10 +6,6 @@ "description": "List releases in a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -30,7 +26,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_releases" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_repository_security_advisories.snap b/pkg/github/__toolsnaps__/list_repository_security_advisories.snap index 465fd881e6..76eadd652c 100644 --- a/pkg/github/__toolsnaps__/list_repository_security_advisories.snap +++ b/pkg/github/__toolsnaps__/list_repository_security_advisories.snap @@ -6,10 +6,6 @@ "description": "List repository security advisories for a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "direction": { "type": "string", @@ -46,7 +42,11 @@ "closed" ] } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_repository_security_advisories" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap b/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap index e7896c55f2..7285903720 100644 --- a/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap +++ b/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap @@ -6,10 +6,6 @@ "description": "List secret scanning alerts in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -43,7 +39,11 @@ "resolved" ] } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_secret_scanning_alerts" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_tags.snap b/pkg/github/__toolsnaps__/list_tags.snap index 5b667d19c6..c8cee7f5a9 100644 --- a/pkg/github/__toolsnaps__/list_tags.snap +++ b/pkg/github/__toolsnaps__/list_tags.snap @@ -6,10 +6,6 @@ "description": "List git tags in a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -30,7 +26,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_tags" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_workflow_jobs.snap b/pkg/github/__toolsnaps__/list_workflow_jobs.snap index 59ff75afc2..31cdbec25b 100644 --- a/pkg/github/__toolsnaps__/list_workflow_jobs.snap +++ b/pkg/github/__toolsnaps__/list_workflow_jobs.snap @@ -6,11 +6,6 @@ "description": "List jobs for a specific workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "filter": { "type": "string", @@ -43,7 +38,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "list_workflow_jobs" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap b/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap index 6d6332d740..28b54f9532 100644 --- a/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap +++ b/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap @@ -6,11 +6,6 @@ "description": "List artifacts for a workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -35,7 +30,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "list_workflow_run_artifacts" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_workflow_runs.snap b/pkg/github/__toolsnaps__/list_workflow_runs.snap index e5353f4904..6c0ff1d47a 100644 --- a/pkg/github/__toolsnaps__/list_workflow_runs.snap +++ b/pkg/github/__toolsnaps__/list_workflow_runs.snap @@ -6,11 +6,6 @@ "description": "List workflow runs for a specific workflow", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "workflow_id" - ], "properties": { "actor": { "type": "string", @@ -92,7 +87,12 @@ "type": "string", "description": "The workflow ID or workflow file name" } - } + }, + "required": [ + "owner", + "repo", + "workflow_id" + ] }, "name": "list_workflow_runs" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_workflows.snap b/pkg/github/__toolsnaps__/list_workflows.snap index f3f52f042f..0d794efe99 100644 --- a/pkg/github/__toolsnaps__/list_workflows.snap +++ b/pkg/github/__toolsnaps__/list_workflows.snap @@ -6,10 +6,6 @@ "description": "List workflows in a repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -30,7 +26,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "list_workflows" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/manage_notification_subscription.snap b/pkg/github/__toolsnaps__/manage_notification_subscription.snap index 4f0d466a08..dd8bb2ad0e 100644 --- a/pkg/github/__toolsnaps__/manage_notification_subscription.snap +++ b/pkg/github/__toolsnaps__/manage_notification_subscription.snap @@ -5,10 +5,6 @@ "description": "Manage a notification subscription: ignore, watch, or delete a notification thread subscription.", "inputSchema": { "type": "object", - "required": [ - "notificationID", - "action" - ], "properties": { "action": { "type": "string", @@ -23,7 +19,11 @@ "type": "string", "description": "The ID of the notification thread." } - } + }, + "required": [ + "notificationID", + "action" + ] }, "name": "manage_notification_subscription" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap b/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap index 82ee40a895..2ae4226af2 100644 --- a/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap +++ b/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap @@ -5,11 +5,6 @@ "description": "Manage a repository notification subscription: ignore, watch, or delete repository notifications subscription for the provided repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "action" - ], "properties": { "action": { "type": "string", @@ -28,7 +23,12 @@ "type": "string", "description": "The name of the repository." } - } + }, + "required": [ + "owner", + "repo", + "action" + ] }, "name": "manage_repository_notification_subscription" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/merge_pull_request.snap b/pkg/github/__toolsnaps__/merge_pull_request.snap index 179805b3a5..cce3702f2c 100644 --- a/pkg/github/__toolsnaps__/merge_pull_request.snap +++ b/pkg/github/__toolsnaps__/merge_pull_request.snap @@ -5,11 +5,6 @@ "description": "Merge a pull request in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "pullNumber" - ], "properties": { "commit_message": { "type": "string", @@ -40,7 +35,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "pullNumber" + ] }, "name": "merge_pull_request", "icons": [ diff --git a/pkg/github/__toolsnaps__/projects_get.snap b/pkg/github/__toolsnaps__/projects_get.snap index 9758de0f2d..eab9732a0a 100644 --- a/pkg/github/__toolsnaps__/projects_get.snap +++ b/pkg/github/__toolsnaps__/projects_get.snap @@ -6,12 +6,6 @@ "description": "Get details about specific GitHub Projects resources.\nUse this tool to get details about individual projects, project fields, and project items by their unique IDs.\n", "inputSchema": { "type": "object", - "required": [ - "method", - "owner_type", - "owner", - "project_number" - ], "properties": { "field_id": { "type": "number", @@ -19,10 +13,10 @@ }, "fields": { "type": "array", - "description": "Specific list of field IDs to include in the response when getting a project item (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included. Only used for 'get_project_item' method.", "items": { "type": "string" - } + }, + "description": "Specific list of field IDs to include in the response when getting a project item (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included. Only used for 'get_project_item' method." }, "item_id": { "type": "number", @@ -53,7 +47,13 @@ "type": "number", "description": "The project's number." } - } + }, + "required": [ + "method", + "owner_type", + "owner", + "project_number" + ] }, "name": "projects_get" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/projects_list.snap b/pkg/github/__toolsnaps__/projects_list.snap index 7cc2e2df7b..17dc51bc44 100644 --- a/pkg/github/__toolsnaps__/projects_list.snap +++ b/pkg/github/__toolsnaps__/projects_list.snap @@ -6,11 +6,6 @@ "description": "Tools for listing GitHub Projects resources.\nUse this tool to list projects for a user or organization, or list project fields and items for a specific project.\n", "inputSchema": { "type": "object", - "required": [ - "method", - "owner_type", - "owner" - ], "properties": { "after": { "type": "string", @@ -22,10 +17,10 @@ }, "fields": { "type": "array", - "description": "Field IDs to include when listing project items (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned. Only used for 'list_project_items' method.", "items": { "type": "string" - } + }, + "description": "Field IDs to include when listing project items (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned. Only used for 'list_project_items' method." }, "method": { "type": "string", @@ -60,7 +55,12 @@ "type": "string", "description": "Filter/query string. For list_projects: filter by title text and state (e.g. \"roadmap is:open\"). For list_project_items: advanced filtering using GitHub's project filtering syntax." } - } + }, + "required": [ + "method", + "owner_type", + "owner" + ] }, "name": "projects_list" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/projects_write.snap b/pkg/github/__toolsnaps__/projects_write.snap index 2224590c53..6e368a58de 100644 --- a/pkg/github/__toolsnaps__/projects_write.snap +++ b/pkg/github/__toolsnaps__/projects_write.snap @@ -6,12 +6,6 @@ "description": "Add, update, or delete project items in a GitHub Project.", "inputSchema": { "type": "object", - "required": [ - "method", - "owner_type", - "owner", - "project_number" - ], "properties": { "item_id": { "type": "number", @@ -54,7 +48,13 @@ "type": "object", "description": "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}. Required for 'update_project_item' method." } - } + }, + "required": [ + "method", + "owner_type", + "owner", + "project_number" + ] }, "name": "projects_write" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/pull_request_read.snap b/pkg/github/__toolsnaps__/pull_request_read.snap index 69b1bd9011..774f1959c1 100644 --- a/pkg/github/__toolsnaps__/pull_request_read.snap +++ b/pkg/github/__toolsnaps__/pull_request_read.snap @@ -6,12 +6,6 @@ "description": "Get information on a specific pull request in GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo", - "pullNumber" - ], "properties": { "method": { "type": "string", @@ -49,7 +43,13 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "method", + "owner", + "repo", + "pullNumber" + ] }, "name": "pull_request_read" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/pull_request_review_write.snap b/pkg/github/__toolsnaps__/pull_request_review_write.snap index 92cc199240..3878d6cb35 100644 --- a/pkg/github/__toolsnaps__/pull_request_review_write.snap +++ b/pkg/github/__toolsnaps__/pull_request_review_write.snap @@ -5,12 +5,6 @@ "description": "Create and/or submit, delete review of a pull request.\n\nAvailable methods:\n- create: Create a new review of a pull request. If \"event\" parameter is provided, the review is submitted. If \"event\" is omitted, a pending review is created.\n- submit_pending: Submit an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request. The \"body\" and \"event\" parameters are used when submitting the review.\n- delete_pending: Delete an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request.\n", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo", - "pullNumber" - ], "properties": { "body": { "type": "string", @@ -50,7 +44,13 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "method", + "owner", + "repo", + "pullNumber" + ] }, "name": "pull_request_review_write" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/push_files.snap b/pkg/github/__toolsnaps__/push_files.snap index 4db764cc94..e86305bae6 100644 --- a/pkg/github/__toolsnaps__/push_files.snap +++ b/pkg/github/__toolsnaps__/push_files.snap @@ -5,13 +5,6 @@ "description": "Push multiple files to a GitHub repository in a single commit", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "branch", - "files", - "message" - ], "properties": { "branch": { "type": "string", @@ -19,13 +12,8 @@ }, "files": { "type": "array", - "description": "Array of file objects to push, each object with path (string) and content (string)", "items": { "type": "object", - "required": [ - "path", - "content" - ], "properties": { "content": { "type": "string", @@ -35,8 +23,13 @@ "type": "string", "description": "path to the file" } - } - } + }, + "required": [ + "path", + "content" + ] + }, + "description": "Array of file objects to push, each object with path (string) and content (string)" }, "message": { "type": "string", @@ -50,7 +43,14 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "branch", + "files", + "message" + ] }, "name": "push_files" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/request_copilot_review.snap b/pkg/github/__toolsnaps__/request_copilot_review.snap index 0bf419d98b..63bd34b80c 100644 --- a/pkg/github/__toolsnaps__/request_copilot_review.snap +++ b/pkg/github/__toolsnaps__/request_copilot_review.snap @@ -5,11 +5,6 @@ "description": "Request a GitHub Copilot code review for a pull request. Use this for automated feedback on pull requests, usually before requesting a human reviewer.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "pullNumber" - ], "properties": { "owner": { "type": "string", @@ -23,7 +18,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "pullNumber" + ] }, "name": "request_copilot_review", "icons": [ diff --git a/pkg/github/__toolsnaps__/rerun_failed_jobs.snap b/pkg/github/__toolsnaps__/rerun_failed_jobs.snap index 2c627637cc..6109d159bf 100644 --- a/pkg/github/__toolsnaps__/rerun_failed_jobs.snap +++ b/pkg/github/__toolsnaps__/rerun_failed_jobs.snap @@ -5,11 +5,6 @@ "description": "Re-run only the failed jobs in a workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -23,7 +18,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "rerun_failed_jobs" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/rerun_workflow_run.snap b/pkg/github/__toolsnaps__/rerun_workflow_run.snap index 00514ee79d..98f831a41c 100644 --- a/pkg/github/__toolsnaps__/rerun_workflow_run.snap +++ b/pkg/github/__toolsnaps__/rerun_workflow_run.snap @@ -5,11 +5,6 @@ "description": "Re-run an entire workflow run", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "run_id" - ], "properties": { "owner": { "type": "string", @@ -23,7 +18,12 @@ "type": "number", "description": "The unique identifier of the workflow run" } - } + }, + "required": [ + "owner", + "repo", + "run_id" + ] }, "name": "rerun_workflow_run" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/run_workflow.snap b/pkg/github/__toolsnaps__/run_workflow.snap index bb35e82132..2d686ad978 100644 --- a/pkg/github/__toolsnaps__/run_workflow.snap +++ b/pkg/github/__toolsnaps__/run_workflow.snap @@ -5,12 +5,6 @@ "description": "Run an Actions workflow by workflow ID or filename", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "workflow_id", - "ref" - ], "properties": { "inputs": { "type": "object", @@ -32,7 +26,13 @@ "type": "string", "description": "The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml)" } - } + }, + "required": [ + "owner", + "repo", + "workflow_id", + "ref" + ] }, "name": "run_workflow" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_code.snap b/pkg/github/__toolsnaps__/search_code.snap index aebd432bfb..0ed437ed9b 100644 --- a/pkg/github/__toolsnaps__/search_code.snap +++ b/pkg/github/__toolsnaps__/search_code.snap @@ -6,9 +6,6 @@ "description": "Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.", "inputSchema": { "type": "object", - "required": [ - "query" - ], "properties": { "order": { "type": "string", @@ -37,7 +34,10 @@ "type": "string", "description": "Sort field ('indexed' only)" } - } + }, + "required": [ + "query" + ] }, "name": "search_code" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_issues.snap b/pkg/github/__toolsnaps__/search_issues.snap index f76a715fb1..f90731d20e 100644 --- a/pkg/github/__toolsnaps__/search_issues.snap +++ b/pkg/github/__toolsnaps__/search_issues.snap @@ -6,9 +6,6 @@ "description": "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue", "inputSchema": { "type": "object", - "required": [ - "query" - ], "properties": { "order": { "type": "string", @@ -58,7 +55,10 @@ "updated" ] } - } + }, + "required": [ + "query" + ] }, "name": "search_issues" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_orgs.snap b/pkg/github/__toolsnaps__/search_orgs.snap index 36eb948aec..1339b6e11a 100644 --- a/pkg/github/__toolsnaps__/search_orgs.snap +++ b/pkg/github/__toolsnaps__/search_orgs.snap @@ -6,9 +6,6 @@ "description": "Find GitHub organizations by name, location, or other organization metadata. Ideal for discovering companies, open source foundations, or teams.", "inputSchema": { "type": "object", - "required": [ - "query" - ], "properties": { "order": { "type": "string", @@ -42,7 +39,10 @@ "joined" ] } - } + }, + "required": [ + "query" + ] }, "name": "search_orgs" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_pull_requests.snap b/pkg/github/__toolsnaps__/search_pull_requests.snap index 2013f5c085..75c68da5df 100644 --- a/pkg/github/__toolsnaps__/search_pull_requests.snap +++ b/pkg/github/__toolsnaps__/search_pull_requests.snap @@ -6,9 +6,6 @@ "description": "Search for pull requests in GitHub repositories using issues search syntax already scoped to is:pr", "inputSchema": { "type": "object", - "required": [ - "query" - ], "properties": { "order": { "type": "string", @@ -58,7 +55,10 @@ "updated" ] } - } + }, + "required": [ + "query" + ] }, "name": "search_pull_requests" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_repositories.snap b/pkg/github/__toolsnaps__/search_repositories.snap index 881bc3816b..76e2200818 100644 --- a/pkg/github/__toolsnaps__/search_repositories.snap +++ b/pkg/github/__toolsnaps__/search_repositories.snap @@ -6,9 +6,6 @@ "description": "Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub.", "inputSchema": { "type": "object", - "required": [ - "query" - ], "properties": { "minimal_output": { "type": "boolean", @@ -48,7 +45,10 @@ "updated" ] } - } + }, + "required": [ + "query" + ] }, "name": "search_repositories" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_users.snap b/pkg/github/__toolsnaps__/search_users.snap index 293107696c..2303ab232a 100644 --- a/pkg/github/__toolsnaps__/search_users.snap +++ b/pkg/github/__toolsnaps__/search_users.snap @@ -6,9 +6,6 @@ "description": "Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members.", "inputSchema": { "type": "object", - "required": [ - "query" - ], "properties": { "order": { "type": "string", @@ -42,7 +39,10 @@ "joined" ] } - } + }, + "required": [ + "query" + ] }, "name": "search_users" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/star_repository.snap b/pkg/github/__toolsnaps__/star_repository.snap index ab1514b3de..f1538d5098 100644 --- a/pkg/github/__toolsnaps__/star_repository.snap +++ b/pkg/github/__toolsnaps__/star_repository.snap @@ -5,10 +5,6 @@ "description": "Star a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -18,7 +14,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "star_repository", "icons": [ diff --git a/pkg/github/__toolsnaps__/sub_issue_write.snap b/pkg/github/__toolsnaps__/sub_issue_write.snap index 1c721a2bb7..b0180fa755 100644 --- a/pkg/github/__toolsnaps__/sub_issue_write.snap +++ b/pkg/github/__toolsnaps__/sub_issue_write.snap @@ -5,13 +5,6 @@ "description": "Add a sub-issue to a parent issue in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo", - "issue_number", - "sub_issue_id" - ], "properties": { "after_id": { "type": "number", @@ -45,7 +38,14 @@ "type": "number", "description": "The ID of the sub-issue to add. ID is not the same as issue number" } - } + }, + "required": [ + "method", + "owner", + "repo", + "issue_number", + "sub_issue_id" + ] }, "name": "sub_issue_write" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/unstar_repository.snap b/pkg/github/__toolsnaps__/unstar_repository.snap index 709453650f..2d2e31aad0 100644 --- a/pkg/github/__toolsnaps__/unstar_repository.snap +++ b/pkg/github/__toolsnaps__/unstar_repository.snap @@ -5,10 +5,6 @@ "description": "Unstar a GitHub repository", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo" - ], "properties": { "owner": { "type": "string", @@ -18,7 +14,11 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo" + ] }, "name": "unstar_repository" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/update_gist.snap b/pkg/github/__toolsnaps__/update_gist.snap index a3907a88c8..9ba5d01d6a 100644 --- a/pkg/github/__toolsnaps__/update_gist.snap +++ b/pkg/github/__toolsnaps__/update_gist.snap @@ -5,11 +5,6 @@ "description": "Update an existing gist", "inputSchema": { "type": "object", - "required": [ - "gist_id", - "filename", - "content" - ], "properties": { "content": { "type": "string", @@ -27,7 +22,12 @@ "type": "string", "description": "ID of the gist to update" } - } + }, + "required": [ + "gist_id", + "filename", + "content" + ] }, "name": "update_gist" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/update_project_item.snap b/pkg/github/__toolsnaps__/update_project_item.snap index 8f5afaa583..badbcf90e7 100644 --- a/pkg/github/__toolsnaps__/update_project_item.snap +++ b/pkg/github/__toolsnaps__/update_project_item.snap @@ -5,13 +5,6 @@ "description": "Update a specific Project item for a user or org", "inputSchema": { "type": "object", - "required": [ - "owner_type", - "owner", - "project_number", - "item_id", - "updated_field" - ], "properties": { "item_id": { "type": "number", @@ -37,7 +30,14 @@ "type": "object", "description": "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}" } - } + }, + "required": [ + "owner_type", + "owner", + "project_number", + "item_id", + "updated_field" + ] }, "name": "update_project_item" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/update_pull_request.snap b/pkg/github/__toolsnaps__/update_pull_request.snap index 6dec2c01f6..822d4348b8 100644 --- a/pkg/github/__toolsnaps__/update_pull_request.snap +++ b/pkg/github/__toolsnaps__/update_pull_request.snap @@ -5,11 +5,6 @@ "description": "Update an existing pull request in a GitHub repository.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "pullNumber" - ], "properties": { "base": { "type": "string", @@ -41,10 +36,10 @@ }, "reviewers": { "type": "array", - "description": "GitHub usernames to request reviews from", "items": { "type": "string" - } + }, + "description": "GitHub usernames to request reviews from" }, "state": { "type": "string", @@ -58,7 +53,12 @@ "type": "string", "description": "New title" } - } + }, + "required": [ + "owner", + "repo", + "pullNumber" + ] }, "name": "update_pull_request" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/update_pull_request_branch.snap b/pkg/github/__toolsnaps__/update_pull_request_branch.snap index 9be1cb0029..9a530a21c8 100644 --- a/pkg/github/__toolsnaps__/update_pull_request_branch.snap +++ b/pkg/github/__toolsnaps__/update_pull_request_branch.snap @@ -5,11 +5,6 @@ "description": "Update the branch of a pull request with the latest changes from the base branch.", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "pullNumber" - ], "properties": { "expectedHeadSha": { "type": "string", @@ -27,7 +22,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "pullNumber" + ] }, "name": "update_pull_request_branch" } \ No newline at end of file diff --git a/pkg/github/dynamic_tools_test.go b/pkg/github/dynamic_tools_test.go index 8d12b78c2a..260b6b9125 100644 --- a/pkg/github/dynamic_tools_test.go +++ b/pkg/github/dynamic_tools_test.go @@ -25,9 +25,10 @@ func createDynamicRequest(args map[string]any) *mcp.CallToolRequest { func TestDynamicTools_ListAvailableToolsets(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) - reg := NewInventory(translations.NullTranslationHelper). + reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() + require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) @@ -73,9 +74,10 @@ func TestDynamicTools_ListAvailableToolsets(t *testing.T) { func TestDynamicTools_GetToolsetTools(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) - reg := NewInventory(translations.NullTranslationHelper). + reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() + require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) @@ -122,9 +124,10 @@ func TestDynamicTools_GetToolsetTools(t *testing.T) { func TestDynamicTools_EnableToolset(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) - reg := NewInventory(translations.NullTranslationHelper). + reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() + require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) @@ -170,9 +173,10 @@ func TestDynamicTools_EnableToolset(t *testing.T) { func TestDynamicTools_EnableToolset_InvalidToolset(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) - reg := NewInventory(translations.NullTranslationHelper). + reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() + require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) @@ -203,7 +207,8 @@ func TestDynamicTools_EnableToolset_InvalidToolset(t *testing.T) { func TestDynamicTools_ToolsetsEnum(t *testing.T) { // Build a registry - reg := NewInventory(translations.NullTranslationHelper).Build() + reg, err := NewInventory(translations.NullTranslationHelper).Build() + require.NoError(t, err) // Get tools to verify they have proper enum values tools := DynamicTools(reg) diff --git a/pkg/github/scope_filter_test.go b/pkg/github/scope_filter_test.go index 451d1a64e7..9cdd4db19b 100644 --- a/pkg/github/scope_filter_test.go +++ b/pkg/github/scope_filter_test.go @@ -167,11 +167,12 @@ func TestCreateToolScopeFilter_Integration(t *testing.T) { filter := CreateToolScopeFilter([]string{"repo"}) // Build inventory with the filter - inv := inventory.NewBuilder(). + inv, err := inventory.NewBuilder(). SetTools(tools). WithToolsets([]string{"test"}). WithFilter(filter). Build() + require.NoError(t, err) // Get available tools availableTools := inv.AvailableTools(context.Background()) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index b15c4fc9a8..4384b730dc 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -309,7 +309,8 @@ func ToStringPtr(s string) *string { // GenerateToolsetsHelp generates the help text for the toolsets flag func GenerateToolsetsHelp() string { // Get toolset group to derive defaults and available toolsets - r := NewInventory(stubTranslator).Build() + // Build() can only fail if WithTools specifies invalid tools - not used here + r, _ := NewInventory(stubTranslator).Build() // Format default tools from metadata using strings.Builder var defaultBuf strings.Builder @@ -391,7 +392,8 @@ func AddDefaultToolset(result []string) []string { result = RemoveToolset(result, string(ToolsetMetadataDefault.ID)) // Get default toolset IDs from the Inventory - r := NewInventory(stubTranslator).Build() + // Build() can only fail if WithTools specifies invalid tools - not used here + r, _ := NewInventory(stubTranslator).Build() for _, id := range r.DefaultToolsetIDs() { if !seen[string(id)] { result = append(result, string(id)) @@ -443,7 +445,8 @@ func CleanTools(toolNames []string) []string { // GetDefaultToolsetIDs returns the IDs of toolsets marked as Default. // This is a convenience function that builds an inventory to determine defaults. func GetDefaultToolsetIDs() []string { - r := NewInventory(stubTranslator).Build() + // Build() can only fail if WithTools specifies invalid tools - not used here + r, _ := NewInventory(stubTranslator).Build() ids := r.DefaultToolsetIDs() result := make([]string, len(ids)) for i, id := range ids { diff --git a/pkg/github/toolset_icons_test.go b/pkg/github/toolset_icons_test.go index fd9cec462b..7cfe4bef77 100644 --- a/pkg/github/toolset_icons_test.go +++ b/pkg/github/toolset_icons_test.go @@ -13,7 +13,8 @@ import ( // This prevents broken icon references from being merged. func TestAllToolsetIconsExist(t *testing.T) { // Get all available toolsets from the inventory - inv := NewInventory(stubTranslator).Build() + inv, err := NewInventory(stubTranslator).Build() + require.NoError(t, err) toolsets := inv.AvailableToolsets() // Also test remote-only toolsets @@ -72,7 +73,8 @@ func TestToolsetMetadataHasIcons(t *testing.T) { "default": true, // Meta-toolset } - inv := NewInventory(stubTranslator).Build() + inv, err := NewInventory(stubTranslator).Build() + require.NoError(t, err) toolsets := inv.AvailableToolsets() for _, ts := range toolsets { diff --git a/pkg/inventory/builder.go b/pkg/inventory/builder.go index 9d135218e6..58abb8ad1e 100644 --- a/pkg/inventory/builder.go +++ b/pkg/inventory/builder.go @@ -2,6 +2,7 @@ package inventory import ( "context" + "fmt" "sort" "strings" ) @@ -150,7 +151,11 @@ func cleanTools(tools []string) []string { // This processes toolset filtering, tool name resolution, and sets up // the inventory for use. The returned Inventory is ready for use with // AvailableTools(), RegisterAll(), etc. -func (b *Builder) Build() *Inventory { +// +// Build returns an error if any tools specified via WithTools() are not recognized +// (i.e., they don't exist in the tool set and are not deprecated aliases). +// This ensures invalid tool configurations fail fast at build time. +func (b *Builder) Build() (*Inventory, error) { r := &Inventory{ tools: b.tools, resourceTemplates: b.resourceTemplates, @@ -190,10 +195,14 @@ func (b *Builder) Build() *Inventory { unrecognizedTools = append(unrecognizedTools, name) } } - r.unrecognizedTools = unrecognizedTools + + // Error out if there are unrecognized tools + if len(unrecognizedTools) > 0 { + return nil, fmt.Errorf("unrecognized tools: %s", strings.Join(unrecognizedTools, ", ")) + } } - return r + return r, nil } // processToolsets processes the toolsetIDs configuration and returns: diff --git a/pkg/inventory/registry.go b/pkg/inventory/registry.go index 7313d58b32..f3691e38ab 100644 --- a/pkg/inventory/registry.go +++ b/pkg/inventory/registry.go @@ -58,8 +58,6 @@ type Inventory struct { filters []ToolFilter // unrecognizedToolsets holds toolset IDs that were requested but don't match any registered toolsets unrecognizedToolsets []string - // unrecognizedTools holds tool names that were requested via WithTools but don't exist - unrecognizedTools []string } // UnrecognizedToolsets returns toolset IDs that were passed to WithToolsets but don't @@ -68,13 +66,6 @@ func (r *Inventory) UnrecognizedToolsets() []string { return r.unrecognizedToolsets } -// UnrecognizedTools returns tool names that were passed to WithTools but don't -// match any registered tools (and are not deprecated aliases). This is used to -// error out when invalid tool names are specified. -func (r *Inventory) UnrecognizedTools() []string { - return r.unrecognizedTools -} - // MCP method constants for use with ForMCPRequest. const ( MCPMethodInitialize = "initialize" @@ -121,7 +112,6 @@ func (r *Inventory) ForMCPRequest(method string, itemName string) *Inventory { featureChecker: r.featureChecker, filters: r.filters, // shared, not modified unrecognizedToolsets: r.unrecognizedToolsets, - unrecognizedTools: r.unrecognizedTools, } // Helper to clear all item types diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 2bb0c7d658..af2a219557 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -7,8 +7,18 @@ import ( "testing" "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/require" ) +// mustBuild is a test helper that calls Build() and fails the test if an error occurs. +// Use this for tests where Build() is not expected to fail. +func mustBuild(t *testing.T, b *Builder) *Inventory { + t.Helper() + inv, err := b.Build() + require.NoError(t, err) + return inv +} + // testToolsetMetadata returns a ToolsetMetadata for testing func testToolsetMetadata(id string) ToolsetMetadata { return ToolsetMetadata{ @@ -65,7 +75,7 @@ func mockTool(name string, toolsetID string, readOnly bool) ServerTool { } func TestNewRegistryEmpty(t *testing.T) { - reg := NewBuilder().Build() + reg := mustBuild(t, NewBuilder()) if len(reg.AvailableTools(context.Background())) != 0 { t.Fatalf("Expected tools to be empty") } @@ -84,7 +94,7 @@ func TestNewRegistryWithTools(t *testing.T) { mockTool("tool3", "toolset2", true), } - reg := NewBuilder().SetTools(tools).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools)) if len(reg.AllTools()) != 3 { t.Errorf("Expected 3 tools, got %d", len(reg.AllTools())) @@ -98,7 +108,7 @@ func TestAvailableTools_NoFilters(t *testing.T) { mockTool("tool_c", "toolset2", true), } - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) available := reg.AvailableTools(context.Background()) if len(available) != 3 { @@ -121,14 +131,14 @@ func TestWithReadOnly(t *testing.T) { } // Build without read-only - should have both tools - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) allTools := reg.AvailableTools(context.Background()) if len(allTools) != 2 { t.Fatalf("Expected 2 tools without read-only, got %d", len(allTools)) } // Build with read-only - should filter out write tools - readOnlyReg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true).Build() + readOnlyReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true)) readOnlyTools := readOnlyReg.AvailableTools(context.Background()) if len(readOnlyTools) != 1 { t.Fatalf("Expected 1 tool in read-only, got %d", len(readOnlyTools)) @@ -146,14 +156,14 @@ func TestWithToolsets(t *testing.T) { } // Build with all toolsets - allReg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + allReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) allTools := allReg.AvailableTools(context.Background()) if len(allTools) != 3 { t.Fatalf("Expected 3 tools without filter, got %d", len(allTools)) } // Build with specific toolsets - filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset3"}).Build() + filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset3"})) filteredTools := filteredReg.AvailableTools(context.Background()) if len(filteredTools) != 2 { @@ -177,7 +187,7 @@ func TestWithToolsetsTrimsWhitespace(t *testing.T) { } // Whitespace should be trimmed - filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{" toolset1 ", " toolset2 "}).Build() + filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{" toolset1 ", " toolset2 "})) filteredTools := filteredReg.AvailableTools(context.Background()) if len(filteredTools) != 2 { @@ -191,7 +201,7 @@ func TestWithToolsetsDeduplicates(t *testing.T) { } // Duplicates should be removed - filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset1", " toolset1 "}).Build() + filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset1", " toolset1 "})) filteredTools := filteredReg.AvailableTools(context.Background()) if len(filteredTools) != 1 { @@ -205,7 +215,7 @@ func TestWithToolsetsIgnoresEmptyStrings(t *testing.T) { } // Empty strings should be ignored - filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{"", "toolset1", " ", ""}).Build() + filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"", "toolset1", " ", ""})) filteredTools := filteredReg.AvailableTools(context.Background()) if len(filteredTools) != 1 { @@ -253,7 +263,7 @@ func TestUnrecognizedToolsets(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - filtered := NewBuilder().SetTools(tools).WithToolsets(tt.input).Build() + filtered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets(tt.input)) unrecognized := filtered.UnrecognizedToolsets() if len(unrecognized) != len(tt.expectedUnrecognized) { @@ -270,7 +280,7 @@ func TestUnrecognizedToolsets(t *testing.T) { } } -func TestUnrecognizedTools(t *testing.T) { +func TestBuildErrorsOnUnrecognizedTools(t *testing.T) { tools := []ServerTool{ mockTool("tool1", "toolset1", true), mockTool("tool2", "toolset2", true), @@ -281,91 +291,93 @@ func TestUnrecognizedTools(t *testing.T) { } tests := []struct { - name string - withTools []string - expectedUnrecognized []string + name string + withTools []string + expectError bool + errorContains string }{ { - name: "all valid", - withTools: []string{"tool1", "tool2"}, - expectedUnrecognized: nil, + name: "all valid", + withTools: []string{"tool1", "tool2"}, + expectError: false, }, { - name: "one invalid", - withTools: []string{"tool1", "blabla"}, - expectedUnrecognized: []string{"blabla"}, + name: "one invalid", + withTools: []string{"tool1", "blabla"}, + expectError: true, + errorContains: "blabla", }, { - name: "multiple invalid", - withTools: []string{"invalid1", "tool1", "invalid2"}, - expectedUnrecognized: []string{"invalid1", "invalid2"}, + name: "multiple invalid", + withTools: []string{"invalid1", "tool1", "invalid2"}, + expectError: true, + errorContains: "invalid1", }, { - name: "deprecated alias is valid", - withTools: []string{"old_tool"}, - expectedUnrecognized: nil, + name: "deprecated alias is valid", + withTools: []string{"old_tool"}, + expectError: false, }, { - name: "mixed valid and deprecated alias", - withTools: []string{"old_tool", "tool2"}, - expectedUnrecognized: nil, + name: "mixed valid and deprecated alias", + withTools: []string{"old_tool", "tool2"}, + expectError: false, }, { - name: "empty input", - withTools: []string{}, - expectedUnrecognized: nil, + name: "empty input", + withTools: []string{}, + expectError: false, }, { - name: "whitespace trimmed from valid tool", - withTools: []string{" tool1 ", " tool2 "}, - expectedUnrecognized: nil, + name: "whitespace trimmed from valid tool", + withTools: []string{" tool1 ", " tool2 "}, + expectError: false, }, { - name: "whitespace trimmed from invalid tool", - withTools: []string{" invalid_tool "}, - expectedUnrecognized: []string{"invalid_tool"}, + name: "whitespace trimmed from invalid tool", + withTools: []string{" invalid_tool "}, + expectError: true, + errorContains: "invalid_tool", }, { - name: "duplicate tools deduplicated", - withTools: []string{"tool1", "tool1"}, - expectedUnrecognized: nil, + name: "duplicate tools deduplicated", + withTools: []string{"tool1", "tool1"}, + expectError: false, }, { - name: "duplicate invalid tools deduplicated", - withTools: []string{"blabla", "blabla"}, - expectedUnrecognized: []string{"blabla"}, + name: "duplicate invalid tools deduplicated", + withTools: []string{"blabla", "blabla"}, + expectError: true, + errorContains: "blabla", }, { - name: "mixed whitespace and duplicates", - withTools: []string{" tool1 ", "tool1", " tool1 "}, - expectedUnrecognized: nil, + name: "mixed whitespace and duplicates", + withTools: []string{" tool1 ", "tool1", " tool1 "}, + expectError: false, }, { - name: "empty strings ignored", - withTools: []string{"", "tool1", " ", ""}, - expectedUnrecognized: nil, + name: "empty strings ignored", + withTools: []string{"", "tool1", " ", ""}, + expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - inv := NewBuilder(). + inv, err := NewBuilder(). SetTools(tools). WithDeprecatedAliases(deprecatedAliases). WithToolsets([]string{"all"}). WithTools(tt.withTools). Build() - unrecognized := inv.UnrecognizedTools() - if len(unrecognized) != len(tt.expectedUnrecognized) { - t.Fatalf("Expected %d unrecognized, got %d: %v", - len(tt.expectedUnrecognized), len(unrecognized), unrecognized) - } - - for i, expected := range tt.expectedUnrecognized { - if unrecognized[i] != expected { - t.Errorf("Expected unrecognized[%d] = %q, got %q", i, expected, unrecognized[i]) - } + if tt.expectError { + require.Error(t, err, "Expected error for unrecognized tools") + require.Contains(t, err.Error(), tt.errorContains) + require.Nil(t, inv) + } else { + require.NoError(t, err) + require.NotNil(t, inv) } }) } @@ -380,7 +392,7 @@ func TestWithTools(t *testing.T) { // WithTools adds additional tools that bypass toolset filtering // When combined with WithToolsets([]), only the additional tools should be available - filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"tool1", "tool3"}).Build() + filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"tool1", "tool3"})) filteredTools := filteredReg.AvailableTools(context.Background()) if len(filteredTools) != 2 { @@ -405,7 +417,7 @@ func TestChainedFilters(t *testing.T) { } // Chain read-only and toolset filter - filtered := NewBuilder().SetTools(tools).WithReadOnly(true).WithToolsets([]string{"toolset1"}).Build() + filtered := mustBuild(t, NewBuilder().SetTools(tools).WithReadOnly(true).WithToolsets([]string{"toolset1"})) result := filtered.AvailableTools(context.Background()) if len(result) != 1 { @@ -423,7 +435,7 @@ func TestToolsetIDs(t *testing.T) { mockTool("tool3", "toolset_b", true), // duplicate toolset } - reg := NewBuilder().SetTools(tools).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools)) ids := reg.ToolsetIDs() if len(ids) != 2 { @@ -442,7 +454,7 @@ func TestToolsetDescriptions(t *testing.T) { mockTool("tool2", "toolset2", true), } - reg := NewBuilder().SetTools(tools).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools)) descriptions := reg.ToolsetDescriptions() if len(descriptions) != 2 { @@ -461,7 +473,7 @@ func TestToolsForToolset(t *testing.T) { mockTool("tool3", "toolset2", true), } - reg := NewBuilder().SetTools(tools).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools)) toolset1Tools := reg.ToolsForToolset("toolset1") if len(toolset1Tools) != 2 { @@ -474,10 +486,10 @@ func TestWithDeprecatedAliases(t *testing.T) { mockTool("new_name", "toolset1", true), } - reg := NewBuilder().SetTools(tools).WithDeprecatedAliases(map[string]string{ + reg := mustBuild(t, NewBuilder().SetTools(tools).WithDeprecatedAliases(map[string]string{ "old_name": "new_name", "get_issue": "issue_read", - }).Build() + })) // Test resolving aliases resolved, aliasesUsed := reg.ResolveToolAliases([]string{"old_name"}) @@ -495,10 +507,10 @@ func TestResolveToolAliases(t *testing.T) { mockTool("some_tool", "toolset1", true), } - reg := NewBuilder().SetTools(tools). + reg := mustBuild(t, NewBuilder().SetTools(tools). WithDeprecatedAliases(map[string]string{ "get_issue": "issue_read", - }).Build() + })) // Test resolving a mix of aliases and canonical names input := []string{"get_issue", "some_tool"} @@ -527,7 +539,7 @@ func TestFindToolByName(t *testing.T) { mockTool("issue_read", "toolset1", true), } - reg := NewBuilder().SetTools(tools).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools)) // Find by name tool, toolsetID, err := reg.FindToolByName("issue_read") @@ -557,7 +569,7 @@ func TestWithToolsAdditive(t *testing.T) { // Test WithTools bypasses toolset filtering // Enable only toolset2, but add issue_read as additional tool - filtered := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset2"}).WithTools([]string{"issue_read"}).Build() + filtered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset2"}).WithTools([]string{"issue_read"})) available := filtered.AvailableTools(context.Background()) if len(available) != 2 { @@ -577,7 +589,7 @@ func TestWithToolsAdditive(t *testing.T) { } // Test WithTools respects read-only mode - readOnlyFiltered := NewBuilder().SetTools(tools).WithReadOnly(true).WithTools([]string{"issue_write"}).Build() + readOnlyFiltered := mustBuild(t, NewBuilder().SetTools(tools).WithReadOnly(true).WithTools([]string{"issue_write"})) available = readOnlyFiltered.AvailableTools(context.Background()) // issue_write should be excluded because read-only applies to additional tools too @@ -587,12 +599,10 @@ func TestWithToolsAdditive(t *testing.T) { } } - // Test WithTools with non-existent tool (should not error, just won't match anything) - nonexistent := NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"nonexistent"}).Build() - available = nonexistent.AvailableTools(context.Background()) - if len(available) != 0 { - t.Errorf("expected 0 tools for non-existent additional tool, got %d", len(available)) - } + // Test WithTools with non-existent tool (should error during Build) + _, err := NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"nonexistent"}).Build() + require.Error(t, err, "expected error for non-existent tool") + require.Contains(t, err.Error(), "nonexistent") } func TestWithToolsResolvesAliases(t *testing.T) { @@ -601,13 +611,12 @@ func TestWithToolsResolvesAliases(t *testing.T) { } // Using deprecated alias should resolve to canonical name - filtered := NewBuilder().SetTools(tools). + filtered := mustBuild(t, NewBuilder().SetTools(tools). WithDeprecatedAliases(map[string]string{ "get_issue": "issue_read", }). WithToolsets([]string{}). - WithTools([]string{"get_issue"}). - Build() + WithTools([]string{"get_issue"})) available := filtered.AvailableTools(context.Background()) if len(available) != 1 { @@ -623,7 +632,7 @@ func TestHasToolset(t *testing.T) { mockTool("tool1", "toolset1", true), } - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) if !reg.HasToolset("toolset1") { t.Error("expected HasToolset to return true for existing toolset") @@ -640,14 +649,14 @@ func TestEnabledToolsetIDs(t *testing.T) { } // Without filter, all toolsets are enabled - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) ids := reg.EnabledToolsetIDs() if len(ids) != 2 { t.Fatalf("Expected 2 enabled toolset IDs, got %d", len(ids)) } // With filter - filtered := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1"}).Build() + filtered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1"})) filteredIDs := filtered.EnabledToolsetIDs() if len(filteredIDs) != 1 { t.Fatalf("Expected 1 enabled toolset ID, got %d", len(filteredIDs)) @@ -664,7 +673,7 @@ func TestAllTools(t *testing.T) { } // Even with read-only filter, AllTools returns everything - readOnlyReg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true).Build() + readOnlyReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true)) allTools := readOnlyReg.AllTools() if len(allTools) != 2 { @@ -729,7 +738,7 @@ func TestForMCPRequest_Initialize(t *testing.T) { mockPrompt("prompt1", "repos"), } - reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodInitialize, "") // Initialize should return empty - capabilities come from ServerOptions @@ -756,7 +765,7 @@ func TestForMCPRequest_ToolsList(t *testing.T) { mockPrompt("prompt1", "repos"), } - reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodToolsList, "") // tools/list should return all tools, no resources or prompts @@ -778,7 +787,7 @@ func TestForMCPRequest_ToolsCall(t *testing.T) { mockTool("list_repos", "repos", true), } - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodToolsCall, "get_me") available := filtered.AvailableTools(context.Background()) @@ -795,7 +804,7 @@ func TestForMCPRequest_ToolsCall_NotFound(t *testing.T) { mockTool("get_me", "context", true), } - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodToolsCall, "nonexistent") if len(filtered.AvailableTools(context.Background())) != 0 { @@ -809,11 +818,11 @@ func TestForMCPRequest_ToolsCall_DeprecatedAlias(t *testing.T) { mockTool("list_commits", "repos", true), } - reg := NewBuilder().SetTools(tools). + reg := mustBuild(t, NewBuilder().SetTools(tools). WithToolsets([]string{"all"}). WithDeprecatedAliases(map[string]string{ "old_get_me": "get_me", - }).Build() + })) // Request using the deprecated alias filtered := reg.ForMCPRequest(MCPMethodToolsCall, "old_get_me") @@ -833,7 +842,7 @@ func TestForMCPRequest_ToolsCall_RespectsFilters(t *testing.T) { } // Apply read-only filter at build time, then ForMCPRequest - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true)) filtered := reg.ForMCPRequest(MCPMethodToolsCall, "create_issue") // The tool exists in the filtered group, but AvailableTools respects read-only @@ -855,7 +864,7 @@ func TestForMCPRequest_ResourcesList(t *testing.T) { mockPrompt("prompt1", "repos"), } - reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodResourcesList, "") if len(filtered.AvailableTools(context.Background())) != 0 { @@ -875,7 +884,7 @@ func TestForMCPRequest_ResourcesRead(t *testing.T) { mockResource("res2", "repos", "branch://{owner}/{repo}/{branch}"), } - reg := NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetResources(resources).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodResourcesRead, "repo://{owner}/{repo}") available := filtered.AvailableResourceTemplates(context.Background()) @@ -899,7 +908,7 @@ func TestForMCPRequest_PromptsList(t *testing.T) { mockPrompt("prompt2", "issues"), } - reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodPromptsList, "") if len(filtered.AvailableTools(context.Background())) != 0 { @@ -919,7 +928,7 @@ func TestForMCPRequest_PromptsGet(t *testing.T) { mockPrompt("prompt2", "issues"), } - reg := NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodPromptsGet, "prompt1") available := filtered.AvailablePrompts(context.Background()) @@ -942,7 +951,7 @@ func TestForMCPRequest_UnknownMethod(t *testing.T) { mockPrompt("prompt1", "repos"), } - reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest("unknown/method", "") // Unknown methods should return empty @@ -969,7 +978,7 @@ func TestForMCPRequest_DoesNotMutateOriginal(t *testing.T) { mockPrompt("prompt1", "repos"), } - original := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + original := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"})) filtered := original.ForMCPRequest(MCPMethodToolsCall, "tool1") // Original should be unchanged @@ -1004,10 +1013,9 @@ func TestForMCPRequest_ChainedWithOtherFilters(t *testing.T) { } // Chain: default toolsets -> read-only -> specific method - reg := NewBuilder().SetTools(tools). + reg := mustBuild(t, NewBuilder().SetTools(tools). WithToolsets([]string{"default"}). - WithReadOnly(true). - Build() + WithReadOnly(true)) filtered := reg.ForMCPRequest(MCPMethodToolsList, "") available := filtered.AvailableTools(context.Background()) @@ -1045,7 +1053,7 @@ func TestForMCPRequest_ResourcesTemplatesList(t *testing.T) { mockResource("res1", "repos", "repo://{owner}/{repo}"), } - reg := NewBuilder().SetTools(tools).SetResources(resources).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).WithToolsets([]string{"all"})) filtered := reg.ForMCPRequest(MCPMethodResourcesTemplatesList, "") // Same behavior as resources/list @@ -1095,7 +1103,7 @@ func TestFeatureFlagEnable(t *testing.T) { } // Without feature checker, tool with FeatureFlagEnable should be excluded - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) available := reg.AvailableTools(context.Background()) if len(available) != 1 { t.Fatalf("Expected 1 tool without feature checker, got %d", len(available)) @@ -1106,7 +1114,7 @@ func TestFeatureFlagEnable(t *testing.T) { // With feature checker returning false, tool should still be excluded checkerFalse := func(_ context.Context, _ string) (bool, error) { return false, nil } - regFalse := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerFalse).Build() + regFalse := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerFalse)) availableFalse := regFalse.AvailableTools(context.Background()) if len(availableFalse) != 1 { t.Fatalf("Expected 1 tool with false checker, got %d", len(availableFalse)) @@ -1116,7 +1124,7 @@ func TestFeatureFlagEnable(t *testing.T) { checkerTrue := func(_ context.Context, flag string) (bool, error) { return flag == "my_feature", nil } - regTrue := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue).Build() + regTrue := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue)) availableTrue := regTrue.AvailableTools(context.Background()) if len(availableTrue) != 2 { t.Fatalf("Expected 2 tools with true checker, got %d", len(availableTrue)) @@ -1130,7 +1138,7 @@ func TestFeatureFlagDisable(t *testing.T) { } // Without feature checker, tool with FeatureFlagDisable should be included (flag is false) - reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) available := reg.AvailableTools(context.Background()) if len(available) != 2 { t.Fatalf("Expected 2 tools without feature checker, got %d", len(available)) @@ -1140,7 +1148,7 @@ func TestFeatureFlagDisable(t *testing.T) { checkerTrue := func(_ context.Context, flag string) (bool, error) { return flag == "kill_switch", nil } - regFiltered := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue).Build() + regFiltered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue)) availableFiltered := regFiltered.AvailableTools(context.Background()) if len(availableFiltered) != 1 { t.Fatalf("Expected 1 tool with kill_switch enabled, got %d", len(availableFiltered)) @@ -1158,21 +1166,21 @@ func TestFeatureFlagBoth(t *testing.T) { // Enable flag not set -> excluded checker1 := func(_ context.Context, _ string) (bool, error) { return false, nil } - reg1 := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker1).Build() + reg1 := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker1)) if len(reg1.AvailableTools(context.Background())) != 0 { t.Error("Tool should be excluded when enable flag is false") } // Enable flag set, disable flag not set -> included checker2 := func(_ context.Context, flag string) (bool, error) { return flag == "new_feature", nil } - reg2 := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker2).Build() + reg2 := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker2)) if len(reg2.AvailableTools(context.Background())) != 1 { t.Error("Tool should be included when enable flag is true and disable flag is false") } // Enable flag set, disable flag also set -> excluded (disable wins) checker3 := func(_ context.Context, _ string) (bool, error) { return true, nil } - reg3 := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker3).Build() + reg3 := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker3)) if len(reg3.AvailableTools(context.Background())) != 0 { t.Error("Tool should be excluded when both flags are true (disable wins)") } @@ -1187,7 +1195,7 @@ func TestFeatureFlagError(t *testing.T) { checkerError := func(_ context.Context, _ string) (bool, error) { return false, fmt.Errorf("simulated error") } - reg := NewBuilder().SetTools(tools).WithFeatureChecker(checkerError).Build() + reg := mustBuild(t, NewBuilder().SetTools(tools).WithFeatureChecker(checkerError)) available := reg.AvailableTools(context.Background()) if len(available) != 0 { t.Errorf("Expected 0 tools when checker errors, got %d", len(available)) @@ -1205,7 +1213,7 @@ func TestFeatureFlagResources(t *testing.T) { } // Without checker, resource with enable flag should be excluded - reg := NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetResources(resources).WithToolsets([]string{"all"})) available := reg.AvailableResourceTemplates(context.Background()) if len(available) != 1 { t.Fatalf("Expected 1 resource without checker, got %d", len(available)) @@ -1213,7 +1221,7 @@ func TestFeatureFlagResources(t *testing.T) { // With checker returning true, both should be included checker := func(_ context.Context, _ string) (bool, error) { return true, nil } - regWithChecker := NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).WithFeatureChecker(checker).Build() + regWithChecker := mustBuild(t, NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).WithFeatureChecker(checker)) if len(regWithChecker.AvailableResourceTemplates(context.Background())) != 2 { t.Errorf("Expected 2 resources with checker, got %d", len(regWithChecker.AvailableResourceTemplates(context.Background()))) } @@ -1230,7 +1238,7 @@ func TestFeatureFlagPrompts(t *testing.T) { } // Without checker, prompt with enable flag should be excluded - reg := NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"})) available := reg.AvailablePrompts(context.Background()) if len(available) != 1 { t.Fatalf("Expected 1 prompt without checker, got %d", len(available)) @@ -1238,7 +1246,7 @@ func TestFeatureFlagPrompts(t *testing.T) { // With checker returning true, both should be included checker := func(_ context.Context, _ string) (bool, error) { return true, nil } - regWithChecker := NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).WithFeatureChecker(checker).Build() + regWithChecker := mustBuild(t, NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).WithFeatureChecker(checker)) if len(regWithChecker.AvailablePrompts(context.Background())) != 2 { t.Errorf("Expected 2 prompts with checker, got %d", len(regWithChecker.AvailablePrompts(context.Background()))) } @@ -1321,7 +1329,7 @@ func TestServerToolEnabled(t *testing.T) { tool := mockTool("test_tool", "toolset1", true) tool.Enabled = tt.enabledFunc - reg := NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"})) available := reg.AvailableTools(context.Background()) if len(available) != tt.expectedCount { @@ -1353,7 +1361,7 @@ func TestServerToolEnabledWithContext(t *testing.T) { return user != nil && user.(string) == "authorized", nil } - reg := NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"}).Build() + reg := mustBuild(t, NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"})) // Without user in context - tool should be excluded available := reg.AvailableTools(context.Background()) @@ -1389,11 +1397,10 @@ func TestBuilderWithFilter(t *testing.T) { return tool.Tool.Name != "tool2", nil } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools(tools). WithToolsets([]string{"all"}). - WithFilter(filter). - Build() + WithFilter(filter)) available := reg.AvailableTools(context.Background()) if len(available) != 2 { @@ -1425,12 +1432,11 @@ func TestBuilderWithMultipleFilters(t *testing.T) { return tool.Tool.Name != "tool3", nil } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools(tools). WithToolsets([]string{"all"}). WithFilter(filter1). - WithFilter(filter2). - Build() + WithFilter(filter2)) available := reg.AvailableTools(context.Background()) if len(available) != 2 { @@ -1460,11 +1466,10 @@ func TestBuilderFilterError(t *testing.T) { return false, fmt.Errorf("filter error") } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools(tools). WithToolsets([]string{"all"}). - WithFilter(filter). - Build() + WithFilter(filter)) available := reg.AvailableTools(context.Background()) if len(available) != 0 { @@ -1490,11 +1495,10 @@ func TestBuilderFilterWithContext(t *testing.T) { return true, nil } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools(tools). WithToolsets([]string{"all"}). - WithFilter(filter). - Build() + WithFilter(filter)) // With public scope - private_tool should be excluded ctxPublic := context.WithValue(context.Background(), scopeKey, "public") @@ -1523,10 +1527,9 @@ func TestEnabledAndFeatureFlagInteraction(t *testing.T) { } // Feature flag not enabled - tool should be excluded despite Enabled returning true - reg1 := NewBuilder(). + reg1 := mustBuild(t, NewBuilder(). SetTools([]ServerTool{tool}). - WithToolsets([]string{"all"}). - Build() + WithToolsets([]string{"all"})) available1 := reg1.AvailableTools(context.Background()) if len(available1) != 0 { t.Error("Tool should be excluded when feature flag is not enabled") @@ -1536,11 +1539,10 @@ func TestEnabledAndFeatureFlagInteraction(t *testing.T) { checker := func(_ context.Context, flag string) (bool, error) { return flag == "my_feature", nil } - reg2 := NewBuilder(). + reg2 := mustBuild(t, NewBuilder(). SetTools([]ServerTool{tool}). WithToolsets([]string{"all"}). - WithFeatureChecker(checker). - Build() + WithFeatureChecker(checker)) available2 := reg2.AvailableTools(context.Background()) if len(available2) != 1 { t.Error("Tool should be included when both Enabled and feature flag pass") @@ -1550,11 +1552,10 @@ func TestEnabledAndFeatureFlagInteraction(t *testing.T) { tool.Enabled = func(_ context.Context) (bool, error) { return false, nil } - reg3 := NewBuilder(). + reg3 := mustBuild(t, NewBuilder(). SetTools([]ServerTool{tool}). WithToolsets([]string{"all"}). - WithFeatureChecker(checker). - Build() + WithFeatureChecker(checker)) available3 := reg3.AvailableTools(context.Background()) if len(available3) != 0 { t.Error("Tool should be excluded when Enabled returns false") @@ -1572,11 +1573,10 @@ func TestEnabledAndBuilderFilterInteraction(t *testing.T) { return false, nil } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools([]ServerTool{tool}). WithToolsets([]string{"all"}). - WithFilter(filter). - Build() + WithFilter(filter)) available := reg.AvailableTools(context.Background()) if len(available) != 0 { @@ -1600,12 +1600,11 @@ func TestAllFiltersInteraction(t *testing.T) { } // All conditions pass - tool should be included - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools([]ServerTool{tool}). WithToolsets([]string{"all"}). WithFeatureChecker(checker). - WithFilter(filter). - Build() + WithFilter(filter)) available := reg.AvailableTools(context.Background()) if len(available) != 1 { @@ -1617,12 +1616,11 @@ func TestAllFiltersInteraction(t *testing.T) { return false, nil } - reg2 := NewBuilder(). + reg2 := mustBuild(t, NewBuilder(). SetTools([]ServerTool{tool}). WithToolsets([]string{"all"}). WithFeatureChecker(checker). - WithFilter(filterFalse). - Build() + WithFilter(filterFalse)) available2 := reg2.AvailableTools(context.Background()) if len(available2) != 0 { @@ -1641,11 +1639,10 @@ func TestFilteredTools(t *testing.T) { return tool.Tool.Name == "tool1", nil } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools(tools). WithToolsets([]string{"all"}). - WithFilter(filter). - Build() + WithFilter(filter)) filtered, err := reg.FilteredTools(context.Background()) if err != nil { @@ -1668,11 +1665,10 @@ func TestFilteredToolsMatchesAvailableTools(t *testing.T) { mockTool("tool3", "toolset2", true), } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools(tools). WithToolsets([]string{"toolset1"}). - WithReadOnly(true). - Build() + WithReadOnly(true)) ctx := context.Background() filtered, err := reg.FilteredTools(ctx) @@ -1722,13 +1718,12 @@ func TestFilteringOrder(t *testing.T) { return true, nil } - reg := NewBuilder(). + reg := mustBuild(t, NewBuilder(). SetTools([]ServerTool{tool}). WithToolsets([]string{"all"}). WithReadOnly(true). // This will exclude the tool (it's not read-only) WithFeatureChecker(checker). - WithFilter(filter). - Build() + WithFilter(filter)) _ = reg.AvailableTools(context.Background()) @@ -1756,10 +1751,9 @@ func TestForMCPRequest_ToolsCall_FeatureFlaggedVariants(t *testing.T) { } // Test 1: Flag is OFF - first tool variant should be available - regFlagOff := NewBuilder(). + regFlagOff := mustBuild(t, NewBuilder(). SetTools(tools). - WithToolsets([]string{"all"}). - Build() + WithToolsets([]string{"all"})) filteredOff := regFlagOff.ForMCPRequest(MCPMethodToolsCall, "get_job_logs") availableOff := filteredOff.AvailableTools(context.Background()) if len(availableOff) != 1 { @@ -1774,11 +1768,10 @@ func TestForMCPRequest_ToolsCall_FeatureFlaggedVariants(t *testing.T) { checker := func(_ context.Context, flag string) (bool, error) { return flag == "consolidated_flag", nil } - regFlagOn := NewBuilder(). + regFlagOn := mustBuild(t, NewBuilder(). SetTools(tools). WithToolsets([]string{"all"}). - WithFeatureChecker(checker). - Build() + WithFeatureChecker(checker)) filteredOn := regFlagOn.ForMCPRequest(MCPMethodToolsCall, "get_job_logs") availableOn := filteredOn.AvailableTools(context.Background()) if len(availableOn) != 1 { @@ -1810,12 +1803,11 @@ func TestWithTools_DeprecatedAliasAndFeatureFlag(t *testing.T) { // Test 1: Flag OFF - old_tool should be available via direct name match // (not via alias resolution to new_tool, since old_tool still exists) - regFlagOff := NewBuilder(). + regFlagOff := mustBuild(t, NewBuilder(). SetTools(tools). WithDeprecatedAliases(deprecatedAliases). WithToolsets([]string{}). // No toolsets enabled - WithTools([]string{"old_tool"}). // Explicitly request old tool - Build() + WithTools([]string{"old_tool"})) // Explicitly request old tool availableOff := regFlagOff.AvailableTools(context.Background()) if len(availableOff) != 1 { t.Fatalf("Flag OFF: Expected 1 tool, got %d", len(availableOff)) @@ -1828,13 +1820,12 @@ func TestWithTools_DeprecatedAliasAndFeatureFlag(t *testing.T) { checker := func(_ context.Context, flag string) (bool, error) { return flag == "my_flag", nil } - regFlagOn := NewBuilder(). + regFlagOn := mustBuild(t, NewBuilder(). SetTools(tools). WithDeprecatedAliases(deprecatedAliases). WithToolsets([]string{}). // No toolsets enabled WithTools([]string{"old_tool"}). // Request old tool name - WithFeatureChecker(checker). - Build() + WithFeatureChecker(checker)) availableOn := regFlagOn.AvailableTools(context.Background()) if len(availableOn) != 1 { t.Fatalf("Flag ON: Expected 1 tool, got %d", len(availableOn))