chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:57 +08:00
commit e30f8ba47c
533 changed files with 115926 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
package buffer
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
)
// maxLineSize is the maximum size for a single log line (10MB).
// GitHub Actions logs can contain extremely long lines (base64 content, minified JS, etc.)
const maxLineSize = 10 * 1024 * 1024
// ProcessResponseAsRingBufferToEnd reads the body of an HTTP response line by line,
// storing only the last maxJobLogLines lines using a ring buffer (sliding window).
// This efficiently retains the most recent lines, overwriting older ones as needed.
//
// Parameters:
//
// httpResp: The HTTP response whose body will be read.
// maxJobLogLines: The maximum number of log lines to retain.
//
// Returns:
//
// string: The concatenated log lines (up to maxJobLogLines), separated by newlines.
// int: The total number of lines read from the response.
// *http.Response: The original HTTP response.
// error: Any error encountered during reading.
//
// The function uses a ring buffer to efficiently store only the last maxJobLogLines lines.
// If the response contains more lines than maxJobLogLines, only the most recent lines are kept.
// Lines exceeding maxLineSize are truncated with a marker.
func ProcessResponseAsRingBufferToEnd(httpResp *http.Response, maxJobLogLines int) (string, int, *http.Response, error) {
if maxJobLogLines <= 0 {
maxJobLogLines = 500
}
if maxJobLogLines > 100000 {
maxJobLogLines = 100000
}
lines := make([]string, maxJobLogLines)
validLines := make([]bool, maxJobLogLines)
totalLines := 0
writeIndex := 0
const readBufferSize = 64 * 1024 // 64KB read buffer
const maxDisplayLength = 1000 // Keep first 1000 chars of truncated lines
readBuf := make([]byte, readBufferSize)
var currentLine strings.Builder
lineTruncated := false
// storeLine saves the current line to the ring buffer and resets state
storeLine := func() {
line := currentLine.String()
if lineTruncated && len(line) > maxDisplayLength {
line = line[:maxDisplayLength]
}
if lineTruncated {
line += "... [TRUNCATED]"
}
lines[writeIndex] = line
validLines[writeIndex] = true
totalLines++
writeIndex = (writeIndex + 1) % maxJobLogLines
currentLine.Reset()
lineTruncated = false
}
// accumulate adds bytes to currentLine up to maxLineSize, sets lineTruncated if exceeded
accumulate := func(data []byte) {
if lineTruncated {
return
}
remaining := maxLineSize - currentLine.Len()
if remaining <= 0 {
lineTruncated = true
return
}
if remaining > len(data) {
remaining = len(data)
}
currentLine.Write(data[:remaining])
if currentLine.Len() >= maxLineSize {
lineTruncated = true
}
}
for {
n, err := httpResp.Body.Read(readBuf)
if n > 0 {
chunk := readBuf[:n]
for len(chunk) > 0 {
newlineIdx := bytes.IndexByte(chunk, '\n')
if newlineIdx < 0 {
accumulate(chunk)
break
}
accumulate(chunk[:newlineIdx])
storeLine()
chunk = chunk[newlineIdx+1:]
}
}
if err == io.EOF {
if currentLine.Len() > 0 {
storeLine()
}
break
}
if err != nil {
return "", 0, httpResp, fmt.Errorf("failed to read log content: %w", err)
}
}
var result []string
linesInBuffer := min(totalLines, maxJobLogLines)
startIndex := 0
if totalLines > maxJobLogLines {
startIndex = writeIndex
}
for i := range linesInBuffer {
idx := (startIndex + i) % maxJobLogLines
if validLines[idx] {
result = append(result, lines[idx])
}
}
return strings.Join(result, "\n"), totalLines, httpResp, nil
}
+176
View File
@@ -0,0 +1,176 @@
package buffer
import (
"fmt"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProcessResponseAsRingBufferToEnd(t *testing.T) {
t.Run("normal lines", func(t *testing.T) {
body := "line1\nline2\nline3\n"
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(body)),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 10)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
assert.Equal(t, 3, totalLines)
assert.Equal(t, "line1\nline2\nline3", result)
})
t.Run("ring buffer keeps last N lines", func(t *testing.T) {
body := "line1\nline2\nline3\nline4\nline5\n"
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(body)),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 3)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
assert.Equal(t, 5, totalLines)
assert.Equal(t, "line3\nline4\nline5", result)
})
t.Run("handles very long line exceeding 10MB", func(t *testing.T) {
// Create a line that exceeds maxLineSize (10MB)
longLine := strings.Repeat("x", 11*1024*1024) // 11MB
body := "line1\n" + longLine + "\nline3\n"
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(body)),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 100)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
// Should have processed lines with truncation marker
assert.Greater(t, totalLines, 0)
assert.Contains(t, result, "TRUNCATED")
})
t.Run("handles line at exactly max size", func(t *testing.T) {
// Create a line just under maxLineSize
longLine := strings.Repeat("a", 1024*1024) // 1MB - should work fine
body := "start\n" + longLine + "\nend\n"
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(body)),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 100)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
assert.Equal(t, 3, totalLines)
assert.Contains(t, result, "start")
assert.Contains(t, result, "end")
})
t.Run("ring buffer with long line in middle of many lines", func(t *testing.T) {
// Create many lines with a long line in the middle
// Ring buffer size is 5, so we should only keep the last 5 lines
var sb strings.Builder
for i := 1; i <= 10; i++ {
sb.WriteString(fmt.Sprintf("line%d\n", i))
}
// Insert an 11MB line (exceeds maxLineSize of 10MB)
longLine := strings.Repeat("x", 11*1024*1024)
sb.WriteString(longLine)
sb.WriteString("\n")
for i := 11; i <= 20; i++ {
sb.WriteString(fmt.Sprintf("line%d\n", i))
}
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(sb.String())),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 5)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
// 10 lines before + 1 long line + 10 lines after = 21 total
assert.Equal(t, 21, totalLines)
// Should only have the last 5 lines (line16 through line20)
assert.Contains(t, result, "line16")
assert.Contains(t, result, "line17")
assert.Contains(t, result, "line18")
assert.Contains(t, result, "line19")
assert.Contains(t, result, "line20")
// Should NOT contain earlier lines
assert.NotContains(t, result, "line1\n")
assert.NotContains(t, result, "line10\n")
// The truncated line should not be in the last 5
assert.NotContains(t, result, "TRUNCATED")
})
t.Run("empty response body", func(t *testing.T) {
resp := &http.Response{
Body: io.NopCloser(strings.NewReader("")),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 10)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
assert.Equal(t, 0, totalLines)
assert.Equal(t, "", result)
})
t.Run("line at exactly maxLineSize boundary", func(t *testing.T) {
// Create a line at exactly maxLineSize (10MB) - should be truncated
exactLine := strings.Repeat("z", 10*1024*1024)
body := "before\n" + exactLine + "\nafter\n"
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(body)),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 10)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
assert.Equal(t, 3, totalLines)
assert.Contains(t, result, "before")
assert.Contains(t, result, "TRUNCATED")
assert.Contains(t, result, "after")
})
t.Run("ring buffer keeps truncated line when in last N", func(t *testing.T) {
// Long line followed by only 2 more lines, with ring buffer size 5
longLine := strings.Repeat("y", 11*1024*1024)
body := "line1\nline2\nline3\n" + longLine + "\nlineA\nlineB\n"
resp := &http.Response{
Body: io.NopCloser(strings.NewReader(body)),
}
result, totalLines, respOut, err := ProcessResponseAsRingBufferToEnd(resp, 5)
if respOut != nil && respOut.Body != nil {
defer respOut.Body.Close()
}
require.NoError(t, err)
assert.Equal(t, 6, totalLines)
// Last 5: line2, line3, truncated, lineA, lineB
assert.Contains(t, result, "line2")
assert.Contains(t, result, "line3")
assert.Contains(t, result, "TRUNCATED")
assert.Contains(t, result, "lineA")
assert.Contains(t, result, "lineB")
// line1 should be rotated out
assert.NotContains(t, result, "line1")
})
}
+19
View File
@@ -0,0 +1,19 @@
package context
import "context"
// graphQLFeaturesKey is a context key for GraphQL feature flags
type graphQLFeaturesKey struct{}
// withGraphQLFeatures adds GraphQL feature flags to the context
func WithGraphQLFeatures(ctx context.Context, features ...string) context.Context {
return context.WithValue(ctx, graphQLFeaturesKey{}, features)
}
// GetGraphQLFeatures retrieves GraphQL feature flags from the context
func GetGraphQLFeatures(ctx context.Context) []string {
if features, ok := ctx.Value(graphQLFeaturesKey{}).([]string); ok {
return features
}
return nil
}
+39
View File
@@ -0,0 +1,39 @@
package context
import "context"
type mcpMethodInfoCtx string
var mcpMethodInfoCtxKey mcpMethodInfoCtx = "mcpmethodinfo"
// MCPMethodInfo contains pre-parsed MCP method information extracted from the JSON-RPC request.
// This is populated early in the request lifecycle to enable:
// - Inventory filtering via ForMCPRequest (only register needed tools/resources/prompts)
// - Avoiding duplicate JSON parsing in middlewares (secret-scanning, scope-challenge)
// - Performance optimization for per-request server creation
type MCPMethodInfo struct {
// Method is the MCP method being called (e.g., "tools/call", "tools/list", "initialize")
Method string
// ItemName is the name of the specific item being accessed (tool name, resource URI, prompt name)
// Only populated for call/get methods (tools/call, prompts/get, resources/read)
ItemName string
// Owner is the repository owner from tool call arguments, if present
Owner string
// Repo is the repository name from tool call arguments, if present
Repo string
// Arguments contains the raw tool arguments for tools/call requests
Arguments map[string]any
}
// WithMCPMethodInfo stores the MCPMethodInfo in the context.
func WithMCPMethodInfo(ctx context.Context, info *MCPMethodInfo) context.Context {
return context.WithValue(ctx, mcpMethodInfoCtxKey, info)
}
// MCPMethod retrieves the MCPMethodInfo from the context.
func MCPMethod(ctx context.Context) (*MCPMethodInfo, bool) {
if info, ok := ctx.Value(mcpMethodInfoCtxKey).(*MCPMethodInfo); ok {
return info, true
}
return nil, false
}
+131
View File
@@ -0,0 +1,131 @@
package context
import "context"
// readonlyCtxKey is a context key for read-only mode
type readonlyCtxKey struct{}
// WithReadonly adds read-only mode state to the context
func WithReadonly(ctx context.Context, enabled bool) context.Context {
return context.WithValue(ctx, readonlyCtxKey{}, enabled)
}
// IsReadonly retrieves the read-only mode state from the context
func IsReadonly(ctx context.Context) bool {
if enabled, ok := ctx.Value(readonlyCtxKey{}).(bool); ok {
return enabled
}
return false
}
// toolsetsCtxKey is a context key for the active toolsets
type toolsetsCtxKey struct{}
// WithToolsets adds the active toolsets to the context
func WithToolsets(ctx context.Context, toolsets []string) context.Context {
return context.WithValue(ctx, toolsetsCtxKey{}, toolsets)
}
// GetToolsets retrieves the active toolsets from the context
func GetToolsets(ctx context.Context) []string {
if toolsets, ok := ctx.Value(toolsetsCtxKey{}).([]string); ok {
return toolsets
}
return nil
}
// toolsCtxKey is a context key for tools
type toolsCtxKey struct{}
// WithTools adds the tools to the context
func WithTools(ctx context.Context, tools []string) context.Context {
return context.WithValue(ctx, toolsCtxKey{}, tools)
}
// GetTools retrieves the tools from the context
func GetTools(ctx context.Context) []string {
if tools, ok := ctx.Value(toolsCtxKey{}).([]string); ok {
return tools
}
return nil
}
// lockdownCtxKey is a context key for lockdown mode
type lockdownCtxKey struct{}
// WithLockdownMode adds lockdown mode state to the context
func WithLockdownMode(ctx context.Context, enabled bool) context.Context {
return context.WithValue(ctx, lockdownCtxKey{}, enabled)
}
// IsLockdownMode retrieves the lockdown mode state from the context
func IsLockdownMode(ctx context.Context) bool {
if enabled, ok := ctx.Value(lockdownCtxKey{}).(bool); ok {
return enabled
}
return false
}
// insidersCtxKey is a context key for insiders mode
type insidersCtxKey struct{}
// WithInsidersMode adds insiders mode state to the context
func WithInsidersMode(ctx context.Context, enabled bool) context.Context {
return context.WithValue(ctx, insidersCtxKey{}, enabled)
}
// IsInsidersMode retrieves the insiders mode state from the context
func IsInsidersMode(ctx context.Context) bool {
if enabled, ok := ctx.Value(insidersCtxKey{}).(bool); ok {
return enabled
}
return false
}
// excludeToolsCtxKey is a context key for excluded tools
type excludeToolsCtxKey struct{}
// WithExcludeTools adds the excluded tools to the context
func WithExcludeTools(ctx context.Context, tools []string) context.Context {
return context.WithValue(ctx, excludeToolsCtxKey{}, tools)
}
// GetExcludeTools retrieves the excluded tools from the context
func GetExcludeTools(ctx context.Context) []string {
if tools, ok := ctx.Value(excludeToolsCtxKey{}).([]string); ok {
return tools
}
return nil
}
// headerFeaturesCtxKey is a context key for raw header feature flags
type headerFeaturesCtxKey struct{}
// WithHeaderFeatures stores the raw feature flags from the X-MCP-Features header into context
func WithHeaderFeatures(ctx context.Context, features []string) context.Context {
return context.WithValue(ctx, headerFeaturesCtxKey{}, features)
}
// GetHeaderFeatures retrieves the raw feature flags from context
func GetHeaderFeatures(ctx context.Context) []string {
if features, ok := ctx.Value(headerFeaturesCtxKey{}).([]string); ok {
return features
}
return nil
}
// uiSupportCtxKey is a context key for MCP Apps UI support
type uiSupportCtxKey struct{}
// WithUISupport stores whether the client supports MCP Apps UI in the context.
// This is used by HTTP/stateless servers where the go-sdk session may not
// persist client capabilities across requests.
func WithUISupport(ctx context.Context, supported bool) context.Context {
return context.WithValue(ctx, uiSupportCtxKey{}, supported)
}
// HasUISupport retrieves the MCP Apps UI support flag from context.
func HasUISupport(ctx context.Context) (supported bool, ok bool) {
v, ok := ctx.Value(uiSupportCtxKey{}).(bool)
return v, ok
}
+42
View File
@@ -0,0 +1,42 @@
package context
import (
"context"
"github.com/github/github-mcp-server/pkg/utils"
)
type tokenCtxKey struct{}
type TokenInfo struct {
Token string
TokenType utils.TokenType
}
// WithTokenInfo adds TokenInfo to the context
func WithTokenInfo(ctx context.Context, tokenInfo *TokenInfo) context.Context {
return context.WithValue(ctx, tokenCtxKey{}, tokenInfo)
}
// GetTokenInfo retrieves the authentication token from the context
func GetTokenInfo(ctx context.Context) (*TokenInfo, bool) {
if tokenInfo, ok := ctx.Value(tokenCtxKey{}).(*TokenInfo); ok {
return tokenInfo, true
}
return nil, false
}
type tokenScopesKey struct{}
// WithTokenScopes adds token scopes to the context
func WithTokenScopes(ctx context.Context, scopes []string) context.Context {
return context.WithValue(ctx, tokenScopesKey{}, scopes)
}
// GetTokenScopes retrieves token scopes from the context
func GetTokenScopes(ctx context.Context) ([]string, bool) {
if scopes, ok := ctx.Value(tokenScopesKey{}).([]string); ok {
return scopes, true
}
return nil, false
}
+266
View File
@@ -0,0 +1,266 @@
package errors
import (
"context"
"encoding/json"
stderrors "errors"
"fmt"
"net/http"
"time"
"github.com/github/github-mcp-server/pkg/utils"
"github.com/google/go-github/v89/github"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type GitHubAPIError struct {
Message string `json:"message"`
Response *github.Response `json:"-"`
Err error `json:"-"`
}
// NewGitHubAPIError creates a new GitHubAPIError with the provided message, response, and error.
func newGitHubAPIError(message string, resp *github.Response, err error) *GitHubAPIError {
return &GitHubAPIError{
Message: message,
Response: resp,
Err: err,
}
}
func (e *GitHubAPIError) Error() string {
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
}
type GitHubGraphQLError struct {
Message string `json:"message"`
Err error `json:"-"`
}
func newGitHubGraphQLError(message string, err error) *GitHubGraphQLError {
return &GitHubGraphQLError{
Message: message,
Err: err,
}
}
func (e *GitHubGraphQLError) Error() string {
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
}
type GitHubRawAPIError struct {
Message string `json:"message"`
Response *http.Response `json:"-"`
Err error `json:"-"`
}
func newGitHubRawAPIError(message string, resp *http.Response, err error) *GitHubRawAPIError {
return &GitHubRawAPIError{
Message: message,
Response: resp,
Err: err,
}
}
func (e *GitHubRawAPIError) Error() string {
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
}
type GitHubErrorKey struct{}
type GitHubCtxErrors struct {
api []*GitHubAPIError
graphQL []*GitHubGraphQLError
raw []*GitHubRawAPIError
}
// ContextWithGitHubErrors updates or creates a context with a pointer to GitHub error information (to be used by middleware).
func ContextWithGitHubErrors(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
// If the context already has GitHubCtxErrors, we just empty the slices to start fresh
val.api = []*GitHubAPIError{}
val.graphQL = []*GitHubGraphQLError{}
val.raw = []*GitHubRawAPIError{}
} else {
// If not, we create a new GitHubCtxErrors and set it in the context
ctx = context.WithValue(ctx, GitHubErrorKey{}, &GitHubCtxErrors{})
}
return ctx
}
// GetGitHubAPIErrors retrieves the slice of GitHubAPIErrors from the context.
func GetGitHubAPIErrors(ctx context.Context) ([]*GitHubAPIError, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
return val.api, nil // return the slice of API errors from the context
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}
// GetGitHubGraphQLErrors retrieves the slice of GitHubGraphQLErrors from the context.
func GetGitHubGraphQLErrors(ctx context.Context) ([]*GitHubGraphQLError, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
return val.graphQL, nil // return the slice of GraphQL errors from the context
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}
// GetGitHubRawAPIErrors retrieves the slice of GitHubRawAPIErrors from the context.
func GetGitHubRawAPIErrors(ctx context.Context) ([]*GitHubRawAPIError, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
return val.raw, nil // return the slice of raw API errors from the context
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}
func NewGitHubAPIErrorToCtx(ctx context.Context, message string, resp *github.Response, err error) (context.Context, error) {
apiErr := newGitHubAPIError(message, resp, err)
if ctx != nil {
_, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling
}
return ctx, nil
}
func NewGitHubGraphQLErrorToCtx(ctx context.Context, message string, err error) (context.Context, error) {
graphQLErr := newGitHubGraphQLError(message, err)
if ctx != nil {
_, _ = addGitHubGraphQLErrorToContext(ctx, graphQLErr) // Explicitly ignore error for graceful handling
}
return ctx, nil
}
func addGitHubAPIErrorToContext(ctx context.Context, err *GitHubAPIError) (context.Context, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
val.api = append(val.api, err) // append the error to the existing slice in the context
return ctx, nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}
func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError) (context.Context, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
val.graphQL = append(val.graphQL, err) // append the error to the existing slice in the context
return ctx, nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}
func addRawAPIErrorToContext(ctx context.Context, err *GitHubRawAPIError) (context.Context, error) {
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
val.raw = append(val.raw, err)
return ctx, nil
}
return nil, fmt.Errorf("context does not contain GitHubCtxErrors")
}
// NewGitHubAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware
func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github.Response, err error) *mcp.CallToolResult {
apiErr := newGitHubAPIError(message, resp, err)
if ctx != nil {
_, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling
}
var rateLimitErr *github.RateLimitError
if stderrors.As(err, &rateLimitErr) {
resetTime := rateLimitErr.Rate.Reset.Time
if !resetTime.IsZero() {
retryIn := time.Until(resetTime).Round(time.Second)
if retryIn > 0 {
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub API rate limit exceeded. Retry after %v.", message, retryIn))
}
}
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub API rate limit exceeded. Wait before retrying.", message))
}
var abuseErr *github.AbuseRateLimitError
if stderrors.As(err, &abuseErr) {
if abuseErr.RetryAfter != nil {
retryAfter := abuseErr.RetryAfter.Round(time.Second)
if retryAfter > 0 {
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub secondary rate limit exceeded. Retry after %v.",
message, retryAfter))
}
}
return utils.NewToolResultError(fmt.Sprintf(
"%s: GitHub secondary rate limit exceeded. Wait before retrying.", message))
}
return utils.NewToolResultErrorFromErr(message, err)
}
// NewGitHubGraphQLErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware
func NewGitHubGraphQLErrorResponse(ctx context.Context, message string, err error) *mcp.CallToolResult {
graphQLErr := newGitHubGraphQLError(message, err)
if ctx != nil {
_, _ = addGitHubGraphQLErrorToContext(ctx, graphQLErr) // Explicitly ignore error for graceful handling
}
return utils.NewToolResultErrorFromErr(message, err)
}
// NewGitHubRawAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware
func NewGitHubRawAPIErrorResponse(ctx context.Context, message string, resp *http.Response, err error) *mcp.CallToolResult {
rawErr := newGitHubRawAPIError(message, resp, err)
if ctx != nil {
_, _ = addRawAPIErrorToContext(ctx, rawErr) // Explicitly ignore error for graceful handling
}
return utils.NewToolResultErrorFromErr(message, err)
}
// NewGitHubAPIStatusErrorResponse handles cases where the API call succeeds (err == nil)
// but returns an unexpected HTTP status code. It creates a synthetic error from the
// status code and response body, then records it in context for observability tracking.
func NewGitHubAPIStatusErrorResponse(ctx context.Context, message string, resp *github.Response, body []byte) *mcp.CallToolResult {
err := fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
return NewGitHubAPIErrorResponse(ctx, message, resp, err)
}
// StructuredResolutionError is a machine-readable error returned by name-resolution
// helpers (e.g. resolving a project field or single-select option by name). Agents
// can parse the JSON body to self-correct without re-prompting.
//
// Kind values:
// - "field_not_found" — no project field matches the supplied name
// - "field_ambiguous" — more than one project field shares the supplied name
// - "option_not_found" — no option on the resolved single-select field matches
// - "option_ambiguous" — duplicate option names on the resolved field
// - "item_not_in_project" — the issue/PR exists but is not an item on the project
// - "wrong_field_type" — the named field is not the data type the caller expected
type StructuredResolutionError struct {
Kind string `json:"error"`
Name string `json:"name,omitempty"`
Field string `json:"field,omitempty"`
Candidates []any `json:"candidates,omitempty"`
Hint string `json:"hint,omitempty"`
}
// Error implements the error interface; the message is the JSON body so that the
// downstream tool result also carries the structured payload as plain text.
func (e *StructuredResolutionError) Error() string {
b, err := json.Marshal(e)
if err != nil {
return fmt.Sprintf(`{"error":%q,"name":%q}`, e.Kind, e.Name)
}
return string(b)
}
// NewStructuredResolutionError constructs a StructuredResolutionError.
func NewStructuredResolutionError(kind, name, hint string, candidates []any) *StructuredResolutionError {
return &StructuredResolutionError{
Kind: kind,
Name: name,
Hint: hint,
Candidates: candidates,
}
}
// NewStructuredResolutionErrorResponse returns an mcp.CallToolResult whose text body
// is the JSON-serialised StructuredResolutionError, suitable for agent self-correction.
func NewStructuredResolutionErrorResponse(err *StructuredResolutionError) *mcp.CallToolResult {
return utils.NewToolResultError(err.Error())
}
+689
View File
@@ -0,0 +1,689 @@
package errors
import (
"context"
"fmt"
"github.com/google/go-github/v89/github"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"
"testing"
"time"
)
func TestGitHubErrorContext(t *testing.T) {
t.Run("API errors can be added to context and retrieved", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
// Create a mock GitHub response
resp := &github.Response{
Response: &http.Response{
StatusCode: 404,
Status: "404 Not Found",
},
}
originalErr := fmt.Errorf("resource not found")
// When we add an API error to the context
updatedCtx, err := NewGitHubAPIErrorToCtx(ctx, "failed to fetch resource", resp, originalErr)
require.NoError(t, err)
// Then we should be able to retrieve the error from the updated context
apiErrors, err := GetGitHubAPIErrors(updatedCtx)
require.NoError(t, err)
require.Len(t, apiErrors, 1)
apiError := apiErrors[0]
assert.Equal(t, "failed to fetch resource", apiError.Message)
assert.Equal(t, resp, apiError.Response)
assert.Equal(t, originalErr, apiError.Err)
assert.Equal(t, "failed to fetch resource: resource not found", apiError.Error())
})
t.Run("GraphQL errors can be added to context and retrieved", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
originalErr := fmt.Errorf("GraphQL query failed")
// When we add a GraphQL error to the context
graphQLErr := newGitHubGraphQLError("failed to execute mutation", originalErr)
updatedCtx, err := addGitHubGraphQLErrorToContext(ctx, graphQLErr)
require.NoError(t, err)
// Then we should be able to retrieve the error from the updated context
gqlErrors, err := GetGitHubGraphQLErrors(updatedCtx)
require.NoError(t, err)
require.Len(t, gqlErrors, 1)
gqlError := gqlErrors[0]
assert.Equal(t, "failed to execute mutation", gqlError.Message)
assert.Equal(t, originalErr, gqlError.Err)
assert.Equal(t, "failed to execute mutation: GraphQL query failed", gqlError.Error())
})
t.Run("Raw API errors can be added to context and retrieved", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
// Create a mock HTTP response
resp := &http.Response{
StatusCode: 404,
Status: "404 Not Found",
}
originalErr := fmt.Errorf("raw content not found")
// When we add a raw API error to the context
rawAPIErr := newGitHubRawAPIError("failed to fetch raw content", resp, originalErr)
updatedCtx, err := addRawAPIErrorToContext(ctx, rawAPIErr)
require.NoError(t, err)
// Then we should be able to retrieve the error from the updated context
rawErrors, err := GetGitHubRawAPIErrors(updatedCtx)
require.NoError(t, err)
require.Len(t, rawErrors, 1)
rawError := rawErrors[0]
assert.Equal(t, "failed to fetch raw content", rawError.Message)
assert.Equal(t, resp, rawError.Response)
assert.Equal(t, originalErr, rawError.Err)
})
t.Run("multiple errors can be accumulated in context", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
// When we add multiple API errors
resp1 := &github.Response{Response: &http.Response{StatusCode: 404}}
resp2 := &github.Response{Response: &http.Response{StatusCode: 403}}
ctx, err := NewGitHubAPIErrorToCtx(ctx, "first error", resp1, fmt.Errorf("not found"))
require.NoError(t, err)
ctx, err = NewGitHubAPIErrorToCtx(ctx, "second error", resp2, fmt.Errorf("forbidden"))
require.NoError(t, err)
// And add a GraphQL error
gqlErr := newGitHubGraphQLError("graphql error", fmt.Errorf("query failed"))
ctx, err = addGitHubGraphQLErrorToContext(ctx, gqlErr)
require.NoError(t, err)
// And add a raw API error
rawErr := newGitHubRawAPIError("raw error", &http.Response{StatusCode: 404}, fmt.Errorf("not found"))
ctx, err = addRawAPIErrorToContext(ctx, rawErr)
require.NoError(t, err)
// Then we should be able to retrieve all errors
apiErrors, err := GetGitHubAPIErrors(ctx)
require.NoError(t, err)
assert.Len(t, apiErrors, 2)
gqlErrors, err := GetGitHubGraphQLErrors(ctx)
require.NoError(t, err)
assert.Len(t, gqlErrors, 1)
rawErrors, err := GetGitHubRawAPIErrors(ctx)
require.NoError(t, err)
assert.Len(t, rawErrors, 1)
// Verify error details
assert.Equal(t, "first error", apiErrors[0].Message)
assert.Equal(t, "second error", apiErrors[1].Message)
assert.Equal(t, "graphql error", gqlErrors[0].Message)
assert.Equal(t, "raw error", rawErrors[0].Message)
})
t.Run("context pointer sharing allows middleware to inspect errors without context propagation", func(t *testing.T) {
// This test demonstrates the key behavior: even when the context itself
// isn't propagated through function calls, the pointer to the error slice
// is shared, allowing middleware to inspect errors that were added later.
// Given a context with GitHub error tracking enabled
originalCtx := ContextWithGitHubErrors(context.Background())
// Simulate a middleware that captures the context early
var middlewareCtx context.Context
// Middleware function that captures the context
middleware := func(ctx context.Context) {
middlewareCtx = ctx // Middleware saves the context reference
}
// Call middleware with the original context
middleware(originalCtx)
// Simulate some business logic that adds errors to the context
// but doesn't propagate the updated context back to middleware
businessLogic := func(ctx context.Context) {
resp := &github.Response{Response: &http.Response{StatusCode: 500}}
// Add an error to the context (this modifies the shared pointer)
_, err := NewGitHubAPIErrorToCtx(ctx, "business logic failed", resp, fmt.Errorf("internal error"))
require.NoError(t, err)
// Add another error
_, err = NewGitHubAPIErrorToCtx(ctx, "second failure", resp, fmt.Errorf("another error"))
require.NoError(t, err)
}
// Execute business logic - note that we don't propagate the returned context
businessLogic(originalCtx)
// Then the middleware should be able to see the errors that were added
// even though it only has a reference to the original context
apiErrors, err := GetGitHubAPIErrors(middlewareCtx)
require.NoError(t, err)
assert.Len(t, apiErrors, 2, "Middleware should see errors added after it captured the context")
assert.Equal(t, "business logic failed", apiErrors[0].Message)
assert.Equal(t, "second failure", apiErrors[1].Message)
})
t.Run("context without GitHub errors returns error", func(t *testing.T) {
// Given a regular context without GitHub error tracking
ctx := context.Background()
// When we try to retrieve errors
apiErrors, err := GetGitHubAPIErrors(ctx)
// Then it should return an error
assert.Error(t, err)
assert.Contains(t, err.Error(), "context does not contain GitHubCtxErrors")
assert.Nil(t, apiErrors)
// Same for GraphQL errors
gqlErrors, err := GetGitHubGraphQLErrors(ctx)
assert.Error(t, err)
assert.Contains(t, err.Error(), "context does not contain GitHubCtxErrors")
assert.Nil(t, gqlErrors)
// Same for raw API errors
rawErrors, err := GetGitHubRawAPIErrors(ctx)
assert.Error(t, err)
assert.Contains(t, err.Error(), "context does not contain GitHubCtxErrors")
assert.Nil(t, rawErrors)
})
t.Run("ContextWithGitHubErrors resets existing errors", func(t *testing.T) {
// Given a context with existing errors
ctx := ContextWithGitHubErrors(context.Background())
resp := &github.Response{Response: &http.Response{StatusCode: 404}}
ctx, err := NewGitHubAPIErrorToCtx(ctx, "existing error", resp, fmt.Errorf("error"))
require.NoError(t, err)
// Add a raw API error too
rawErr := newGitHubRawAPIError("existing raw error", &http.Response{StatusCode: 404}, fmt.Errorf("error"))
ctx, err = addRawAPIErrorToContext(ctx, rawErr)
require.NoError(t, err)
// Verify errors exist
apiErrors, err := GetGitHubAPIErrors(ctx)
require.NoError(t, err)
assert.Len(t, apiErrors, 1)
rawErrors, err := GetGitHubRawAPIErrors(ctx)
require.NoError(t, err)
assert.Len(t, rawErrors, 1)
// When we call ContextWithGitHubErrors again
resetCtx := ContextWithGitHubErrors(ctx)
// Then all errors should be cleared
apiErrors, err = GetGitHubAPIErrors(resetCtx)
require.NoError(t, err)
assert.Len(t, apiErrors, 0, "API errors should be reset")
rawErrors, err = GetGitHubRawAPIErrors(resetCtx)
require.NoError(t, err)
assert.Len(t, rawErrors, 0, "Raw API errors should be reset")
})
t.Run("NewGitHubAPIErrorResponse creates MCP error result and stores context error", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
resp := &github.Response{Response: &http.Response{StatusCode: 422}}
originalErr := fmt.Errorf("validation failed")
// When we create an API error response
result := NewGitHubAPIErrorResponse(ctx, "API call failed", resp, originalErr)
// Then it should return an MCP error result
require.NotNil(t, result)
assert.True(t, result.IsError)
// And the error should be stored in the context
apiErrors, err := GetGitHubAPIErrors(ctx)
require.NoError(t, err)
require.Len(t, apiErrors, 1)
apiError := apiErrors[0]
assert.Equal(t, "API call failed", apiError.Message)
assert.Equal(t, resp, apiError.Response)
assert.Equal(t, originalErr, apiError.Err)
})
t.Run("NewGitHubGraphQLErrorResponse creates MCP error result and stores context error", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
originalErr := fmt.Errorf("mutation failed")
// When we create a GraphQL error response
result := NewGitHubGraphQLErrorResponse(ctx, "GraphQL call failed", originalErr)
// Then it should return an MCP error result
require.NotNil(t, result)
assert.True(t, result.IsError)
// And the error should be stored in the context
gqlErrors, err := GetGitHubGraphQLErrors(ctx)
require.NoError(t, err)
require.Len(t, gqlErrors, 1)
gqlError := gqlErrors[0]
assert.Equal(t, "GraphQL call failed", gqlError.Message)
assert.Equal(t, originalErr, gqlError.Err)
})
t.Run("NewGitHubAPIStatusErrorResponse creates MCP error result from status code", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
resp := &github.Response{Response: &http.Response{StatusCode: 422}}
body := []byte(`{"message": "Validation Failed"}`)
// When we create a status error response
result := NewGitHubAPIStatusErrorResponse(ctx, "failed to create issue", resp, body)
// Then it should return an MCP error result
require.NotNil(t, result)
assert.True(t, result.IsError)
// And the error should be stored in the context
apiErrors, err := GetGitHubAPIErrors(ctx)
require.NoError(t, err)
require.Len(t, apiErrors, 1)
apiError := apiErrors[0]
assert.Equal(t, "failed to create issue", apiError.Message)
assert.Equal(t, resp, apiError.Response)
// The synthetic error should contain the status code and body
assert.Contains(t, apiError.Err.Error(), "unexpected status 422")
assert.Contains(t, apiError.Err.Error(), "Validation Failed")
})
t.Run("NewGitHubAPIErrorToCtx with uninitialized context does not error", func(t *testing.T) {
// Given a regular context without GitHub error tracking initialized
ctx := context.Background()
// Create a mock GitHub response
resp := &github.Response{
Response: &http.Response{
StatusCode: 500,
Status: "500 Internal Server Error",
},
}
originalErr := fmt.Errorf("internal server error")
// When we try to add an API error to an uninitialized context
updatedCtx, err := NewGitHubAPIErrorToCtx(ctx, "failed operation", resp, originalErr)
// Then it should not return an error (graceful handling)
assert.NoError(t, err, "NewGitHubAPIErrorToCtx should handle uninitialized context gracefully")
assert.Equal(t, ctx, updatedCtx, "Context should be returned unchanged when not initialized")
// And attempting to retrieve errors should still return an error since context wasn't initialized
apiErrors, err := GetGitHubAPIErrors(updatedCtx)
assert.Error(t, err)
assert.Contains(t, err.Error(), "context does not contain GitHubCtxErrors")
assert.Nil(t, apiErrors)
})
t.Run("NewGitHubAPIErrorToCtx with nil context does not error", func(t *testing.T) {
// Given a nil context
var ctx context.Context
// Create a mock GitHub response
resp := &github.Response{
Response: &http.Response{
StatusCode: 400,
Status: "400 Bad Request",
},
}
originalErr := fmt.Errorf("bad request")
// When we try to add an API error to a nil context
updatedCtx, err := NewGitHubAPIErrorToCtx(ctx, "failed with nil context", resp, originalErr)
// Then it should not return an error (graceful handling)
assert.NoError(t, err, "NewGitHubAPIErrorToCtx should handle nil context gracefully")
assert.Nil(t, updatedCtx, "Context should remain nil when passed as nil")
})
}
func TestGitHubErrorTypes(t *testing.T) {
t.Run("GitHubAPIError implements error interface", func(t *testing.T) {
resp := &github.Response{Response: &http.Response{StatusCode: 404}}
originalErr := fmt.Errorf("not found")
apiErr := newGitHubAPIError("test message", resp, originalErr)
// Should implement error interface
var err error = apiErr
assert.Equal(t, "test message: not found", err.Error())
})
t.Run("GitHubGraphQLError implements error interface", func(t *testing.T) {
originalErr := fmt.Errorf("query failed")
gqlErr := newGitHubGraphQLError("test message", originalErr)
// Should implement error interface
var err error = gqlErr
assert.Equal(t, "test message: query failed", err.Error())
})
}
// TestMiddlewareScenario demonstrates a realistic middleware scenario
func TestMiddlewareScenario(t *testing.T) {
t.Run("realistic middleware error collection scenario", func(t *testing.T) {
// Simulate a realistic HTTP middleware scenario
// 1. Request comes in, middleware sets up error tracking
ctx := ContextWithGitHubErrors(context.Background())
// 2. Middleware stores reference to context for later inspection
var middlewareCtx context.Context
setupMiddleware := func(ctx context.Context) context.Context {
middlewareCtx = ctx
return ctx
}
// 3. Setup middleware
ctx = setupMiddleware(ctx)
// 4. Simulate multiple service calls that add errors
simulateServiceCall1 := func(ctx context.Context) {
resp := &github.Response{Response: &http.Response{StatusCode: 403}}
_, err := NewGitHubAPIErrorToCtx(ctx, "insufficient permissions", resp, fmt.Errorf("forbidden"))
require.NoError(t, err)
}
simulateServiceCall2 := func(ctx context.Context) {
resp := &github.Response{Response: &http.Response{StatusCode: 404}}
_, err := NewGitHubAPIErrorToCtx(ctx, "resource not found", resp, fmt.Errorf("not found"))
require.NoError(t, err)
}
simulateGraphQLCall := func(ctx context.Context) {
gqlErr := newGitHubGraphQLError("mutation failed", fmt.Errorf("invalid input"))
_, err := addGitHubGraphQLErrorToContext(ctx, gqlErr)
require.NoError(t, err)
}
// 5. Execute service calls (without context propagation)
simulateServiceCall1(ctx)
simulateServiceCall2(ctx)
simulateGraphQLCall(ctx)
// 6. Middleware inspects errors at the end of request processing
finalizeMiddleware := func(ctx context.Context) ([]string, []string) {
var apiErrorMessages []string
var gqlErrorMessages []string
if apiErrors, err := GetGitHubAPIErrors(ctx); err == nil {
for _, apiErr := range apiErrors {
apiErrorMessages = append(apiErrorMessages, apiErr.Message)
}
}
if gqlErrors, err := GetGitHubGraphQLErrors(ctx); err == nil {
for _, gqlErr := range gqlErrors {
gqlErrorMessages = append(gqlErrorMessages, gqlErr.Message)
}
}
return apiErrorMessages, gqlErrorMessages
}
// 7. Middleware can see all errors that were added during request processing
apiMessages, gqlMessages := finalizeMiddleware(middlewareCtx)
// Verify all errors were captured
assert.Len(t, apiMessages, 2)
assert.Contains(t, apiMessages, "insufficient permissions")
assert.Contains(t, apiMessages, "resource not found")
assert.Len(t, gqlMessages, 1)
assert.Contains(t, gqlMessages, "mutation failed")
})
}
// requireErrorText asserts that result is a non-nil MCP tool error and returns its text content.
func requireErrorText(t *testing.T, result *mcp.CallToolResult) string {
t.Helper()
require.NotNil(t, result)
require.True(t, result.IsError)
require.NotEmpty(t, result.Content)
text, ok := result.Content[0].(*mcp.TextContent)
require.True(t, ok, "expected *mcp.TextContent, got %T", result.Content[0])
return text.Text
}
// assertContextHasError asserts that exactly one error is stored in ctx and it matches expectedErr.
//
//nolint:revive // t must be first for test helpers; context-as-argument doesn't apply here
func assertContextHasError(t *testing.T, ctx context.Context, expectedErr error) {
t.Helper()
apiErrors, err := GetGitHubAPIErrors(ctx)
require.NoError(t, err)
require.Len(t, apiErrors, 1)
assert.Equal(t, expectedErr, apiErrors[0].Err)
}
func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
t.Run("RateLimitError produces clean message with retry time", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
resetTime := time.Now().Add(30 * time.Minute)
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
// Capture expected duration before the call so both use the same time.Until snapshot
expectedRetryIn := time.Until(resetTime).Round(time.Second)
// When we create an API error response for a rate limit error
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
// Then the message should be clean and actionable (no raw URLs or status codes)
text := requireErrorText(t, result)
assert.Contains(t, text, fmt.Sprintf("GitHub API rate limit exceeded. Retry after %v.", expectedRetryIn))
assert.NotContains(t, text, "https://")
assert.NotContains(t, text, "403")
// And the original error should still be stored in context for middleware
assertContextHasError(t, ctx, rateLimitErr)
})
t.Run("AbuseRateLimitError with RetryAfter produces clean message with wait time", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
retryAfter := 47 * time.Second
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "You have exceeded a secondary rate limit.",
RetryAfter: &retryAfter,
}
resp := &github.Response{Response: abuseErr.Response}
// When we create an API error response for a secondary rate limit error
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, abuseErr)
// And the message should include the specific retry duration
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Retry after 47s.")
assert.NotContains(t, text, "https://")
assert.NotContains(t, text, "403")
// And the original error should still be stored in context for middleware
assertContextHasError(t, ctx, abuseErr)
})
t.Run("AbuseRateLimitError without RetryAfter produces clean message without wait time", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "You have exceeded a secondary rate limit.",
RetryAfter: nil,
}
resp := &github.Response{Response: abuseErr.Response}
// When we create an API error response for a secondary rate limit error without retry info
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, abuseErr)
// And the message should be clean and actionable
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Wait before retrying.")
assert.NotContains(t, text, "https://")
assert.NotContains(t, text, "403")
// And the original error should still be stored in context for middleware
assertContextHasError(t, ctx, abuseErr)
})
t.Run("AbuseRateLimitError with sub-second RetryAfter falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
// 200ms rounds to 0s, so should fall back to the generic wait message
retryAfter := 200 * time.Millisecond
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "You have exceeded a secondary rate limit.",
RetryAfter: &retryAfter,
}
resp := &github.Response{Response: abuseErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, abuseErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Wait before retrying.")
})
t.Run("RateLimitError with reset time in the past falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
resetTime := time.Now().Add(-5 * time.Second) // already passed
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub API rate limit exceeded. Wait before retrying.")
})
t.Run("RateLimitError with sub-second reset time falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
// 250ms in the future: still positive, but rounds to 0s, so should fall back
resetTime := time.Now().Add(250 * time.Millisecond)
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub API rate limit exceeded. Wait before retrying.")
})
t.Run("RateLimitError with zero reset time falls back to wait message", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{}, // zero Reset time
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
resp := &github.Response{Response: rateLimitErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, rateLimitErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub API rate limit exceeded. Wait before retrying.")
})
t.Run("wrapped RateLimitError is handled via errors.As", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
resetTime := time.Now().Add(20 * time.Minute)
rateLimitErr := &github.RateLimitError{
Rate: github.Rate{Reset: github.Timestamp{Time: resetTime}},
Response: &http.Response{StatusCode: 403},
Message: "API rate limit exceeded",
}
wrappedErr := fmt.Errorf("transport layer: %w", rateLimitErr)
resp := &github.Response{Response: rateLimitErr.Response}
// Capture expected duration before the call so both use the same time.Until snapshot
expectedRetryIn := time.Until(resetTime).Round(time.Second)
result := NewGitHubAPIErrorResponse(ctx, "search code", resp, wrappedErr)
text := requireErrorText(t, result)
assert.Contains(t, text, fmt.Sprintf("GitHub API rate limit exceeded. Retry after %v.", expectedRetryIn))
assert.NotContains(t, text, "https://")
})
t.Run("wrapped AbuseRateLimitError is handled via errors.As", func(t *testing.T) {
ctx := ContextWithGitHubErrors(context.Background())
retryAfter := 30 * time.Second
abuseErr := &github.AbuseRateLimitError{
Response: &http.Response{StatusCode: 403},
Message: "secondary rate limit",
RetryAfter: &retryAfter,
}
wrappedErr := fmt.Errorf("transport layer: %w", abuseErr)
resp := &github.Response{Response: abuseErr.Response}
result := NewGitHubAPIErrorResponse(ctx, "create issue", resp, wrappedErr)
text := requireErrorText(t, result)
assert.Contains(t, text, "GitHub secondary rate limit exceeded. Retry after 30s.")
assert.NotContains(t, text, "https://")
})
t.Run("non-rate-limit GitHub API error passes through the original error message", func(t *testing.T) {
// Given a context with GitHub error tracking enabled
ctx := ContextWithGitHubErrors(context.Background())
resp := &github.Response{Response: &http.Response{StatusCode: 422}}
originalErr := fmt.Errorf("validation failed")
// When we create an API error response for a non-rate-limit error
result := NewGitHubAPIErrorResponse(ctx, "API call failed", resp, originalErr)
// Then the message should contain the original error text unchanged
text := requireErrorText(t, result)
assert.Contains(t, text, "validation failed")
})
}
+44
View File
@@ -0,0 +1,44 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get details of GitHub Actions resources (workflows, workflow runs, jobs, and artifacts)"
},
"description": "Get details about specific GitHub Actions resources.\nUse this tool to get details about individual workflows, workflow runs, jobs, and artifacts by their unique IDs.\n",
"inputSchema": {
"properties": {
"method": {
"description": "The method to execute",
"enum": [
"get_workflow",
"get_workflow_run",
"get_workflow_job",
"download_workflow_run_artifact",
"get_workflow_run_usage",
"get_workflow_run_logs_url"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"resource_id": {
"description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' method.\n- Provide a workflow run ID for 'get_workflow_run', 'get_workflow_run_usage', and 'get_workflow_run_logs_url' methods.\n- Provide an artifact ID for 'download_workflow_run_artifact' method.\n- Provide a job ID for 'get_workflow_job' method.\n",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo",
"resource_id"
],
"type": "object"
},
"name": "actions_get"
}
+129
View File
@@ -0,0 +1,129 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List GitHub Actions workflows in a repository"
},
"description": "Tools for listing GitHub Actions resources.\nUse this tool to list workflows in a repository, or list workflow runs, jobs, and artifacts for a specific workflow or workflow run.\n",
"inputSchema": {
"properties": {
"method": {
"description": "The action to perform",
"enum": [
"list_workflows",
"list_workflow_runs",
"list_workflow_jobs",
"list_workflow_run_artifacts"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (default: 1)",
"minimum": 1,
"type": "number"
},
"per_page": {
"description": "Results per page for pagination (default: 30, max: 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"resource_id": {
"description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Do not provide any resource ID for 'list_workflows' method.\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method, or omit to list all workflow runs in the repository.\n- Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods.\n",
"type": "string"
},
"workflow_jobs_filter": {
"description": "Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs'",
"properties": {
"filter": {
"description": "Filters jobs by their completed_at timestamp",
"enum": [
"latest",
"all"
],
"type": "string"
}
},
"type": "object"
},
"workflow_runs_filter": {
"description": "Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs'",
"properties": {
"actor": {
"description": "Filter to a specific GitHub user's workflow runs.",
"type": "string"
},
"branch": {
"description": "Filter workflow runs to a specific Git branch. Use the name of the branch.",
"type": "string"
},
"event": {
"description": "Filter workflow runs to a specific event type",
"enum": [
"branch_protection_rule",
"check_run",
"check_suite",
"create",
"delete",
"deployment",
"deployment_status",
"discussion",
"discussion_comment",
"fork",
"gollum",
"issue_comment",
"issues",
"label",
"merge_group",
"milestone",
"page_build",
"public",
"pull_request",
"pull_request_review",
"pull_request_review_comment",
"pull_request_target",
"push",
"registry_package",
"release",
"repository_dispatch",
"schedule",
"status",
"watch",
"workflow_call",
"workflow_dispatch",
"workflow_run"
],
"type": "string"
},
"status": {
"description": "Filter workflow runs to only runs with a specific status",
"enum": [
"queued",
"in_progress",
"completed",
"requested",
"waiting"
],
"type": "string"
}
},
"type": "object"
}
},
"required": [
"method",
"owner",
"repo"
],
"type": "object"
},
"name": "actions_list"
}
@@ -0,0 +1,56 @@
{
"annotations": {
"destructiveHint": true,
"idempotentHint": false,
"readOnlyHint": false,
"title": "Trigger GitHub Actions workflow actions"
},
"description": "Trigger GitHub Actions workflow operations, including running, re-running, cancelling workflow runs, and deleting workflow run logs.",
"inputSchema": {
"properties": {
"inputs": {
"description": "Inputs the workflow accepts. Only used for 'run_workflow' method.",
"properties": {},
"type": "object"
},
"method": {
"description": "The method to execute",
"enum": [
"run_workflow",
"rerun_workflow_run",
"rerun_failed_jobs",
"cancel_workflow_run",
"delete_workflow_run_logs"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"ref": {
"description": "The git reference for the workflow. The reference can be a branch or tag name. Required for 'run_workflow' method.",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"run_id": {
"description": "The ID of the workflow run. Required for all methods except 'run_workflow'.",
"type": "number"
},
"workflow_id": {
"description": "The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method.",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo"
],
"type": "object"
},
"name": "actions_run_trigger"
}
@@ -0,0 +1,74 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Add review comment to the requester's latest pending pull request review"
},
"description": "Add review comment to the requester's latest pending pull request review. A pending review needs to already exist to call this (check with the user if not sure).",
"inputSchema": {
"properties": {
"body": {
"description": "The text of the review comment",
"type": "string"
},
"line": {
"description": "The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range",
"type": "number"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"path": {
"description": "The relative path to the file that necessitates a comment",
"type": "string"
},
"pullNumber": {
"description": "Pull request number",
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"side": {
"description": "The side of the diff to comment on. LEFT indicates the previous state, RIGHT indicates the new state",
"enum": [
"LEFT",
"RIGHT"
],
"type": "string"
},
"startLine": {
"description": "For multi-line comments, the first line of the range that the comment applies to",
"type": "number"
},
"startSide": {
"description": "For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state",
"enum": [
"LEFT",
"RIGHT"
],
"type": "string"
},
"subjectType": {
"description": "The level at which the comment is targeted",
"enum": [
"FILE",
"LINE"
],
"type": "string"
}
},
"required": [
"owner",
"repo",
"pullNumber",
"path",
"body",
"subjectType"
],
"type": "object"
},
"name": "add_comment_to_pending_review"
}
@@ -0,0 +1,54 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Add comment to issue or pull request"
},
"description": "Add a comment and/or reaction to a specific issue or issue comment in a GitHub repository. Use this tool with pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add or react to review comments. At least one of body or reaction is required.",
"inputSchema": {
"properties": {
"body": {
"description": "Comment content. Required unless reaction is provided.",
"type": "string"
},
"comment_id": {
"description": "The numeric ID of the issue or pull request comment to react to. Use this for reactions to comments; omit it to react to the issue or pull request itself. Cannot be combined with body.",
"minimum": 1,
"type": "number"
},
"issue_number": {
"description": "Issue or pull request number to comment on or react to.",
"type": "number"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"reaction": {
"description": "Emoji reaction to add. Required unless body is provided.",
"enum": [
"+1",
"-1",
"laugh",
"confused",
"heart",
"hooray",
"rocket",
"eyes"
],
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"issue_number"
],
"type": "object"
},
"name": "add_issue_comment"
}
@@ -0,0 +1,49 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Add Reaction to Issue or Pull Request Comment"
},
"description": "Add a reaction to an issue or pull request comment.",
"inputSchema": {
"properties": {
"comment_id": {
"description": "The issue or pull request comment ID",
"minimum": 1,
"type": "number"
},
"content": {
"description": "The emoji reaction type",
"enum": [
"+1",
"-1",
"laugh",
"confused",
"heart",
"hooray",
"rocket",
"eyes"
],
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"comment_id",
"content"
],
"type": "object"
},
"name": "add_issue_comment_reaction"
}
@@ -0,0 +1,49 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Add Reaction to Issue or Pull Request"
},
"description": "Add a reaction to an issue or pull request.",
"inputSchema": {
"properties": {
"content": {
"description": "The emoji reaction type",
"enum": [
"+1",
"-1",
"laugh",
"confused",
"heart",
"hooray",
"rocket",
"eyes"
],
"type": "string"
},
"issue_number": {
"description": "The issue number",
"minimum": 1,
"type": "number"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"issue_number",
"content"
],
"type": "object"
},
"name": "add_issue_reaction"
}
@@ -0,0 +1,77 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Add Pull Request Review Comment"
},
"description": "Add a review comment to the current user's pending pull request review.",
"inputSchema": {
"properties": {
"body": {
"description": "The comment body",
"type": "string"
},
"line": {
"description": "The line number in the diff to comment on (optional)",
"type": "number"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"path": {
"description": "The relative path of the file to comment on",
"type": "string"
},
"pullNumber": {
"description": "The pull request number",
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"side": {
"description": "The side of the diff to comment on (optional)",
"enum": [
"LEFT",
"RIGHT"
],
"type": "string"
},
"startLine": {
"description": "The start line of a multi-line comment (optional)",
"type": "number"
},
"startSide": {
"description": "The start side of a multi-line comment (optional)",
"enum": [
"LEFT",
"RIGHT"
],
"type": "string"
},
"subjectType": {
"description": "The subject type of the comment",
"enum": [
"FILE",
"LINE"
],
"type": "string"
}
},
"required": [
"owner",
"repo",
"pullNumber",
"path",
"body",
"subjectType"
],
"type": "object"
},
"name": "add_pull_request_review_comment"
}
@@ -0,0 +1,49 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Add Pull Request Review Comment Reaction"
},
"description": "Add a reaction to a pull request review comment.",
"inputSchema": {
"properties": {
"comment_id": {
"description": "The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).",
"minimum": 1,
"type": "number"
},
"content": {
"description": "The emoji reaction type",
"enum": [
"+1",
"-1",
"laugh",
"confused",
"heart",
"hooray",
"rocket",
"eyes"
],
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"comment_id",
"content"
],
"type": "object"
},
"name": "add_pull_request_review_comment_reaction"
}
@@ -0,0 +1,54 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Add reply to pull request comment"
},
"description": "Add a reply and/or reaction to an existing pull request comment. This can create a new comment linked as a reply to the specified comment, add an emoji reaction to the specified comment, or do both. At least one of body or reaction is required.",
"inputSchema": {
"properties": {
"body": {
"description": "The text of the reply. Required unless reaction is provided.",
"type": "string"
},
"commentId": {
"description": "The numeric ID of the pull request review comment to reply or react to. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).",
"minimum": 1,
"type": "number"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"pullNumber": {
"description": "Pull request number. Required when body is provided.",
"type": "number"
},
"reaction": {
"description": "Emoji reaction to add. Required unless body is provided.",
"enum": [
"+1",
"-1",
"laugh",
"confused",
"heart",
"hooray",
"rocket",
"eyes"
],
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"commentId"
],
"type": "object"
},
"name": "add_reply_to_pull_request_comment"
}
@@ -0,0 +1,43 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Add Sub-Issue"
},
"description": "Add a sub-issue to a parent issue.",
"inputSchema": {
"properties": {
"issue_number": {
"description": "The parent issue number",
"minimum": 1,
"type": "number"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"replace_parent": {
"description": "If true, reparent the sub-issue if it already has a parent",
"type": "boolean"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sub_issue_id": {
"description": "The ID of the sub-issue to add. ID is not the same as issue number",
"type": "number"
}
},
"required": [
"owner",
"repo",
"issue_number",
"sub_issue_id"
],
"type": "object"
},
"name": "add_sub_issue"
}
@@ -0,0 +1,51 @@
{
"annotations": {
"idempotentHint": true,
"readOnlyHint": false,
"title": "Assign Copilot to issue"
},
"description": "Assign Copilot to a specific issue in a GitHub repository.\n\nThis tool can help with the following outcomes:\n- a Pull Request created with source code changes to resolve the issue\n\n\nMore information can be found at:\n- https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot\n",
"icons": [
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=",
"theme": "light"
},
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==",
"theme": "dark"
}
],
"inputSchema": {
"properties": {
"base_ref": {
"description": "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch",
"type": "string"
},
"custom_instructions": {
"description": "Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description",
"type": "string"
},
"issue_number": {
"description": "Issue number",
"type": "number"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"issue_number"
],
"type": "object"
},
"name": "assign_copilot_to_issue"
}
@@ -0,0 +1,35 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Create branch"
},
"description": "Create a new branch in a GitHub repository",
"inputSchema": {
"properties": {
"branch": {
"description": "Name for new branch",
"type": "string"
},
"from_branch": {
"description": "Source branch (defaults to repo default)",
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"branch"
],
"type": "object"
},
"name": "create_branch"
}
+35
View File
@@ -0,0 +1,35 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Create Gist"
},
"description": "Create a new gist",
"inputSchema": {
"properties": {
"content": {
"description": "Content for simple single-file gist creation",
"type": "string"
},
"description": {
"description": "Description of the gist",
"type": "string"
},
"filename": {
"description": "Filename for simple single-file gist creation",
"type": "string"
},
"public": {
"default": false,
"description": "Whether the gist is public",
"type": "boolean"
}
},
"required": [
"filename",
"content"
],
"type": "object"
},
"name": "create_gist"
}
@@ -0,0 +1,37 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Create Issue"
},
"description": "Create a new issue in a GitHub repository with a title and optional body.",
"inputSchema": {
"properties": {
"body": {
"description": "Issue body content (optional)",
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"title": {
"description": "Issue title",
"type": "string"
}
},
"required": [
"owner",
"repo",
"title"
],
"type": "object"
},
"name": "create_issue"
}
@@ -0,0 +1,50 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Create or update file"
},
"description": "Create or update a single file in a GitHub repository. \nIf updating, you should provide the SHA of the file you want to update. Use this tool to create or update a file in a GitHub repository remotely; do not use it for local file operations.\n\nIn order to obtain the SHA of original file version before updating, use the following git command:\ngit rev-parse \u003cbranch\u003e:\u003cpath to file\u003e\n\nSHA MUST be provided for existing file updates.\n",
"inputSchema": {
"properties": {
"branch": {
"description": "Branch to create/update the file in",
"type": "string"
},
"content": {
"description": "Content of the file",
"type": "string"
},
"message": {
"description": "Commit message",
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"path": {
"description": "Path where to create/update the file",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sha": {
"description": "The blob SHA of the file being replaced. Required if the file already exists.",
"type": "string"
}
},
"required": [
"owner",
"repo",
"path",
"content",
"message",
"branch"
],
"type": "object"
},
"name": "create_or_update_file"
}
@@ -0,0 +1,69 @@
{
"_meta": {
"ui": {
"resourceUri": "ui://github-mcp-server/pr-write",
"visibility": [
"model",
"app"
]
}
},
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Open new pull request"
},
"description": "Create a new pull request in a GitHub repository.",
"inputSchema": {
"properties": {
"base": {
"description": "Branch to merge into",
"type": "string"
},
"body": {
"description": "PR description",
"type": "string"
},
"draft": {
"description": "Create as draft PR",
"type": "boolean"
},
"head": {
"description": "Branch containing changes",
"type": "string"
},
"maintainer_can_modify": {
"description": "Allow maintainer edits",
"type": "boolean"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"reviewers": {
"description": "GitHub usernames or ORG/team-slug team reviewers to request reviews from",
"items": {
"type": "string"
},
"type": "array"
},
"title": {
"description": "PR title",
"type": "string"
}
},
"required": [
"owner",
"repo",
"title",
"head",
"base"
],
"type": "object"
},
"name": "create_pull_request"
}
@@ -0,0 +1,51 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Create Pull Request Review"
},
"description": "Create a review on a pull request. If event is provided, the review is submitted immediately; otherwise a pending review is created.",
"inputSchema": {
"properties": {
"body": {
"description": "The review body text (optional)",
"type": "string"
},
"commitID": {
"description": "The SHA of the commit to review (optional, defaults to latest)",
"type": "string"
},
"event": {
"description": "The review action to perform. If omitted, creates a pending review.",
"enum": [
"APPROVE",
"REQUEST_CHANGES",
"COMMENT"
],
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"pullNumber": {
"description": "The pull request number",
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"pullNumber"
],
"type": "object"
},
"name": "create_pull_request_review"
}
@@ -0,0 +1,38 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Create repository"
},
"description": "Create a new GitHub repository in your account or specified organization",
"inputSchema": {
"properties": {
"autoInit": {
"description": "Initialize with README",
"type": "boolean"
},
"description": {
"description": "Repository description",
"type": "string"
},
"name": {
"description": "Repository name",
"type": "string"
},
"organization": {
"description": "Organization to create the repository in (omit to create in your personal account)",
"type": "string"
},
"private": {
"default": true,
"description": "Whether the repository should be private. Defaults to true (private) when omitted.",
"type": "boolean"
}
},
"required": [
"name"
],
"type": "object"
},
"name": "create_repository"
}
+42
View File
@@ -0,0 +1,42 @@
{
"annotations": {
"destructiveHint": true,
"idempotentHint": false,
"readOnlyHint": false,
"title": "Delete file"
},
"description": "Delete a file from a GitHub repository",
"inputSchema": {
"properties": {
"branch": {
"description": "Branch to delete the file from",
"type": "string"
},
"message": {
"description": "Commit message",
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"path": {
"description": "Path to the file to delete",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"path",
"message",
"branch"
],
"type": "object"
},
"name": "delete_file"
}
@@ -0,0 +1,34 @@
{
"annotations": {
"destructiveHint": true,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Delete Pending Pull Request Review"
},
"description": "Delete a pending pull request review.",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"pullNumber": {
"description": "The pull request number",
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"pullNumber"
],
"type": "object"
},
"name": "delete_pending_pull_request_review"
}
@@ -0,0 +1,50 @@
{
"annotations": {
"destructiveHint": true,
"idempotentHint": false,
"readOnlyHint": false,
"title": "Manage discussion comments"
},
"description": "Write operations for discussion comments.\nSupports adding top-level comments, replying to existing comments, updating comment content, deleting comments, and marking or unmarking comments as the answer.",
"inputSchema": {
"properties": {
"body": {
"description": "Comment content (required for 'add', 'reply', and 'update' methods)",
"type": "string"
},
"commentNodeID": {
"description": "The Node ID of the discussion comment (required for 'reply', 'update', 'delete', 'mark_answer', and 'unmark_answer' methods). For 'reply', this is the top-level comment to reply to; GitHub Discussions only support one level of nesting.",
"type": "string"
},
"discussionNumber": {
"description": "Discussion number (required for 'add' and 'reply' methods)",
"type": "number"
},
"method": {
"description": "Write operation to perform on a discussion comment.\nOptions are:\n- 'add' - adds a new top-level comment to a discussion.\n- 'reply' - replies to a top-level discussion comment (GitHub Discussions only support one level of nesting).\n- 'update' - updates an existing discussion comment.\n- 'delete' - deletes a discussion comment.\n- 'mark_answer' - marks a discussion comment as the answer (Q\u0026A only).\n- 'unmark_answer' - unmarks a discussion comment as the answer (Q\u0026A only).\n",
"enum": [
"add",
"reply",
"update",
"delete",
"mark_answer",
"unmark_answer"
],
"type": "string"
},
"owner": {
"description": "Repository owner (required for 'add' and 'reply' methods)",
"type": "string"
},
"repo": {
"description": "Repository name (required for 'add' and 'reply' methods)",
"type": "string"
}
},
"required": [
"method"
],
"type": "object"
},
"name": "discussion_comment_write"
}
@@ -0,0 +1,30 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Dismiss notification"
},
"description": "Dismiss a notification by marking it as read or done",
"inputSchema": {
"properties": {
"state": {
"description": "The new state of the notification (read/done)",
"enum": [
"read",
"done"
],
"type": "string"
},
"threadID": {
"description": "The ID of the notification thread",
"type": "string"
}
},
"required": [
"threadID",
"state"
],
"type": "object"
},
"name": "dismiss_notification"
}
@@ -0,0 +1,42 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Fork repository"
},
"description": "Fork a GitHub repository to your account or specified organization",
"icons": [
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACuElEQVRIibWTTUhUYRiFn/fOdYyoydQxk4LEGzN3RudaLYL+qRaBQYsIItoHCW37ISNbRwUFLWoRZBEt+4EIooKoTdZQ6TWaNIgouzJkuGhG731b6JTojDNBntX3ne+c97zfH8wzZCbREm9bZ4hsQvkeDvl3+/r6xuYqEIvFFgdSvRuDqCrPMu6bVyUDrITTjdI1jR8KBbrj/fs3Q8WLp5p9Qx4BzVOUInIm058+XdAY0ztH6RLhSpAza1RlI2jENzhfqntfjAugEdTYMFEtS0GvonrKslNrZwWIhDYDMh6Wo4ODvaMfB9LPFaMHZGvJ8xHdAlzPDLx+8Smd/pE39SggAptnB2gwDBD6ReJvhSCpMFyq/uSa/NFX5UMJgGCaxywMwiH/bi4wh0SCOy1x5waiCUF2gnSW3AByEfSSZTsPVXFF9CDC4ALx7xU0ocLA87x8tG7ZHRUShsheVMKInMy46culArIj317WRpd7KB2GsAl4bKoccN2330t5ALBsJ7ASTvecoun6hNNt2U5QbM0oRip8E6Wt0gCUFPC12FKoGFnX0BgBDtVGG3/W1qzqz2a/5IrpLGt9pLahvhPhCKrnsiPDT2dqZv1kgGQyGc4FZg+wr8I93F6y0DzY29s7XlHAnw7j7dswgg2oRCYZPTBluzk51VEwXmQG0k8qbGRuWHbqiWWn/qlY0Uv+n5j3gKKvaCaSyeSimrqms4hsB4kurW9c0bSs/pnneflyXrOcACCn5jWEPSr0AAgczvlVTVT+ykojFlvTZNmOWvHU8QJnJVInLNtR2163vJy/7B0EpjYAqBhugVMVF8A3goZy/rJHFGa8P4fpCXosHm9PqwbiwzHAqyLvlvPP+dEKWG23dyh6C1g0RY0Jsv+Dm77/XwIAWlpbVzJh7gLAnHjw8d27z5V65xW/AVGM6Ekx9nZCAAAAAElFTkSuQmCC",
"theme": "light"
},
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABoUlEQVRIibWUPS+EQRSFz0hsxaIgIZHYllDYiiiUEn6Bn0Dho6Nhf4GPjkYn8ZEgGqFRSNBoVTQKdhEROsk+mrt2svvu7Gutk7yZzJlz77nzztyR/hmulADSkkYk5SQdO+c+QwmAZkkTktolXTjnbkLiDJCniHsgFdCnTFNAHliuJE6bYANoAYaBF+AwYHBkmiGgFdi0HINR4lmrotXjVoG3gMEbsOLN2yzHTIFr8PRZG3s9rs/jo5At0fd6fFk1TfY/X4A14MyqmQrsYNo0pxbzCtwBTZUCUsAh8GHCKaDspnl6ZyZ3FnMA9AR2/BOYBzJVhUV9BshHrTVEkZKeJPXHNZA0IOkxttrrhzkgGdAlgXnTLv3GIAHsEh87QGNUrooHaEajkoYlFXYxaeO2je+SLp1z57Grr2J4DvwqWaVDrhv+3SAWrMvXgWcgZ10b3a01GuwDX8CWfV/AXr2Sd9lVXPC4ReM6q8XHOYMOG2897rZkrXZY0+WAK6DHHsRr4xJ/NjCTcXstC/gAxuPEBju5xKRb0phNT5xzD7UUW3d8A4p92DZKdSwEAAAAAElFTkSuQmCC",
"theme": "dark"
}
],
"inputSchema": {
"properties": {
"organization": {
"description": "Organization to fork to",
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "fork_repository"
}
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get code quality finding"
},
"description": "Get details of a specific code quality finding in a GitHub repository.",
"inputSchema": {
"properties": {
"findingNumber": {
"description": "The number of the finding.",
"type": "number"
},
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
}
},
"required": [
"owner",
"repo",
"findingNumber"
],
"type": "object"
},
"name": "get_code_quality_finding"
}
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get code scanning alert"
},
"description": "Get details of a specific code scanning alert in a GitHub repository.",
"inputSchema": {
"properties": {
"alertNumber": {
"description": "The number of the alert.",
"type": "number"
},
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
}
},
"required": [
"owner",
"repo",
"alertNumber"
],
"type": "object"
},
"name": "get_code_scanning_alert"
}
+52
View File
@@ -0,0 +1,52 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get commit details"
},
"description": "Get details for a commit from a GitHub repository",
"inputSchema": {
"properties": {
"detail": {
"default": "stats",
"description": "Level of detail to include for changed files. \"none\" omits stats and files entirely. \"stats\" (default) includes per-file metadata: filename, status, and lines-of-code counts (additions, deletions, changes), with no patch content. \"full_patch\" additionally includes the unified diff content for each file and can be very large.",
"enum": [
"none",
"stats",
"full_patch"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sha": {
"description": "Commit SHA, branch name, or tag name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"sha"
],
"type": "object"
},
"name": "get_commit"
}
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get dependabot alert"
},
"description": "Get details of a specific dependabot alert in a GitHub repository.",
"inputSchema": {
"properties": {
"alertNumber": {
"description": "The number of the alert.",
"type": "number"
},
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
}
},
"required": [
"owner",
"repo",
"alertNumber"
],
"type": "object"
},
"name": "get_dependabot_alert"
}
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get discussion"
},
"description": "Get a specific discussion by ID",
"inputSchema": {
"properties": {
"discussionNumber": {
"description": "Discussion Number",
"type": "number"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"discussionNumber"
],
"type": "object"
},
"name": "get_discussion"
}
@@ -0,0 +1,45 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get discussion comments"
},
"description": "Get comments from a discussion",
"inputSchema": {
"properties": {
"after": {
"description": "Cursor for pagination. Use the cursor from the previous response.",
"type": "string"
},
"discussionNumber": {
"description": "Discussion Number",
"type": "number"
},
"includeReplies": {
"description": "When true, each top-level comment will include its replies nested within it (up to 100 replies per comment, which is the GitHub API maximum). Defaults to false.",
"type": "boolean"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"discussionNumber"
],
"type": "object"
},
"name": "get_discussion_comments"
}
@@ -0,0 +1,55 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get file blame information"
},
"description": "Get git blame information for a file, showing the commit that last modified each line. Ranges share commit metadata via the top-level 'commits' map keyed by SHA. Use 'start_line'/'end_line' to restrict the result to a window of the file, and 'perPage'/'after' to cursor-page through returned ranges. Matching ranges are capped at 1000; when the cap is hit 'truncated' is set to true and 'total_ranges' reports the pre-cap match count.",
"inputSchema": {
"properties": {
"after": {
"description": "Cursor for pagination. Use the cursor from the previous response.",
"type": "string"
},
"end_line": {
"description": "Optional 1-based ending line of the window of interest. Must be \u003e= start_line when both are provided.",
"minimum": 1,
"type": "number"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"path": {
"description": "Path to the file in the repository, relative to the repository root",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"ref": {
"description": "Git reference (branch, tag, or commit SHA). Defaults to the repository's default branch (HEAD).",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"start_line": {
"description": "Optional 1-based starting line of the window of interest. Only ranges overlapping [start_line, end_line] are returned, clamped to the window.",
"minimum": 1,
"type": "number"
}
},
"required": [
"owner",
"repo",
"path"
],
"type": "object"
},
"name": "get_file_blame"
}
@@ -0,0 +1,39 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get file or directory contents"
},
"description": "Get the contents of a file or directory from a GitHub repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"path": {
"default": "/",
"description": "Path to file/directory",
"type": "string"
},
"ref": {
"description": "Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sha": {
"description": "Accepts optional commit SHA. If specified, it will be used instead of ref",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "get_file_contents"
}
@@ -0,0 +1,57 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get file or directory contents"
},
"description": "Get the contents of a file or directory from a GitHub repository",
"inputSchema": {
"properties": {
"fields": {
"description": "Subset of fields to return for each entry when the path is a directory. If omitted, all fields are returned. Ignored when the path is a single file. Use this to reduce response size when listing directories and you only need specific fields, e.g. just 'name' and 'type'.",
"items": {
"enum": [
"type",
"name",
"path",
"size",
"sha",
"url",
"git_url",
"html_url",
"download_url"
],
"type": "string"
},
"type": "array"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"path": {
"default": "/",
"description": "Path to file/directory",
"type": "string"
},
"ref": {
"description": "Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sha": {
"description": "Accepts optional commit SHA. If specified, it will be used instead of ref",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "get_file_contents"
}
+21
View File
@@ -0,0 +1,21 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get Gist Content"
},
"description": "Get gist content of a particular gist, by gist ID",
"inputSchema": {
"properties": {
"gist_id": {
"description": "The ID of the gist",
"type": "string"
}
},
"required": [
"gist_id"
],
"type": "object"
},
"name": "get_gist"
}
@@ -0,0 +1,21 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get a global security advisory"
},
"description": "Get a global security advisory",
"inputSchema": {
"properties": {
"ghsaId": {
"description": "GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx).",
"type": "string"
}
},
"required": [
"ghsaId"
],
"type": "object"
},
"name": "get_global_security_advisory"
}
@@ -0,0 +1,46 @@
{
"annotations": {
"readOnlyHint": true,
"title": "Get job logs"
},
"description": "Download logs for a specific workflow job or efficiently get all failed job logs for a workflow run",
"inputSchema": {
"properties": {
"failed_only": {
"description": "When true, gets logs for all failed jobs in run_id",
"type": "boolean"
},
"job_id": {
"description": "The unique identifier of the workflow job (required for single job logs)",
"type": "number"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"return_content": {
"description": "Returns actual log content instead of URLs",
"type": "boolean"
},
"run_id": {
"description": "Workflow run ID (required when using failed_only)",
"type": "number"
},
"tail_lines": {
"default": 500,
"description": "Number of lines to return from the end of the log",
"type": "number"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "get_job_logs"
}
+31
View File
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get a specific label from a repository"
},
"description": "Get a specific label from a repository.",
"inputSchema": {
"properties": {
"name": {
"description": "Label name.",
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization name)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"name"
],
"type": "object"
},
"name": "get_label"
}
@@ -0,0 +1,26 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get latest release"
},
"description": "Get the latest release in a GitHub repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "get_latest_release"
}
+22
View File
@@ -0,0 +1,22 @@
{
"_meta": {
"ui": {
"resourceUri": "ui://github-mcp-server/get-me",
"visibility": [
"model",
"app"
]
}
},
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get my user profile"
},
"description": "Get details of the authenticated GitHub user. Use this when a request is about the user's own profile for GitHub. Or when information is missing to build other tool calls.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "get_me"
}
@@ -0,0 +1,21 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get notification details"
},
"description": "Get detailed information for a specific GitHub notification, always call this tool when the user asks for details about a specific notification, if you don't know the ID list notifications first.",
"inputSchema": {
"properties": {
"notificationID": {
"description": "The ID of the notification",
"type": "string"
}
},
"required": [
"notificationID"
],
"type": "object"
},
"name": "get_notification_details"
}
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get a release by tag name"
},
"description": "Get a specific release by its tag name in a GitHub repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"tag": {
"description": "Tag name (e.g., 'v1.0.0')",
"type": "string"
}
},
"required": [
"owner",
"repo",
"tag"
],
"type": "object"
},
"name": "get_release_by_tag"
}
@@ -0,0 +1,39 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get repository tree"
},
"description": "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"path_filter": {
"description": "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)",
"type": "string"
},
"recursive": {
"default": false,
"description": "Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false",
"type": "boolean"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"tree_sha": {
"description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "get_repository_tree"
}
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get secret scanning alert"
},
"description": "Get details of a specific secret scanning alert in a GitHub repository.",
"inputSchema": {
"properties": {
"alertNumber": {
"description": "The number of the alert.",
"type": "number"
},
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
}
},
"required": [
"owner",
"repo",
"alertNumber"
],
"type": "object"
},
"name": "get_secret_scanning_alert"
}
+31
View File
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get tag details"
},
"description": "Get details about a specific git tag in a GitHub repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"tag": {
"description": "Tag name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"tag"
],
"type": "object"
},
"name": "get_tag"
}
@@ -0,0 +1,26 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get team members"
},
"description": "Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials",
"inputSchema": {
"properties": {
"org": {
"description": "Organization login (owner) that contains the team.",
"type": "string"
},
"team_slug": {
"description": "Team slug",
"type": "string"
}
},
"required": [
"org",
"team_slug"
],
"type": "object"
},
"name": "get_team_members"
}
+18
View File
@@ -0,0 +1,18 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get teams"
},
"description": "Get details of the teams the user is a member of. Limited to organizations accessible with current credentials",
"inputSchema": {
"properties": {
"user": {
"description": "Username to get teams for. If not provided, uses the authenticated user.",
"type": "string"
}
},
"type": "object"
},
"name": "get_teams"
}
@@ -0,0 +1,51 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Read issue dependencies"
},
"description": "Read an issue's dependency relationships in a GitHub repository: the issues that block it (blocked_by) or the issues it blocks (blocking).",
"inputSchema": {
"properties": {
"issue_number": {
"description": "The number of the issue",
"type": "number"
},
"method": {
"description": "The read operation to perform on a single issue's dependencies.\nOptions are:\n1. get_blocked_by - List the issues that block this issue (this issue is blocked by them).\n2. get_blocking - List the issues that this issue blocks.\n",
"enum": [
"get_blocked_by",
"get_blocking"
],
"type": "string"
},
"owner": {
"description": "The owner of the repository",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "The name of the repository",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo",
"issue_number"
],
"type": "object"
},
"name": "issue_dependency_read"
}
@@ -0,0 +1,62 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Change issue dependency"
},
"description": "Add or remove an issue dependency relationship in a GitHub repository. Use type 'blocked_by' to record that the subject issue is blocked by a related issue, or type 'blocking' to record that the subject issue blocks a related issue. The related issue defaults to the same repository as the subject unless related_owner/related_repo are provided.",
"inputSchema": {
"properties": {
"issue_number": {
"description": "The number of the subject issue",
"type": "number"
},
"method": {
"description": "The action to perform.\nOptions are:\n- 'add' - create the dependency relationship.\n- 'remove' - delete the dependency relationship.",
"enum": [
"add",
"remove"
],
"type": "string"
},
"owner": {
"description": "The owner of the subject issue's repository",
"type": "string"
},
"related_issue_number": {
"description": "The number of the related issue to link or unlink",
"type": "number"
},
"related_owner": {
"description": "The owner of the related issue's repository. Defaults to 'owner' when omitted.",
"type": "string"
},
"related_repo": {
"description": "The name of the related issue's repository. Defaults to 'repo' when omitted.",
"type": "string"
},
"repo": {
"description": "The name of the subject issue's repository",
"type": "string"
},
"type": {
"description": "The relationship direction relative to the subject issue.\nOptions are:\n- 'blocked_by' - the subject issue is blocked by the related issue.\n- 'blocking' - the subject issue blocks the related issue.",
"enum": [
"blocked_by",
"blocking"
],
"type": "string"
}
},
"required": [
"method",
"type",
"owner",
"repo",
"issue_number",
"related_issue_number"
],
"type": "object"
},
"name": "issue_dependency_write"
}
+54
View File
@@ -0,0 +1,54 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get issue details"
},
"description": "Get information about a specific issue in a GitHub repository.",
"inputSchema": {
"properties": {
"issue_number": {
"description": "The number of the issue",
"type": "number"
},
"method": {
"description": "The read operation to perform on a single issue.\nOptions are:\n1. get - Get issue details. Also returns best-effort hierarchy flags (`has_parent`, `has_children`); `parent` and `sub_issues_summary` are optional relationship summaries.\n2. get_comments - Get issue comments.\n3. get_sub_issues - Get sub-issues (children) of the issue.\n4. get_parent - Get the parent issue, if this issue is a sub-issue of another.\n5. get_labels - Get labels assigned to the issue.\n",
"enum": [
"get",
"get_comments",
"get_sub_issues",
"get_parent",
"get_labels"
],
"type": "string"
},
"owner": {
"description": "The owner of the repository",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "The name of the repository",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo",
"issue_number"
],
"type": "object"
},
"name": "issue_read"
}
+135
View File
@@ -0,0 +1,135 @@
{
"_meta": {
"ui": {
"resourceUri": "ui://github-mcp-server/issue-write",
"visibility": [
"model",
"app"
]
}
},
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Create or update issue/pull request"
},
"description": "Create a new or update an existing issue in a GitHub repository.",
"inputSchema": {
"properties": {
"assignees": {
"description": "Usernames to assign to this issue",
"items": {
"type": "string"
},
"type": "array"
},
"body": {
"description": "Issue body content",
"type": "string"
},
"duplicate_of": {
"description": "Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'.",
"type": "number"
},
"issue_fields": {
"description": "Issue field values to set or clear. Each item requires 'field_name' and exactly one of 'value', 'field_option_name', or 'delete: true'.",
"items": {
"additionalProperties": false,
"properties": {
"delete": {
"description": "Set to true to clear this field's current value on the issue. Cannot be combined with 'value' or 'field_option_name'.",
"enum": [
true
],
"type": "boolean"
},
"field_name": {
"description": "Issue field name (case-insensitive). Must match a field returned by list_issue_fields for this repository or its organization.",
"type": "string"
},
"field_option_name": {
"description": "Option name for single-select fields. Validated against the field's options before the API call. Cannot be combined with 'value' or 'delete'.",
"type": "string"
},
"value": {
"description": "Value to set. Use for text, number, and date fields (date as YYYY-MM-DD). For single-select fields, prefer 'field_option_name' so the option is validated before the API call. Cannot be combined with 'field_option_name' or 'delete'.",
"type": [
"string",
"number",
"boolean"
]
}
},
"required": [
"field_name"
],
"type": "object"
},
"type": "array"
},
"issue_number": {
"description": "Issue number to update",
"type": "number"
},
"labels": {
"description": "Labels to apply to this issue",
"items": {
"type": "string"
},
"type": "array"
},
"method": {
"description": "Write operation to perform on a single issue.\nOptions are:\n- 'create' - creates a new issue.\n- 'update' - updates an existing issue.\n",
"enum": [
"create",
"update"
],
"type": "string"
},
"milestone": {
"description": "Milestone number",
"type": "number"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"state": {
"description": "New state",
"enum": [
"open",
"closed"
],
"type": "string"
},
"state_reason": {
"description": "Reason for the state change. Ignored unless state is changed.",
"enum": [
"completed",
"not_planned",
"duplicate"
],
"type": "string"
},
"title": {
"description": "Issue title",
"type": "string"
},
"type": {
"description": "Type of this issue. Only use if issue types are enabled for this repository. Use list_issue_types tool to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo"
],
"type": "object"
},
"name": "issue_write"
}
+53
View File
@@ -0,0 +1,53 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Write operations on repository labels"
},
"description": "Perform write operations on repository labels. To set labels on issues, use the 'update_issue' tool.",
"inputSchema": {
"properties": {
"color": {
"description": "Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'.",
"type": "string"
},
"description": {
"description": "Label description text. Optional for 'create' and 'update'.",
"type": "string"
},
"method": {
"description": "Operation to perform: 'create', 'update', or 'delete'",
"enum": [
"create",
"update",
"delete"
],
"type": "string"
},
"name": {
"description": "Label name - required for all operations",
"type": "string"
},
"new_name": {
"description": "New name for the label (used only with 'update' method to rename)",
"type": "string"
},
"owner": {
"description": "Repository owner (username or organization name)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo",
"name"
],
"type": "object"
},
"name": "label_write"
}
@@ -0,0 +1,37 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List branches"
},
"description": "List branches in a GitHub repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_branches"
}
@@ -0,0 +1,69 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List code scanning alerts"
},
"description": "List code scanning alerts in a GitHub repository.",
"inputSchema": {
"properties": {
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"ref": {
"description": "The Git reference for the results you want to list.",
"type": "string"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
},
"severity": {
"description": "Filter code scanning alerts by severity",
"enum": [
"critical",
"high",
"medium",
"low",
"warning",
"note",
"error"
],
"type": "string"
},
"state": {
"default": "open",
"description": "Filter code scanning alerts by state. Defaults to open",
"enum": [
"open",
"closed",
"dismissed",
"fixed"
],
"type": "string"
},
"tool_name": {
"description": "The name of the tool used for code scanning.",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_code_scanning_alerts"
}
@@ -0,0 +1,57 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List commits"
},
"description": "Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).",
"inputSchema": {
"properties": {
"author": {
"description": "Author username or email address to filter commits by",
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"path": {
"description": "Only commits containing this file path will be returned",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sha": {
"description": "Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA.",
"type": "string"
},
"since": {
"description": "Only commits after this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)",
"type": "string"
},
"until": {
"description": "Only commits before this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_commits"
}
@@ -0,0 +1,71 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List commits"
},
"description": "Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).",
"inputSchema": {
"properties": {
"author": {
"description": "Author username or email address to filter commits by",
"type": "string"
},
"fields": {
"description": "Subset of fields to return for each commit. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields, e.g. just 'sha' and 'html_url'.",
"items": {
"enum": [
"sha",
"html_url",
"commit",
"author",
"committer"
],
"type": "string"
},
"type": "array"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"path": {
"description": "Only commits containing this file path will be returned",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sha": {
"description": "Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA.",
"type": "string"
},
"since": {
"description": "Only commits after this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)",
"type": "string"
},
"until": {
"description": "Only commits before this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_commits"
}
@@ -0,0 +1,57 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List dependabot alerts"
},
"description": "List dependabot alerts in a GitHub repository.",
"inputSchema": {
"properties": {
"after": {
"description": "Cursor for pagination. Use the cursor from the previous response.",
"type": "string"
},
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
},
"severity": {
"description": "Filter dependabot alerts by severity",
"enum": [
"low",
"medium",
"high",
"critical"
],
"type": "string"
},
"state": {
"default": "open",
"description": "Filter dependabot alerts by state. Defaults to open",
"enum": [
"open",
"fixed",
"dismissed",
"auto_dismissed"
],
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_dependabot_alerts"
}
@@ -0,0 +1,25 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List discussion categories"
},
"description": "List discussion categories with their id and name, for a repository or organisation.",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name. If not provided, discussion categories will be queried at the organisation level.",
"type": "string"
}
},
"required": [
"owner"
],
"type": "object"
},
"name": "list_discussion_categories"
}
@@ -0,0 +1,55 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List discussions"
},
"description": "List discussions for a repository or organisation.",
"inputSchema": {
"properties": {
"after": {
"description": "Cursor for pagination. Use the cursor from the previous response.",
"type": "string"
},
"category": {
"description": "Optional filter by discussion category ID. If provided, only discussions with this category are listed.",
"type": "string"
},
"direction": {
"description": "Order direction.",
"enum": [
"ASC",
"DESC"
],
"type": "string"
},
"orderBy": {
"description": "Order discussions by field. If provided, the 'direction' also needs to be provided.",
"enum": [
"CREATED_AT",
"UPDATED_AT"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name. If not provided, discussions will be queried at the organisation level.",
"type": "string"
}
},
"required": [
"owner"
],
"type": "object"
},
"name": "list_discussions"
}
+33
View File
@@ -0,0 +1,33 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List Gists"
},
"description": "List gists for a user",
"inputSchema": {
"properties": {
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"since": {
"description": "Only gists updated after this time (ISO 8601 timestamp)",
"type": "string"
},
"username": {
"description": "GitHub username (omit for authenticated user's gists)",
"type": "string"
}
},
"type": "object"
},
"name": "list_gists"
}
@@ -0,0 +1,88 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List global security advisories"
},
"description": "List global security advisories from GitHub.",
"inputSchema": {
"properties": {
"affects": {
"description": "Filter advisories by affected package or version (e.g. \"package1,package2@1.0.0\").",
"type": "string"
},
"cveId": {
"description": "Filter by CVE ID.",
"type": "string"
},
"cwes": {
"description": "Filter by Common Weakness Enumeration IDs (e.g. [\"79\", \"284\", \"22\"]).",
"items": {
"type": "string"
},
"type": "array"
},
"ecosystem": {
"description": "Filter by package ecosystem.",
"enum": [
"actions",
"composer",
"erlang",
"go",
"maven",
"npm",
"nuget",
"other",
"pip",
"pub",
"rubygems",
"rust"
],
"type": "string"
},
"ghsaId": {
"description": "Filter by GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx).",
"type": "string"
},
"isWithdrawn": {
"description": "Whether to only return withdrawn advisories.",
"type": "boolean"
},
"modified": {
"description": "Filter by publish or update date or date range (ISO 8601 date or range).",
"type": "string"
},
"published": {
"description": "Filter by publish date or date range (ISO 8601 date or range).",
"type": "string"
},
"severity": {
"description": "Filter by severity.",
"enum": [
"unknown",
"low",
"medium",
"high",
"critical"
],
"type": "string"
},
"type": {
"default": "reviewed",
"description": "Advisory type.",
"enum": [
"reviewed",
"malware",
"unreviewed"
],
"type": "string"
},
"updated": {
"description": "Filter by update date or date range (ISO 8601 date or range).",
"type": "string"
}
},
"type": "object"
},
"name": "list_global_security_advisories"
}
@@ -0,0 +1,25 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List issue fields"
},
"description": "List issue fields for a repository or organization. Returns field definitions including name, type (text, number, date, single_select), and for single_select fields the list of valid option names. When repo is omitted, returns org-level fields directly.",
"inputSchema": {
"properties": {
"owner": {
"description": "The account owner of the repository or organization. The name is not case sensitive.",
"type": "string"
},
"repo": {
"description": "The name of the repository. When provided, returns fields for this specific repository (inherited from its organization). When omitted, returns org-level fields directly.",
"type": "string"
}
},
"required": [
"owner"
],
"type": "object"
},
"name": "list_issue_fields"
}
@@ -0,0 +1,25 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List available issue types"
},
"description": "List supported issue types for a repository or its owner organization. When repo is omitted, returns org-level issue types directly.",
"inputSchema": {
"properties": {
"owner": {
"description": "The account owner of the repository or organization.",
"type": "string"
},
"repo": {
"description": "The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly.",
"type": "string"
}
},
"required": [
"owner"
],
"type": "object"
},
"name": "list_issue_types"
}
+93
View File
@@ -0,0 +1,93 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List issues"
},
"description": "List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter.",
"inputSchema": {
"properties": {
"after": {
"description": "Cursor for pagination. Use the cursor from the previous response.",
"type": "string"
},
"direction": {
"description": "Order direction. If provided, the 'orderBy' also needs to be provided.",
"enum": [
"ASC",
"DESC"
],
"type": "string"
},
"field_filters": {
"description": "Filter by custom issue field values. Each entry takes a field_name and a value; the server looks up the field and coerces the value to its type (single-select option name, text, number, or YYYY-MM-DD date).",
"items": {
"properties": {
"field_name": {
"description": "Name of the custom field (e.g. \"Priority\"). Case-insensitive.",
"type": "string"
},
"value": {
"description": "Value to filter on. For single-select fields, the option name (e.g. \"P1\"). For dates, YYYY-MM-DD. For numbers, the numeric value as a string. For text, the text value.",
"type": "string"
}
},
"required": [
"field_name",
"value"
],
"type": "object"
},
"type": "array"
},
"labels": {
"description": "Filter by labels",
"items": {
"type": "string"
},
"type": "array"
},
"orderBy": {
"description": "Order issues by field. If provided, the 'direction' also needs to be provided.",
"enum": [
"CREATED_AT",
"UPDATED_AT",
"COMMENTS"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"since": {
"description": "Filter by date (ISO 8601 timestamp)",
"type": "string"
},
"state": {
"description": "Filter by state, by default both open and closed issues are returned when not provided",
"enum": [
"OPEN",
"CLOSED"
],
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_issues"
}
@@ -0,0 +1,112 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List issues"
},
"description": "List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter.",
"inputSchema": {
"properties": {
"after": {
"description": "Cursor for pagination. Use the cursor from the previous response.",
"type": "string"
},
"direction": {
"description": "Order direction. If provided, the 'orderBy' also needs to be provided.",
"enum": [
"ASC",
"DESC"
],
"type": "string"
},
"field_filters": {
"description": "Filter by custom issue field values. Each entry takes a field_name and a value; the server looks up the field and coerces the value to its type (single-select option name, text, number, or YYYY-MM-DD date).",
"items": {
"properties": {
"field_name": {
"description": "Name of the custom field (e.g. \"Priority\"). Case-insensitive.",
"type": "string"
},
"value": {
"description": "Value to filter on. For single-select fields, the option name (e.g. \"P1\"). For dates, YYYY-MM-DD. For numbers, the numeric value as a string. For text, the text value.",
"type": "string"
}
},
"required": [
"field_name",
"value"
],
"type": "object"
},
"type": "array"
},
"fields": {
"description": "Subset of fields to return for each issue. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' and 'field_values' in particular drops the largest per-result data.",
"items": {
"enum": [
"number",
"title",
"body",
"state",
"user",
"labels",
"comments",
"created_at",
"updated_at",
"field_values"
],
"type": "string"
},
"type": "array"
},
"labels": {
"description": "Filter by labels",
"items": {
"type": "string"
},
"type": "array"
},
"orderBy": {
"description": "Order issues by field. If provided, the 'direction' also needs to be provided.",
"enum": [
"CREATED_AT",
"UPDATED_AT",
"COMMENTS"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"since": {
"description": "Filter by date (ISO 8601 timestamp)",
"type": "string"
},
"state": {
"description": "Filter by state, by default both open and closed issues are returned when not provided",
"enum": [
"OPEN",
"CLOSED"
],
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_issues"
}
+26
View File
@@ -0,0 +1,26 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List labels from a repository"
},
"description": "List labels from a repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner (username or organization name) - required for all operations",
"type": "string"
},
"repo": {
"description": "Repository name - required for all operations",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_label"
}
@@ -0,0 +1,50 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List notifications"
},
"description": "Lists all GitHub notifications for the authenticated user, including unread notifications, mentions, review requests, assignments, and updates on issues or pull requests. Use this tool whenever the user asks what to work on next, requests a summary of their GitHub activity, wants to see pending reviews, or needs to check for new updates or tasks. This tool is the primary way to discover actionable items, reminders, and outstanding work on GitHub. Always call this tool when asked what to work on next, what is pending, or what needs attention in GitHub.",
"inputSchema": {
"properties": {
"before": {
"description": "Only show notifications updated before the given time (ISO 8601 format)",
"type": "string"
},
"filter": {
"description": "Filter notifications to, use default unless specified. Read notifications are ones that have already been acknowledged by the user. Participating notifications are those that the user is directly involved in, such as issues or pull requests they have commented on or created.",
"enum": [
"default",
"include_read_notifications",
"only_participating"
],
"type": "string"
},
"owner": {
"description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Optional repository name. If provided with owner, only notifications for this repository are listed.",
"type": "string"
},
"since": {
"description": "Only show notifications updated after the given time (ISO 8601 format)",
"type": "string"
}
},
"type": "object"
},
"name": "list_notifications"
}
@@ -0,0 +1,48 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List org repository security advisories"
},
"description": "List repository security advisories for a GitHub organization.",
"inputSchema": {
"properties": {
"direction": {
"description": "Sort direction.",
"enum": [
"asc",
"desc"
],
"type": "string"
},
"org": {
"description": "The organization login.",
"type": "string"
},
"sort": {
"description": "Sort field.",
"enum": [
"created",
"updated",
"published"
],
"type": "string"
},
"state": {
"description": "Filter by advisory state.",
"enum": [
"triage",
"draft",
"published",
"closed"
],
"type": "string"
}
},
"required": [
"org"
],
"type": "object"
},
"name": "list_org_repository_security_advisories"
}
@@ -0,0 +1,72 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List pull requests"
},
"description": "List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.",
"inputSchema": {
"properties": {
"base": {
"description": "Filter by base branch",
"type": "string"
},
"direction": {
"description": "Sort direction",
"enum": [
"asc",
"desc"
],
"type": "string"
},
"head": {
"description": "Filter by head user/org and branch",
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sort": {
"description": "Sort by",
"enum": [
"created",
"updated",
"popularity",
"long-running"
],
"type": "string"
},
"state": {
"description": "Filter by state",
"enum": [
"open",
"closed",
"all"
],
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_pull_requests"
}
@@ -0,0 +1,106 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List pull requests"
},
"description": "List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.",
"inputSchema": {
"properties": {
"base": {
"description": "Filter by base branch",
"type": "string"
},
"direction": {
"description": "Sort direction",
"enum": [
"asc",
"desc"
],
"type": "string"
},
"fields": {
"description": "Subset of fields to return for each pull request. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' in particular drops the largest per-result data.",
"items": {
"enum": [
"number",
"title",
"body",
"state",
"draft",
"merged",
"mergeable_state",
"html_url",
"user",
"labels",
"assignees",
"requested_reviewers",
"merged_by",
"head",
"base",
"additions",
"deletions",
"changed_files",
"commits",
"comments",
"created_at",
"updated_at",
"closed_at",
"merged_at",
"milestone"
],
"type": "string"
},
"type": "array"
},
"head": {
"description": "Filter by head user/org and branch",
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sort": {
"description": "Sort by",
"enum": [
"created",
"updated",
"popularity",
"long-running"
],
"type": "string"
},
"state": {
"description": "Filter by state",
"enum": [
"open",
"closed",
"all"
],
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_pull_requests"
}
@@ -0,0 +1,37 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List releases"
},
"description": "List releases in a GitHub repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_releases"
}
@@ -0,0 +1,55 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List releases"
},
"description": "List releases in a GitHub repository",
"inputSchema": {
"properties": {
"fields": {
"description": "Subset of fields to return for each release. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' in particular drops the largest per-release data.",
"items": {
"enum": [
"id",
"tag_name",
"name",
"body",
"html_url",
"published_at",
"prerelease",
"draft",
"author"
],
"type": "string"
},
"type": "array"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_releases"
}
@@ -0,0 +1,46 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List repository collaborators"
},
"description": "List collaborators of a GitHub repository. Results are paginated; the response includes `nextPage`, `prevPage`, `firstPage`, and `lastPage` fields. To get the next page, use the `nextPage` value as the `page` parameter.",
"inputSchema": {
"properties": {
"affiliation": {
"description": "Filter by affiliation. Can be one of: 'outside' (outside collaborators), 'direct' (all with permissions regardless of org membership), 'all' (all collaborators). Default: 'all'",
"enum": [
"outside",
"direct",
"all"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (default 1, min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (default 30, min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_repository_collaborators"
}
@@ -0,0 +1,53 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List repository security advisories"
},
"description": "List repository security advisories for a GitHub repository.",
"inputSchema": {
"properties": {
"direction": {
"description": "Sort direction.",
"enum": [
"asc",
"desc"
],
"type": "string"
},
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
},
"sort": {
"description": "Sort field.",
"enum": [
"created",
"updated",
"published"
],
"type": "string"
},
"state": {
"description": "Filter by advisory state.",
"enum": [
"triage",
"draft",
"published",
"closed"
],
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_repository_security_advisories"
}
@@ -0,0 +1,61 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List secret scanning alerts"
},
"description": "List secret scanning alerts in a GitHub repository.",
"inputSchema": {
"properties": {
"owner": {
"description": "The owner of the repository.",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
},
"resolution": {
"description": "Filter by resolution",
"enum": [
"false_positive",
"wont_fix",
"revoked",
"pattern_edited",
"pattern_deleted",
"used_in_tests"
],
"type": "string"
},
"secret_type": {
"description": "A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter.",
"type": "string"
},
"state": {
"description": "Filter by state",
"enum": [
"open",
"resolved"
],
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_secret_scanning_alerts"
}
@@ -0,0 +1,45 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List starred repositories"
},
"description": "List starred repositories",
"inputSchema": {
"properties": {
"direction": {
"description": "The direction to sort the results by.",
"enum": [
"asc",
"desc"
],
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"sort": {
"description": "How to sort the results. Can be either 'created' (when the repository was starred) or 'updated' (when the repository was last pushed to).",
"enum": [
"created",
"updated"
],
"type": "string"
},
"username": {
"description": "Username to list starred repositories for. Defaults to the authenticated user.",
"type": "string"
}
},
"type": "object"
},
"name": "list_starred_repositories"
}
+37
View File
@@ -0,0 +1,37 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List tags"
},
"description": "List git tags in a GitHub repository",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo"
],
"type": "object"
},
"name": "list_tags"
}
@@ -0,0 +1,31 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Manage notification subscription"
},
"description": "Manage a notification subscription: ignore, watch, or delete a notification thread subscription.",
"inputSchema": {
"properties": {
"action": {
"description": "Action to perform: ignore, watch, or delete the notification subscription.",
"enum": [
"ignore",
"watch",
"delete"
],
"type": "string"
},
"notificationID": {
"description": "The ID of the notification thread.",
"type": "string"
}
},
"required": [
"notificationID",
"action"
],
"type": "object"
},
"name": "manage_notification_subscription"
}
@@ -0,0 +1,36 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Manage repository notification subscription"
},
"description": "Manage a repository notification subscription: ignore, watch, or delete repository notifications subscription for the provided repository.",
"inputSchema": {
"properties": {
"action": {
"description": "Action to perform: ignore, watch, or delete the repository notification subscription.",
"enum": [
"ignore",
"watch",
"delete"
],
"type": "string"
},
"owner": {
"description": "The account owner of the repository.",
"type": "string"
},
"repo": {
"description": "The name of the repository.",
"type": "string"
}
},
"required": [
"owner",
"repo",
"action"
],
"type": "object"
},
"name": "manage_repository_notification_subscription"
}
@@ -0,0 +1,26 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Mark all notifications as read"
},
"description": "Mark all notifications as read",
"inputSchema": {
"properties": {
"lastReadAt": {
"description": "Describes the last point that notifications were checked (optional). Default: Now",
"type": "string"
},
"owner": {
"description": "Optional repository owner. If provided with repo, only notifications for this repository are marked as read.",
"type": "string"
},
"repo": {
"description": "Optional repository name. If provided with owner, only notifications for this repository are marked as read.",
"type": "string"
}
},
"type": "object"
},
"name": "mark_all_notifications_read"
}
@@ -0,0 +1,60 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Merge pull request"
},
"description": "Merge a pull request in a GitHub repository.",
"icons": [
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACeElEQVRIibWVTUhUYRSGn/e74+iiQih1F9Vcmj9sptylUVBYkO4jcNeuJBdFKxe1CYQokGrRKjCEdtmqwEVmtqomQWeiUdc2EBUtUufe0yLHn1KLGXtX5zvn4zz3vd8f/Gfp90Qs0drmpA6MT1EveDo1NfV92wB+KnMdo39Nfs4L7eSHD5Nz1QJcJYglWtsw+iUehAuRRjO1g+0KHLerbb4OIHnHAC1FdW129s3XmUJuwnBDoOPbA7BwHsD7QWq1HKYN5msBRCpB1AueLoSROSkciSUyj5ClhE6BLtYC8CpBqVRabNrdMmIiJdQjuUbQ1WI+d78WwIbykxnzU9np7ejlNq2YxQ4ebNtTKyCyWcEgYl55EDj/a7ihFEtkLkr0As2YxjwL+9aem00dCEYNzvnJzLDvH27aaM5y80HEnKGHKGwPnEbT6fSOvzpAmrDQnkncpC7siiUzz2QqIPu25iOuGBorTufO/AJmH0v2ajHwuoHhrQHATOH9rQPJ7IjDLgs6kZ0F6it1AzArVcZLdUE+WnYgmv/uYFmz+dxH4NJGNT+RfYLCE7F4tn0pGkxHy94AmBm8/GfAVvIs7AukUTkbj5YdYIbZ9WJh8m1lzrrbNB4/tD+QuyPsdCibF26gmM/dY/NdRDqd3rEYeN04mswYL+ZXm68DxOPxnWXXMClsp+GGhCWBTtClYj53t1qXK78oVH2XYB/mHZ0pvHsN4Cczzw3rBaoGrJ6D5ZUvN1i+kjI0LWiptjmscbC88hZZCAf2trZeq1v0UsJ6wF7UAlhxUMxPvkW6AboQLbvPcjaO+BIx11cL4I9H308eOiLRQUhpOx79/66fNKzrOCYNDm0AAAAASUVORK5CYII=",
"theme": "light"
},
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABjElEQVRIibWVPS/DURTGnysSC0HiZdWVrZ28JDaLT8BHaBsMdjqZJDXiAzC2LF5mX6GtATGiIsGARH+Gnj9X8a/kf3uWe3Py3Oc559xz75E6bK7VAWQkzUi6lXTonHsOpgYUgAZfdgmkQpFnjHwb6AemgDpQCiWwYlEPeL4i8JCEt8vb39g67vkmPH8yA3qt5nVgCzi1jLJBBEwkBZSAdxPKAj86LYQQQCU4cYvAKzDUSYF3YC+uRIAD8sA58ACU//VuTODE1n1g+A9c3jBH1tJ1a5TeCPNrdACSCpKeJG1IepN0LKkm6dGDrkqqOOdm7dyUpDNJi865PUnqjsvEObcJHEhaljQnaV5STwvszttXbR2J441KtB4LauLKVpZpYBDYte8mHUogZTWPrAGstTtQBl6AayDX7qHZD7AALMVGDvQBV5ZyETi2qHLtMvmXWRQAk57vBKgl4fV/0+jmq56vImk0icCnAWm7pB3riGngnlADx0TW+T4yL4CxJJy/Df20mkP/TqGHfifsA7INs3X5i3+yAAAAAElFTkSuQmCC",
"theme": "dark"
}
],
"inputSchema": {
"properties": {
"commit_message": {
"description": "Extra detail for merge commit",
"type": "string"
},
"commit_title": {
"description": "Title for merge commit",
"type": "string"
},
"merge_method": {
"description": "Merge method",
"enum": [
"merge",
"squash",
"rebase"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"pullNumber": {
"description": "Pull request number",
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"pullNumber"
],
"type": "object"
},
"name": "merge_pull_request"
}
@@ -0,0 +1,69 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get details of GitHub Projects resources"
},
"description": "Get details about specific GitHub Projects resources.\nUse this tool to get details about individual projects, project fields, and project items by their unique IDs.\n",
"inputSchema": {
"properties": {
"field_id": {
"description": "The field's ID. Required for 'get_project_field' method.",
"type": "number"
},
"field_names": {
"description": "Specific list of field names to include in the response when getting a project item (e.g. [\"Status\", \"Priority\"]). Resolved server-side to field IDs — pass this instead of 'fields' when you only know the human-readable names. Mutually exclusive with 'fields' — provide one, not both. Only used for 'get_project_item' method.",
"items": {
"type": "string"
},
"type": "array"
},
"fields": {
"description": "Specific list of field IDs to include in the response when getting a project item (e.g. [\"102589\", \"985201\", \"169875\"]). If neither 'fields' nor 'field_names' is provided, only the title field is included. Mutually exclusive with 'field_names' — provide one, not both. Only used for 'get_project_item' method.",
"items": {
"type": "string"
},
"type": "array"
},
"item_id": {
"description": "The item's ID. Required for 'get_project_item' method.",
"type": "number"
},
"method": {
"description": "The method to execute",
"enum": [
"get_project",
"get_project_field",
"get_project_item",
"get_project_status_update"
],
"type": "string"
},
"owner": {
"description": "The owner (user or organization login). The name is not case sensitive.",
"type": "string"
},
"owner_type": {
"description": "Owner type (user or org). If not provided, will be automatically detected.",
"enum": [
"user",
"org"
],
"type": "string"
},
"project_number": {
"description": "The project's number.",
"type": "number"
},
"status_update_id": {
"description": "The node ID of the project status update. Required for 'get_project_status_update' method.",
"type": "string"
}
},
"required": [
"method"
],
"type": "object"
},
"name": "projects_get"
}
@@ -0,0 +1,74 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "List GitHub Projects resources"
},
"description": "Tools for listing GitHub Projects resources.\nUse this tool to list projects for a user or organization, or list project fields and items for a specific project.\n",
"inputSchema": {
"properties": {
"after": {
"description": "Forward pagination cursor from previous pageInfo.nextCursor.",
"type": "string"
},
"before": {
"description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
"type": "string"
},
"field_names": {
"description": "Field names to include when listing project items (e.g. [\"Status\", \"Priority\"]). Resolved server-side to field IDs — pass this instead of 'fields' when you only know the human-readable names. Names that fail to resolve return a structured error. Mutually exclusive with 'fields' — provide one, not both. Only used for 'list_project_items' method.",
"items": {
"type": "string"
},
"type": "array"
},
"fields": {
"description": "Field IDs to include when listing project items (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this (and without 'field_names'), only titles returned. Mutually exclusive with 'field_names' — provide one, not both. Only used for 'list_project_items' method.",
"items": {
"type": "string"
},
"type": "array"
},
"method": {
"description": "The action to perform",
"enum": [
"list_projects",
"list_project_fields",
"list_project_items",
"list_project_status_updates"
],
"type": "string"
},
"owner": {
"description": "The owner (user or organization login). The name is not case sensitive.",
"type": "string"
},
"owner_type": {
"description": "Owner type (user or org). If not provided, will automatically try both.",
"enum": [
"user",
"org"
],
"type": "string"
},
"per_page": {
"description": "Results per page (max 50)",
"type": "number"
},
"project_number": {
"description": "The project's number. Required for 'list_project_fields', 'list_project_items', and 'list_project_status_updates' methods.",
"type": "number"
},
"query": {
"description": "Filter/query string. For list_projects: filter by title text and state (e.g. \"roadmap is:open\"). For list_project_items: advanced filtering using GitHub's project filtering syntax.",
"type": "string"
}
},
"required": [
"method",
"owner"
],
"type": "object"
},
"name": "projects_list"
}
@@ -0,0 +1,141 @@
{
"annotations": {
"destructiveHint": true,
"idempotentHint": false,
"readOnlyHint": false,
"title": "Manage GitHub Projects"
},
"description": "Create and manage GitHub Projects: create projects, add/update/delete items, create status updates, and add iteration fields.",
"inputSchema": {
"properties": {
"body": {
"description": "The body of the status update (markdown). Used for 'create_project_status_update' method.",
"type": "string"
},
"field_name": {
"description": "The name of the iteration field (e.g. 'Sprint'). Required for 'create_iteration_field' method.",
"type": "string"
},
"issue_number": {
"description": "The issue number. Required for 'add_project_item' when item_type is 'issue'. Also accepted by 'update_project_item' to resolve the item by issue number (combine with item_owner and item_repo).",
"type": "number"
},
"item_id": {
"description": "The project item ID. Required for 'delete_project_item'. For 'update_project_item', provide either item_id, or (item_owner + item_repo + issue_number) to resolve the item by issue.",
"type": "number"
},
"item_owner": {
"description": "The owner (user or organization) of the repository containing the issue or pull request. Required for 'add_project_item' method. Also accepted by 'update_project_item' when resolving the item by issue number.",
"type": "string"
},
"item_repo": {
"description": "The name of the repository containing the issue or pull request. Required for 'add_project_item' method. Also accepted by 'update_project_item' when resolving the item by issue number.",
"type": "string"
},
"item_type": {
"description": "The item's type, either issue or pull_request. Required for 'add_project_item' method.",
"enum": [
"issue",
"pull_request"
],
"type": "string"
},
"iteration_duration": {
"description": "Duration in days for iterations of the field (e.g. 7 for weekly, 14 for bi-weekly). Required for 'create_iteration_field' method.",
"type": "number"
},
"iterations": {
"description": "Custom iterations for 'create_iteration_field' method. Only set this when you need iterations with varying durations, breaks between them, or specific titles. Otherwise omit it: GitHub auto-creates three iterations of 'iteration_duration' days starting on 'start_date', which is the right choice for most cases.",
"items": {
"additionalProperties": false,
"properties": {
"duration": {
"description": "Duration in days",
"type": "number"
},
"start_date": {
"description": "Start date in YYYY-MM-DD format",
"type": "string"
},
"title": {
"description": "Iteration title (e.g. 'Sprint 1')",
"type": "string"
}
},
"required": [
"title",
"start_date",
"duration"
],
"type": "object"
},
"type": "array"
},
"method": {
"description": "The method to execute",
"enum": [
"add_project_item",
"update_project_item",
"delete_project_item",
"create_project_status_update",
"create_project",
"create_iteration_field"
],
"type": "string"
},
"owner": {
"description": "The project owner (user or organization login). The name is not case sensitive.",
"type": "string"
},
"owner_type": {
"description": "Owner type (user or org). Required for 'create_project' method. If not provided for other methods, will be automatically detected.",
"enum": [
"user",
"org"
],
"type": "string"
},
"project_number": {
"description": "The project's number. Required for all methods except 'create_project'.",
"type": "number"
},
"pull_request_number": {
"description": "The pull request number (use when item_type is 'pull_request' for 'add_project_item' method). Provide either issue_number or pull_request_number.",
"type": "number"
},
"start_date": {
"description": "Start date in YYYY-MM-DD format. Used for 'create_project_status_update' and 'create_iteration_field' methods.",
"type": "string"
},
"status": {
"description": "The status of the project. Used for 'create_project_status_update' method.",
"enum": [
"INACTIVE",
"ON_TRACK",
"AT_RISK",
"OFF_TRACK",
"COMPLETE"
],
"type": "string"
},
"target_date": {
"description": "The target date of the status update in YYYY-MM-DD format. Used for 'create_project_status_update' method.",
"type": "string"
},
"title": {
"description": "The project title. Required for 'create_project' method.",
"type": "string"
},
"updated_field": {
"description": "Object describing the field to update and its new value. Required for 'update_project_item'. Two shapes are accepted: (1) by ID — {\"id\": 123456, \"value\": \"...\"}; (2) by name — {\"name\": \"Status\", \"value\": \"In Progress\"}. For single-select fields, option-name resolution requires the by-name shape; on the by-ID shape, pass the option ID. Set value to null to clear the field.",
"type": "object"
}
},
"required": [
"method",
"owner"
],
"type": "object"
},
"name": "projects_write"
}
@@ -0,0 +1,62 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Get details for a single pull request"
},
"description": "Get information on a specific pull request in GitHub repository.",
"inputSchema": {
"properties": {
"after": {
"description": "Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page.",
"type": "string"
},
"method": {
"description": "Action to specify what pull request data needs to be retrieved from GitHub. \nPossible options: \n 1. get - Get details of a specific pull request.\n 2. get_diff - Get the diff of a pull request.\n 3. get_status - Get combined commit status of a head commit in a pull request.\n 4. get_files - Get the list of files changed in a pull request. Use with pagination parameters to control the number of results returned.\n 5. get_commits - Get the list of commits on a pull request. Use with pagination parameters to control the number of results returned.\n 6. get_review_comments - Get review threads on a pull request. Each thread contains logically grouped review comments made on the same code location during pull request reviews. Returns threads with metadata (isResolved, isOutdated, isCollapsed) and their associated comments. Use cursor-based pagination (perPage, after) to control results.\n 7. get_reviews - Get the reviews on a pull request. When asked for review comments, use get_review_comments method. Use with pagination parameters to control the number of results returned.\n 8. get_comments - Get comments on a pull request. Use this if user doesn't specifically want review comments. Use with pagination parameters to control the number of results returned.\n 9. get_check_runs - Get check runs for the head commit of a pull request. Check runs are the individual CI/CD jobs and checks that run on the PR.\n",
"enum": [
"get",
"get_diff",
"get_status",
"get_files",
"get_commits",
"get_review_comments",
"get_reviews",
"get_comments",
"get_check_runs"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"pullNumber": {
"description": "Pull request number",
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo",
"pullNumber"
],
"type": "object"
},
"name": "pull_request_read"
}
@@ -0,0 +1,64 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Write operations (create, submit, delete) on pull request reviews"
},
"description": "Create and/or submit, delete review of a pull request.\n\nAvailable methods:\n- create: Create a new review of a pull request. If \"event\" parameter is provided, the review is submitted. If \"event\" is omitted, a pending review is created.\n- submit_pending: Submit an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request. The \"body\" and \"event\" parameters are used when submitting the review.\n- delete_pending: Delete an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request.\n- resolve_thread: Resolve a review thread. Requires only \"threadId\" parameter with the thread's node ID (e.g., PRRT_kwDOxxx). The owner, repo, and pullNumber parameters are not used for this method. Resolving an already-resolved thread is a no-op.\n- unresolve_thread: Unresolve a previously resolved review thread. Requires only \"threadId\" parameter. The owner, repo, and pullNumber parameters are not used for this method. Unresolving an already-unresolved thread is a no-op.\n",
"inputSchema": {
"properties": {
"body": {
"description": "Review comment text",
"type": "string"
},
"commitID": {
"description": "SHA of commit to review",
"type": "string"
},
"event": {
"description": "Review action to perform.",
"enum": [
"APPROVE",
"REQUEST_CHANGES",
"COMMENT"
],
"type": "string"
},
"method": {
"description": "The write operation to perform on pull request review.",
"enum": [
"create",
"submit_pending",
"delete_pending",
"resolve_thread",
"unresolve_thread"
],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"pullNumber": {
"description": "Pull request number",
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"threadId": {
"description": "The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments.",
"type": "string"
}
},
"required": [
"method",
"owner",
"repo",
"pullNumber"
],
"type": "object"
},
"name": "pull_request_review_write"
}
+59
View File
@@ -0,0 +1,59 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Push files to repository"
},
"description": "Push multiple files to a GitHub repository in a single commit",
"inputSchema": {
"properties": {
"branch": {
"description": "Branch to push to",
"type": "string"
},
"files": {
"description": "Array of file objects to push, each object with path (string) and content (string)",
"items": {
"additionalProperties": false,
"properties": {
"content": {
"description": "file content",
"type": "string"
},
"path": {
"description": "path to the file",
"type": "string"
}
},
"required": [
"path",
"content"
],
"type": "object"
},
"type": "array"
},
"message": {
"description": "Commit message",
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"branch",
"files",
"message"
],
"type": "object"
},
"name": "push_files"
}
@@ -0,0 +1,39 @@
{
"annotations": {
"destructiveHint": true,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Remove Sub-Issue"
},
"description": "Remove a sub-issue from a parent issue.",
"inputSchema": {
"properties": {
"issue_number": {
"description": "The parent issue number",
"minimum": 1,
"type": "number"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sub_issue_id": {
"description": "The ID of the sub-issue to remove. ID is not the same as issue number",
"type": "number"
}
},
"required": [
"owner",
"repo",
"issue_number",
"sub_issue_id"
],
"type": "object"
},
"name": "remove_sub_issue"
}
@@ -0,0 +1,47 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Reprioritize Sub-Issue"
},
"description": "Reprioritize (reorder) a sub-issue relative to other sub-issues.",
"inputSchema": {
"properties": {
"after_id": {
"description": "The ID of the sub-issue to place this after (either after_id OR before_id should be specified)",
"type": "number"
},
"before_id": {
"description": "The ID of the sub-issue to place this before (either after_id OR before_id should be specified)",
"type": "number"
},
"issue_number": {
"description": "The parent issue number",
"minimum": 1,
"type": "number"
},
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"sub_issue_id": {
"description": "The ID of the sub-issue to reorder. ID is not the same as issue number",
"type": "number"
}
},
"required": [
"owner",
"repo",
"issue_number",
"sub_issue_id"
],
"type": "object"
},
"name": "reprioritize_sub_issue"
}
@@ -0,0 +1,43 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": false,
"title": "Request Copilot review"
},
"description": "Request a GitHub Copilot code review for a pull request. Use this for automated feedback on pull requests, usually before requesting a human reviewer.",
"icons": [
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=",
"theme": "light"
},
{
"mimeType": "image/png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==",
"theme": "dark"
}
],
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner",
"type": "string"
},
"pullNumber": {
"description": "Pull request number",
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
}
},
"required": [
"owner",
"repo",
"pullNumber"
],
"type": "object"
},
"name": "request_copilot_review"
}
@@ -0,0 +1,42 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Request Pull Request Reviewers"
},
"description": "Request reviewers for a pull request.",
"inputSchema": {
"properties": {
"owner": {
"description": "Repository owner (username or organization)",
"type": "string"
},
"pullNumber": {
"description": "The pull request number",
"minimum": 1,
"type": "number"
},
"repo": {
"description": "Repository name",
"type": "string"
},
"reviewers": {
"description": "GitHub usernames or ORG/team-slug team reviewers to request reviews from",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"owner",
"repo",
"pullNumber",
"reviewers"
],
"type": "object"
},
"name": "request_pull_request_reviewers"
}
@@ -0,0 +1,23 @@
{
"annotations": {
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Resolve Review Thread"
},
"description": "Resolve a review thread on a pull request. Resolving an already-resolved thread is a no-op.",
"inputSchema": {
"properties": {
"threadID": {
"description": "The node ID of the review thread to resolve (e.g., PRRT_kwDOxxx)",
"type": "string"
}
},
"required": [
"threadID"
],
"type": "object"
},
"name": "resolve_review_thread"
}
+44
View File
@@ -0,0 +1,44 @@
{
"annotations": {
"idempotentHint": false,
"readOnlyHint": true,
"title": "Search code"
},
"description": "Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.",
"inputSchema": {
"properties": {
"order": {
"description": "Sort order for results",
"enum": [
"asc",
"desc"
],
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"query": {
"description": "Search query (GitHub code search REST). Implicit AND between terms; supports `OR`, `NOT`, and `\"quoted phrase\"` for exact match. Qualifiers: `repo:owner/repo`, `org:`, `user:`, `language:`, `path:dir` (prefix match), `filename:exact.ext`, `extension:`, `in:file`, `in:path`, `size:`, `is:archived`, `is:fork`. Max 256 chars. Examples: `WithContext language:go org:github`; `\"package main\" repo:o/r`; `func extension:go path:cmd repo:o/r`; `NOT TODO language:go repo:o/r`.",
"type": "string"
},
"sort": {
"description": "Sort field ('indexed' only)",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "search_code"
}

Some files were not shown because too many files have changed in this diff Show More