docs/error-handling.md
This document describes the error handling patterns used in the GitHub MCP Server, specifically how we handle GitHub API errors and avoid direct use of mcp-go error types.
The GitHub MCP Server implements a custom error handling approach that serves two primary purposes:
This dual approach enables better observability and debugging capabilities, particularly for remote server deployments where understanding the nature of failures (rate limiting, authentication, 404s, 500s, etc.) is crucial for validation and monitoring.
Used for REST API errors from the GitHub API:
type GitHubAPIError struct {
Message string `json:"message"`
Response *github.Response `json:"-"`
Err error `json:"-"`
}
Used for GraphQL API errors from the GitHub API:
type GitHubGraphQLError struct {
Message string `json:"message"`
Err error `json:"-"`
}
Instead of directly returning mcp.NewToolResultError(), use:
return ghErrors.NewGitHubAPIErrorResponse(ctx, message, response, err), nil
This function:
GitHubAPIError with the provided message, response, and errorreturn ghErrors.NewGitHubGraphQLErrorResponse(ctx, message, err), nil
The error handling system uses context to store errors for later inspection:
// Initialize context with error tracking
ctx = errors.ContextWithGitHubErrors(ctx)
// Retrieve errors for inspection (typically in middleware)
apiErrors, err := errors.GetGitHubAPIErrors(ctx)
graphqlErrors, err := errors.GetGitHubGraphQLErrors(ctx)
This approach was designed to work around current limitations in mcp-go where context is not propagated through each step of request processing. By storing errors in context values, middleware can inspect them without requiring context propagation.
Error storage operations in context are designed to fail gracefully - if context storage fails, the tool will still return an appropriate error response to the client.
errors.Is checks without logging PIIfunc GetIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("get_issue", /* ... */),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
owner, err := RequiredParam[string](request, "owner")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
}
issue, resp, err := client.Issues.Get(ctx, owner, repo, issueNumber)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
"failed to get issue",
resp,
err,
), nil
}
return MarshalledTextResult(issue), nil
}
}
This approach ensures that both the client receives an appropriate error response and any middleware can inspect the underlying GitHub API error for monitoring and debugging purposes.