Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0164670
Upgrade MCP Go SDK to v1.2.0-pre.1 and add Octicon icons to tools
SamMorrowDrums Dec 13, 2025
c9d74c3
Update third-party licenses for SDK upgrade
SamMorrowDrums Dec 13, 2025
b6bf6cc
Address review feedback: enum size validation, mutation fix, tests
SamMorrowDrums Dec 14, 2025
a89bf6e
Add GitHub mark icon to server metadata
SamMorrowDrums Dec 15, 2025
d750be6
Fix rebase conflicts: use Registry methods and NullTranslationHelper
SamMorrowDrums Dec 15, 2025
83b24eb
Use embedded data URIs for Octicon icons
SamMorrowDrums Dec 15, 2025
4c3ab5a
Convert icons from SVG to PNG for MCP client compatibility
SamMorrowDrums Dec 15, 2025
b0080e1
Add mark-github icon for server metadata
SamMorrowDrums Dec 15, 2025
20878ea
Add light/dark theme icons for tools, resources, and prompts
SamMorrowDrums Dec 15, 2025
a8cc231
Use 24px icons with SVG fill modification for themes
SamMorrowDrums Dec 15, 2025
163e134
Add specific icons for each repository resource type
SamMorrowDrums Dec 15, 2025
c619997
fix: restore Icon fields to toolset metadata and add icons to docs
SamMorrowDrums Dec 17, 2025
6f1f609
feat: add icons to individual tools in documentation
SamMorrowDrums Dec 17, 2025
8e2583b
fix: use repo-local icons with picture element for GitHub theme support
SamMorrowDrums Dec 17, 2025
f4a8d47
fix: remove redundant icons from individual tools
SamMorrowDrums Dec 17, 2025
6e7948c
Add icons to remote server toolsets documentation
SamMorrowDrums Dec 17, 2025
3df07ae
Fix icon paths for docs/remote-server.md
SamMorrowDrums Dec 17, 2025
54fac00
Add remote-only toolsets with auto-generated documentation and icons …
SamMorrowDrums Dec 17, 2025
03d2f16
Add icon validation tests and single source of truth for required icons
SamMorrowDrums Dec 17, 2025
cb1ccd4
fix: remove unused icon parameter from writeToolDoc
SamMorrowDrums Dec 17, 2025
238263b
fix: combine icon with name column in remote docs for proper table re…
SamMorrowDrums Dec 17, 2025
2032928
Merge branch 'main' into SamMorrowDrums/add-icons-to-tools
SamMorrowDrums Dec 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ These are one time installations required to be able to test your changes locall
- Update snapshots and run tests: `UPDATE_TOOLSNAPS=true go test ./...`
- Update readme documentation: `script/generate-docs`
- If renaming a tool, add a deprecation alias (see [Tool Renaming Guide](docs/tool-renaming.md))
- For toolset and icon configuration, see [Toolsets and Icons Guide](docs/toolsets-and-icons.md)
6. Push to your fork and [submit a pull request][pr] targeting the `main` branch
7. Pat yourself on the back and wait for your pull request to be reviewed and merged.

Expand Down
76 changes: 38 additions & 38 deletions README.md

Large diffs are not rendered by default.

104 changes: 91 additions & 13 deletions cmd/github-mcp-server/generate_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,52 @@ func generateRemoteServerDocs(docsPath string) error {
return err
}

// Also generate remote-only toolsets section
remoteOnlyDoc := generateRemoteOnlyToolsetsDoc()
updatedContent, err = replaceSection(updatedContent, "START AUTOMATED REMOTE TOOLSETS", "END AUTOMATED REMOTE TOOLSETS", remoteOnlyDoc)
if err != nil {
return err
}

return os.WriteFile(docsPath, []byte(updatedContent), 0600) //#nosec G306
}

// octiconImg returns an img tag for an Octicon that works with GitHub's light/dark theme.
// Uses picture element with prefers-color-scheme for automatic theme switching.
// References icons from the repo's pkg/octicons/icons directory.
// Optional pathPrefix for files in subdirectories (e.g., "../" for docs/).
func octiconImg(name string, pathPrefix ...string) string {
if name == "" {
return ""
}
prefix := ""
if len(pathPrefix) > 0 {
prefix = pathPrefix[0]
}
// Use picture element with media queries for light/dark mode support
// GitHub renders these correctly in markdown
lightIcon := fmt.Sprintf("%spkg/octicons/icons/%s-light.png", prefix, name)
darkIcon := fmt.Sprintf("%spkg/octicons/icons/%s-dark.png", prefix, name)
return fmt.Sprintf(`<picture><source media="(prefers-color-scheme: dark)" srcset="%s"><source media="(prefers-color-scheme: light)" srcset="%s"><img src="%s" width="20" height="20" alt="%s"></picture>`, darkIcon, lightIcon, lightIcon, name)
}

