6b3c375492
* Upgrade MCP Go SDK to v1.2.0-pre.1 and add Octicon icons to tools - Upgrade MCP Go SDK from v1.1.0 to v1.2.0-pre.1 for Icon support - Add Icon field to ToolsetMetadata for Octicon name assignment - Add OcticonURL() helper to generate CDN URLs for Octicon SVGs - Add Icons() method on ToolsetMetadata to generate MCP Icon objects - Apply icons automatically in RegisterFunc when tool is registered - Add icons to all 22 toolset metadata constants with appropriate Octicons - Update server.go to use new Capabilities API (fixes deprecation warnings) This demonstrates how the toolsets refactor makes adding new features simpler: icons are defined once in ToolsetMetadata and automatically applied to all tools in that toolset during registration. * Update third-party licenses for SDK upgrade * Address review feedback: enum size validation, mutation fix, tests - Replace runtime size validation with compile-time enum type (Size with SizeSM=16, SizeLG=24) - Fix RegisterFunc mutation by making shallow copy of tool before modifying Icons - Add comprehensive tests for octicons package (URL, Icons, Size constants) - Add toolsets tests for ToolsetMetadata.Icons(), RegisterFunc mutation prevention, and existing icon preservation - Improve icon choices for better visual semantics: - actions: play → workflow (more specific to GitHub Actions) - secret_protection: key → shield-lock (better represents protection) - gists: code → logo-gist (dedicated gist icon exists) * Add GitHub mark icon to server metadata Add the mark-github octicon to the server's Implementation struct so that MCP clients can display the GitHub logo for this server. The icon is provided in both 16x16 and 24x24 SVG sizes. * Fix rebase conflicts: use Registry methods and NullTranslationHelper - Remove duplicate old toolsets functions (AvailableToolsets, GetValidToolsetIDs, GetDefaultToolsetIDs) - Use Registry.AvailableToolsets() and Registry.HasToolset() instead - Replace stubTranslator with translations.NullTranslationHelper - Use new SDK Capabilities struct instead of deprecated HasTools/HasResources/HasPrompts - Add icon-related tests to registry_test.go * Use embedded data URIs for Octicon icons - Embed SVG icons using go:embed for offline use and faster loading - Convert icons to base64 data URIs at runtime - Fall back to CDN URL for non-embedded icons - Add test to verify all toolset icons are properly embedded - 44 SVG files (22 icons × 2 sizes) totaling ~27KB * Convert icons from SVG to PNG for MCP client compatibility MCP clients don't support SVG data URIs, so convert all embedded icons to PNG format using rsvg-convert. Changes: - Convert all 44 SVG icons to PNG format - Add 8 new icons: copilot, git-merge, repo-forked, star-fill - Update octicons.go to use PNG MIME type - Add script/fetch-icons for easy icon management - Update tests and toolsnaps for PNG format * Add mark-github icon for server metadata * Add light/dark theme icons for tools, resources, and prompts - Switch from size-based (16/24px) to theme-based (light/dark) icons - Use only 16x16 icons for smaller bundle size - Generate white (inverted) icons for dark theme backgrounds - Add icons to resources and prompts (auto-applied from toolset metadata) - Add 'file' icon for repository content resources - Update fetch-icons script to generate both theme variants * Use 24px icons with SVG fill modification for themes - Switch from 16px to 24px icons for better visibility - Use SVG fill attribute (#24292f for light, #ffffff for dark) instead of ImageMagick color inversion for cleaner theme variants - Remove ImageMagick dependency from fetch-icons script * Add specific icons for each repository resource type - repository_content: repo icon - repository_content_branch: git-branch icon - repository_content_commit: git-commit icon (new) - repository_content_tag: tag icon - repository_content_pr: git-pull-request icon Resources now have explicit icons set rather than relying on toolset fallback. * fix: restore Icon fields to toolset metadata and add icons to docs - Add Icon field to all ToolsetMetadata definitions (lost during rebase conflict resolution) - Update doc generator to include Octicon icons in toolsets table - Update doc generator to include icons in tool section headers - Use Primer Octicons CDN for GitHub markdown compatibility * feat: add icons to individual tools in documentation * fix: use repo-local icons with picture element for GitHub theme support - Reference icons from pkg/octicons/icons/ instead of external CDN - Use picture element with prefers-color-scheme for light/dark mode - GitHub markdown renderer will display these correctly * fix: remove redundant icons from individual tools Icons are kept on section headers and toolsets table only - having the same icon on every tool within a section was visually noisy and redundant. * Add icons to remote server toolsets documentation * Fix icon paths for docs/remote-server.md * Add remote-only toolsets with auto-generated documentation and icons guide - Add ToolsetMetadataCopilot, ToolsetMetadataCopilotSpaces, ToolsetMetadataSupportSearch - Add RemoteOnlyToolsets() function to return remote-only toolset metadata - Update doc generator to auto-generate remote-only toolsets table with icons - Create docs/toolsets-and-icons.md explaining how to add icons to toolsets - Add link to icons guide in CONTRIBUTING.md * Add icon validation tests and single source of truth for required icons - Add pkg/octicons/required_icons.txt as single source of truth for icons - Add RequiredIcons() function to read the required icons list - Update script/fetch-icons to read from required_icons.txt - Update octicons_test.go to use RequiredIcons() instead of hardcoded list - Add pkg/github/toolset_icons_test.go with: - TestAllToolsetIconsExist: validates all toolset icons are embedded - TestToolsetMetadataHasIcons: ensures all toolsets have icons set - Add 'book' icon for SupportSearch toolset - Update docs/toolsets-and-icons.md with fetch-icons and CI validation docs * fix: remove unused icon parameter from writeToolDoc - Remove unused 'icon' parameter from writeToolDoc function signature - Fix whitespace inconsistency in octicons_test.go - Fixes lint failure: unused-parameter revive error * fix: combine icon with name column in remote docs for proper table rendering - Move icon from separate column to Name column with <br> separator - Keep <picture> element for light/dark theme support - Remove empty icon column that was collapsing to zero width - Remove unused octiconSimpleImg function
460 lines
13 KiB
Go
460 lines
13 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/github/github-mcp-server/pkg/inventory"
|
|
"github.com/github/github-mcp-server/pkg/translations"
|
|
"github.com/google/go-github/v79/github"
|
|
"github.com/shurcooL/githubv4"
|
|
)
|
|
|
|
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",
|
|
}
|
|
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",
|
|
}
|
|
ToolsetMetadataPullRequests = inventory.ToolsetMetadata{
|
|
ID: "pull_requests",
|
|
Description: "GitHub Pull Request related tools",
|
|
Default: true,
|
|
Icon: "git-pull-request",
|
|
}
|
|
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",
|
|
}
|
|
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",
|
|
}
|
|
ToolsetMetadataExperiments = inventory.ToolsetMetadata{
|
|
ID: "experiments",
|
|
Description: "Experimental features that are not considered stable yet",
|
|
Icon: "beaker",
|
|
}
|
|
ToolsetMetadataDiscussions = inventory.ToolsetMetadata{
|
|
ID: "discussions",
|
|
Description: "GitHub Discussions related tools",
|
|
Icon: "comment-discussion",
|
|
}
|
|
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",
|
|
}
|
|
ToolsetMetadataStargazers = inventory.ToolsetMetadata{
|
|
ID: "stargazers",
|
|
Description: "GitHub Stargazers related tools",
|
|
Icon: "star",
|
|
}
|
|
ToolsetMetadataDynamic = inventory.ToolsetMetadata{
|
|
ID: "dynamic",
|
|
Description: "Discover GitHub MCP tools that can help achieve tasks by enabling additional sets of tools, you can control the enablement of any toolset to access its tools when this toolset is enabled.",
|
|
Icon: "tools",
|
|
}
|
|
ToolsetLabels = inventory.ToolsetMetadata{
|
|
ID: "labels",
|
|
Description: "GitHub Labels related tools",
|
|
Icon: "tag",
|
|
}
|
|
|
|
// Remote-only toolsets - these are only available in the remote MCP server
|
|
// but are documented here for consistency and to enable automated documentation.
|
|
ToolsetMetadataCopilot = inventory.ToolsetMetadata{
|
|
ID: "copilot",
|
|
Description: "Copilot related tools",
|
|
Icon: "copilot",
|
|
}
|
|
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 []inventory.ServerTool{
|
|
// Context tools
|
|
GetMe(t),
|
|
GetTeams(t),
|
|
GetTeamMembers(t),
|
|
|
|
// Repository tools
|
|
SearchRepositories(t),
|
|
GetFileContents(t),
|
|
ListCommits(t),
|
|
SearchCode(t),
|
|
GetCommit(t),
|
|
ListBranches(t),
|
|
ListTags(t),
|
|
GetTag(t),
|
|
ListReleases(t),
|
|
GetLatestRelease(t),
|
|
GetReleaseByTag(t),
|
|
CreateOrUpdateFile(t),
|
|
CreateRepository(t),
|
|
ForkRepository(t),
|
|
CreateBranch(t),
|
|
PushFiles(t),
|
|
DeleteFile(t),
|
|
ListStarredRepositories(t),
|
|
StarRepository(t),
|
|
UnstarRepository(t),
|
|
|
|
// Git tools
|
|
GetRepositoryTree(t),
|
|
|
|
// Issue tools
|
|
IssueRead(t),
|
|
SearchIssues(t),
|
|
ListIssues(t),
|
|
ListIssueTypes(t),
|
|
IssueWrite(t),
|
|
AddIssueComment(t),
|
|
AssignCopilotToIssue(t),
|
|
SubIssueWrite(t),
|
|
|
|
// User tools
|
|
SearchUsers(t),
|
|
|
|
// Organization tools
|
|
SearchOrgs(t),
|
|
|
|
// Pull request tools
|
|
PullRequestRead(t),
|
|
ListPullRequests(t),
|
|
SearchPullRequests(t),
|
|
MergePullRequest(t),
|
|
UpdatePullRequestBranch(t),
|
|
CreatePullRequest(t),
|
|
UpdatePullRequest(t),
|
|
RequestCopilotReview(t),
|
|
PullRequestReviewWrite(t),
|
|
AddCommentToPendingReview(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),
|
|
ListDiscussionCategories(t),
|
|
|
|
// Actions tools
|
|
ListWorkflows(t),
|
|
ListWorkflowRuns(t),
|
|
GetWorkflowRun(t),
|
|
GetWorkflowRunLogs(t),
|
|
ListWorkflowJobs(t),
|
|
GetJobLogs(t),
|
|
ListWorkflowRunArtifacts(t),
|
|
DownloadWorkflowRunArtifact(t),
|
|
GetWorkflowRunUsage(t),
|
|
RunWorkflow(t),
|
|
RerunWorkflowRun(t),
|
|
RerunFailedJobs(t),
|
|
CancelWorkflowRun(t),
|
|
DeleteWorkflowRunLogs(t),
|
|
|
|
// Security advisories tools
|
|
ListGlobalSecurityAdvisories(t),
|
|
GetGlobalSecurityAdvisory(t),
|
|
ListRepositorySecurityAdvisories(t),
|
|
ListOrgRepositorySecurityAdvisories(t),
|
|
|
|
// Gist tools
|
|
ListGists(t),
|
|
GetGist(t),
|
|
CreateGist(t),
|
|
UpdateGist(t),
|
|
|
|
// Project tools
|
|
ListProjects(t),
|
|
GetProject(t),
|
|
ListProjectFields(t),
|
|
GetProjectField(t),
|
|
ListProjectItems(t),
|
|
GetProjectItem(t),
|
|
AddProjectItem(t),
|
|
DeleteProjectItem(t),
|
|
UpdateProjectItem(t),
|
|
|
|
// Label tools
|
|
GetLabel(t),
|
|
GetLabelForLabelsToolset(t),
|
|
ListLabels(t),
|
|
LabelWrite(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
|
|
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 and dynamic for display)
|
|
allToolsets := r.AvailableToolsets("context", "dynamic")
|
|
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
|
|
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 {
|
|
for _, tool := range tools {
|
|
if tool == toCheck {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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 {
|
|
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{
|
|
ToolsetMetadataCopilot,
|
|
ToolsetMetadataCopilotSpaces,
|
|
ToolsetMetadataSupportSearch,
|
|
}
|
|
}
|