Skip to content

Commit 7173b3c

Browse files
Merge branch 'main' into actions-consolidation-ff
2 parents 94c7455 + 97feb5c commit 7173b3c

File tree

103 files changed

+1140
-198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+1140
-198
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ These are one time installations required to be able to test your changes locall
4040
- Update snapshots and run tests: `UPDATE_TOOLSNAPS=true go test ./...`
4141
- Update readme documentation: `script/generate-docs`
4242
- If renaming a tool, add a deprecation alias (see [Tool Renaming Guide](docs/tool-renaming.md))
43+
- For toolset and icon configuration, see [Toolsets and Icons Guide](docs/toolsets-and-icons.md)
4344
6. Push to your fork and [submit a pull request][pr] targeting the `main` branch
4445
7. Pat yourself on the back and wait for your pull request to be reviewed and merged.
4546

README.md

Lines changed: 38 additions & 38 deletions
Large diffs are not rendered by default.

cmd/github-mcp-server/generate_docs.go

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func generateAllDocs() error {
3636
// File to edit, function to generate its docs
3737
{"README.md", generateReadmeDocs},
3838
{"docs/remote-server.md", generateRemoteServerDocs},
39-
{"docs/deprecated-tool-aliases.md", generateDeprecatedAliasesDocs},
39+
{"docs/tool-renaming.md", generateDeprecatedAliasesDocs},
4040
} {
4141
if err := doc.fn(doc.path); err != nil {
4242
return fmt.Errorf("failed to generate docs for %s: %w", doc.path, err)
@@ -101,23 +101,52 @@ func generateRemoteServerDocs(docsPath string) error {
101101
return err
102102
}
103103

104+
// Also generate remote-only toolsets section
105+
remoteOnlyDoc := generateRemoteOnlyToolsetsDoc()
106+
updatedContent, err = replaceSection(updatedContent, "START AUTOMATED REMOTE TOOLSETS", "END AUTOMATED REMOTE TOOLSETS", remoteOnlyDoc)
107+
if err != nil {
108+
return err
109+
}
110+
104111
return os.WriteFile(docsPath, []byte(updatedContent), 0600) //#nosec G306
105112
}
106113

114+
// octiconImg returns an img tag for an Octicon that works with GitHub's light/dark theme.
115+
// Uses picture element with prefers-color-scheme for automatic theme switching.
116+
// References icons from the repo's pkg/octicons/icons directory.
117+
// Optional pathPrefix for files in subdirectories (e.g., "../" for docs/).
118+
func octiconImg(name string, pathPrefix ...string) string {
119+
if name == "" {
120+
return ""
121+
}
122+
prefix := ""
123+
if len(pathPrefix) > 0 {
124+
prefix = pathPrefix[0]
125+
}
126+
// Use picture element with media queries for light/dark mode support
127+
// GitHub renders these correctly in markdown
128+
lightIcon := fmt.Sprintf("%spkg/octicons/icons/%s-light.png", prefix, name)
129+
darkIcon := fmt.Sprintf("%spkg/octicons/icons/%s-dark.png", prefix, name)
130+
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)
131+
}
132+
107133
func generateToolsetsDoc(i *inventory.Inventory) string {
108134
var buf strings.Builder
109135

110-
// Add table header and separator
111-
buf.WriteString("| Toolset | Description |\n")
112-
buf.WriteString("| ----------------------- | ------------------------------------------------------------- |\n")
136+
// Add table header and separator (with icon column)
137+
buf.WriteString("| | Toolset | Description |\n")
138+
buf.WriteString("| --- | ----------------------- | ------------------------------------------------------------- |\n")
113139

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

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

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

139169
writeSection := func() {
@@ -145,7 +175,11 @@ func generateToolsDoc(r *inventory.Inventory) string {
145175
}
146176
firstSection = false
147177
sectionName := formatToolsetName(string(currentToolsetID))
148-
fmt.Fprintf(&buf, "<details>\n\n<summary>%s</summary>\n\n%s\n\n</details>", sectionName, strings.TrimSuffix(toolBuf.String(), "\n\n"))
178+
icon := octiconImg(currentToolsetIcon)
179+
if icon != "" {
180+
icon += " "
181+
}
182+
fmt.Fprintf(&buf, "<details>\n\n<summary>%s%s</summary>\n\n%s\n\n</details>", icon, sectionName, strings.TrimSuffix(toolBuf.String(), "\n\n"))
149183
toolBuf.Reset()
150184
}
151185

@@ -154,6 +188,7 @@ func generateToolsDoc(r *inventory.Inventory) string {
154188
if tool.Toolset.ID != currentToolsetID {
155189
writeSection()
156190
currentToolsetID = tool.Toolset.ID
191+
currentToolsetIcon = tool.Toolset.Icon
157192
}
158193
writeToolDoc(&toolBuf, tool.Tool)
159194
toolBuf.WriteString("\n\n")
@@ -190,7 +225,7 @@ func formatToolsetName(name string) string {
190225
}
191226

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

196231
// Parameters
@@ -302,12 +337,13 @@ func generateRemoteToolsetsDoc() string {
302337
// Build inventory - stateless
303338
r := github.NewInventory(t).Build()
304339

305-
// Generate table header
306-
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
307-
buf.WriteString("|----------------|--------------------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n")
340+
// Generate table header (icon is combined with Name column)
341+
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
342+
buf.WriteString("| ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- |\n")
308343

309344
// Add "all" toolset first (special case)
310-
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")
345+
allIcon := octiconImg("apps", "../")
346+
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)
311347

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

332-
fmt.Fprintf(&buf, "| %-14s | %-48s | %-53s | %-218s | %-110s | %-288s |\n",
368+
icon := octiconImg(ts.Icon, "../")
369+
fmt.Fprintf(&buf, "| %s<br>%s | %s | %s | %s | [read-only](%s) | %s |\n",
370+
icon,
333371
formattedName,
334372
ts.Description,
335373
apiURL,
336374
installLink,
337-
fmt.Sprintf("[read-only](%s)", readonlyURL),
375+
readonlyURL,
338376
readonlyInstallLink,
339377
)
340378
}
341379

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

383+
func generateRemoteOnlyToolsetsDoc() string {
384+
var buf strings.Builder
385+
386+
// Generate table header (icon is combined with Name column)
387+
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
388+
buf.WriteString("| ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- |\n")
389+
390+
// Use RemoteOnlyToolsets from github package
391+
for _, ts := range github.RemoteOnlyToolsets() {
392+
idStr := string(ts.ID)
393+
394+
formattedName := formatToolsetName(idStr)
395+
apiURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s", idStr)
396+
readonlyURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s/readonly", idStr)
397+
398+
// Create install config JSON (URL encoded)
399+
installConfig := url.QueryEscape(fmt.Sprintf(`{"type": "http","url": "%s"}`, apiURL))
400+
readonlyConfig := url.QueryEscape(fmt.Sprintf(`{"type": "http","url": "%s"}`, readonlyURL))
401+
402+
// Fix URL encoding to use %20 instead of + for spaces
403+
installConfig = strings.ReplaceAll(installConfig, "+", "%20")
404+
readonlyConfig = strings.ReplaceAll(readonlyConfig, "+", "%20")
405+
406+
installLink := fmt.Sprintf("[Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, installConfig)
407+
readonlyInstallLink := fmt.Sprintf("[Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, readonlyConfig)
408+
409+
icon := octiconImg(ts.Icon, "../")
410+
fmt.Fprintf(&buf, "| %s<br>%s | %s | %s | %s | [read-only](%s) | %s |\n",
411+
icon,
412+
formattedName,
413+
ts.Description,
414+
apiURL,
415+
installLink,
416+
readonlyURL,
417+
readonlyInstallLink,
418+
)
419+
}
420+
421+
return strings.TrimSuffix(buf.String(), "\n")
422+
}
345423
func generateDeprecatedAliasesDocs(docsPath string) error {
346424
// Read the current file
347425
content, err := os.ReadFile(docsPath) //#nosec G304

docs/deprecated-tool-aliases.md

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)