chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,512 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v89/github"
|
||||
"github.com/shurcooL/githubv4"
|
||||
|
||||
"github.com/github/github-mcp-server/pkg/inventory"
|
||||
"github.com/github/github-mcp-server/pkg/translations"
|
||||
)
|
||||
|
||||
type GetClientFn func(context.Context) (*github.Client, error)
|
||||
type GetGQLClientFn func(context.Context) (*githubv4.Client, error)
|
||||
|
||||
// Toolset metadata constants - these define all available toolsets and their descriptions.
|
||||
// Tools use these constants to declare which toolset they belong to.
|
||||
// Icons are Octicon names from https://primer.style/foundations/icons
|
||||
var (
|
||||
ToolsetMetadataAll = inventory.ToolsetMetadata{
|
||||
ID: "all",
|
||||
Description: "Special toolset that enables all available toolsets",
|
||||
Icon: "apps",
|
||||
}
|
||||
ToolsetMetadataDefault = inventory.ToolsetMetadata{
|
||||
ID: "default",
|
||||
Description: "Special toolset that enables the default toolset configuration. When no toolsets are specified, this is the set that is enabled",
|
||||
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,
|
||||
}
|
||||
ToolsetMetadataRepos = inventory.ToolsetMetadata{
|
||||
ID: "repos",
|
||||
Description: "GitHub Repository related tools",
|
||||
Default: true,
|
||||
Icon: "repo",
|
||||
}
|
||||
ToolsetMetadataGit = inventory.ToolsetMetadata{
|
||||
ID: "git",
|
||||
Description: "GitHub Git API related tools for low-level Git operations",
|
||||
Icon: "git-branch",
|
||||
}
|
||||
ToolsetMetadataIssues = inventory.ToolsetMetadata{
|
||||
ID: "issues",
|
||||
Description: "GitHub Issues related tools",
|
||||
Default: true,
|
||||
Icon: "issue-opened",
|
||||
InstructionsFunc: generateIssuesToolsetInstructions,
|
||||
}
|
||||
ToolsetMetadataPullRequests = inventory.ToolsetMetadata{
|
||||
ID: "pull_requests",
|
||||
Description: "GitHub Pull Request related tools",
|
||||
Default: true,
|
||||
Icon: "git-pull-request",
|
||||
InstructionsFunc: generatePullRequestsToolsetInstructions,
|
||||
}
|
||||
ToolsetMetadataUsers = inventory.ToolsetMetadata{
|
||||
ID: "users",
|
||||
Description: "GitHub User related tools",
|
||||
Default: true,
|
||||
Icon: "people",
|
||||
}
|
||||
ToolsetMetadataOrgs = inventory.ToolsetMetadata{
|
||||
ID: "orgs",
|
||||
Description: "GitHub Organization related tools",
|
||||
Icon: "organization",
|
||||
}
|
||||
ToolsetMetadataActions = inventory.ToolsetMetadata{
|
||||
ID: "actions",
|
||||
Description: "GitHub Actions workflows and CI/CD operations",
|
||||
Icon: "workflow",
|
||||
}
|
||||
ToolsetMetadataCodeQuality = inventory.ToolsetMetadata{
|
||||
ID: "code_quality",
|
||||
Description: "GitHub Code Quality related tools",
|
||||
Icon: "code-square",
|
||||
}
|
||||
ToolsetMetadataCodeSecurity = inventory.ToolsetMetadata{
|
||||
ID: "code_security",
|
||||
Description: "Code security related tools, such as GitHub Code Scanning",
|
||||
Icon: "codescan",
|
||||
}
|
||||
ToolsetMetadataSecretProtection = inventory.ToolsetMetadata{
|
||||
ID: "secret_protection",
|
||||
Description: "Secret protection related tools, such as GitHub Secret Scanning",
|
||||
Icon: "shield-lock",
|
||||
}
|
||||
ToolsetMetadataDependabot = inventory.ToolsetMetadata{
|
||||
ID: "dependabot",
|
||||
Description: "Dependabot tools",
|
||||
Icon: "dependabot",
|
||||
}
|
||||
ToolsetMetadataNotifications = inventory.ToolsetMetadata{
|
||||
ID: "notifications",
|
||||
Description: "GitHub Notifications related tools",
|
||||
Icon: "bell",
|
||||
}
|
||||
ToolsetMetadataDiscussions = inventory.ToolsetMetadata{
|
||||
ID: "discussions",
|
||||
Description: "GitHub Discussions related tools",
|
||||
Icon: "comment-discussion",
|
||||
InstructionsFunc: generateDiscussionsToolsetInstructions,
|
||||
}
|
||||
ToolsetMetadataGists = inventory.ToolsetMetadata{
|
||||
ID: "gists",
|
||||
Description: "GitHub Gist related tools",
|
||||
Icon: "logo-gist",
|
||||
}
|
||||
ToolsetMetadataSecurityAdvisories = inventory.ToolsetMetadata{
|
||||
ID: "security_advisories",
|
||||
Description: "Security advisories related tools",
|
||||
Icon: "shield",
|
||||
}
|
||||
ToolsetMetadataProjects = inventory.ToolsetMetadata{
|
||||
ID: "projects",
|
||||
Description: "GitHub Projects related tools",
|
||||
Icon: "project",
|
||||
InstructionsFunc: generateProjectsToolsetInstructions,
|
||||
}
|
||||
ToolsetMetadataStargazers = inventory.ToolsetMetadata{
|
||||
ID: "stargazers",
|
||||
Description: "GitHub Stargazers related tools",
|
||||
Icon: "star",
|
||||
}
|
||||
ToolsetLabels = inventory.ToolsetMetadata{
|
||||
ID: "labels",
|
||||
Description: "GitHub Labels related tools",
|
||||
Icon: "tag",
|
||||
}
|
||||
|
||||
ToolsetMetadataCopilot = inventory.ToolsetMetadata{
|
||||
ID: "copilot",
|
||||
Description: "Copilot related tools",
|
||||
Default: true,
|
||||
Icon: "copilot",
|
||||
}
|
||||
|
||||
// Feature flag names for granular tool variants.
|
||||
// When active, consolidated tools are replaced by single-purpose granular tools.
|
||||
FeatureFlagIssuesGranular = "issues_granular"
|
||||
FeatureFlagPullRequestsGranular = "pull_requests_granular"
|
||||
)
|
||||
|
||||
// HeaderAllowedFeatureFlags returns the feature flags that clients may enable via
|
||||
// the X-MCP-Features header. It delegates to AllowedFeatureFlags as the single
|
||||
// source of truth.
|
||||
func HeaderAllowedFeatureFlags() []string {
|
||||
return slices.Clone(AllowedFeatureFlags)
|
||||
}
|
||||
|
||||
var (
|
||||
// Remote-only toolsets - these are only available in the remote MCP server
|
||||
// but are documented here for consistency and to enable automated documentation.
|
||||
ToolsetMetadataCopilotSpaces = inventory.ToolsetMetadata{
|
||||
ID: "copilot_spaces",
|
||||
Description: "Copilot Spaces tools",
|
||||
Icon: "copilot",
|
||||
}
|
||||
ToolsetMetadataSupportSearch = inventory.ToolsetMetadata{
|
||||
ID: "github_support_docs_search",
|
||||
Description: "Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ...",
|
||||
Icon: "book",
|
||||
}
|
||||
)
|
||||
|
||||
// AllTools returns all tools with their embedded toolset metadata.
|
||||
// Tool functions return ServerTool directly with toolset info.
|
||||
func AllTools(t translations.TranslationHelperFunc) []inventory.ServerTool {
|
||||
return withCSVOutput([]inventory.ServerTool{
|
||||
// Context tools
|
||||
GetMe(t),
|
||||
GetTeams(t),
|
||||
GetTeamMembers(t),
|
||||
|
||||
// Repository tools
|
||||
SearchRepositories(t),
|
||||
GetFileContents(t),
|
||||
LegacyGetFileContents(t),
|
||||
ListCommits(t),
|
||||
LegacyListCommits(t),
|
||||
SearchCode(t),
|
||||
LegacySearchCode(t),
|
||||
SearchCommits(t),
|
||||
GetCommit(t),
|
||||
GetFileBlame(t),
|
||||
ListBranches(t),
|
||||
ListTags(t),
|
||||
GetTag(t),
|
||||
ListReleases(t),
|
||||
LegacyListReleases(t),
|
||||
GetLatestRelease(t),
|
||||
GetReleaseByTag(t),
|
||||
CreateOrUpdateFile(t),
|
||||
CreateRepository(t),
|
||||
ForkRepository(t),
|
||||
CreateBranch(t),
|
||||
PushFiles(t),
|
||||
DeleteFile(t),
|
||||
ListStarredRepositories(t),
|
||||
StarRepository(t),
|
||||
UnstarRepository(t),
|
||||
ListRepositoryCollaborators(t),
|
||||
|
||||
// Git tools
|
||||
GetRepositoryTree(t),
|
||||
|
||||
// Issue tools
|
||||
IssueRead(t),
|
||||
SearchIssues(t),
|
||||
LegacySearchIssues(t),
|
||||
ListIssues(t),
|
||||
LegacyListIssues(t),
|
||||
ListIssueTypes(t),
|
||||
ListIssueFields(t),
|
||||
IssueWrite(t),
|
||||
AddIssueComment(t),
|
||||
SubIssueWrite(t),
|
||||
IssueDependencyRead(t),
|
||||
IssueDependencyWrite(t),
|
||||
|
||||
// User tools
|
||||
SearchUsers(t),
|
||||
|
||||
// Organization tools
|
||||
SearchOrgs(t),
|
||||
|
||||
// Pull request tools
|
||||
PullRequestRead(t),
|
||||
ListPullRequests(t),
|
||||
LegacyListPullRequests(t),
|
||||
SearchPullRequests(t),
|
||||
LegacySearchPullRequests(t),
|
||||
MergePullRequest(t),
|
||||
UpdatePullRequestBranch(t),
|
||||
CreatePullRequest(t),
|
||||
UpdatePullRequest(t),
|
||||
PullRequestReviewWrite(t),
|
||||
AddCommentToPendingReview(t),
|
||||
AddReplyToPullRequestComment(t),
|
||||
|
||||
// Copilot tools
|
||||
AssignCopilotToIssue(t),
|
||||
RequestCopilotReview(t),
|
||||
|
||||
// Code quality tools
|
||||
GetCodeQualityFinding(t),
|
||||
|
||||
// Code security tools
|
||||
GetCodeScanningAlert(t),
|
||||
ListCodeScanningAlerts(t),
|
||||
|
||||
// Secret protection tools
|
||||
GetSecretScanningAlert(t),
|
||||
ListSecretScanningAlerts(t),
|
||||
|
||||
// Dependabot tools
|
||||
GetDependabotAlert(t),
|
||||
ListDependabotAlerts(t),
|
||||
|
||||
// Notification tools
|
||||
ListNotifications(t),
|
||||
GetNotificationDetails(t),
|
||||
DismissNotification(t),
|
||||
MarkAllNotificationsRead(t),
|
||||
ManageNotificationSubscription(t),
|
||||
ManageRepositoryNotificationSubscription(t),
|
||||
|
||||
// Discussion tools
|
||||
ListDiscussions(t),
|
||||
GetDiscussion(t),
|
||||
GetDiscussionComments(t),
|
||||
DiscussionCommentWrite(t),
|
||||
ListDiscussionCategories(t),
|
||||
|
||||
// Actions tools
|
||||
ActionsList(t),
|
||||
ActionsGet(t),
|
||||
ActionsRunTrigger(t),
|
||||
ActionsGetJobLogs(t),
|
||||
|
||||
// Security advisories tools
|
||||
ListGlobalSecurityAdvisories(t),
|
||||
GetGlobalSecurityAdvisory(t),
|
||||
ListRepositorySecurityAdvisories(t),
|
||||
ListOrgRepositorySecurityAdvisories(t),
|
||||
|
||||
// Gist tools
|
||||
ListGists(t),
|
||||
GetGist(t),
|
||||
CreateGist(t),
|
||||
UpdateGist(t),
|
||||
|
||||
// Project tools
|
||||
ProjectsList(t),
|
||||
ProjectsGet(t),
|
||||
ProjectsWrite(t),
|
||||
|
||||
// Label tools
|
||||
GetLabel(t),
|
||||
GetLabelForLabelsToolset(t),
|
||||
ListLabels(t),
|
||||
LabelWrite(t),
|
||||
|
||||
// UI tools (insiders only)
|
||||
UIGet(t),
|
||||
|
||||
// Granular issue tools (feature-flagged, replace consolidated issue_write/sub_issue_write)
|
||||
GranularCreateIssue(t),
|
||||
GranularUpdateIssueTitle(t),
|
||||
GranularUpdateIssueBody(t),
|
||||
GranularUpdateIssueAssignees(t),
|
||||
GranularUpdateIssueLabels(t),
|
||||
GranularUpdateIssueMilestone(t),
|
||||
GranularUpdateIssueType(t),
|
||||
GranularUpdateIssueState(t),
|
||||
GranularAddSubIssue(t),
|
||||
GranularRemoveSubIssue(t),
|
||||
GranularReprioritizeSubIssue(t),
|
||||
GranularSetIssueFields(t),
|
||||
GranularAddIssueReaction(t),
|
||||
GranularAddIssueCommentReaction(t),
|
||||
|
||||
// Granular pull request tools (feature-flagged, replace consolidated update_pull_request/pull_request_review_write)
|
||||
GranularUpdatePullRequestTitle(t),
|
||||
GranularUpdatePullRequestBody(t),
|
||||
GranularUpdatePullRequestState(t),
|
||||
GranularUpdatePullRequestDraftState(t),
|
||||
GranularRequestPullRequestReviewers(t),
|
||||
GranularCreatePullRequestReview(t),
|
||||
GranularSubmitPendingPullRequestReview(t),
|
||||
GranularDeletePendingPullRequestReview(t),
|
||||
GranularAddPullRequestReviewComment(t),
|
||||
GranularResolveReviewThread(t),
|
||||
GranularUnresolveReviewThread(t),
|
||||
GranularAddPullRequestReviewCommentReaction(t),
|
||||
})
|
||||
}
|
||||
|
||||
// ToBoolPtr converts a bool to a *bool pointer.
|
||||
func ToBoolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// ToStringPtr converts a string to a *string pointer.
|
||||
// Returns nil if the string is empty.
|
||||
func ToStringPtr(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
// GenerateToolsetsHelp generates the help text for the toolsets flag
|
||||
func GenerateToolsetsHelp() string {
|
||||
// Get toolset group to derive defaults and available toolsets
|
||||
// Build() can only fail if WithTools specifies invalid tools - not used here
|
||||
r, _ := NewInventory(stubTranslator).Build()
|
||||
|
||||
// Format default tools from metadata using strings.Builder
|
||||
var defaultBuf strings.Builder
|
||||
defaultIDs := r.DefaultToolsetIDs()
|
||||
for i, id := range defaultIDs {
|
||||
if i > 0 {
|
||||
defaultBuf.WriteString(", ")
|
||||
}
|
||||
defaultBuf.WriteString(string(id))
|
||||
}
|
||||
|
||||
// Get all available toolsets (excludes context for display)
|
||||
allToolsets := r.AvailableToolsets("context")
|
||||
var availableBuf strings.Builder
|
||||
const maxLineLength = 70
|
||||
currentLine := ""
|
||||
|
||||
for i, toolset := range allToolsets {
|
||||
id := string(toolset.ID)
|
||||
switch {
|
||||
case i == 0:
|
||||
currentLine = id
|
||||
case len(currentLine)+len(id)+2 <= maxLineLength:
|
||||
currentLine += ", " + id
|
||||
default:
|
||||
if availableBuf.Len() > 0 {
|
||||
availableBuf.WriteString(",\n\t ")
|
||||
}
|
||||
availableBuf.WriteString(currentLine)
|
||||
currentLine = id
|
||||
}
|
||||
}
|
||||
if currentLine != "" {
|
||||
if availableBuf.Len() > 0 {
|
||||
availableBuf.WriteString(",\n\t ")
|
||||
}
|
||||
availableBuf.WriteString(currentLine)
|
||||
}
|
||||
|
||||
// Build the complete help text using strings.Builder
|
||||
var buf strings.Builder
|
||||
buf.WriteString("Comma-separated list of tool groups to enable (no spaces).\n")
|
||||
buf.WriteString("Available: ")
|
||||
buf.WriteString(availableBuf.String())
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString("Special toolset keywords:\n")
|
||||
buf.WriteString(" - all: Enables all available toolsets\n")
|
||||
buf.WriteString(" - default: Enables the default toolset configuration of:\n\t ")
|
||||
buf.WriteString(defaultBuf.String())
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString("Examples:\n")
|
||||
buf.WriteString(" - --toolsets=actions,gists,notifications\n")
|
||||
buf.WriteString(" - Default + additional: --toolsets=default,actions,gists\n")
|
||||
buf.WriteString(" - All tools: --toolsets=all")
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// stubTranslator is a passthrough translator for cases where we need an Inventory
|
||||
// but don't need actual translations (e.g., getting toolset IDs for CLI help).
|
||||
func stubTranslator(_, fallback string) string { return fallback }
|
||||
|
||||
// AddDefaultToolset removes the default toolset and expands it to the actual default toolset IDs
|
||||
func AddDefaultToolset(result []string) []string {
|
||||
hasDefault := false
|
||||
seen := make(map[string]bool)
|
||||
for _, toolset := range result {
|
||||
seen[toolset] = true
|
||||
if toolset == string(ToolsetMetadataDefault.ID) {
|
||||
hasDefault = true
|
||||
}
|
||||
}
|
||||
|
||||
// Only expand if "default" keyword was found
|
||||
if !hasDefault {
|
||||
return result
|
||||
}
|
||||
|
||||
result = RemoveToolset(result, string(ToolsetMetadataDefault.ID))
|
||||
|
||||
// Get default toolset IDs from the Inventory
|
||||
// Build() can only fail if WithTools specifies invalid tools - not used here
|
||||
r, _ := NewInventory(stubTranslator).Build()
|
||||
for _, id := range r.DefaultToolsetIDs() {
|
||||
if !seen[string(id)] {
|
||||
result = append(result, string(id))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func RemoveToolset(tools []string, toRemove string) []string {
|
||||
result := make([]string, 0, len(tools))
|
||||
for _, tool := range tools {
|
||||
if tool != toRemove {
|
||||
result = append(result, tool)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ContainsToolset(tools []string, toCheck string) bool {
|
||||
return slices.Contains(tools, toCheck)
|
||||
}
|
||||
|
||||
// CleanTools cleans tool names by removing duplicates and trimming whitespace.
|
||||
// Validation of tool existence is done during registration.
|
||||
func CleanTools(toolNames []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
result := make([]string, 0, len(toolNames))
|
||||
|
||||
// Remove duplicates and trim whitespace
|
||||
for _, tool := range toolNames {
|
||||
trimmed := strings.TrimSpace(tool)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
if !seen[trimmed] {
|
||||
seen[trimmed] = true
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetDefaultToolsetIDs returns the IDs of toolsets marked as Default.
|
||||
// This is a convenience function that builds an inventory to determine defaults.
|
||||
func GetDefaultToolsetIDs() []string {
|
||||
// Build() can only fail if WithTools specifies invalid tools - not used here
|
||||
r, _ := NewInventory(stubTranslator).Build()
|
||||
ids := r.DefaultToolsetIDs()
|
||||
result := make([]string, len(ids))
|
||||
for i, id := range ids {
|
||||
result[i] = string(id)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// RemoteOnlyToolsets returns toolset metadata for toolsets that are only
|
||||
// available in the remote MCP server. These are documented but not registered
|
||||
// in the local server.
|
||||
func RemoteOnlyToolsets() []inventory.ToolsetMetadata {
|
||||
return []inventory.ToolsetMetadata{
|
||||
ToolsetMetadataCopilotSpaces,
|
||||
ToolsetMetadataSupportSearch,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user