Skip to content
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ The following sets of tools are available:
| `discussions` | GitHub Discussions related tools |
| `experiments` | Experimental features that are not considered stable yet |
| `gists` | GitHub Gist related tools |
| `github_docs` | GitHub Docs search related tools |
| `issues` | GitHub Issues related tools |
| `labels` | GitHub Labels related tools |
| `notifications` | GitHub Notifications related tools |
Expand Down Expand Up @@ -630,6 +631,18 @@ The following sets of tools are available:

<details>

<summary>GitHub Docs</summary>

- **search_github_docs** - Search GitHub Docs
- `language`: Language code for documentation. Options: 'en' (default), 'es', 'ja', 'pt', 'zh', 'ru', 'fr', 'ko', 'de' (string, optional)
- `max_results`: Maximum number of results to return (default: 10, max: 100) (number, optional)
- `query`: Search query for GitHub documentation. Examples: 'actions workflow syntax', 'pull request review', 'GitHub Pages' (string, required)
- `version`: GitHub version to search. Options: 'dotcom' (default, free/pro/team), 'ghec' (GitHub Enterprise Cloud), or a specific GHES version like '3.12' (string, optional)

</details>

<details>

<summary>Issues</summary>

- **add_issue_comment** - Add comment to issue
Expand Down
2 changes: 2 additions & 0 deletions cmd/github-mcp-server/generate_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ func formatToolsetName(name string) string {
return "Secret Protection"
case "orgs":
return "Organizations"
case "github_docs":
return "GitHub Docs"
default:
// Fallback: capitalize first letter and replace underscores with spaces
parts := strings.Split(name, "_")
Expand Down
1 change: 1 addition & 0 deletions docs/remote-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to
| Discussions | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) |
| Experiments | Experimental features that are not considered stable yet | https://api.githubcopilot.com/mcp/x/experiments | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/experiments/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%2Freadonly%22%7D) |
| Gists | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) |
| GitHub Docs | GitHub Docs search related tools | https://api.githubcopilot.com/mcp/x/github_docs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_docs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_docs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_docs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_docs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_docs%2Freadonly%22%7D) |
| Issues | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) |
| Labels | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) |
| Notifications | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) |
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM=
github.com/google/go-github/v74 v74.0.0/go.mod h1:ubn/YdyftV80VPSI26nSJvaEsTOnsjrxG3o9kJhcyak=
github.com/google/go-github/v76 v76.0.0 h1:MCa9VQn+VG5GG7Y7BAkBvSRUN3o+QpaEOuZwFPJmdFA=
github.com/google/go-github/v76 v76.0.0/go.mod h1:38+d/8pYDO4fBLYfBhXF5EKO0wA3UkXBjfmQapFsNCQ=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
Expand Down
32 changes: 32 additions & 0 deletions pkg/github/__toolsnaps__/search_github_docs.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"annotations": {
"title": "Search GitHub Docs",
"readOnlyHint": true
},
"description": "Search GitHub's official documentation at docs.github.com. Use this to find help articles, guides, and API documentation for GitHub features and products.",
"inputSchema": {
"properties": {
"language": {
"description": "Language code for documentation. Options: 'en' (default), 'es', 'ja', 'pt', 'zh', 'ru', 'fr', 'ko', 'de'",
"type": "string"
},
"max_results": {
"description": "Maximum number of results to return (default: 10, max: 100)",
"type": "number"
},
"query": {
"description": "Search query for GitHub documentation. Examples: 'actions workflow syntax', 'pull request review', 'GitHub Pages'",
"type": "string"
},
"version": {
"description": "GitHub version to search. Options: 'dotcom' (default, free/pro/team), 'ghec' (GitHub Enterprise Cloud), or a specific GHES version like '3.12'",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "search_github_docs"
}
144 changes: 144 additions & 0 deletions pkg/github/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package github

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"

"github.com/github/github-mcp-server/pkg/translations"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)

// DocsSearchResult represents a single search result from GitHub Docs
type DocsSearchResult struct {
Title string `json:"title"`
URL string `json:"url"`
Breadcrumbs string `json:"breadcrumbs"`
Content string `json:"content,omitempty"`
}

// DocsSearchResponse represents the response from GitHub Docs search API
type DocsSearchResponse struct {
Meta struct {
Found struct {
Value int `json:"value"`
} `json:"found"`
Took struct {
PrettyMs string `json:"pretty_ms"`
} `json:"took"`
} `json:"meta"`
Hits []DocsSearchResult `json:"hits"`
}

// SearchGitHubDocs creates a tool to search GitHub documentation.
func SearchGitHubDocs(t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("search_github_docs",
mcp.WithDescription(t("TOOL_SEARCH_GITHUB_DOCS_DESCRIPTION", "Search GitHub's official documentation at docs.github.com. Use this to find help articles, guides, and API documentation for GitHub features and products.")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
Title: t("TOOL_SEARCH_GITHUB_DOCS_USER_TITLE", "Search GitHub Docs"),
ReadOnlyHint: ToBoolPtr(true),
}),
mcp.WithString("query",
mcp.Required(),
mcp.Description("Search query for GitHub documentation. Examples: 'actions workflow syntax', 'pull request review', 'GitHub Pages'"),
),
mcp.WithString("version",
mcp.Description("GitHub version to search. Options: 'dotcom' (default, free/pro/team), 'ghec' (GitHub Enterprise Cloud), or a specific GHES version like '3.12'"),
),
mcp.WithString("language",
mcp.Description("Language code for documentation. Options: 'en' (default), 'es', 'ja', 'pt', 'zh', 'ru', 'fr', 'ko', 'de'"),
),
mcp.WithNumber("max_results",
mcp.Description("Maximum number of results to return (default: 10, max: 100)"),
),
),
func(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
query, err := RequiredParam[string](request, "query")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

version, err := OptionalParam[string](request, "version")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
if version == "" {
version = "dotcom"
}

language, err := OptionalParam[string](request, "language")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
if language == "" {
language = "en"
}

maxResults, err := OptionalIntParam(request, "max_results")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

// Check if max_results was explicitly provided
_, maxResultsProvided := request.GetArguments()["max_results"]
if maxResultsProvided {
// Validate max_results only if it was provided
if maxResults < 1 || maxResults > 100 {
return mcp.NewToolResultError("max_results must be between 1 and 100"), nil
}
} else {
// Use default if not provided
maxResults = 10
}

// Build the search URL with client_name parameter
searchURL := fmt.Sprintf("https://docs.github.com/api/search/v1?version=%s&language=%s&query=%s&limit=%d&client_name=github-mcp-server",
url.QueryEscape(version),
url.QueryEscape(language),
url.QueryEscape(query),
maxResults,
)

// Make the HTTP request
// #nosec G107 - URL is constructed from validated parameters with proper escaping
resp, err := http.Get(searchURL)
Comment on lines +107 to +108
Copy link

Copilot AI Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nosec comment justifies URL construction with proper escaping, but the URL concatenation in line 99-104 uses url.QueryEscape which is intended for query parameter values. For the entire URL construction, consider using url.Values with Encode() or ensure each component is escaped with the appropriate function (e.g., url.PathEscape for path segments if needed).

Copilot uses AI. Check for mistakes.
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to search GitHub Docs: %v", err)), nil
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return mcp.NewToolResultError(fmt.Sprintf("GitHub Docs API returned status %d: %s", resp.StatusCode, string(body))), nil
}

// Parse the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to read response body: %v", err)), nil
}

var searchResp DocsSearchResponse
if err := json.Unmarshal(body, &searchResp); err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to parse response: %v", err)), nil
}

// Format the results
result := map[string]interface{}{
"total_results": searchResp.Meta.Found.Value,
"search_time": searchResp.Meta.Took.PrettyMs,
"results": searchResp.Hits,
}

resultJSON, err := json.Marshal(result)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to format results: %v", err)), nil
}

return mcp.NewToolResultText(string(resultJSON)), nil
}
}
Loading
Loading