func generateToolsetsDoc(i *inventory.Inventory) string {
var buf strings.Builder

// Add table header and separator
buf.WriteString("| Toolset | Description |\n")
buf.WriteString("| ----------------------- | ------------------------------------------------------------- |\n")
// Add table header and separator (with icon column)
buf.WriteString("| | Toolset | Description |\n")
buf.WriteString("| --- | ----------------------- | ------------------------------------------------------------- |\n")

// Add the context toolset row with custom description (strongly recommended)
buf.WriteString("| `context` | **Strongly recommended**: Tools that provide context about the current user and GitHub context you are operating in |\n")
// Get context toolset for its icon
contextIcon := octiconImg("person")
fmt.Fprintf(&buf, "| %s | `context` | **Strongly recommended**: Tools that provide context about the current user and GitHub context you are operating in |\n", contextIcon)

// AvailableToolsets() returns toolsets that have tools, sorted by ID
// Exclude context (custom description above) and dynamic (internal only)
for _, ts := range i.AvailableToolsets("context", "dynamic") {
fmt.Fprintf(&buf, "| `%s` | %s |\n", ts.ID, ts.Description)
icon := octiconImg(ts.Icon)
fmt.Fprintf(&buf, "| %s | `%s` | %s |\n", icon, ts.ID, ts.Description)
}

return strings.TrimSuffix(buf.String(), "\n")
Expand All @@ -134,6 +163,7 @@ func generateToolsDoc(r *inventory.Inventory) string {
var buf strings.Builder
var toolBuf strings.Builder
var currentToolsetID inventory.ToolsetID
var currentToolsetIcon string
firstSection := true

writeSection := func() {
Expand All @@ -145,7 +175,11 @@ func generateToolsDoc(r *inventory.Inventory) string {
}
firstSection = false
sectionName := formatToolsetName(string(currentToolsetID))
fmt.Fprintf(&buf, "<details>\n\n<summary>%s</summary>\n\n%s\n\n</details>", sectionName, strings.TrimSuffix(toolBuf.String(), "\n\n"))
icon := octiconImg(currentToolsetIcon)
if icon != "" {
icon += " "
}
fmt.Fprintf(&buf, "<details>\n\n<summary>%s%s</summary>\n\n%s\n\n</details>", icon, sectionName, strings.TrimSuffix(toolBuf.String(), "\n\n"))
toolBuf.Reset()
}

Expand All @@ -154,6 +188,7 @@ func generateToolsDoc(r *inventory.Inventory) string {
if tool.Toolset.ID != currentToolsetID {
writeSection()
currentToolsetID = tool.Toolset.ID
currentToolsetIcon = tool.Toolset.Icon
}
writeToolDoc(&toolBuf, tool.Tool)
toolBuf.WriteString("\n\n")
Expand Down Expand Up @@ -190,7 +225,7 @@ func formatToolsetName(name string) string {
}

func writeToolDoc(buf *strings.Builder, tool mcp.Tool) {
// Tool name only (using annotation name instead of verbose description)
// Tool name (no icon - section header already has the toolset icon)
fmt.Fprintf(buf, "- **%s** - %s\n", tool.Name, tool.Annotations.Title)

// Parameters
Expand Down Expand Up @@ -302,12 +337,13 @@ func generateRemoteToolsetsDoc() string {
// Build inventory - stateless
r := github.NewInventory(t).Build()

// Generate table header
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
buf.WriteString("|----------------|--------------------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n")
// Generate table header (icon is combined with Name column)
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
buf.WriteString("| ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- |\n")

// Add "all" toolset first (special case)
buf.WriteString("| all | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2F%22%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Freadonly%22%7D) |\n")
allIcon := octiconImg("apps", "../")
fmt.Fprintf(&buf, "| %s<br>all | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2F%%22%%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2Freadonly%%22%%7D) |\n", allIcon)

// AvailableToolsets() returns toolsets that have tools, sorted by ID
// Exclude context (handled separately) and dynamic (internal only)
Expand All @@ -329,19 +365,61 @@ func generateRemoteToolsetsDoc() string {
installLink := fmt.Sprintf("[Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, installConfig)
readonlyInstallLink := fmt.Sprintf("[Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, readonlyConfig)

fmt.Fprintf(&buf, "| %-14s | %-48s | %-53s | %-218s | %-110s | %-288s |\n",
icon := octiconImg(ts.Icon, "../")
fmt.Fprintf(&buf, "| %s<br>%s | %s | %s | %s | [read-only](%s) | %s |\n",
icon,
formattedName,
ts.Description,
apiURL,
installLink,
fmt.Sprintf("[read-only](%s)", readonlyURL),
readonlyURL,
readonlyInstallLink,
)
}

return strings.TrimSuffix(buf.String(), "\n")
}

func generateRemoteOnlyToolsetsDoc() string {
var buf strings.Builder

// Generate table header (icon is combined with Name column)
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
buf.WriteString("| ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- |\n")

// Use RemoteOnlyToolsets from github package
for _, ts := range github.RemoteOnlyToolsets() {
idStr := string(ts.ID)

formattedName := formatToolsetName(idStr)
apiURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s", idStr)
readonlyURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s/readonly", idStr)

// Create install config JSON (URL encoded)
installConfig := url.QueryEscape(fmt.Sprintf(`{"type": "http","url": "%s"}`, apiURL))
readonlyConfig := url.QueryEscape(fmt.Sprintf(`{"type": "http","url": "%s"}`, readonlyURL))

// Fix URL encoding to use %20 instead of + for spaces
installConfig = strings.ReplaceAll(installConfig, "+", "%20")
readonlyConfig = strings.ReplaceAll(readonlyConfig, "+", "%20")

installLink := fmt.Sprintf("[Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, installConfig)
readonlyInstallLink := fmt.Sprintf("[Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, readonlyConfig)

icon := octiconImg(ts.Icon, "../")
fmt.Fprintf(&buf, "| %s<br>%s | %s | %s | %s | [read-only](%s) | %s |\n",
icon,
formattedName,
ts.Description,
apiURL,
installLink,
readonlyURL,
readonlyInstallLink,
)
}

return strings.TrimSuffix(buf.String(), "\n")
}
func generateDeprecatedAliasesDocs(docsPath string) error {
// Read the current file
content, err := os.ReadFile(docsPath) //#nosec G304
Expand Down
Loading