From 2140dd3f8a4e1a8f5aab8a70fc9ad1f12934db5e Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Thu, 23 Apr 2026 19:55:43 +0200 Subject: [PATCH 1/7] feat: replace server instructions with skill resources Replace the server instructions system with MCP skill resources that follow the skill:// URI convention for discovery by MCP clients. Each skill resource covers a domain of tools and provides YAML frontmatter (name, description, allowed-tools) plus markdown guidance. All tools remain available by default (all tools mode). Skills provide contextual guidance that clients can discover via resources/list. 16 skills covering all toolsets: context, repos, issues, pull-requests, code-security, actions, discussions, projects, notifications, gists, users-orgs, security-advisories, labels, git, stargazers, copilot. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/ghmcp/server.go | 1 - pkg/github/server.go | 4 +- pkg/github/skill_resources.go | 517 +++++++++++++++++++++++++++++ pkg/github/skill_resources_test.go | 84 +++++ pkg/github/tools.go | 41 +-- pkg/github/toolset_instructions.go | 108 ------ pkg/http/handler.go | 2 - pkg/inventory/builder.go | 22 +- pkg/inventory/instructions.go | 43 --- pkg/inventory/instructions_test.go | 265 --------------- pkg/inventory/registry.go | 6 - pkg/inventory/server_tool.go | 3 - 12 files changed, 628 insertions(+), 468 deletions(-) create mode 100644 pkg/github/skill_resources.go create mode 100644 pkg/github/skill_resources_test.go delete mode 100644 pkg/github/toolset_instructions.go delete mode 100644 pkg/inventory/instructions.go delete mode 100644 pkg/inventory/instructions_test.go diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index b1925bffd3..497c8d3af0 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -143,7 +143,6 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se WithToolsets(github.ResolvedEnabledToolsets(cfg.DynamicToolsets, cfg.EnabledToolsets, cfg.EnabledTools)). WithTools(github.CleanTools(cfg.EnabledTools)). WithExcludeTools(cfg.ExcludeTools). - WithServerInstructions(). WithFeatureChecker(featureChecker) // Apply token scope filtering if scopes are known (for PAT filtering) diff --git a/pkg/github/server.go b/pkg/github/server.go index ee41e90e9e..b19701691b 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -81,7 +81,6 @@ type MCPServerOption func(*mcp.ServerOptions) func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependencies, inv *inventory.Inventory, middleware ...mcp.Middleware) (*mcp.Server, error) { // Create the MCP server serverOpts := &mcp.ServerOptions{ - Instructions: inv.Instructions(), Logger: cfg.Logger, CompletionHandler: CompletionsHandler(deps.GetClient), } @@ -125,6 +124,9 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci registerDynamicTools(ghServer, inv, deps, cfg.Translator) } + // Register skill resources for MCP clients that support skills-based discovery + RegisterSkillResources(ghServer) + return ghServer, nil } diff --git a/pkg/github/skill_resources.go b/pkg/github/skill_resources.go new file mode 100644 index 0000000000..5d9433bdf5 --- /dev/null +++ b/pkg/github/skill_resources.go @@ -0,0 +1,517 @@ +package github + +import ( + "context" + "fmt" + "strings" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// skillDefinition holds the metadata and content for a single skill resource. +type skillDefinition struct { + // name is the skill identifier used in frontmatter and URI + name string + // description is a short summary of the skill's purpose + description string + // allowedTools lists the MCP tool names associated with this skill + allowedTools []string + // body is the markdown instruction content (after frontmatter) + body string +} + +// allSkills returns all skill definitions for the GitHub MCP Server. +// Each skill covers a domain of tools and provides guidance for using them. +func allSkills() []skillDefinition { + return []skillDefinition{ + skillContext(), + skillRepos(), + skillIssues(), + skillPullRequests(), + skillCodeSecurity(), + skillActions(), + skillDiscussions(), + skillProjects(), + skillNotifications(), + skillGists(), + skillUsersOrgs(), + skillSecurityAdvisories(), + skillLabels(), + skillGit(), + skillStargazers(), + skillCopilot(), + } +} + +func skillContext() skillDefinition { + return skillDefinition{ + name: "context", + description: "Understand the current user and their GitHub context", + allowedTools: []string{ + "get_me", + "get_teams", + "get_team_members", + }, + body: `# GitHub Context + +Always call **get_me** first to understand the current user's permissions and context. + +- **get_me** — returns the authenticated user's profile and permissions +- **get_teams** — lists teams the user belongs to +- **get_team_members** — lists members of a specific team +`, + } +} + +func skillRepos() skillDefinition { + return skillDefinition{ + name: "repos", + description: "Manage GitHub repositories, branches, files, releases, and code search", + allowedTools: []string{ + "search_repositories", + "get_file_contents", + "list_commits", + "search_code", + "get_commit", + "list_branches", + "list_tags", + "get_tag", + "list_releases", + "get_latest_release", + "get_release_by_tag", + "create_or_update_file", + "create_repository", + "fork_repository", + "create_branch", + "push_files", + "delete_file", + }, + body: `# GitHub Repositories + +Tools for managing GitHub repositories, browsing code, and working with branches, tags, and releases. + +## Tool Selection +- Use **search_repositories** for finding repos by criteria and **search_code** for finding code containing specific patterns. +- Use **list_*** tools for broad retrieval with pagination (e.g., all branches, all commits). + +## Context Management +- Use pagination with batches of 5-10 items. +- Set **minimal_output** to true when full details are not needed. + +## Sorting +For search tools, use separate **sort** and **order** parameters — do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'). +`, + } +} + +func skillIssues() skillDefinition { + return skillDefinition{ + name: "issues", + description: "Create, read, update, and search GitHub issues with sub-issues and types", + allowedTools: []string{ + "issue_read", + "search_issues", + "list_issues", + "list_issue_types", + "issue_write", + "add_issue_comment", + "sub_issue_write", + "get_label", + // Granular tools (feature-flagged alternatives to issue_write/sub_issue_write) + "create_issue", + "update_issue_title", + "update_issue_body", + "update_issue_assignees", + "update_issue_labels", + "update_issue_milestone", + "update_issue_type", + "update_issue_state", + "add_sub_issue", + "remove_sub_issue", + "reprioritize_sub_issue", + "set_issue_fields", + }, + body: `# GitHub Issues + +Tools for creating, reading, updating, and searching GitHub issues. + +## Workflow +1. Check **list_issue_types** first for organizations to discover proper issue types. +2. Use **search_issues** before creating new issues to avoid duplicates. +3. Always set **state_reason** when closing issues. + +## Tool Selection +- Use **search_issues** for targeted queries with specific criteria or keywords. +- Use **list_issues** for broad retrieval of all issues with basic filtering and pagination. +`, + } +} + +func skillPullRequests() skillDefinition { + return skillDefinition{ + name: "pull-requests", + description: "Create, review, merge, and manage GitHub pull requests", + allowedTools: []string{ + "pull_request_read", + "list_pull_requests", + "search_pull_requests", + "merge_pull_request", + "update_pull_request_branch", + "create_pull_request", + "update_pull_request", + "pull_request_review_write", + "add_comment_to_pending_review", + "add_reply_to_pull_request_comment", + // Granular tools (feature-flagged alternatives to update_pull_request/pull_request_review_write) + "update_pull_request_title", + "update_pull_request_body", + "update_pull_request_state", + "update_pull_request_draft_state", + "request_pull_request_reviewers", + "create_pull_request_review", + "submit_pending_pull_request_review", + "delete_pending_pull_request_review", + "add_pull_request_review_comment", + "resolve_review_thread", + "unresolve_review_thread", + }, + body: `# GitHub Pull Requests + +Tools for creating, reviewing, merging, and managing GitHub pull requests. + +## PR Review Workflow +For complex reviews with line-specific comments: +1. Use **pull_request_review_write** with method 'create' to create a pending review. +2. Use **add_comment_to_pending_review** to add line comments. +3. Use **pull_request_review_write** with method 'submit_pending' to submit the review. + +## Creating Pull Requests +Before creating a PR, search for pull request templates in the repository. Template files are called pull_request_template.md or located in the '.github/PULL_REQUEST_TEMPLATE' directory. Use the template content to structure the PR description. + +## Tool Selection +- Use **search_pull_requests** for targeted queries with specific criteria. +- Use **list_pull_requests** for broad retrieval with basic filtering and pagination. +`, + } +} + +func skillCodeSecurity() skillDefinition { + return skillDefinition{ + name: "code-security", + description: "View code scanning alerts, secret scanning alerts, and Dependabot alerts", + allowedTools: []string{ + "get_code_scanning_alert", + "list_code_scanning_alerts", + "get_secret_scanning_alert", + "list_secret_scanning_alerts", + "get_dependabot_alert", + "list_dependabot_alerts", + }, + body: `# Code Security + +Tools for viewing security alerts across GitHub repositories. + +## Alert Types +- **Code Scanning** — static analysis alerts (CodeQL, third-party tools) +- **Secret Scanning** — detected secrets and credentials in code +- **Dependabot** — vulnerable dependency alerts + +Use **list_*** tools to get an overview of alerts, then **get_*** to inspect specific alerts in detail. +`, + } +} + +func skillActions() skillDefinition { + return skillDefinition{ + name: "actions", + description: "View and trigger GitHub Actions workflows, runs, and job logs", + allowedTools: []string{ + "actions_list", + "actions_get", + "actions_run_trigger", + "get_job_logs", + }, + body: `# GitHub Actions + +Tools for interacting with GitHub Actions workflows and CI/CD operations. + +- **actions_list** — list workflow runs for a repository +- **actions_get** — get details of a specific workflow run +- **actions_run_trigger** — trigger a workflow run +- **get_job_logs** — retrieve logs from a specific job +`, + } +} + +func skillDiscussions() skillDefinition { + return skillDefinition{ + name: "discussions", + description: "Browse and read GitHub Discussions and their categories", + allowedTools: []string{ + "list_discussions", + "get_discussion", + "get_discussion_comments", + "list_discussion_categories", + }, + body: `# GitHub Discussions + +Tools for browsing and reading GitHub Discussions. + +Use **list_discussion_categories** to understand available categories before creating discussions. Filter by category for better organization. +`, + } +} + +func skillProjects() skillDefinition { + return skillDefinition{ + name: "projects", + description: "Manage GitHub Projects (v2) — list items, update fields, and track status", + allowedTools: []string{ + "projects_list", + "projects_get", + "projects_write", + }, + body: `# GitHub Projects + +Tools for managing GitHub Projects (v2). + +## Workflow +1. Call **projects_list** to find projects. +2. Use **projects_get** with list_project_fields to understand available fields and get IDs/types. +3. Use **projects_get** with list_project_items (with pagination) to browse items. +4. Use **projects_write** for updates. + +## Status Updates +Use list_project_status_updates to read recent project status updates (newest first). Use get_project_status_update with a node ID to get a single update. Use create_project_status_update to create a new status update. + +## Field Usage +- Call list_project_fields first to understand available fields and get IDs/types before filtering. +- Use EXACT returned field names (case-insensitive match). Don't invent names or IDs. +- Only include filters for fields that exist and are relevant. + +## Pagination +- Loop while pageInfo.hasNextPage=true using after=pageInfo.nextCursor. +- Keep query, fields, per_page IDENTICAL on every page. + +## Query Syntax for list_project_items +- AND: space-separated (label:bug priority:high) +- OR: comma inside one qualifier (label:bug,critical) +- NOT: leading '-' (-label:wontfix) +- Ranges: points:1..3, updated:<@today-30d +- Wildcards: title:*crash*, label:bug* +- Type filters: is:issue, is:pr +- State: state:open, state:closed, state:merged +- Assignment: assignee:@me, assignee:username +`, + } +} + +func skillNotifications() skillDefinition { + return skillDefinition{ + name: "notifications", + description: "View and manage GitHub notifications and subscriptions", + allowedTools: []string{ + "list_notifications", + "get_notification_details", + "dismiss_notification", + "mark_all_notifications_read", + "manage_notification_subscription", + "manage_repository_notification_subscription", + }, + body: `# GitHub Notifications + +Tools for viewing and managing GitHub notifications. + +- **list_notifications** — list unread and read notifications +- **get_notification_details** — get details of a specific notification +- **dismiss_notification** — mark a notification as done +- **mark_all_notifications_read** — mark all notifications as read +- **manage_notification_subscription** — manage thread subscription settings +- **manage_repository_notification_subscription** — manage repository notification settings +`, + } +} + +func skillGists() skillDefinition { + return skillDefinition{ + name: "gists", + description: "Create, read, and update GitHub Gists", + allowedTools: []string{ + "list_gists", + "get_gist", + "create_gist", + "update_gist", + }, + body: `# GitHub Gists + +Tools for managing GitHub Gists — lightweight code snippets and file sharing. + +- **list_gists** — list gists for the authenticated user +- **get_gist** — retrieve a specific gist by ID +- **create_gist** — create a new gist (public or private) +- **update_gist** — update an existing gist's files or description +`, + } +} + +func skillUsersOrgs() skillDefinition { + return skillDefinition{ + name: "users-orgs", + description: "Search for GitHub users and organizations", + allowedTools: []string{ + "search_users", + "search_orgs", + }, + body: `# GitHub Users & Organizations + +Tools for searching GitHub users and organizations. + +- **search_users** — search for users by username, name, location, or other criteria +- **search_orgs** — search for organizations by name or other criteria + +Use separate **sort** and **order** parameters for sorting results — do not include 'sort:' syntax in query strings. +`, + } +} + +func skillSecurityAdvisories() skillDefinition { + return skillDefinition{ + name: "security-advisories", + description: "Browse global and repository-level security advisories", + allowedTools: []string{ + "list_global_security_advisories", + "get_global_security_advisory", + "list_repository_security_advisories", + "list_org_repository_security_advisories", + }, + body: `# Security Advisories + +Tools for browsing security advisories on GitHub. + +- **list_global_security_advisories** — search the GitHub Advisory Database +- **get_global_security_advisory** — get details of a specific global advisory +- **list_repository_security_advisories** — list advisories for a specific repository +- **list_org_repository_security_advisories** — list advisories across an organization's repositories +`, + } +} + +func skillLabels() skillDefinition { + return skillDefinition{ + name: "labels", + description: "Manage GitHub issue and PR labels", + allowedTools: []string{ + "list_label", + "list_labels", + "label_write", + }, + body: `# GitHub Labels + +Tools for managing labels on GitHub repositories. + +- **list_label** / **list_labels** — list labels for a repository +- **label_write** — create, update, or delete labels +`, + } +} + +func skillGit() skillDefinition { + return skillDefinition{ + name: "git", + description: "Low-level Git operations via the GitHub Git API", + allowedTools: []string{ + "get_repository_tree", + }, + body: `# GitHub Git API + +Low-level Git operations via the GitHub API. + +- **get_repository_tree** — retrieve the tree structure of a repository at a given ref, useful for understanding repository layout +`, + } +} + +func skillStargazers() skillDefinition { + return skillDefinition{ + name: "stargazers", + description: "Star and unstar repositories, list starred repositories", + allowedTools: []string{ + "list_starred_repositories", + "star_repository", + "unstar_repository", + }, + body: `# GitHub Stars + +Tools for managing repository stars. + +- **list_starred_repositories** — list repositories starred by the authenticated user +- **star_repository** — star a repository +- **unstar_repository** — unstar a repository +`, + } +} + +func skillCopilot() skillDefinition { + return skillDefinition{ + name: "copilot", + description: "Assign Copilot to issues and request Copilot reviews on pull requests", + allowedTools: []string{ + "assign_copilot_to_issue", + "request_copilot_review", + }, + body: `# GitHub Copilot + +Tools for using GitHub Copilot in your workflow. + +- **assign_copilot_to_issue** — assign Copilot as a collaborator on an issue +- **request_copilot_review** — request a Copilot review on a pull request +`, + } +} + +// buildSkillContent builds the full SKILL.md content with YAML frontmatter. +func buildSkillContent(skill skillDefinition) string { + var b strings.Builder + b.WriteString("---\n") + fmt.Fprintf(&b, "name: %s\n", skill.name) + fmt.Fprintf(&b, "description: %s\n", skill.description) + b.WriteString("allowed-tools:\n") + for _, tool := range skill.allowedTools { + fmt.Fprintf(&b, " - %s\n", tool) + } + b.WriteString("---\n\n") + b.WriteString(skill.body) + return b.String() +} + +// RegisterSkillResources registers all skill resources with the MCP server. +// Each skill is a static resource with a skill:// URI that can be discovered +// by MCP clients supporting the skills pattern. +func RegisterSkillResources(s *mcp.Server) { + for _, skill := range allSkills() { + content := buildSkillContent(skill) + uri := fmt.Sprintf("skill://github/%s/SKILL.md", skill.name) + + s.AddResource( + &mcp.Resource{ + URI: uri, + Name: fmt.Sprintf("%s/SKILL.md", skill.name), + Description: skill.description, + MIMEType: "text/markdown", + }, + func(skillContent string, skillURI string) mcp.ResourceHandler { + return func(_ context.Context, _ *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { + return &mcp.ReadResourceResult{ + Contents: []*mcp.ResourceContents{ + { + URI: skillURI, + MIMEType: "text/markdown", + Text: skillContent, + }, + }, + }, nil + } + }(content, uri), + ) + } +} diff --git a/pkg/github/skill_resources_test.go b/pkg/github/skill_resources_test.go new file mode 100644 index 0000000000..c7ad2e8b84 --- /dev/null +++ b/pkg/github/skill_resources_test.go @@ -0,0 +1,84 @@ +package github + +import ( + "testing" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAllSkillsCoverAllToolsets(t *testing.T) { + // Collect all tool names from AllTools + allToolNames := make(map[string]bool) + for _, tool := range AllTools(stubTranslator) { + allToolNames[tool.Tool.Name] = true + } + + // Collect all tool names covered by skills + coveredTools := make(map[string]bool) + for _, skill := range allSkills() { + for _, toolName := range skill.allowedTools { + coveredTools[toolName] = true + } + } + + // Every tool should be covered by at least one skill + for toolName := range allToolNames { + assert.True(t, coveredTools[toolName], "tool %q is not covered by any skill", toolName) + } +} + +func TestBuildSkillContent(t *testing.T) { + skill := skillDefinition{ + name: "test-skill", + description: "A test skill", + allowedTools: []string{"tool_a", "tool_b"}, + body: "# Test\n\nUse these tools.\n", + } + + content := buildSkillContent(skill) + + assert.Contains(t, content, "---\n") + assert.Contains(t, content, "name: test-skill\n") + assert.Contains(t, content, "description: A test skill\n") + assert.Contains(t, content, " - tool_a\n") + assert.Contains(t, content, " - tool_b\n") + assert.Contains(t, content, "# Test\n") +} + +func TestSkillResourceURIs(t *testing.T) { + skills := allSkills() + require.NotEmpty(t, skills) + + uris := make(map[string]bool) + names := make(map[string]bool) + + for _, skill := range skills { + uri := "skill://github/" + skill.name + "/SKILL.md" + + assert.False(t, uris[uri], "duplicate skill URI: %s", uri) + uris[uri] = true + + assert.False(t, names[skill.name], "duplicate skill name: %s", skill.name) + names[skill.name] = true + + assert.NotEmpty(t, skill.description, "skill %s has empty description", skill.name) + assert.NotEmpty(t, skill.allowedTools, "skill %s has no allowed tools", skill.name) + assert.NotEmpty(t, skill.body, "skill %s has empty body", skill.name) + } +} + +func TestRegisterSkillResources(t *testing.T) { + server := mcp.NewServer(&mcp.Implementation{ + Name: "test-server", + Version: "0.0.1", + }, nil) + + // Should not panic + RegisterSkillResources(server) + + // Verify the expected number of skills were registered by counting definitions + skills := allSkills() + assert.Equal(t, 16, len(skills), "expected 16 skills covering all toolsets") +} diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 559088f6d6..ef915259c3 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -29,11 +29,10 @@ var ( Icon: "check-circle", } ToolsetMetadataContext = inventory.ToolsetMetadata{ - ID: "context", - Description: "Tools that provide context about the current user and GitHub context you are operating in", - Default: true, - Icon: "person", - InstructionsFunc: generateContextToolsetInstructions, + ID: "context", + Description: "Tools that provide context about the current user and GitHub context you are operating in", + Default: true, + Icon: "person", } ToolsetMetadataRepos = inventory.ToolsetMetadata{ ID: "repos", @@ -47,18 +46,16 @@ var ( Icon: "git-branch", } ToolsetMetadataIssues = inventory.ToolsetMetadata{ - ID: "issues", - Description: "GitHub Issues related tools", - Default: true, - Icon: "issue-opened", - InstructionsFunc: generateIssuesToolsetInstructions, + ID: "issues", + Description: "GitHub Issues related tools", + Default: true, + Icon: "issue-opened", } ToolsetMetadataPullRequests = inventory.ToolsetMetadata{ - ID: "pull_requests", - Description: "GitHub Pull Request related tools", - Default: true, - Icon: "git-pull-request", - InstructionsFunc: generatePullRequestsToolsetInstructions, + ID: "pull_requests", + Description: "GitHub Pull Request related tools", + Default: true, + Icon: "git-pull-request", } ToolsetMetadataUsers = inventory.ToolsetMetadata{ ID: "users", @@ -97,10 +94,9 @@ var ( Icon: "bell", } ToolsetMetadataDiscussions = inventory.ToolsetMetadata{ - ID: "discussions", - Description: "GitHub Discussions related tools", - Icon: "comment-discussion", - InstructionsFunc: generateDiscussionsToolsetInstructions, + ID: "discussions", + Description: "GitHub Discussions related tools", + Icon: "comment-discussion", } ToolsetMetadataGists = inventory.ToolsetMetadata{ ID: "gists", @@ -113,10 +109,9 @@ var ( Icon: "shield", } ToolsetMetadataProjects = inventory.ToolsetMetadata{ - ID: "projects", - Description: "GitHub Projects related tools", - Icon: "project", - InstructionsFunc: generateProjectsToolsetInstructions, + ID: "projects", + Description: "GitHub Projects related tools", + Icon: "project", } ToolsetMetadataStargazers = inventory.ToolsetMetadata{ ID: "stargazers", diff --git a/pkg/github/toolset_instructions.go b/pkg/github/toolset_instructions.go deleted file mode 100644 index bc9da4e65c..0000000000 --- a/pkg/github/toolset_instructions.go +++ /dev/null @@ -1,108 +0,0 @@ -package github - -import "github.com/github/github-mcp-server/pkg/inventory" - -// Toolset instruction functions - these generate context-aware instructions for each toolset. -// They are called during inventory build to generate server instructions. - -func generateContextToolsetInstructions(_ *inventory.Inventory) string { - return "Always call 'get_me' first to understand current user permissions and context." -} - -func generateIssuesToolsetInstructions(_ *inventory.Inventory) string { - return `## Issues - -Check 'list_issue_types' first for organizations to use proper issue types. Use 'search_issues' before creating new issues to avoid duplicates. Always set 'state_reason' when closing issues.` -} - -func generatePullRequestsToolsetInstructions(inv *inventory.Inventory) string { - instructions := `## Pull Requests - -PR review workflow: Always use 'pull_request_review_write' with method 'create' to create a pending review, then 'add_comment_to_pending_review' to add comments, and finally 'pull_request_review_write' with method 'submit_pending' to submit the review for complex reviews with line-specific comments.` - - if inv.HasToolset("repos") { - instructions += ` - -Before creating a pull request, search for pull request templates in the repository. Template files are called pull_request_template.md or they're located in '.github/PULL_REQUEST_TEMPLATE' directory. Use the template content to structure the PR description and then call create_pull_request tool.` - } - return instructions -} - -func generateDiscussionsToolsetInstructions(_ *inventory.Inventory) string { - return `## Discussions - -Use 'list_discussion_categories' to understand available categories before creating discussions. Filter by category for better organization.` -} - -func generateProjectsToolsetInstructions(_ *inventory.Inventory) string { - return `## Projects - -Workflow: 1) list_project_fields (get field IDs), 2) list_project_items (with pagination), 3) optional updates. - -Status updates: Use list_project_status_updates to read recent project status updates (newest first). Use get_project_status_update with a node ID to get a single update. Use create_project_status_update to create a new status update for a project. - -Field usage: - - Call list_project_fields first to understand available fields and get IDs/types before filtering. - - Use EXACT returned field names (case-insensitive match). Don't invent names or IDs. - - Iteration synonyms (sprint/cycle) only if that field exists; map to the actual name (e.g. sprint:@current). - - Only include filters for fields that exist and are relevant. - -Pagination (mandatory): - - Loop while pageInfo.hasNextPage=true using after=pageInfo.nextCursor. - - Keep query, fields, per_page IDENTICAL on every page. - - Use before=pageInfo.prevCursor only when explicitly navigating to a previous page. - -Counting rules: - - Count items array length after full pagination. - - Never count field objects, content, or nested arrays as separate items. - -Summary vs list: - - Summaries ONLY if user uses verbs: analyze | summarize | summary | report | overview | insights. - - Listing verbs (list/show/get/fetch/display/enumerate) → enumerate + total. - -Self-check before returning: - - Paginated fully - - Correct IDs used - - Field names valid - - Summary only if requested. - -Return COMPLETE data or state what's missing (e.g. pages skipped). - -list_project_items query rules: -Query string - For advanced filtering of project items using GitHub's project filtering syntax: - -MUST reflect user intent; strongly prefer explicit content type if narrowed: - - "open issues" → state:open is:issue - - "merged PRs" → state:merged is:pr - - "items updated this week" → updated:>@today-7d (omit type only if mixed desired) - - "list all P1 priority items" → priority:p1 (omit state if user wants all, omit type if user specifies "items") - - "list all open P2 issues" → is:issue state:open priority:p2 (include state if user wants open or closed, include type if user specifies "issues" or "PRs") - - "all open issues I'm working on" → is:issue state:open assignee:@me - -Query Construction Heuristics: - a. Extract type nouns: issues → is:issue | PRs, Pulls, or Pull Requests → is:pr | tasks/tickets → is:issue (ask if ambiguity) - b. Map temporal phrases: "this week" → updated:>@today-7d - c. Map negations: "excluding wontfix" → -label:wontfix - d. Map priority adjectives: "high/sev1/p1" → priority:high OR priority:p1 (choose based on field presence) - e. When filtering by label, always use wildcard matching to account for cross-repository differences or emojis: (e.g. "bug 🐛" → label:*bug*) - f. When filtering by milestone, always use wildcard matching to account for cross-repository differences: (e.g. "v1.0" → milestone:*v1.0*) - -Syntax Essentials (items): - AND: space-separated. (label:bug priority:high). - OR: comma inside one qualifier (label:bug,critical). - NOT: leading '-' (-label:wontfix). - Hyphenate multi-word field names. (team-name:"Backend Team", story-points:>5). - Quote multi-word values. (status:"In Review" team-name:"Backend Team"). - Ranges: points:1..3, updated:<@today-30d. - Wildcards: title:*crash*, label:bug*. - Assigned to User: assignee:@me | assignee:username | no:assignee - -Common Qualifier Glossary (items): - is:issue | is:pr | state:open|closed|merged | assignee:@me|username | label:NAME | status:VALUE | - priority:p1|high | sprint-name:@current | team-name:"Backend Team" | parent-issue:"org/repo#123" | - updated:>@today-7d | title:*text* | -label:wontfix | label:bug,critical | no:assignee | has:label - -Never: - - Infer field IDs; fetch via list_project_fields. - - Drop 'fields' param on subsequent pages if field values are needed.` -} diff --git a/pkg/http/handler.go b/pkg/http/handler.go index 1ae4713216..9e8a630521 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -290,8 +290,6 @@ func DefaultInventoryFactory(cfg *ServerConfig, t translations.TranslationHelper b = InventoryFiltersForRequest(r, b) b = PATScopeFilter(b, r, scopeFetcher) - b.WithServerInstructions() - return b.Build() } } diff --git a/pkg/inventory/builder.go b/pkg/inventory/builder.go index b9a0d8548b..a9db54164b 100644 --- a/pkg/inventory/builder.go +++ b/pkg/inventory/builder.go @@ -46,13 +46,12 @@ type Builder struct { deprecatedAliases map[string]string // Configuration options (processed at Build time) - readOnly bool - toolsetIDs []string // raw input, processed at Build() - toolsetIDsIsNil bool // tracks if nil was passed (nil = defaults) - additionalTools []string // raw input, processed at Build() - featureChecker FeatureFlagChecker - filters []ToolFilter // filters to apply to all tools - generateInstructions bool + readOnly bool + toolsetIDs []string // raw input, processed at Build() + toolsetIDsIsNil bool // tracks if nil was passed (nil = defaults) + additionalTools []string // raw input, processed at Build() + featureChecker FeatureFlagChecker + filters []ToolFilter // filters to apply to all tools } // NewBuilder creates a new Builder. @@ -95,11 +94,6 @@ func (b *Builder) WithReadOnly(readOnly bool) *Builder { return b } -func (b *Builder) WithServerInstructions() *Builder { - b.generateInstructions = true - return b -} - // WithToolsets specifies which toolsets should be enabled. // Special keywords: // - "all": enables all toolsets @@ -267,10 +261,6 @@ func (b *Builder) Build() (*Inventory, error) { } } - if b.generateInstructions { - r.instructions = generateInstructions(r) - } - return r, nil } diff --git a/pkg/inventory/instructions.go b/pkg/inventory/instructions.go deleted file mode 100644 index 02e90cd200..0000000000 --- a/pkg/inventory/instructions.go +++ /dev/null @@ -1,43 +0,0 @@ -package inventory - -import ( - "os" - "strings" -) - -// generateInstructions creates server instructions based on enabled toolsets -func generateInstructions(inv *Inventory) string { - // For testing - add a flag to disable instructions - if os.Getenv("DISABLE_INSTRUCTIONS") == "true" { - return "" // Baseline mode - } - - var instructions []string - - // Base instruction with context management - baseInstruction := `The GitHub MCP Server provides tools to interact with GitHub platform. - -Tool selection guidance: - 1. Use 'list_*' tools for broad, simple retrieval and pagination of all items of a type (e.g., all issues, all PRs, all branches) with basic filtering. - 2. Use 'search_*' tools for targeted queries with specific criteria, keywords, or complex filters (e.g., issues with certain text, PRs by author, code containing functions). - -Context management: - 1. Use pagination whenever possible with batches of 5-10 items. - 2. Use minimal_output parameter set to true if the full information is not needed to accomplish a task. - -Tool usage guidance: - 1. For 'search_*' tools: Use separate 'sort' and 'order' parameters if available for sorting results - do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'), not sorting instructions.` - - instructions = append(instructions, baseInstruction) - - // Collect instructions from each enabled toolset - for _, toolset := range inv.EnabledToolsets() { - if toolset.InstructionsFunc != nil { - if toolsetInstructions := toolset.InstructionsFunc(inv); toolsetInstructions != "" { - instructions = append(instructions, toolsetInstructions) - } - } - } - - return strings.Join(instructions, " ") -} diff --git a/pkg/inventory/instructions_test.go b/pkg/inventory/instructions_test.go deleted file mode 100644 index e8e369b3db..0000000000 --- a/pkg/inventory/instructions_test.go +++ /dev/null @@ -1,265 +0,0 @@ -package inventory - -import ( - "os" - "strings" - "testing" -) - -// createTestInventory creates an inventory with the specified toolsets for testing. -// All toolsets are enabled by default using WithToolsets([]string{"all"}). -func createTestInventory(toolsets []ToolsetMetadata) *Inventory { - // Create tools for each toolset so they show up in AvailableToolsets() - var tools []ServerTool - for _, ts := range toolsets { - tools = append(tools, ServerTool{ - Toolset: ts, - }) - } - - inv, _ := NewBuilder(). - SetTools(tools). - WithToolsets([]string{"all"}). - Build() - - return inv -} - -func TestGenerateInstructions(t *testing.T) { - tests := []struct { - name string - toolsets []ToolsetMetadata - expectedEmpty bool - }{ - { - name: "empty toolsets", - toolsets: []ToolsetMetadata{}, - expectedEmpty: false, // base instructions are always included - }, - { - name: "toolset with instructions", - toolsets: []ToolsetMetadata{ - { - ID: "test", - Description: "Test toolset", - InstructionsFunc: func(_ *Inventory) string { - return "Test instructions" - }, - }, - }, - expectedEmpty: false, - }, - { - name: "toolset without instructions", - toolsets: []ToolsetMetadata{ - { - ID: "test", - Description: "Test toolset", - }, - }, - expectedEmpty: false, // base instructions still included - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - inv := createTestInventory(tt.toolsets) - result := generateInstructions(inv) - - if tt.expectedEmpty { - if result != "" { - t.Errorf("Expected empty instructions but got: %s", result) - } - } else { - if result == "" { - t.Errorf("Expected non-empty instructions but got empty result") - } - } - }) - } -} - -func TestGenerateInstructionsWithDisableFlag(t *testing.T) { - tests := []struct { - name string - disableEnvValue string - expectedEmpty bool - }{ - { - name: "DISABLE_INSTRUCTIONS=true returns empty", - disableEnvValue: "true", - expectedEmpty: true, - }, - { - name: "DISABLE_INSTRUCTIONS=false returns normal instructions", - disableEnvValue: "false", - expectedEmpty: false, - }, - { - name: "DISABLE_INSTRUCTIONS unset returns normal instructions", - disableEnvValue: "", - expectedEmpty: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Save original env value - originalValue := os.Getenv("DISABLE_INSTRUCTIONS") - defer func() { - if originalValue == "" { - os.Unsetenv("DISABLE_INSTRUCTIONS") - } else { - os.Setenv("DISABLE_INSTRUCTIONS", originalValue) - } - }() - - // Set test env value - if tt.disableEnvValue == "" { - os.Unsetenv("DISABLE_INSTRUCTIONS") - } else { - os.Setenv("DISABLE_INSTRUCTIONS", tt.disableEnvValue) - } - - inv := createTestInventory([]ToolsetMetadata{ - {ID: "test", Description: "Test"}, - }) - result := generateInstructions(inv) - - if tt.expectedEmpty { - if result != "" { - t.Errorf("Expected empty instructions but got: %s", result) - } - } else { - if result == "" { - t.Errorf("Expected non-empty instructions but got empty result") - } - } - }) - } -} - -func TestToolsetInstructionsFunc(t *testing.T) { - tests := []struct { - name string - toolsets []ToolsetMetadata - expectedToContain string - notExpectedToContain string - }{ - { - name: "toolset with context-aware instructions includes extra text when dependency present", - toolsets: []ToolsetMetadata{ - {ID: "repos", Description: "Repos"}, - { - ID: "pull_requests", - Description: "PRs", - InstructionsFunc: func(inv *Inventory) string { - instructions := "PR base instructions" - if inv.HasToolset("repos") { - instructions += " PR template instructions" - } - return instructions - }, - }, - }, - expectedToContain: "PR template instructions", - }, - { - name: "toolset with context-aware instructions excludes extra text when dependency missing", - toolsets: []ToolsetMetadata{ - { - ID: "pull_requests", - Description: "PRs", - InstructionsFunc: func(inv *Inventory) string { - instructions := "PR base instructions" - if inv.HasToolset("repos") { - instructions += " PR template instructions" - } - return instructions - }, - }, - }, - notExpectedToContain: "PR template instructions", - }, - { - name: "toolset without InstructionsFunc returns no toolset-specific instructions", - toolsets: []ToolsetMetadata{ - {ID: "test", Description: "Test without instructions"}, - }, - notExpectedToContain: "## Test", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - inv := createTestInventory(tt.toolsets) - result := generateInstructions(inv) - - if tt.expectedToContain != "" && !strings.Contains(result, tt.expectedToContain) { - t.Errorf("Expected result to contain '%s', but it did not. Result: %s", tt.expectedToContain, result) - } - - if tt.notExpectedToContain != "" && strings.Contains(result, tt.notExpectedToContain) { - t.Errorf("Did not expect result to contain '%s', but it did. Result: %s", tt.notExpectedToContain, result) - } - }) - } -} - -// TestGenerateInstructionsOnlyEnabledToolsets verifies that generateInstructions -// only includes instructions from enabled toolsets, not all available toolsets. -// This is a regression test for https://github.com/github/github-mcp-server/issues/1897 -func TestGenerateInstructionsOnlyEnabledToolsets(t *testing.T) { - // Create tools for multiple toolsets - reposToolset := ToolsetMetadata{ - ID: "repos", - Description: "Repository tools", - InstructionsFunc: func(_ *Inventory) string { - return "REPOS_INSTRUCTIONS" - }, - } - issuesToolset := ToolsetMetadata{ - ID: "issues", - Description: "Issue tools", - InstructionsFunc: func(_ *Inventory) string { - return "ISSUES_INSTRUCTIONS" - }, - } - prsToolset := ToolsetMetadata{ - ID: "pull_requests", - Description: "PR tools", - InstructionsFunc: func(_ *Inventory) string { - return "PRS_INSTRUCTIONS" - }, - } - - tools := []ServerTool{ - {Toolset: reposToolset}, - {Toolset: issuesToolset}, - {Toolset: prsToolset}, - } - - // Build inventory with only "repos" toolset enabled - inv, err := NewBuilder(). - SetTools(tools). - WithToolsets([]string{"repos"}). - Build() - if err != nil { - t.Fatalf("Failed to build inventory: %v", err) - } - - result := generateInstructions(inv) - - // Should contain instructions from enabled toolset - if !strings.Contains(result, "REPOS_INSTRUCTIONS") { - t.Errorf("Expected instructions to contain 'REPOS_INSTRUCTIONS' for enabled toolset, but it did not. Result: %s", result) - } - - // Should NOT contain instructions from non-enabled toolsets - if strings.Contains(result, "ISSUES_INSTRUCTIONS") { - t.Errorf("Did not expect instructions to contain 'ISSUES_INSTRUCTIONS' for disabled toolset, but it did. Result: %s", result) - } - if strings.Contains(result, "PRS_INSTRUCTIONS") { - t.Errorf("Did not expect instructions to contain 'PRS_INSTRUCTIONS' for disabled toolset, but it did. Result: %s", result) - } -} diff --git a/pkg/inventory/registry.go b/pkg/inventory/registry.go index e2cd3a9e67..e4bccd0d11 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 - // server instructions hold high-level instructions for agents to use the server effectively - instructions string } // UnrecognizedToolsets returns toolset IDs that were passed to WithToolsets but don't @@ -316,7 +314,3 @@ func (r *Inventory) EnabledToolsets() []ToolsetMetadata { } return result } - -func (r *Inventory) Instructions() string { - return r.instructions -} diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 752a4c2bd0..095bedf2bf 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -31,9 +31,6 @@ type ToolsetMetadata struct { // Use the base name without size suffix, e.g., "repo" not "repo-16". // See https://primer.style/foundations/icons for available icons. Icon string - // InstructionsFunc optionally returns instructions for this toolset. - // It receives the inventory so it can check what other toolsets are enabled. - InstructionsFunc func(inv *Inventory) string } // Icons returns MCP Icon objects for this toolset, or nil if no icon is set. From d820b492a574455c9caf06fda3d3a49d0972cfb8 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 24 Apr 2026 10:50:04 +0200 Subject: [PATCH 2/7] refactor: use backtick tool references in skill body content Update all 16 skill definitions to use explicit backtick-formatted tool names with '## Available Tools' sections instead of bold markdown references. This makes tool names more reliably parseable by models that consume the SKILL.md content via load_skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/skill_resources.go | 196 +++++++++++++++++++++++----------- 1 file changed, 131 insertions(+), 65 deletions(-) diff --git a/pkg/github/skill_resources.go b/pkg/github/skill_resources.go index 5d9433bdf5..e348a43bb9 100644 --- a/pkg/github/skill_resources.go +++ b/pkg/github/skill_resources.go @@ -54,11 +54,12 @@ func skillContext() skillDefinition { }, body: `# GitHub Context -Always call **get_me** first to understand the current user's permissions and context. +Always call ` + "`get_me`" + ` first to understand the current user's permissions and context. -- **get_me** — returns the authenticated user's profile and permissions -- **get_teams** — lists teams the user belongs to -- **get_team_members** — lists members of a specific team +## Available Tools +- ` + "`get_me`" + ` — get the authenticated user's profile and permissions +- ` + "`get_teams`" + ` — list teams the user belongs to +- ` + "`get_team_members`" + ` — list members of a specific team `, } } @@ -90,16 +91,20 @@ func skillRepos() skillDefinition { Tools for managing GitHub repositories, browsing code, and working with branches, tags, and releases. -## Tool Selection -- Use **search_repositories** for finding repos by criteria and **search_code** for finding code containing specific patterns. -- Use **list_*** tools for broad retrieval with pagination (e.g., all branches, all commits). - -## Context Management -- Use pagination with batches of 5-10 items. -- Set **minimal_output** to true when full details are not needed. +## Available Tools +- ` + "`search_repositories`" + ` — find repos by name, topic, language, org +- ` + "`get_file_contents`" + ` — read files or directories from a repo +- ` + "`list_commits`" + ` — get commit history for a branch or path +- ` + "`search_code`" + ` — search for code patterns across repos +- ` + "`get_commit`" + ` — get details of a specific commit +- ` + "`list_branches`" + ` / ` + "`list_tags`" + ` — list branches or tags +- ` + "`get_tag`" + ` — get details of a specific tag +- ` + "`list_releases`" + ` / ` + "`get_latest_release`" + ` / ` + "`get_release_by_tag`" + ` — browse releases +- ` + "`create_or_update_file`" + ` / ` + "`push_files`" + ` / ` + "`delete_file`" + ` — modify files +- ` + "`create_repository`" + ` / ` + "`fork_repository`" + ` / ` + "`create_branch`" + ` — create repos or branches ## Sorting -For search tools, use separate **sort** and **order** parameters — do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'). +For search tools, use separate ` + "`sort`" + ` and ` + "`order`" + ` parameters — do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'). `, } } @@ -135,14 +140,32 @@ func skillIssues() skillDefinition { Tools for creating, reading, updating, and searching GitHub issues. -## Workflow -1. Check **list_issue_types** first for organizations to discover proper issue types. -2. Use **search_issues** before creating new issues to avoid duplicates. -3. Always set **state_reason** when closing issues. +## Available Tools +- ` + "`issue_read`" + ` — get details of a specific issue +- ` + "`search_issues`" + ` — search for issues with specific criteria or keywords +- ` + "`list_issues`" + ` — list issues with basic filtering and pagination +- ` + "`list_issue_types`" + ` — list available issue types for an organization +- ` + "`issue_write`" + ` — create or update an issue (composite tool) +- ` + "`add_issue_comment`" + ` — add a comment to an issue +- ` + "`sub_issue_write`" + ` — manage sub-issues (composite tool) +- ` + "`get_label`" + ` — get details of a specific label +- ` + "`create_issue`" + ` — create a new issue +- ` + "`update_issue_title`" + ` — update an issue's title +- ` + "`update_issue_body`" + ` — update an issue's body +- ` + "`update_issue_assignees`" + ` — update an issue's assignees +- ` + "`update_issue_labels`" + ` — update an issue's labels +- ` + "`update_issue_milestone`" + ` — update an issue's milestone +- ` + "`update_issue_type`" + ` — update an issue's type +- ` + "`update_issue_state`" + ` — update an issue's state (open/closed) +- ` + "`add_sub_issue`" + ` — add a sub-issue to a parent issue +- ` + "`remove_sub_issue`" + ` — remove a sub-issue from a parent +- ` + "`reprioritize_sub_issue`" + ` — change the priority of a sub-issue +- ` + "`set_issue_fields`" + ` — set custom project fields on an issue -## Tool Selection -- Use **search_issues** for targeted queries with specific criteria or keywords. -- Use **list_issues** for broad retrieval of all issues with basic filtering and pagination. +## Workflow +1. Call ` + "`list_issue_types`" + ` first for organizations to discover proper issue types. +2. Call ` + "`search_issues`" + ` before creating new issues to avoid duplicates. +3. Always set ` + "`state_reason`" + ` when closing issues. `, } } @@ -179,18 +202,37 @@ func skillPullRequests() skillDefinition { Tools for creating, reviewing, merging, and managing GitHub pull requests. +## Available Tools +- ` + "`pull_request_read`" + ` — get details of a specific pull request +- ` + "`list_pull_requests`" + ` — list pull requests with basic filtering and pagination +- ` + "`search_pull_requests`" + ` — search for pull requests with specific criteria +- ` + "`merge_pull_request`" + ` — merge a pull request +- ` + "`update_pull_request_branch`" + ` — update a PR branch with the base branch +- ` + "`create_pull_request`" + ` — create a new pull request +- ` + "`update_pull_request`" + ` — update a pull request (composite tool) +- ` + "`pull_request_review_write`" + ` — manage PR reviews (composite tool) +- ` + "`add_comment_to_pending_review`" + ` — add a line comment to a pending review +- ` + "`add_reply_to_pull_request_comment`" + ` — reply to a PR review comment +- ` + "`update_pull_request_title`" + ` — update a PR's title +- ` + "`update_pull_request_body`" + ` — update a PR's body +- ` + "`update_pull_request_state`" + ` — update a PR's state (open/closed) +- ` + "`update_pull_request_draft_state`" + ` — convert between draft and ready +- ` + "`request_pull_request_reviewers`" + ` — request reviewers for a PR +- ` + "`create_pull_request_review`" + ` — create a new PR review +- ` + "`submit_pending_pull_request_review`" + ` — submit a pending review +- ` + "`delete_pending_pull_request_review`" + ` — delete a pending review +- ` + "`add_pull_request_review_comment`" + ` — add a review comment to a PR +- ` + "`resolve_review_thread`" + ` — resolve a review thread +- ` + "`unresolve_review_thread`" + ` — unresolve a review thread + ## PR Review Workflow For complex reviews with line-specific comments: -1. Use **pull_request_review_write** with method 'create' to create a pending review. -2. Use **add_comment_to_pending_review** to add line comments. -3. Use **pull_request_review_write** with method 'submit_pending' to submit the review. +1. Call ` + "`create_pull_request_review`" + ` or ` + "`pull_request_review_write`" + ` with method 'create' to create a pending review. +2. Call ` + "`add_comment_to_pending_review`" + ` to add line comments. +3. Call ` + "`submit_pending_pull_request_review`" + ` or ` + "`pull_request_review_write`" + ` with method 'submit_pending' to submit. ## Creating Pull Requests Before creating a PR, search for pull request templates in the repository. Template files are called pull_request_template.md or located in the '.github/PULL_REQUEST_TEMPLATE' directory. Use the template content to structure the PR description. - -## Tool Selection -- Use **search_pull_requests** for targeted queries with specific criteria. -- Use **list_pull_requests** for broad retrieval with basic filtering and pagination. `, } } @@ -211,12 +253,15 @@ func skillCodeSecurity() skillDefinition { Tools for viewing security alerts across GitHub repositories. -## Alert Types -- **Code Scanning** — static analysis alerts (CodeQL, third-party tools) -- **Secret Scanning** — detected secrets and credentials in code -- **Dependabot** — vulnerable dependency alerts +## Available Tools +- ` + "`get_code_scanning_alert`" + ` — get details of a specific code scanning alert +- ` + "`list_code_scanning_alerts`" + ` — list code scanning alerts for a repo +- ` + "`get_secret_scanning_alert`" + ` — get details of a specific secret scanning alert +- ` + "`list_secret_scanning_alerts`" + ` — list secret scanning alerts for a repo +- ` + "`get_dependabot_alert`" + ` — get details of a specific Dependabot alert +- ` + "`list_dependabot_alerts`" + ` — list Dependabot alerts for a repo -Use **list_*** tools to get an overview of alerts, then **get_*** to inspect specific alerts in detail. +Use ` + "`list_*`" + ` tools to get an overview of alerts, then ` + "`get_*`" + ` to inspect specific alerts in detail. `, } } @@ -235,10 +280,11 @@ func skillActions() skillDefinition { Tools for interacting with GitHub Actions workflows and CI/CD operations. -- **actions_list** — list workflow runs for a repository -- **actions_get** — get details of a specific workflow run -- **actions_run_trigger** — trigger a workflow run -- **get_job_logs** — retrieve logs from a specific job +## Available Tools +- ` + "`actions_list`" + ` — list workflow runs for a repository +- ` + "`actions_get`" + ` — get details of a specific workflow run +- ` + "`actions_run_trigger`" + ` — trigger a workflow run +- ` + "`get_job_logs`" + ` — retrieve logs from a specific job `, } } @@ -257,7 +303,13 @@ func skillDiscussions() skillDefinition { Tools for browsing and reading GitHub Discussions. -Use **list_discussion_categories** to understand available categories before creating discussions. Filter by category for better organization. +## Available Tools +- ` + "`list_discussions`" + ` — list discussions in a repository +- ` + "`get_discussion`" + ` — get details of a specific discussion +- ` + "`get_discussion_comments`" + ` — get comments on a discussion +- ` + "`list_discussion_categories`" + ` — list available discussion categories + +Call ` + "`list_discussion_categories`" + ` to understand available categories before filtering discussions. `, } } @@ -275,11 +327,16 @@ func skillProjects() skillDefinition { Tools for managing GitHub Projects (v2). +## Available Tools +- ` + "`projects_list`" + ` — list projects for a user, org, or repo +- ` + "`projects_get`" + ` — get project details, fields, items, or status updates +- ` + "`projects_write`" + ` — create/update/delete project items, fields, or status updates + ## Workflow -1. Call **projects_list** to find projects. -2. Use **projects_get** with list_project_fields to understand available fields and get IDs/types. -3. Use **projects_get** with list_project_items (with pagination) to browse items. -4. Use **projects_write** for updates. +1. Call ` + "`projects_list`" + ` to find projects. +2. Call ` + "`projects_get`" + ` with list_project_fields to understand available fields and get IDs/types. +3. Call ` + "`projects_get`" + ` with list_project_items (with pagination) to browse items. +4. Call ` + "`projects_write`" + ` for updates. ## Status Updates Use list_project_status_updates to read recent project status updates (newest first). Use get_project_status_update with a node ID to get a single update. Use create_project_status_update to create a new status update. @@ -322,12 +379,13 @@ func skillNotifications() skillDefinition { Tools for viewing and managing GitHub notifications. -- **list_notifications** — list unread and read notifications -- **get_notification_details** — get details of a specific notification -- **dismiss_notification** — mark a notification as done -- **mark_all_notifications_read** — mark all notifications as read -- **manage_notification_subscription** — manage thread subscription settings -- **manage_repository_notification_subscription** — manage repository notification settings +## Available Tools +- ` + "`list_notifications`" + ` — list unread and read notifications +- ` + "`get_notification_details`" + ` — get details of a specific notification +- ` + "`dismiss_notification`" + ` — mark a notification as done +- ` + "`mark_all_notifications_read`" + ` — mark all notifications as read +- ` + "`manage_notification_subscription`" + ` — manage thread subscription settings +- ` + "`manage_repository_notification_subscription`" + ` — manage repository notification settings `, } } @@ -346,10 +404,11 @@ func skillGists() skillDefinition { Tools for managing GitHub Gists — lightweight code snippets and file sharing. -- **list_gists** — list gists for the authenticated user -- **get_gist** — retrieve a specific gist by ID -- **create_gist** — create a new gist (public or private) -- **update_gist** — update an existing gist's files or description +## Available Tools +- ` + "`list_gists`" + ` — list gists for the authenticated user +- ` + "`get_gist`" + ` — retrieve a specific gist by ID +- ` + "`create_gist`" + ` — create a new gist (public or private) +- ` + "`update_gist`" + ` — update an existing gist's files or description `, } } @@ -366,10 +425,11 @@ func skillUsersOrgs() skillDefinition { Tools for searching GitHub users and organizations. -- **search_users** — search for users by username, name, location, or other criteria -- **search_orgs** — search for organizations by name or other criteria +## Available Tools +- ` + "`search_users`" + ` — search for users by username, name, location, or other criteria +- ` + "`search_orgs`" + ` — search for organizations by name or other criteria -Use separate **sort** and **order** parameters for sorting results — do not include 'sort:' syntax in query strings. +For search tools, use separate ` + "`sort`" + ` and ` + "`order`" + ` parameters — do not include 'sort:' syntax in query strings. `, } } @@ -388,10 +448,11 @@ func skillSecurityAdvisories() skillDefinition { Tools for browsing security advisories on GitHub. -- **list_global_security_advisories** — search the GitHub Advisory Database -- **get_global_security_advisory** — get details of a specific global advisory -- **list_repository_security_advisories** — list advisories for a specific repository -- **list_org_repository_security_advisories** — list advisories across an organization's repositories +## Available Tools +- ` + "`list_global_security_advisories`" + ` — search the GitHub Advisory Database +- ` + "`get_global_security_advisory`" + ` — get details of a specific global advisory +- ` + "`list_repository_security_advisories`" + ` — list advisories for a specific repository +- ` + "`list_org_repository_security_advisories`" + ` — list advisories across an organization's repositories `, } } @@ -409,8 +470,10 @@ func skillLabels() skillDefinition { Tools for managing labels on GitHub repositories. -- **list_label** / **list_labels** — list labels for a repository -- **label_write** — create, update, or delete labels +## Available Tools +- ` + "`list_label`" + ` — get a specific label by name +- ` + "`list_labels`" + ` — list all labels for a repository +- ` + "`label_write`" + ` — create, update, or delete labels `, } } @@ -426,7 +489,8 @@ func skillGit() skillDefinition { Low-level Git operations via the GitHub API. -- **get_repository_tree** — retrieve the tree structure of a repository at a given ref, useful for understanding repository layout +## Available Tools +- ` + "`get_repository_tree`" + ` — retrieve the tree structure of a repository at a given ref, useful for understanding repository layout `, } } @@ -444,9 +508,10 @@ func skillStargazers() skillDefinition { Tools for managing repository stars. -- **list_starred_repositories** — list repositories starred by the authenticated user -- **star_repository** — star a repository -- **unstar_repository** — unstar a repository +## Available Tools +- ` + "`list_starred_repositories`" + ` — list repositories starred by the authenticated user +- ` + "`star_repository`" + ` — star a repository +- ` + "`unstar_repository`" + ` — unstar a repository `, } } @@ -463,8 +528,9 @@ func skillCopilot() skillDefinition { Tools for using GitHub Copilot in your workflow. -- **assign_copilot_to_issue** — assign Copilot as a collaborator on an issue -- **request_copilot_review** — request a Copilot review on a pull request +## Available Tools +- ` + "`assign_copilot_to_issue`" + ` — assign Copilot as a collaborator on an issue +- ` + "`request_copilot_review`" + ` — request a Copilot review on a pull request `, } } From d79de8c9869f06c10823c277d3f3467bca1c6923 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 24 Apr 2026 11:26:33 +0200 Subject: [PATCH 3/7] refactor: replace toolset-oriented skills with 27 workflow-oriented skills Replace 16 toolset-oriented skills (repos, issues, pull-requests, etc.) with 27 workflow-oriented skills that map to real user intents: PR workflows: create-pr, review-pr, self-review-pr, address-pr-feedback, merge-pr Issue workflows: triage-issues, create-issue, manage-sub-issues CI/CD: debug-ci, trigger-workflow Security: security-audit, fix-dependabot, research-vulnerability Code exploration: explore-repo, search-code, trace-history Projects: manage-project Notifications: handle-notifications Releases: prepare-release Repository management: manage-repo, manage-labels Collaboration: contribute-oss, browse-discussions, delegate-to-copilot Discovery: discover-github, share-snippet, get-context Each skill focuses on a specific workflow with non-obvious guidance, anti-patterns, and identity-aware instructions (e.g., reviewing someone else's PR vs self-reviewing your own). Tools overlap across skills based on workflow needs rather than API domain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/skill_resources.go | 707 ++++++++++++++--------------- pkg/github/skill_resources_test.go | 2 +- 2 files changed, 332 insertions(+), 377 deletions(-) diff --git a/pkg/github/skill_resources.go b/pkg/github/skill_resources.go index e348a43bb9..48b213dce3 100644 --- a/pkg/github/skill_resources.go +++ b/pkg/github/skill_resources.go @@ -21,352 +21,339 @@ type skillDefinition struct { } // allSkills returns all skill definitions for the GitHub MCP Server. -// Each skill covers a domain of tools and provides guidance for using them. +// Each skill maps to a user workflow and provides targeted guidance. func allSkills() []skillDefinition { return []skillDefinition{ - skillContext(), - skillRepos(), - skillIssues(), - skillPullRequests(), - skillCodeSecurity(), - skillActions(), - skillDiscussions(), - skillProjects(), - skillNotifications(), - skillGists(), - skillUsersOrgs(), - skillSecurityAdvisories(), - skillLabels(), - skillGit(), - skillStargazers(), - skillCopilot(), + skillGetContext(), + skillExploreRepo(), + skillSearchCode(), + skillTraceHistory(), + skillCreatePR(), + skillReviewPR(), + skillSelfReviewPR(), + skillAddressPRFeedback(), + skillMergePR(), + skillTriageIssues(), + skillCreateIssue(), + skillManageSubIssues(), + skillDebugCI(), + skillTriggerWorkflow(), + skillSecurityAudit(), + skillFixDependabot(), + skillResearchVulnerability(), + skillManageProject(), + skillHandleNotifications(), + skillPrepareRelease(), + skillManageRepo(), + skillManageLabels(), + skillContributeOSS(), + skillBrowseDiscussions(), + skillDelegateCopilot(), + skillDiscoverGitHub(), + skillShareSnippet(), } } -func skillContext() skillDefinition { +func skillGetContext() skillDefinition { return skillDefinition{ - name: "context", - description: "Understand the current user and their GitHub context", + name: "get-context", + description: "Understand the current user, their permissions, and team membership", allowedTools: []string{ "get_me", "get_teams", "get_team_members", }, - body: `# GitHub Context - -Always call ` + "`get_me`" + ` first to understand the current user's permissions and context. - -## Available Tools -- ` + "`get_me`" + ` — get the authenticated user's profile and permissions -- ` + "`get_teams`" + ` — list teams the user belongs to -- ` + "`get_team_members`" + ` — list members of a specific team -`, + body: "# Get Context\n\nAlways call `get_me` first to establish who you are and what you can access.\n\n## Available Tools\n- `get_me` — your authenticated profile and permissions\n- `get_teams` — teams you belong to\n- `get_team_members` — members of a specific team\n", } } -func skillRepos() skillDefinition { +func skillExploreRepo() skillDefinition { return skillDefinition{ - name: "repos", - description: "Manage GitHub repositories, branches, files, releases, and code search", + name: "explore-repo", + description: "Understand an unfamiliar codebase quickly", allowedTools: []string{ - "search_repositories", + "get_repository_tree", "get_file_contents", - "list_commits", "search_code", - "get_commit", + "list_commits", "list_branches", "list_tags", - "get_tag", - "list_releases", - "get_latest_release", - "get_release_by_tag", - "create_or_update_file", - "create_repository", - "fork_repository", - "create_branch", - "push_files", - "delete_file", }, - body: `# GitHub Repositories - -Tools for managing GitHub repositories, browsing code, and working with branches, tags, and releases. - -## Available Tools -- ` + "`search_repositories`" + ` — find repos by name, topic, language, org -- ` + "`get_file_contents`" + ` — read files or directories from a repo -- ` + "`list_commits`" + ` — get commit history for a branch or path -- ` + "`search_code`" + ` — search for code patterns across repos -- ` + "`get_commit`" + ` — get details of a specific commit -- ` + "`list_branches`" + ` / ` + "`list_tags`" + ` — list branches or tags -- ` + "`get_tag`" + ` — get details of a specific tag -- ` + "`list_releases`" + ` / ` + "`get_latest_release`" + ` / ` + "`get_release_by_tag`" + ` — browse releases -- ` + "`create_or_update_file`" + ` / ` + "`push_files`" + ` / ` + "`delete_file`" + ` — modify files -- ` + "`create_repository`" + ` / ` + "`fork_repository`" + ` / ` + "`create_branch`" + ` — create repos or branches - -## Sorting -For search tools, use separate ` + "`sort`" + ` and ` + "`order`" + ` parameters — do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'). -`, + body: "# Explore Repository\n\nUnderstand a new codebase systematically without reading every file.\n\n## Available Tools\n- `get_repository_tree` — full directory tree at any ref\n- `get_file_contents` — read files and directories\n- `search_code` — find patterns across the codebase\n- `list_commits` — recent commit history\n- `list_branches` / `list_tags` — branches and tags\n\n## Workflow\n1. `get_repository_tree` at root for structure overview.\n2. Read README.md, CONTRIBUTING.md, and build/config files.\n3. `list_commits` on main branch to find actively-changing areas.\n4. `search_code` for imports and entry points to understand architecture.\n\nStart with structure, then drill into active areas. Don't read every file.\n", } } -func skillIssues() skillDefinition { +func skillSearchCode() skillDefinition { return skillDefinition{ - name: "issues", - description: "Create, read, update, and search GitHub issues with sub-issues and types", + name: "search-code", + description: "Find code patterns, symbols, and examples across GitHub", allowedTools: []string{ - "issue_read", - "search_issues", - "list_issues", - "list_issue_types", - "issue_write", - "add_issue_comment", - "sub_issue_write", - "get_label", - // Granular tools (feature-flagged alternatives to issue_write/sub_issue_write) - "create_issue", - "update_issue_title", - "update_issue_body", - "update_issue_assignees", - "update_issue_labels", - "update_issue_milestone", - "update_issue_type", - "update_issue_state", - "add_sub_issue", - "remove_sub_issue", - "reprioritize_sub_issue", - "set_issue_fields", + "search_code", + "search_repositories", + "get_file_contents", }, - body: `# GitHub Issues - -Tools for creating, reading, updating, and searching GitHub issues. - -## Available Tools -- ` + "`issue_read`" + ` — get details of a specific issue -- ` + "`search_issues`" + ` — search for issues with specific criteria or keywords -- ` + "`list_issues`" + ` — list issues with basic filtering and pagination -- ` + "`list_issue_types`" + ` — list available issue types for an organization -- ` + "`issue_write`" + ` — create or update an issue (composite tool) -- ` + "`add_issue_comment`" + ` — add a comment to an issue -- ` + "`sub_issue_write`" + ` — manage sub-issues (composite tool) -- ` + "`get_label`" + ` — get details of a specific label -- ` + "`create_issue`" + ` — create a new issue -- ` + "`update_issue_title`" + ` — update an issue's title -- ` + "`update_issue_body`" + ` — update an issue's body -- ` + "`update_issue_assignees`" + ` — update an issue's assignees -- ` + "`update_issue_labels`" + ` — update an issue's labels -- ` + "`update_issue_milestone`" + ` — update an issue's milestone -- ` + "`update_issue_type`" + ` — update an issue's type -- ` + "`update_issue_state`" + ` — update an issue's state (open/closed) -- ` + "`add_sub_issue`" + ` — add a sub-issue to a parent issue -- ` + "`remove_sub_issue`" + ` — remove a sub-issue from a parent -- ` + "`reprioritize_sub_issue`" + ` — change the priority of a sub-issue -- ` + "`set_issue_fields`" + ` — set custom project fields on an issue - -## Workflow -1. Call ` + "`list_issue_types`" + ` first for organizations to discover proper issue types. -2. Call ` + "`search_issues`" + ` before creating new issues to avoid duplicates. -3. Always set ` + "`state_reason`" + ` when closing issues. -`, + body: "# Search Code\n\nFind specific code patterns across GitHub repositories.\n\n## Available Tools\n- `search_code` — search code with language:, org:, path: qualifiers\n- `search_repositories` — find repos by name, topic, language\n- `get_file_contents` — read full file context around matches\n\n## Query Tips\n- Use qualifiers in query: `language:go`, `org:github`, `path:src/`.\n- Do NOT put `sort:` in the query string — use the separate `sort` parameter.\n- After finding matches, read the full file with `get_file_contents` for context.\n", } } -func skillPullRequests() skillDefinition { +func skillTraceHistory() skillDefinition { return skillDefinition{ - name: "pull-requests", - description: "Create, review, merge, and manage GitHub pull requests", + name: "trace-history", + description: "Understand why code changed by tracing commits and PRs", allowedTools: []string{ + "list_commits", + "get_commit", + "search_pull_requests", "pull_request_read", + }, + body: "# Trace Code History\n\nUnderstand why code changed by following the commit to PR to discussion chain.\n\n## Available Tools\n- `list_commits` — commit history, filterable by path\n- `get_commit` — full commit details and diff\n- `search_pull_requests` — find PRs by commit SHA or keywords\n- `pull_request_read` — read PR description and review discussion\n\n## Workflow\n1. `list_commits` with path filter to find relevant commits.\n2. `get_commit` to see what changed.\n3. `search_pull_requests` to find the PR (search by commit SHA or title keywords).\n4. `pull_request_read` for the PR description and review comments — this has the *why*.\n\nCommit messages say *what*. PR descriptions say *why*. Review comments say *what was considered*.\n", + } +} + +func skillCreatePR() skillDefinition { + return skillDefinition{ + name: "create-pr", + description: "Create a well-structured pull request that reviews smoothly", + allowedTools: []string{ + "create_pull_request", + "get_file_contents", + "create_branch", + "push_files", + "request_pull_request_reviewers", "list_pull_requests", "search_pull_requests", - "merge_pull_request", - "update_pull_request_branch", - "create_pull_request", - "update_pull_request", + }, + body: "# Create Pull Request\n\nCreate a PR that communicates intent clearly and reviews smoothly.\n\n## Available Tools\n- `create_pull_request` — create the PR\n- `get_file_contents` — read PR templates from repo\n- `create_branch` — create a feature branch\n- `push_files` — push multiple files in one commit\n- `request_pull_request_reviewers` — request reviewers\n- `list_pull_requests` / `search_pull_requests` — check for existing PRs\n\n## Workflow\n1. Look for PR template in `.github/`, `docs/`, or root (`pull_request_template.md`).\n2. Check for existing PRs on the same branch with `list_pull_requests`.\n3. Create PR with template-structured description.\n4. Link issues using \"Closes #N\" or \"Fixes #N\" in the body.\n5. Request reviewers who know the affected code areas.\n\nNever create a PR without a description. Use the template if one exists.\n", + } +} + +func skillReviewPR() skillDefinition { + return skillDefinition{ + name: "review-pr", + description: "Conduct a thorough code review of a pull request", + allowedTools: []string{ + "pull_request_read", + "get_file_contents", + "search_code", "pull_request_review_write", - "add_comment_to_pending_review", - "add_reply_to_pull_request_comment", - // Granular tools (feature-flagged alternatives to update_pull_request/pull_request_review_write) - "update_pull_request_title", - "update_pull_request_body", - "update_pull_request_state", - "update_pull_request_draft_state", - "request_pull_request_reviewers", "create_pull_request_review", + "add_pull_request_review_comment", + "add_comment_to_pending_review", "submit_pending_pull_request_review", "delete_pending_pull_request_review", - "add_pull_request_review_comment", + "add_reply_to_pull_request_comment", "resolve_review_thread", "unresolve_review_thread", }, - body: `# GitHub Pull Requests - -Tools for creating, reviewing, merging, and managing GitHub pull requests. - -## Available Tools -- ` + "`pull_request_read`" + ` — get details of a specific pull request -- ` + "`list_pull_requests`" + ` — list pull requests with basic filtering and pagination -- ` + "`search_pull_requests`" + ` — search for pull requests with specific criteria -- ` + "`merge_pull_request`" + ` — merge a pull request -- ` + "`update_pull_request_branch`" + ` — update a PR branch with the base branch -- ` + "`create_pull_request`" + ` — create a new pull request -- ` + "`update_pull_request`" + ` — update a pull request (composite tool) -- ` + "`pull_request_review_write`" + ` — manage PR reviews (composite tool) -- ` + "`add_comment_to_pending_review`" + ` — add a line comment to a pending review -- ` + "`add_reply_to_pull_request_comment`" + ` — reply to a PR review comment -- ` + "`update_pull_request_title`" + ` — update a PR's title -- ` + "`update_pull_request_body`" + ` — update a PR's body -- ` + "`update_pull_request_state`" + ` — update a PR's state (open/closed) -- ` + "`update_pull_request_draft_state`" + ` — convert between draft and ready -- ` + "`request_pull_request_reviewers`" + ` — request reviewers for a PR -- ` + "`create_pull_request_review`" + ` — create a new PR review -- ` + "`submit_pending_pull_request_review`" + ` — submit a pending review -- ` + "`delete_pending_pull_request_review`" + ` — delete a pending review -- ` + "`add_pull_request_review_comment`" + ` — add a review comment to a PR -- ` + "`resolve_review_thread`" + ` — resolve a review thread -- ` + "`unresolve_review_thread`" + ` — unresolve a review thread - -## PR Review Workflow -For complex reviews with line-specific comments: -1. Call ` + "`create_pull_request_review`" + ` or ` + "`pull_request_review_write`" + ` with method 'create' to create a pending review. -2. Call ` + "`add_comment_to_pending_review`" + ` to add line comments. -3. Call ` + "`submit_pending_pull_request_review`" + ` or ` + "`pull_request_review_write`" + ` with method 'submit_pending' to submit. - -## Creating Pull Requests -Before creating a PR, search for pull request templates in the repository. Template files are called pull_request_template.md or located in the '.github/PULL_REQUEST_TEMPLATE' directory. Use the template content to structure the PR description. -`, + body: "# Review Pull Request\n\nYou are reviewing someone else's PR. Be thorough, constructive, and decisive.\n\n## Available Tools\n- `pull_request_read` — get diff, files, status, review comments, check runs\n- `get_file_contents` / `search_code` — read context beyond the diff\n- `create_pull_request_review` — start a pending review\n- `add_pull_request_review_comment` / `add_comment_to_pending_review` — add line comments\n- `submit_pending_pull_request_review` — submit with verdict\n- `delete_pending_pull_request_review` — discard pending review\n- `add_reply_to_pull_request_comment` — reply to existing comments\n- `resolve_review_thread` / `unresolve_review_thread` — manage threads\n\n## Workflow\n1. Read PR description and linked issues to understand intent.\n2. Check CI status with `pull_request_read` (method: get_status).\n3. Read the full diff with `pull_request_read` (method: get_diff).\n4. Create a pending review, add all comments, then submit once with a verdict.\n5. Always submit with approve, request_changes, or comment — don't leave orphan comments.\n\n## Anti-Patterns\n- Don't approve with failing CI.\n- Don't leave comments without submitting a review — pending reviews are invisible to the author.\n- Don't resolve threads you didn't start — that's the author's responsibility.\n- Read ALL changed files before commenting — your concern may be addressed elsewhere in the diff.\n", } } -func skillCodeSecurity() skillDefinition { +func skillSelfReviewPR() skillDefinition { return skillDefinition{ - name: "code-security", - description: "View code scanning alerts, secret scanning alerts, and Dependabot alerts", + name: "self-review-pr", + description: "Review your own PR before requesting team review", allowedTools: []string{ - "get_code_scanning_alert", - "list_code_scanning_alerts", - "get_secret_scanning_alert", - "list_secret_scanning_alerts", - "get_dependabot_alert", - "list_dependabot_alerts", + "pull_request_read", + "get_file_contents", + "search_code", + "actions_get", + "get_job_logs", + "update_pull_request", + "update_pull_request_body", + "update_pull_request_title", + "request_pull_request_reviewers", }, - body: `# Code Security + body: "# Self-Review PR\n\nReview your own PR before asking others. Catch what you can so reviewers focus on what matters.\n\n## Available Tools\n- `pull_request_read` — read your diff, CI status, and files\n- `get_file_contents` — check PR template compliance\n- `search_code` — verify changes match codebase patterns\n- `actions_get` / `get_job_logs` — investigate CI failures\n- `update_pull_request` / `update_pull_request_body` / `update_pull_request_title` — fix PR metadata\n- `request_pull_request_reviewers` — request reviewers when ready\n\n## Checklist\n1. Read your own diff — look for debug code, TODOs, unintended changes.\n2. Check CI passes — if failing, fix before requesting review.\n3. Verify description links relevant issues and follows the PR template.\n4. Verify title follows repo conventions (conventional commits, etc.).\n5. Request reviewers who own the affected code.\n\nDon't request review with failing CI. Reviewers notice when you haven't self-reviewed.\n", + } +} -Tools for viewing security alerts across GitHub repositories. +func skillAddressPRFeedback() skillDefinition { + return skillDefinition{ + name: "address-pr-feedback", + description: "Handle review comments on your PR and push fixes", + allowedTools: []string{ + "pull_request_read", + "add_reply_to_pull_request_comment", + "resolve_review_thread", + "push_files", + "create_or_update_file", + "update_pull_request_branch", + "request_pull_request_reviewers", + }, + body: "# Address PR Feedback\n\nYou received review feedback. Address it systematically, not piecemeal.\n\n## Available Tools\n- `pull_request_read` — read all review comments and threads\n- `add_reply_to_pull_request_comment` — respond to reviewer comments\n- `resolve_review_thread` — mark threads as resolved\n- `push_files` / `create_or_update_file` — push fixes\n- `update_pull_request_branch` — rebase/merge with base branch\n- `request_pull_request_reviewers` — re-request review after addressing\n\n## Workflow\n1. Read ALL comments before responding — comments may be related.\n2. Group related feedback and address together in one commit.\n3. Reply to each comment explaining what you changed (or why you disagree).\n4. Resolve threads only after addressing the concern — not before.\n5. Push fixes, then re-request review.\n\nDon't resolve threads without responding. Don't push fixes without explaining them in the thread.\n", + } +} -## Available Tools -- ` + "`get_code_scanning_alert`" + ` — get details of a specific code scanning alert -- ` + "`list_code_scanning_alerts`" + ` — list code scanning alerts for a repo -- ` + "`get_secret_scanning_alert`" + ` — get details of a specific secret scanning alert -- ` + "`list_secret_scanning_alerts`" + ` — list secret scanning alerts for a repo -- ` + "`get_dependabot_alert`" + ` — get details of a specific Dependabot alert -- ` + "`list_dependabot_alerts`" + ` — list Dependabot alerts for a repo +func skillMergePR() skillDefinition { + return skillDefinition{ + name: "merge-pr", + description: "Get a PR to merge-ready state and merge it", + allowedTools: []string{ + "pull_request_read", + "merge_pull_request", + "update_pull_request_branch", + "update_pull_request_state", + "update_pull_request_draft_state", + "actions_get", + }, + body: "# Merge Pull Request\n\nVerify a PR is ready and merge it.\n\n## Available Tools\n- `pull_request_read` — check status, reviews, and CI\n- `merge_pull_request` — merge the PR\n- `update_pull_request_branch` — update branch if behind base\n- `update_pull_request_draft_state` — convert draft to ready\n- `actions_get` — check workflow run details\n\n## Pre-Merge Checklist\n1. CI: all checks must pass (use `pull_request_read` with get_status).\n2. Reviews: required approvals present, no outstanding changes_requested.\n3. Branch: if behind base, call `update_pull_request_branch`.\n4. Draft: convert to ready with `update_pull_request_draft_state` if needed.\n5. Merge method: match repo conventions (merge, squash, or rebase).\n\nNever merge with failing checks. Never merge draft PRs without converting first.\n", + } +} -Use ` + "`list_*`" + ` tools to get an overview of alerts, then ` + "`get_*`" + ` to inspect specific alerts in detail. -`, +func skillTriageIssues() skillDefinition { + return skillDefinition{ + name: "triage-issues", + description: "Categorize, deduplicate, and prioritize incoming issues", + allowedTools: []string{ + "list_issues", + "search_issues", + "issue_read", + "list_issue_types", + "issue_write", + "update_issue_labels", + "update_issue_type", + "update_issue_milestone", + "update_issue_state", + "update_issue_title", + "update_issue_body", + "update_issue_assignees", + "add_issue_comment", + "set_issue_fields", + "list_labels", + "get_label", + }, + body: "# Triage Issues\n\nSystematically process incoming issues: categorize, deduplicate, and prioritize.\n\n## Available Tools\n- `list_issues` / `search_issues` / `issue_read` — find and read issues\n- `list_issue_types` — discover org issue types\n- `update_issue_labels` / `update_issue_type` / `update_issue_milestone` — categorize\n- `update_issue_state` — close duplicates or invalid issues\n- `add_issue_comment` — ask for info or note triage decisions\n- `list_labels` / `get_label` — check available labels\n\n## Workflow\n1. `list_issue_types` to understand the org's issue taxonomy.\n2. For each new issue:\n a. `search_issues` for duplicates before doing anything else.\n b. Apply labels for type (bug, feature, docs) and priority.\n c. Set issue type if the org uses typed issues.\n d. Assign to milestone if applicable.\n e. Close duplicates with state_reason not_planned and link to the original.\n3. Comment on issues that need more info from the reporter.\n\nAlways set state_reason when closing: completed or not_planned. Never close without a reason.\n", } } -func skillActions() skillDefinition { +func skillCreateIssue() skillDefinition { return skillDefinition{ - name: "actions", - description: "View and trigger GitHub Actions workflows, runs, and job logs", + name: "create-issue", + description: "Create well-structured, searchable, actionable issues", allowedTools: []string{ - "actions_list", - "actions_get", - "actions_run_trigger", - "get_job_logs", + "create_issue", + "search_issues", + "list_issue_types", + "get_file_contents", + "list_labels", }, - body: `# GitHub Actions + body: "# Create Issue\n\nCreate issues that are easy to find, understand, and act on.\n\n## Available Tools\n- `create_issue` — create the issue\n- `search_issues` — check for duplicates first\n- `list_issue_types` — discover available issue types\n- `get_file_contents` — read issue templates in .github/ISSUE_TEMPLATE/\n- `list_labels` — see available labels\n\n## Workflow\n1. Search for existing issues to avoid duplicates.\n2. Check .github/ISSUE_TEMPLATE/ for templates and use them.\n3. `list_issue_types` if the org supports typed issues.\n4. Create with appropriate type, labels, and milestone.\n\nWrite actionable titles: \"Fix X when Y\" not \"X is broken\". Include reproduction steps for bugs.\n", + } +} -Tools for interacting with GitHub Actions workflows and CI/CD operations. +func skillManageSubIssues() skillDefinition { + return skillDefinition{ + name: "manage-sub-issues", + description: "Break down large issues into trackable sub-tasks", + allowedTools: []string{ + "issue_read", + "create_issue", + "sub_issue_write", + "add_sub_issue", + "remove_sub_issue", + "reprioritize_sub_issue", + "search_issues", + }, + body: "# Manage Sub-Issues\n\nBreak down epics and large issues into small, trackable sub-tasks.\n\n## Available Tools\n- `issue_read` — read parent issue details\n- `create_issue` — create sub-issue\n- `add_sub_issue` — link sub-issue to parent\n- `remove_sub_issue` — unlink a sub-issue\n- `reprioritize_sub_issue` — reorder sub-issues by priority\n- `search_issues` — find related issues\n\n## Workflow\n1. Read the parent issue to understand full scope.\n2. Break into small, independently completable pieces — each should map to one PR.\n3. `add_sub_issue` to link each to the parent.\n4. `reprioritize_sub_issue` to order by dependency (do X before Y).\n\nKeep parent issue description updated as the breakdown evolves.\n", + } +} -## Available Tools -- ` + "`actions_list`" + ` — list workflow runs for a repository -- ` + "`actions_get`" + ` — get details of a specific workflow run -- ` + "`actions_run_trigger`" + ` — trigger a workflow run -- ` + "`get_job_logs`" + ` — retrieve logs from a specific job -`, +func skillDebugCI() skillDefinition { + return skillDefinition{ + name: "debug-ci", + description: "Investigate and fix failing GitHub Actions workflows", + allowedTools: []string{ + "actions_get", + "get_job_logs", + "actions_list", + "get_file_contents", + "pull_request_read", + }, + body: "# Debug CI Failure\n\nInvestigate failing GitHub Actions systematically.\n\n## Available Tools\n- `actions_get` — workflow run details, job list (use get_workflow_run, list_workflow_jobs)\n- `get_job_logs` — logs from a specific failed job\n- `actions_list` — list recent runs for comparison\n- `get_file_contents` — read workflow YAML definitions\n- `pull_request_read` — check PR-linked CI status\n\n## Workflow\n1. `actions_get` with get_workflow_run for the failed run.\n2. `actions_get` with list_workflow_jobs to find which jobs failed.\n3. `get_job_logs` for EACH failed job — don't stop at the first one.\n4. Read the workflow file in .github/workflows/ to understand the pipeline.\n5. Compare with recent passing runs via `actions_list` to spot what changed.\n\n## Anti-Patterns\n- Don't just rerun without reading logs — flaky tests need fixes, not retries.\n- Don't read only the first failure — later jobs may reveal the root cause.\n- Check if the failure is in workflow config vs application code.\n", } } -func skillDiscussions() skillDefinition { +func skillTriggerWorkflow() skillDefinition { return skillDefinition{ - name: "discussions", - description: "Browse and read GitHub Discussions and their categories", + name: "trigger-workflow", + description: "Run, rerun, or cancel GitHub Actions workflow runs", allowedTools: []string{ - "list_discussions", - "get_discussion", - "get_discussion_comments", - "list_discussion_categories", + "actions_run_trigger", + "actions_get", + "actions_list", + "get_job_logs", }, - body: `# GitHub Discussions + body: "# Trigger Workflow\n\nRun, rerun, or cancel GitHub Actions workflows.\n\n## Available Tools\n- `actions_run_trigger` — run_workflow, rerun_workflow_run, rerun_failed_jobs, cancel_workflow_run\n- `actions_get` — list_workflows, get_workflow details\n- `actions_list` — list recent runs\n- `get_job_logs` — check results after run completes\n\n## Tips\n- Use rerun_failed_jobs instead of full rerun when only some jobs failed — faster.\n- Check workflow definition for required inputs before triggering with run_workflow.\n- Use cancel_workflow_run for stuck or unnecessary in-progress runs.\n", + } +} -Tools for browsing and reading GitHub Discussions. +func skillSecurityAudit() skillDefinition { + return skillDefinition{ + name: "security-audit", + description: "Systematically review code scanning, secret, and dependency alerts", + allowedTools: []string{ + "list_code_scanning_alerts", + "get_code_scanning_alert", + "list_secret_scanning_alerts", + "get_secret_scanning_alert", + "list_dependabot_alerts", + "get_dependabot_alert", + "get_file_contents", + "search_code", + }, + body: "# Security Audit\n\nSystematically review all security alerts across a repository.\n\n## Available Tools\n- `list_code_scanning_alerts` / `get_code_scanning_alert` — static analysis findings\n- `list_secret_scanning_alerts` / `get_secret_scanning_alert` — exposed credentials\n- `list_dependabot_alerts` / `get_dependabot_alert` — vulnerable dependencies\n- `get_file_contents` / `search_code` — review code around alerts\n\n## Triage Order\n1. Secret scanning first — exposed credentials need immediate rotation.\n2. Code scanning — static analysis alerts, prioritize critical/high severity.\n3. Dependabot — vulnerable dependencies, prioritize by CVSS score.\n\nFor each alert: read full details, review the affected code, check if the same pattern exists elsewhere with `search_code`.\n\nDon't dismiss alerts without understanding them. Check if previously-dismissed alerts were properly triaged.\n", + } +} -## Available Tools -- ` + "`list_discussions`" + ` — list discussions in a repository -- ` + "`get_discussion`" + ` — get details of a specific discussion -- ` + "`get_discussion_comments`" + ` — get comments on a discussion -- ` + "`list_discussion_categories`" + ` — list available discussion categories +func skillFixDependabot() skillDefinition { + return skillDefinition{ + name: "fix-dependabot", + description: "Handle vulnerable dependency alerts and update PRs", + allowedTools: []string{ + "list_dependabot_alerts", + "get_dependabot_alert", + "search_pull_requests", + "list_pull_requests", + "get_file_contents", + }, + body: "# Fix Dependabot Alerts\n\nHandle vulnerable dependency alerts systematically.\n\n## Available Tools\n- `list_dependabot_alerts` / `get_dependabot_alert` — list and inspect alerts\n- `search_pull_requests` / `list_pull_requests` — find existing Dependabot PRs\n- `get_file_contents` — read dependency files\n\n## Workflow\n1. List alerts sorted by severity — fix critical/high first.\n2. Check if Dependabot already opened a PR for each alert.\n3. For alerts with PRs: review the PR and merge if CI passes.\n4. For alerts without PRs: check if the fix requires a major version bump.\n5. Group related dependency updates into logical batches.\n\nCheck the alert's fixed_in version to understand the required update scope before acting.\n", + } +} -Call ` + "`list_discussion_categories`" + ` to understand available categories before filtering discussions. -`, +func skillResearchVulnerability() skillDefinition { + return skillDefinition{ + name: "research-vulnerability", + description: "Query the GitHub Advisory Database for security advisories", + allowedTools: []string{ + "list_global_security_advisories", + "get_global_security_advisory", + "list_repository_security_advisories", + "list_org_repository_security_advisories", + }, + body: "# Research Vulnerability\n\nQuery the GitHub Advisory Database for known vulnerabilities.\n\n## Available Tools\n- `list_global_security_advisories` — search the GitHub Advisory Database\n- `get_global_security_advisory` — get advisory details by GHSA ID\n- `list_repository_security_advisories` — advisories for a specific repo\n- `list_org_repository_security_advisories` — advisories across an org\n\nUse GHSA IDs (e.g., GHSA-xxxx-xxxx-xxxx) for specific lookups. Filter by ecosystem (npm, pip, go) and severity.\n", } } -func skillProjects() skillDefinition { +func skillManageProject() skillDefinition { return skillDefinition{ - name: "projects", - description: "Manage GitHub Projects (v2) — list items, update fields, and track status", + name: "manage-project", + description: "Track and update work items in GitHub Projects (v2)", allowedTools: []string{ "projects_list", "projects_get", "projects_write", + "search_issues", + "search_pull_requests", }, - body: `# GitHub Projects - -Tools for managing GitHub Projects (v2). - -## Available Tools -- ` + "`projects_list`" + ` — list projects for a user, org, or repo -- ` + "`projects_get`" + ` — get project details, fields, items, or status updates -- ` + "`projects_write`" + ` — create/update/delete project items, fields, or status updates - -## Workflow -1. Call ` + "`projects_list`" + ` to find projects. -2. Call ` + "`projects_get`" + ` with list_project_fields to understand available fields and get IDs/types. -3. Call ` + "`projects_get`" + ` with list_project_items (with pagination) to browse items. -4. Call ` + "`projects_write`" + ` for updates. - -## Status Updates -Use list_project_status_updates to read recent project status updates (newest first). Use get_project_status_update with a node ID to get a single update. Use create_project_status_update to create a new status update. - -## Field Usage -- Call list_project_fields first to understand available fields and get IDs/types before filtering. -- Use EXACT returned field names (case-insensitive match). Don't invent names or IDs. -- Only include filters for fields that exist and are relevant. - -## Pagination -- Loop while pageInfo.hasNextPage=true using after=pageInfo.nextCursor. -- Keep query, fields, per_page IDENTICAL on every page. - -## Query Syntax for list_project_items -- AND: space-separated (label:bug priority:high) -- OR: comma inside one qualifier (label:bug,critical) -- NOT: leading '-' (-label:wontfix) -- Ranges: points:1..3, updated:<@today-30d -- Wildcards: title:*crash*, label:bug* -- Type filters: is:issue, is:pr -- State: state:open, state:closed, state:merged -- Assignment: assignee:@me, assignee:username -`, + body: "# Manage Project Board\n\nTrack and update work items in GitHub Projects (v2).\n\n## Available Tools\n- `projects_list` — find projects for a user, org, or repo\n- `projects_get` — get project details, fields, items, status updates\n- `projects_write` — update project items, fields, and status\n- `search_issues` / `search_pull_requests` — find items to add\n\n## Workflow\n1. `projects_list` to find the project.\n2. `projects_get` with list_project_fields to understand field names, IDs, and types.\n3. `projects_get` with list_project_items to browse current items.\n4. `projects_write` to update fields, add items, or post status updates.\n\n## Critical Rules\n- Always call list_project_fields first — use EXACT field names (case-insensitive). Never guess field IDs.\n- Paginate: loop while pageInfo.hasNextPage=true using after=pageInfo.nextCursor.\n- Keep query, fields, and per_page identical across pages.\n\n## Query Syntax for list_project_items\n- AND: space-separated (label:bug priority:high)\n- OR: comma inside qualifier (label:bug,critical)\n- NOT: leading dash (-label:wontfix)\n- State: state:open, state:closed, state:merged\n- Type: is:issue, is:pr\n- Assignment: assignee:@me\n", } } -func skillNotifications() skillDefinition { +func skillHandleNotifications() skillDefinition { return skillDefinition{ - name: "notifications", - description: "View and manage GitHub notifications and subscriptions", + name: "handle-notifications", + description: "Process your GitHub notification queue efficiently", allowedTools: []string{ "list_notifications", "get_notification_details", @@ -375,163 +362,131 @@ func skillNotifications() skillDefinition { "manage_notification_subscription", "manage_repository_notification_subscription", }, - body: `# GitHub Notifications - -Tools for viewing and managing GitHub notifications. - -## Available Tools -- ` + "`list_notifications`" + ` — list unread and read notifications -- ` + "`get_notification_details`" + ` — get details of a specific notification -- ` + "`dismiss_notification`" + ` — mark a notification as done -- ` + "`mark_all_notifications_read`" + ` — mark all notifications as read -- ` + "`manage_notification_subscription`" + ` — manage thread subscription settings -- ` + "`manage_repository_notification_subscription`" + ` — manage repository notification settings -`, + body: "# Handle Notifications\n\nProcess notifications by priority, not just mark them read.\n\n## Available Tools\n- `list_notifications` — list by unread, repo, or reason\n- `get_notification_details` — full context for a notification\n- `dismiss_notification` — mark as done\n- `mark_all_notifications_read` — mark all read\n- `manage_notification_subscription` — subscribe/unsubscribe from threads\n- `manage_repository_notification_subscription` — per-repo notification settings\n\n## Triage by Reason\n1. review_requested — someone needs your review (act first).\n2. mention / assign — you are directly involved (act next).\n3. ci_activity — check if your CI is failing.\n4. subscribed — threads you are watching (lowest priority).\n\nUse `get_notification_details` before acting — don't dismiss blindly.\nUnsubscribe from noisy repos with `manage_repository_notification_subscription`.\n\nDon't use `mark_all_notifications_read` without triaging — you will miss action items.\n", } } -func skillGists() skillDefinition { +func skillPrepareRelease() skillDefinition { return skillDefinition{ - name: "gists", - description: "Create, read, and update GitHub Gists", + name: "prepare-release", + description: "Compile release notes from commits and merged PRs", allowedTools: []string{ - "list_gists", - "get_gist", - "create_gist", - "update_gist", + "list_releases", + "get_latest_release", + "get_release_by_tag", + "list_tags", + "get_tag", + "list_commits", + "search_pull_requests", }, - body: `# GitHub Gists - -Tools for managing GitHub Gists — lightweight code snippets and file sharing. - -## Available Tools -- ` + "`list_gists`" + ` — list gists for the authenticated user -- ` + "`get_gist`" + ` — retrieve a specific gist by ID -- ` + "`create_gist`" + ` — create a new gist (public or private) -- ` + "`update_gist`" + ` — update an existing gist's files or description -`, + body: "# Prepare Release\n\nCompile release notes from merged PRs and commits since the last release.\n\n## Available Tools\n- `list_releases` / `get_latest_release` / `get_release_by_tag` — browse releases\n- `list_tags` / `get_tag` — version tags\n- `list_commits` — commits since last release\n- `search_pull_requests` — find merged PRs in the range\n\n## Workflow\n1. `get_latest_release` to find the last version tag.\n2. `list_commits` since that tag to see all changes.\n3. `search_pull_requests` for merged PRs in the range — PR descriptions are richer than commits.\n4. Group changes: breaking changes, features, bug fixes, docs.\n5. Link PR numbers in release notes for traceability.\n\nUse PR titles and labels for categorization — commit messages alone are often too terse.\n", } } -func skillUsersOrgs() skillDefinition { +func skillManageRepo() skillDefinition { return skillDefinition{ - name: "users-orgs", - description: "Search for GitHub users and organizations", + name: "manage-repo", + description: "Create repos, manage branches, and push file changes", allowedTools: []string{ - "search_users", - "search_orgs", + "create_repository", + "fork_repository", + "create_branch", + "create_or_update_file", + "push_files", + "delete_file", + "get_file_contents", + "search_repositories", }, - body: `# GitHub Users & Organizations - -Tools for searching GitHub users and organizations. - -## Available Tools -- ` + "`search_users`" + ` — search for users by username, name, location, or other criteria -- ` + "`search_orgs`" + ` — search for organizations by name or other criteria - -For search tools, use separate ` + "`sort`" + ` and ` + "`order`" + ` parameters — do not include 'sort:' syntax in query strings. -`, + body: "# Manage Repository\n\nCreate repos, branches, and manage file contents.\n\n## Available Tools\n- `create_repository` — create a new repo\n- `fork_repository` — fork an existing repo\n- `create_branch` — create a branch\n- `create_or_update_file` — single file create/update with commit\n- `push_files` — push multiple files in one commit\n- `delete_file` — delete a file with commit\n- `get_file_contents` — read files and directories\n- `search_repositories` — find existing repos\n\n## Tips\n- Use `push_files` for multi-file changes — creates a single atomic commit.\n- Use `create_or_update_file` only for single-file operations.\n- Include README, LICENSE, and .gitignore when creating new repos.\n- Fork for contributing to others' projects. Create new repos for new projects.\n", } } -func skillSecurityAdvisories() skillDefinition { +func skillManageLabels() skillDefinition { return skillDefinition{ - name: "security-advisories", - description: "Browse global and repository-level security advisories", + name: "manage-labels", + description: "Set up and maintain a consistent label scheme", allowedTools: []string{ - "list_global_security_advisories", - "get_global_security_advisory", - "list_repository_security_advisories", - "list_org_repository_security_advisories", + "list_labels", + "list_label", + "label_write", + "search_issues", }, - body: `# Security Advisories - -Tools for browsing security advisories on GitHub. - -## Available Tools -- ` + "`list_global_security_advisories`" + ` — search the GitHub Advisory Database -- ` + "`get_global_security_advisory`" + ` — get details of a specific global advisory -- ` + "`list_repository_security_advisories`" + ` — list advisories for a specific repository -- ` + "`list_org_repository_security_advisories`" + ` — list advisories across an organization's repositories -`, + body: "# Manage Labels\n\nCreate a consistent, useful label system for a repository.\n\n## Available Tools\n- `list_labels` / `list_label` — browse existing labels\n- `label_write` — create, update, or delete labels\n- `search_issues` — check label usage before deleting\n\n## Best Practices\n- Use prefixed names: type:bug, type:feature, priority:high, status:needs-triage.\n- Use consistent colors within categories (all type: labels same color family).\n- Write helpful descriptions — they appear in the label picker.\n- Check label usage with `search_issues` before deleting or renaming.\n- Aim for 15-25 labels total. Too many means none get used consistently.\n", } } -func skillLabels() skillDefinition { +func skillContributeOSS() skillDefinition { return skillDefinition{ - name: "labels", - description: "Manage GitHub issue and PR labels", + name: "contribute-oss", + description: "Fork, branch, and submit PRs to external repositories", allowedTools: []string{ - "list_label", - "list_labels", - "label_write", + "fork_repository", + "create_branch", + "push_files", + "create_pull_request", + "get_file_contents", + "search_repositories", + "pull_request_read", }, - body: `# GitHub Labels - -Tools for managing labels on GitHub repositories. - -## Available Tools -- ` + "`list_label`" + ` — get a specific label by name -- ` + "`list_labels`" + ` — list all labels for a repository -- ` + "`label_write`" + ` — create, update, or delete labels -`, + body: "# Contribute to Open Source\n\nWorkflow for contributing to repos you don't have write access to.\n\n## Available Tools\n- `fork_repository` — fork upstream to your account\n- `create_branch` — create feature branch on your fork\n- `push_files` — push changes to your fork\n- `create_pull_request` — PR from your fork to upstream\n- `get_file_contents` — read CONTRIBUTING.md and templates\n- `search_repositories` — find the repo\n- `pull_request_read` — track your PR status\n\n## Workflow\n1. Read CONTRIBUTING.md and CODE_OF_CONDUCT.md first.\n2. Fork the repo, create a feature branch (not main).\n3. Keep changes small and focused — one concern per PR.\n4. Follow the project's existing code style.\n5. Create PR with clear description linking related issues.\n\nLook for good-first-issue labels to find starter tasks. Don't submit large PRs without discussing scope first in an issue.\n", } } -func skillGit() skillDefinition { +func skillBrowseDiscussions() skillDefinition { return skillDefinition{ - name: "git", - description: "Low-level Git operations via the GitHub Git API", + name: "browse-discussions", + description: "Read and explore GitHub Discussions and categories", allowedTools: []string{ - "get_repository_tree", + "list_discussions", + "get_discussion", + "get_discussion_comments", + "list_discussion_categories", }, - body: `# GitHub Git API - -Low-level Git operations via the GitHub API. + body: "# Browse Discussions\n\nRead and explore GitHub Discussions.\n\n## Available Tools\n- `list_discussions` — list discussions in a repo\n- `get_discussion` — get discussion details\n- `get_discussion_comments` — read comments and replies\n- `list_discussion_categories` — list available categories\n\nCall `list_discussion_categories` first to understand the discussion structure. Filter by category to find relevant conversations.\n", + } +} -## Available Tools -- ` + "`get_repository_tree`" + ` — retrieve the tree structure of a repository at a given ref, useful for understanding repository layout -`, +func skillDelegateCopilot() skillDefinition { + return skillDefinition{ + name: "delegate-to-copilot", + description: "Assign Copilot to issues and request Copilot PR reviews", + allowedTools: []string{ + "assign_copilot_to_issue", + "request_copilot_review", + "issue_read", + "pull_request_read", + }, + body: "# Delegate to Copilot\n\nUse GitHub Copilot for automated issue work and PR reviews.\n\n## Available Tools\n- `assign_copilot_to_issue` — assign Copilot to work on an issue\n- `request_copilot_review` — request Copilot review on a PR\n- `issue_read` — check issue details before assigning\n- `pull_request_read` — check PR before requesting review\n\n## Tips\n- Write clear, specific issue descriptions — vague issues produce vague results.\n- Ensure the issue is well-scoped (single concern) before assigning Copilot.\n- Use Copilot review for initial feedback, then follow up with human review for nuanced concerns.\n", } } -func skillStargazers() skillDefinition { +func skillDiscoverGitHub() skillDefinition { return skillDefinition{ - name: "stargazers", - description: "Star and unstar repositories, list starred repositories", + name: "discover-github", + description: "Search for users, organizations, and repositories", allowedTools: []string{ + "search_users", + "search_orgs", + "search_repositories", "list_starred_repositories", "star_repository", "unstar_repository", }, - body: `# GitHub Stars - -Tools for managing repository stars. - -## Available Tools -- ` + "`list_starred_repositories`" + ` — list repositories starred by the authenticated user -- ` + "`star_repository`" + ` — star a repository -- ` + "`unstar_repository`" + ` — unstar a repository -`, + body: "# Discover GitHub\n\nSearch for users, organizations, and repositories across GitHub.\n\n## Available Tools\n- `search_users` — find users by name, location, or profile\n- `search_orgs` — find organizations\n- `search_repositories` — find repos by name, topic, language, org\n- `list_starred_repositories` — your starred repos\n- `star_repository` / `unstar_repository` — manage stars\n\n## Search Tips\n- Use qualifiers: language:go, org:github, topic:mcp, stars:>100.\n- Use separate `sort` and `order` parameters — don't put sort: in query strings.\n- Star useful repos to build a personal reference library.\n", } } -func skillCopilot() skillDefinition { +func skillShareSnippet() skillDefinition { return skillDefinition{ - name: "copilot", - description: "Assign Copilot to issues and request Copilot reviews on pull requests", + name: "share-snippet", + description: "Create and manage code snippets via GitHub Gists", allowedTools: []string{ - "assign_copilot_to_issue", - "request_copilot_review", + "create_gist", + "update_gist", + "list_gists", + "get_gist", }, - body: `# GitHub Copilot - -Tools for using GitHub Copilot in your workflow. - -## Available Tools -- ` + "`assign_copilot_to_issue`" + ` — assign Copilot as a collaborator on an issue -- ` + "`request_copilot_review`" + ` — request a Copilot review on a pull request -`, + body: "# Share Snippet\n\nCreate and manage code snippets via GitHub Gists.\n\n## Available Tools\n- `create_gist` — create a new gist (public or private)\n- `update_gist` — update files or description\n- `list_gists` — list your gists\n- `get_gist` — retrieve a specific gist\n\nGists support multiple files per gist. Use descriptive filenames with proper extensions for syntax highlighting.\n", } } diff --git a/pkg/github/skill_resources_test.go b/pkg/github/skill_resources_test.go index c7ad2e8b84..70871459d9 100644 --- a/pkg/github/skill_resources_test.go +++ b/pkg/github/skill_resources_test.go @@ -80,5 +80,5 @@ func TestRegisterSkillResources(t *testing.T) { // Verify the expected number of skills were registered by counting definitions skills := allSkills() - assert.Equal(t, 16, len(skills), "expected 16 skills covering all toolsets") + assert.Equal(t, 27, len(skills), "expected 27 workflow-oriented skills") } From 184979c4a843d1d41b2175f5372fbd4605d60983 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 24 Apr 2026 12:09:06 +0200 Subject: [PATCH 4/7] refactor: make skill descriptions trigger-friendly per agent skills spec Update all 27 skill descriptions to include both what the skill does AND when to use it, following the agent skills specification guidance that descriptions are the primary triggering mechanism. Descriptions now explicitly list user intents that should activate each skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/skill_resources.go | 54 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/pkg/github/skill_resources.go b/pkg/github/skill_resources.go index 48b213dce3..bc32d24e11 100644 --- a/pkg/github/skill_resources.go +++ b/pkg/github/skill_resources.go @@ -57,7 +57,7 @@ func allSkills() []skillDefinition { func skillGetContext() skillDefinition { return skillDefinition{ name: "get-context", - description: "Understand the current user, their permissions, and team membership", + description: "Understand the current user, their permissions, and team membership. Use when starting any workflow, checking who you are, what you can access, or looking up team membership.", allowedTools: []string{ "get_me", "get_teams", @@ -70,7 +70,7 @@ func skillGetContext() skillDefinition { func skillExploreRepo() skillDefinition { return skillDefinition{ name: "explore-repo", - description: "Understand an unfamiliar codebase quickly", + description: "Understand an unfamiliar codebase quickly. Use when exploring a new repo, understanding project structure, finding entry points, or getting oriented in code you haven't seen before.", allowedTools: []string{ "get_repository_tree", "get_file_contents", @@ -86,7 +86,7 @@ func skillExploreRepo() skillDefinition { func skillSearchCode() skillDefinition { return skillDefinition{ name: "search-code", - description: "Find code patterns, symbols, and examples across GitHub", + description: "Find code patterns, symbols, and examples across GitHub. Use when searching for code, finding how something is implemented, locating files, or looking for usage examples across repositories.", allowedTools: []string{ "search_code", "search_repositories", @@ -99,7 +99,7 @@ func skillSearchCode() skillDefinition { func skillTraceHistory() skillDefinition { return skillDefinition{ name: "trace-history", - description: "Understand why code changed by tracing commits and PRs", + description: "Understand why code changed by tracing commits and PRs. Use when investigating git history, finding who changed something, understanding the motivation behind a change, or tracking down when a bug was introduced.", allowedTools: []string{ "list_commits", "get_commit", @@ -113,7 +113,7 @@ func skillTraceHistory() skillDefinition { func skillCreatePR() skillDefinition { return skillDefinition{ name: "create-pr", - description: "Create a well-structured pull request that reviews smoothly", + description: "Create a well-structured pull request that reviews smoothly. Use when opening a new PR, pushing changes for review, or submitting code changes to a repository.", allowedTools: []string{ "create_pull_request", "get_file_contents", @@ -130,7 +130,7 @@ func skillCreatePR() skillDefinition { func skillReviewPR() skillDefinition { return skillDefinition{ name: "review-pr", - description: "Conduct a thorough code review of a pull request", + description: "Conduct a thorough code review of a pull request. Use when reviewing someone else's PR, checking code changes, leaving review comments, approving or requesting changes.", allowedTools: []string{ "pull_request_read", "get_file_contents", @@ -152,7 +152,7 @@ func skillReviewPR() skillDefinition { func skillSelfReviewPR() skillDefinition { return skillDefinition{ name: "self-review-pr", - description: "Review your own PR before requesting team review", + description: "Review your own PR before requesting team review. Use when you want to self-check your PR, verify CI status, polish description, or prepare your changes for review.", allowedTools: []string{ "pull_request_read", "get_file_contents", @@ -171,7 +171,7 @@ func skillSelfReviewPR() skillDefinition { func skillAddressPRFeedback() skillDefinition { return skillDefinition{ name: "address-pr-feedback", - description: "Handle review comments on your PR and push fixes", + description: "Handle review comments on your PR and push fixes. Use when you received PR feedback, need to respond to reviewer comments, resolve threads, or push fixes based on review.", allowedTools: []string{ "pull_request_read", "add_reply_to_pull_request_comment", @@ -188,7 +188,7 @@ func skillAddressPRFeedback() skillDefinition { func skillMergePR() skillDefinition { return skillDefinition{ name: "merge-pr", - description: "Get a PR to merge-ready state and merge it", + description: "Get a PR to merge-ready state and merge it. Use when merging a pull request, checking if a PR is ready to merge, updating a PR branch, or converting a draft PR.", allowedTools: []string{ "pull_request_read", "merge_pull_request", @@ -204,7 +204,7 @@ func skillMergePR() skillDefinition { func skillTriageIssues() skillDefinition { return skillDefinition{ name: "triage-issues", - description: "Categorize, deduplicate, and prioritize incoming issues", + description: "Categorize, deduplicate, and prioritize incoming issues. Use when triaging issues, labeling bugs, organizing a backlog, closing duplicates, or processing new issue reports.", allowedTools: []string{ "list_issues", "search_issues", @@ -230,7 +230,7 @@ func skillTriageIssues() skillDefinition { func skillCreateIssue() skillDefinition { return skillDefinition{ name: "create-issue", - description: "Create well-structured, searchable, actionable issues", + description: "Create well-structured, searchable, actionable issues. Use when filing a bug report, requesting a feature, creating a task, or opening any new GitHub issue.", allowedTools: []string{ "create_issue", "search_issues", @@ -245,7 +245,7 @@ func skillCreateIssue() skillDefinition { func skillManageSubIssues() skillDefinition { return skillDefinition{ name: "manage-sub-issues", - description: "Break down large issues into trackable sub-tasks", + description: "Break down large issues into trackable sub-tasks. Use when decomposing epics, creating task breakdowns, organizing work into smaller pieces, or managing parent-child issue relationships.", allowedTools: []string{ "issue_read", "create_issue", @@ -262,7 +262,7 @@ func skillManageSubIssues() skillDefinition { func skillDebugCI() skillDefinition { return skillDefinition{ name: "debug-ci", - description: "Investigate and fix failing GitHub Actions workflows", + description: "Investigate and fix failing GitHub Actions workflows. Use when CI is failing, a workflow run errored, you need to read build logs, or debug why tests aren't passing.", allowedTools: []string{ "actions_get", "get_job_logs", @@ -277,7 +277,7 @@ func skillDebugCI() skillDefinition { func skillTriggerWorkflow() skillDefinition { return skillDefinition{ name: "trigger-workflow", - description: "Run, rerun, or cancel GitHub Actions workflow runs", + description: "Run, rerun, or cancel GitHub Actions workflow runs. Use when triggering a deployment, rerunning failed jobs, canceling a stuck workflow, or dispatching a workflow manually.", allowedTools: []string{ "actions_run_trigger", "actions_get", @@ -291,7 +291,7 @@ func skillTriggerWorkflow() skillDefinition { func skillSecurityAudit() skillDefinition { return skillDefinition{ name: "security-audit", - description: "Systematically review code scanning, secret, and dependency alerts", + description: "Systematically review code scanning, secret, and dependency alerts. Use when auditing repo security, checking for vulnerabilities, reviewing CodeQL alerts, or investigating exposed secrets.", allowedTools: []string{ "list_code_scanning_alerts", "get_code_scanning_alert", @@ -309,7 +309,7 @@ func skillSecurityAudit() skillDefinition { func skillFixDependabot() skillDefinition { return skillDefinition{ name: "fix-dependabot", - description: "Handle vulnerable dependency alerts and update PRs", + description: "Handle vulnerable dependency alerts and update PRs. Use when fixing Dependabot alerts, updating vulnerable packages, reviewing dependency update PRs, or managing supply chain security.", allowedTools: []string{ "list_dependabot_alerts", "get_dependabot_alert", @@ -324,7 +324,7 @@ func skillFixDependabot() skillDefinition { func skillResearchVulnerability() skillDefinition { return skillDefinition{ name: "research-vulnerability", - description: "Query the GitHub Advisory Database for security advisories", + description: "Query the GitHub Advisory Database for security advisories. Use when researching CVEs, looking up GHSA IDs, checking if a package has known vulnerabilities, or reviewing security advisories for a repo or org.", allowedTools: []string{ "list_global_security_advisories", "get_global_security_advisory", @@ -338,7 +338,7 @@ func skillResearchVulnerability() skillDefinition { func skillManageProject() skillDefinition { return skillDefinition{ name: "manage-project", - description: "Track and update work items in GitHub Projects (v2)", + description: "Track and update work items in GitHub Projects (v2). Use when managing a project board, updating issue status fields, adding items to a project, querying project items, or posting project status updates.", allowedTools: []string{ "projects_list", "projects_get", @@ -353,7 +353,7 @@ func skillManageProject() skillDefinition { func skillHandleNotifications() skillDefinition { return skillDefinition{ name: "handle-notifications", - description: "Process your GitHub notification queue efficiently", + description: "Process your GitHub notification queue efficiently. Use when checking notifications, clearing your inbox, managing subscriptions, or finding out what needs your attention on GitHub.", allowedTools: []string{ "list_notifications", "get_notification_details", @@ -369,7 +369,7 @@ func skillHandleNotifications() skillDefinition { func skillPrepareRelease() skillDefinition { return skillDefinition{ name: "prepare-release", - description: "Compile release notes from commits and merged PRs", + description: "Compile release notes from commits and merged PRs. Use when preparing a release, writing a changelog, summarizing changes since last version, or reviewing what shipped.", allowedTools: []string{ "list_releases", "get_latest_release", @@ -386,7 +386,7 @@ func skillPrepareRelease() skillDefinition { func skillManageRepo() skillDefinition { return skillDefinition{ name: "manage-repo", - description: "Create repos, manage branches, and push file changes", + description: "Create repos, manage branches, and push file changes. Use when creating a new repository, making a branch, committing files via the API, forking a repo, or managing repository contents.", allowedTools: []string{ "create_repository", "fork_repository", @@ -404,7 +404,7 @@ func skillManageRepo() skillDefinition { func skillManageLabels() skillDefinition { return skillDefinition{ name: "manage-labels", - description: "Set up and maintain a consistent label scheme", + description: "Set up and maintain a consistent label scheme. Use when creating labels, organizing a label system, cleaning up labels, or standardizing label naming across a repository.", allowedTools: []string{ "list_labels", "list_label", @@ -418,7 +418,7 @@ func skillManageLabels() skillDefinition { func skillContributeOSS() skillDefinition { return skillDefinition{ name: "contribute-oss", - description: "Fork, branch, and submit PRs to external repositories", + description: "Fork, branch, and submit PRs to external repositories. Use when contributing to open source, forking a repo to make changes, or submitting a pull request to a project you don't own.", allowedTools: []string{ "fork_repository", "create_branch", @@ -435,7 +435,7 @@ func skillContributeOSS() skillDefinition { func skillBrowseDiscussions() skillDefinition { return skillDefinition{ name: "browse-discussions", - description: "Read and explore GitHub Discussions and categories", + description: "Read and explore GitHub Discussions and categories. Use when browsing discussions, reading community conversations, checking discussion categories, or looking for answers in a project's discussions.", allowedTools: []string{ "list_discussions", "get_discussion", @@ -449,7 +449,7 @@ func skillBrowseDiscussions() skillDefinition { func skillDelegateCopilot() skillDefinition { return skillDefinition{ name: "delegate-to-copilot", - description: "Assign Copilot to issues and request Copilot PR reviews", + description: "Assign Copilot to issues and request Copilot PR reviews. Use when you want Copilot to work on an issue, get an automated code review, or delegate tasks to GitHub Copilot.", allowedTools: []string{ "assign_copilot_to_issue", "request_copilot_review", @@ -463,7 +463,7 @@ func skillDelegateCopilot() skillDefinition { func skillDiscoverGitHub() skillDefinition { return skillDefinition{ name: "discover-github", - description: "Search for users, organizations, and repositories", + description: "Search for users, organizations, and repositories. Use when finding GitHub users, looking up organizations, discovering repos by topic or language, or managing your starred repositories.", allowedTools: []string{ "search_users", "search_orgs", @@ -479,7 +479,7 @@ func skillDiscoverGitHub() skillDefinition { func skillShareSnippet() skillDefinition { return skillDefinition{ name: "share-snippet", - description: "Create and manage code snippets via GitHub Gists", + description: "Create and manage code snippets via GitHub Gists. Use when sharing a code snippet, creating a quick paste, saving notes as a gist, or managing your existing gists.", allowedTools: []string{ "create_gist", "update_gist", From eb1865f1cfce24e0221bc55af8c0f8218dee8101 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 24 Apr 2026 12:33:11 +0200 Subject: [PATCH 5/7] fix: filter skill allowedTools against registered tools at startup Skills now only include tools that are actually registered with the MCP server, based on the active toolset configuration. Skills with no available tools are skipped entirely. This prevents skills from referencing non-existent tools (e.g., gist tools when the gists toolset isn't enabled). RegisterSkillResources now accepts a map of available tool names built from the inventory at startup time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/server.go | 10 ++++- pkg/github/skill_resources.go | 27 ++++++++++++- pkg/github/skill_resources_test.go | 63 +++++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/pkg/github/server.go b/pkg/github/server.go index b19701691b..4cb05797bf 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -124,8 +124,14 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci registerDynamicTools(ghServer, inv, deps, cfg.Translator) } - // Register skill resources for MCP clients that support skills-based discovery - RegisterSkillResources(ghServer) + // Register skill resources for MCP clients that support skills-based discovery. + // Filter allowedTools against actually-registered tools so skills only reference + // tools that exist for the current toolset configuration. + availableTools := make(map[string]struct{}) + for _, tool := range inv.AllTools() { + availableTools[tool.Tool.Name] = struct{}{} + } + RegisterSkillResources(ghServer, availableTools) return ghServer, nil } diff --git a/pkg/github/skill_resources.go b/pkg/github/skill_resources.go index bc32d24e11..e0b33767a5 100644 --- a/pkg/github/skill_resources.go +++ b/pkg/github/skill_resources.go @@ -508,8 +508,18 @@ func buildSkillContent(skill skillDefinition) string { // RegisterSkillResources registers all skill resources with the MCP server. // Each skill is a static resource with a skill:// URI that can be discovered // by MCP clients supporting the skills pattern. -func RegisterSkillResources(s *mcp.Server) { +// The availableTools set filters each skill's allowedTools to only include +// tools that are actually registered, ensuring skills work correctly +// regardless of which toolsets are enabled. +func RegisterSkillResources(s *mcp.Server, availableTools map[string]struct{}) { for _, skill := range allSkills() { + // Filter allowedTools to only include tools that are actually registered + filtered := filterAvailableTools(skill.allowedTools, availableTools) + if len(filtered) == 0 { + continue // Skip skills with no available tools + } + skill.allowedTools = filtered + content := buildSkillContent(skill) uri := fmt.Sprintf("skill://github/%s/SKILL.md", skill.name) @@ -536,3 +546,18 @@ func RegisterSkillResources(s *mcp.Server) { ) } } + +// filterAvailableTools returns only the tool names that exist in the available set. +// If availableTools is nil, all tools are returned (no filtering). +func filterAvailableTools(tools []string, availableTools map[string]struct{}) []string { + if availableTools == nil { + return tools + } + var filtered []string + for _, t := range tools { + if _, ok := availableTools[t]; ok { + filtered = append(filtered, t) + } + } + return filtered +} diff --git a/pkg/github/skill_resources_test.go b/pkg/github/skill_resources_test.go index 70871459d9..6192a75877 100644 --- a/pkg/github/skill_resources_test.go +++ b/pkg/github/skill_resources_test.go @@ -75,10 +75,69 @@ func TestRegisterSkillResources(t *testing.T) { Version: "0.0.1", }, nil) - // Should not panic - RegisterSkillResources(server) + // Should not panic with nil (no filtering) + RegisterSkillResources(server, nil) // Verify the expected number of skills were registered by counting definitions skills := allSkills() assert.Equal(t, 27, len(skills), "expected 27 workflow-oriented skills") } + +func TestFilterAvailableTools(t *testing.T) { + tests := []struct { + name string + tools []string + available map[string]struct{} + expected []string + }{ + { + name: "nil available returns all tools", + tools: []string{"a", "b", "c"}, + available: nil, + expected: []string{"a", "b", "c"}, + }, + { + name: "filters to only available tools", + tools: []string{"a", "b", "c"}, + available: map[string]struct{}{"a": {}, "c": {}}, + expected: []string{"a", "c"}, + }, + { + name: "returns nil when no tools match", + tools: []string{"a", "b"}, + available: map[string]struct{}{"x": {}}, + expected: nil, + }, + { + name: "empty available set filters everything", + tools: []string{"a", "b"}, + available: map[string]struct{}{}, + expected: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := filterAvailableTools(tc.tools, tc.available) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestRegisterSkillResourcesFiltering(t *testing.T) { + t.Parallel() + // Only make get_me available — skills that have no overlap should be skipped + available := map[string]struct{}{"get_me": {}} + + server := mcp.NewServer(&mcp.Implementation{ + Name: "test-server", + Version: "0.0.1", + }, nil) + + RegisterSkillResources(server, available) + + // get-context skill includes get_me, so it should be registered. + // Skills with zero available tools should be skipped entirely. + // We can't easily count resources on mcp.Server, but we verify no panic + // and the logic is correct via TestFilterAvailableTools above. +} From 209f851c87a908960ff777d12277e371afe2690e Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 24 Apr 2026 12:35:22 +0200 Subject: [PATCH 6/7] fix: register all tools from all toolsets when skills are enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When skills handle progressive disclosure, the server should expose all tools via tools/list regardless of toolset configuration. The client decides tool visibility through skill allowedTools, not the server. This replaces the previous filtering approach — now inv.AllTools() registers every tool with the MCP server, and skills reference the full tool surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/server.go | 10 ++--- pkg/github/skill_resources.go | 27 +------------ pkg/github/skill_resources_test.go | 63 +----------------------------- 3 files changed, 8 insertions(+), 92 deletions(-) diff --git a/pkg/github/server.go b/pkg/github/server.go index 4cb05797bf..791c2428a7 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -125,13 +125,13 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci } // Register skill resources for MCP clients that support skills-based discovery. - // Filter allowedTools against actually-registered tools so skills only reference - // tools that exist for the current toolset configuration. - availableTools := make(map[string]struct{}) + // When skills are present, register ALL tools from all toolsets so that skills + // can progressively reveal the full tool surface. The skills handle disclosure; + // the server exposes everything. for _, tool := range inv.AllTools() { - availableTools[tool.Tool.Name] = struct{}{} + tool.RegisterFunc(ghServer, deps) } - RegisterSkillResources(ghServer, availableTools) + RegisterSkillResources(ghServer) return ghServer, nil } diff --git a/pkg/github/skill_resources.go b/pkg/github/skill_resources.go index e0b33767a5..bc32d24e11 100644 --- a/pkg/github/skill_resources.go +++ b/pkg/github/skill_resources.go @@ -508,18 +508,8 @@ func buildSkillContent(skill skillDefinition) string { // RegisterSkillResources registers all skill resources with the MCP server. // Each skill is a static resource with a skill:// URI that can be discovered // by MCP clients supporting the skills pattern. -// The availableTools set filters each skill's allowedTools to only include -// tools that are actually registered, ensuring skills work correctly -// regardless of which toolsets are enabled. -func RegisterSkillResources(s *mcp.Server, availableTools map[string]struct{}) { +func RegisterSkillResources(s *mcp.Server) { for _, skill := range allSkills() { - // Filter allowedTools to only include tools that are actually registered - filtered := filterAvailableTools(skill.allowedTools, availableTools) - if len(filtered) == 0 { - continue // Skip skills with no available tools - } - skill.allowedTools = filtered - content := buildSkillContent(skill) uri := fmt.Sprintf("skill://github/%s/SKILL.md", skill.name) @@ -546,18 +536,3 @@ func RegisterSkillResources(s *mcp.Server, availableTools map[string]struct{}) { ) } } - -// filterAvailableTools returns only the tool names that exist in the available set. -// If availableTools is nil, all tools are returned (no filtering). -func filterAvailableTools(tools []string, availableTools map[string]struct{}) []string { - if availableTools == nil { - return tools - } - var filtered []string - for _, t := range tools { - if _, ok := availableTools[t]; ok { - filtered = append(filtered, t) - } - } - return filtered -} diff --git a/pkg/github/skill_resources_test.go b/pkg/github/skill_resources_test.go index 6192a75877..70871459d9 100644 --- a/pkg/github/skill_resources_test.go +++ b/pkg/github/skill_resources_test.go @@ -75,69 +75,10 @@ func TestRegisterSkillResources(t *testing.T) { Version: "0.0.1", }, nil) - // Should not panic with nil (no filtering) - RegisterSkillResources(server, nil) + // Should not panic + RegisterSkillResources(server) // Verify the expected number of skills were registered by counting definitions skills := allSkills() assert.Equal(t, 27, len(skills), "expected 27 workflow-oriented skills") } - -func TestFilterAvailableTools(t *testing.T) { - tests := []struct { - name string - tools []string - available map[string]struct{} - expected []string - }{ - { - name: "nil available returns all tools", - tools: []string{"a", "b", "c"}, - available: nil, - expected: []string{"a", "b", "c"}, - }, - { - name: "filters to only available tools", - tools: []string{"a", "b", "c"}, - available: map[string]struct{}{"a": {}, "c": {}}, - expected: []string{"a", "c"}, - }, - { - name: "returns nil when no tools match", - tools: []string{"a", "b"}, - available: map[string]struct{}{"x": {}}, - expected: nil, - }, - { - name: "empty available set filters everything", - tools: []string{"a", "b"}, - available: map[string]struct{}{}, - expected: nil, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - result := filterAvailableTools(tc.tools, tc.available) - assert.Equal(t, tc.expected, result) - }) - } -} - -func TestRegisterSkillResourcesFiltering(t *testing.T) { - t.Parallel() - // Only make get_me available — skills that have no overlap should be skipped - available := map[string]struct{}{"get_me": {}} - - server := mcp.NewServer(&mcp.Implementation{ - Name: "test-server", - Version: "0.0.1", - }, nil) - - RegisterSkillResources(server, available) - - // get-context skill includes get_me, so it should be registered. - // Skills with zero available tools should be skipped entirely. - // We can't easily count resources on mcp.Server, but we verify no panic - // and the logic is correct via TestFilterAvailableTools above. -} From 1a394400846cdd7b18774577e913302ae968cba8 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 24 Apr 2026 12:40:23 +0200 Subject: [PATCH 7/7] feat: make all toolsets default so skills have full tool surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All toolsets are now enabled by default (Default: true). This ensures every tool is registered out of the box, which is required for skills to work — skills handle progressive tool discovery, so the server should expose the full surface. Users can still restrict with --toolsets if needed. Reverts the inv.AllTools() registration workaround from server.go since the normal RegisterAll path now picks up everything. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/server.go | 6 ---- pkg/github/tools.go | 13 +++++++++ pkg/github/tools_test.go | 63 ++++++++++++++++++++-------------------- 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/pkg/github/server.go b/pkg/github/server.go index 791c2428a7..01c9c018a5 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -125,12 +125,6 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci } // Register skill resources for MCP clients that support skills-based discovery. - // When skills are present, register ALL tools from all toolsets so that skills - // can progressively reveal the full tool surface. The skills handle disclosure; - // the server exposes everything. - for _, tool := range inv.AllTools() { - tool.RegisterFunc(ghServer, deps) - } RegisterSkillResources(ghServer) return ghServer, nil diff --git a/pkg/github/tools.go b/pkg/github/tools.go index ef915259c3..24f4afafae 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -43,6 +43,7 @@ var ( ToolsetMetadataGit = inventory.ToolsetMetadata{ ID: "git", Description: "GitHub Git API related tools for low-level Git operations", + Default: true, Icon: "git-branch", } ToolsetMetadataIssues = inventory.ToolsetMetadata{ @@ -66,56 +67,67 @@ var ( ToolsetMetadataOrgs = inventory.ToolsetMetadata{ ID: "orgs", Description: "GitHub Organization related tools", + Default: true, Icon: "organization", } ToolsetMetadataActions = inventory.ToolsetMetadata{ ID: "actions", Description: "GitHub Actions workflows and CI/CD operations", + Default: true, Icon: "workflow", } ToolsetMetadataCodeSecurity = inventory.ToolsetMetadata{ ID: "code_security", Description: "Code security related tools, such as GitHub Code Scanning", + Default: true, Icon: "codescan", } ToolsetMetadataSecretProtection = inventory.ToolsetMetadata{ ID: "secret_protection", Description: "Secret protection related tools, such as GitHub Secret Scanning", + Default: true, Icon: "shield-lock", } ToolsetMetadataDependabot = inventory.ToolsetMetadata{ ID: "dependabot", Description: "Dependabot tools", + Default: true, Icon: "dependabot", } ToolsetMetadataNotifications = inventory.ToolsetMetadata{ ID: "notifications", Description: "GitHub Notifications related tools", + Default: true, Icon: "bell", } ToolsetMetadataDiscussions = inventory.ToolsetMetadata{ ID: "discussions", Description: "GitHub Discussions related tools", + Default: true, Icon: "comment-discussion", } ToolsetMetadataGists = inventory.ToolsetMetadata{ ID: "gists", Description: "GitHub Gist related tools", + Default: true, Icon: "logo-gist", } ToolsetMetadataSecurityAdvisories = inventory.ToolsetMetadata{ ID: "security_advisories", Description: "Security advisories related tools", + Default: true, Icon: "shield", } ToolsetMetadataProjects = inventory.ToolsetMetadata{ ID: "projects", Description: "GitHub Projects related tools", + Default: true, Icon: "project", } ToolsetMetadataStargazers = inventory.ToolsetMetadata{ ID: "stargazers", Description: "GitHub Stargazers related tools", + Default: true, Icon: "star", } ToolsetMetadataDynamic = inventory.ToolsetMetadata{ @@ -126,6 +138,7 @@ var ( ToolsetLabels = inventory.ToolsetMetadata{ ID: "labels", Description: "GitHub Labels related tools", + Default: true, Icon: "tag", } diff --git a/pkg/github/tools_test.go b/pkg/github/tools_test.go index 2bcd2d5259..e977fe0b8f 100644 --- a/pkg/github/tools_test.go +++ b/pkg/github/tools_test.go @@ -8,6 +8,28 @@ import ( ) func TestAddDefaultToolset(t *testing.T) { + allDefaultToolsets := []string{ + "actions", + "code_security", + "context", + "copilot", + "dependabot", + "discussions", + "gists", + "git", + "issues", + "labels", + "notifications", + "orgs", + "projects", + "pull_requests", + "repos", + "secret_protection", + "security_advisories", + "stargazers", + "users", + } + tests := []struct { name string input []string @@ -19,42 +41,19 @@ func TestAddDefaultToolset(t *testing.T) { expected: []string{"actions", "gists"}, }, { - name: "default keyword present - expand and remove default", - input: []string{"default"}, - expected: []string{ - "context", - "copilot", - "repos", - "issues", - "pull_requests", - "users", - }, + name: "default keyword present - expand and remove default", + input: []string{"default"}, + expected: allDefaultToolsets, }, { - name: "default with additional toolsets", - input: []string{"default", "actions", "gists"}, - expected: []string{ - "actions", - "gists", - "context", - "copilot", - "repos", - "issues", - "pull_requests", - "users", - }, + name: "default with additional toolsets", + input: []string{"default", "actions", "gists"}, + expected: allDefaultToolsets, }, { - name: "default with overlapping toolsets - should not duplicate", - input: []string{"default", "context", "repos"}, - expected: []string{ - "context", - "copilot", - "repos", - "issues", - "pull_requests", - "users", - }, + name: "default with overlapping toolsets - should not duplicate", + input: []string{"default", "context", "repos"}, + expected: allDefaultToolsets, }, { name: "empty input",