Files
Tommaso Moro 778f5bb6a3 chore(deps): bump go-github v87 → v89 and resolve breaking changes (#2840)
* chore(deps): bump go-github v87 -> v89 and resolve breaking changes

Bumps google/go-github from v87 to v89 across the module and fixes the
resulting breaking changes. No tool or behavior changes.

- Rewrite all import paths go-github/v87 -> go-github/v89.
- gists.go: Gists.Create now takes CreateGistRequest by value and
  Gists.Edit is renamed to Gists.Update taking UpdateGistRequest.
- repositories_test.go: adapt to RepositoryRelease fields that became
  value types in v89 (ID, TagName, Draft).
- Regenerate third-party license files for the new module path.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Don't clear gist description on update when omitted

update_gist always sent Description as a pointer to the OptionalParam
zero value (""), so omitting description would overwrite an existing gist
description with an empty string. Only set UpdateGistRequest.Description
when the caller actually provided the argument; an explicit empty string
still clears it. Adds a test asserting the description key is absent from
the PATCH body when omitted and present when set.

This addresses a pre-existing behavior surfaced while migrating to the
v89 gist request types.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-08 15:08:59 +01:00

172 lines
5.9 KiB
Go

package github
import (
"context"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/google/go-github/v89/github"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// setIFCLabel writes the given IFC security label into a tool result's _meta
// under the "ifc" key, allocating the Meta map if necessary.
func setIFCLabel(r *mcp.CallToolResult, label ifc.SecurityLabel) {
if r.Meta == nil {
r.Meta = mcp.Meta{}
}
r.Meta["ifc"] = label
}
func shouldAttachIFCLabel(ctx context.Context, deps ToolDependencies, r *mcp.CallToolResult) bool {
return r != nil && !r.IsError && deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels)
}
// attachStaticIFCLabel attaches a fixed IFC label to a successful tool result
// when IFC labels are enabled. It is used by tools whose label does not depend
// on any repository visibility lookup (e.g. security alerts, global
// advisories, team membership, notification subjects).
//
// Error results are left untouched, and the label is omitted entirely when the
// IFC feature flag is disabled.
func attachStaticIFCLabel(ctx context.Context, deps ToolDependencies, r *mcp.CallToolResult, label ifc.SecurityLabel) *mcp.CallToolResult {
if !shouldAttachIFCLabel(ctx, deps, r) {
return r
}
setIFCLabel(r, label)
return r
}
// attachRepoVisibilityIFCLabel attaches an IFC label derived from a single
// repository's visibility to a successful tool result when IFC labels are
// enabled. The concrete label is produced by labelFn, which receives whether
// the repository is private.
//
// The repository visibility is resolved via FetchRepoIsPrivate. Consistent
// with the other IFC-labeled tools, if the visibility lookup fails the label
// is omitted rather than risking a misclassification. Error results and the
// disabled-feature case are left untouched.
func attachRepoVisibilityIFCLabel(
ctx context.Context,
deps ToolDependencies,
client *github.Client,
owner, repo string,
r *mcp.CallToolResult,
labelFn func(isPrivate bool) ifc.SecurityLabel,
) *mcp.CallToolResult {
if !shouldAttachIFCLabel(ctx, deps, r) {
return r
}
isPrivate, err := FetchRepoIsPrivate(ctx, client, owner, repo)
if err != nil {
return r
}
setIFCLabel(r, labelFn(isPrivate))
return r
}
// ifcSearchPostProcessOption returns a searchOption that attaches IFC labels to
// a multi-repository search result. The feature-flag check is centralized here
// (mirroring the attach* helpers above) rather than in each search tool
// handler: when IFC labels are disabled it returns a no-op option, so callers
// can pass it unconditionally to searchHandler.
func ifcSearchPostProcessOption(ctx context.Context, deps ToolDependencies) searchOption {
if !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return func(*searchConfig) {}
}
return withSearchPostProcess(searchIssuesIFCPostProcess(deps))
}
// attachRepoVisibilityIFCLabelLazy is like attachRepoVisibilityIFCLabel but
// resolves the REST client itself, only when IFC labels are enabled. It is used
// by tools whose handler holds a GraphQL client (or no client yet) and would
// otherwise have to acquire a REST client solely to compute the label. The
// feature-flag check is centralized here so callers can invoke it
// unconditionally; if the client cannot be obtained or the visibility lookup
// fails, the label is omitted rather than risking a misclassification.
func attachRepoVisibilityIFCLabelLazy(
ctx context.Context,
deps ToolDependencies,
owner, repo string,
r *mcp.CallToolResult,
labelFn func(isPrivate bool) ifc.SecurityLabel,
) *mcp.CallToolResult {
if !shouldAttachIFCLabel(ctx, deps, r) {
return r
}
client, err := deps.GetClient(ctx)
if err != nil {
return r
}
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, labelFn)
}
// attachJoinedIFCLabel attaches an IFC label computed by joining a set of
// per-item visibilities (true == private) when IFC labels are enabled. joinFn
// is the lattice join for the relevant item kind (e.g. ifc.LabelSearchIssues or
// ifc.LabelProjectList). The visibility slice is cheap to build from an
// already-fetched response, so callers may construct it unconditionally and let
// this helper own the feature-flag gate.
func attachJoinedIFCLabel(
ctx context.Context,
deps ToolDependencies,
r *mcp.CallToolResult,
visibilities []bool,
joinFn func([]bool) ifc.SecurityLabel,
) *mcp.CallToolResult {
if !shouldAttachIFCLabel(ctx, deps, r) {
return r
}
setIFCLabel(r, joinFn(visibilities))
return r
}
func attachProjectVisibilityIFCLabel(
ctx context.Context,
deps ToolDependencies,
r *mcp.CallToolResult,
isPrivate bool,
labelFn func(isPrivate bool) ifc.SecurityLabel,
) *mcp.CallToolResult {
if !shouldAttachIFCLabel(ctx, deps, r) {
return r
}
setIFCLabel(r, labelFn(isPrivate))
return r
}
// newRepoVisibilityIFCLabeler returns a closure that attaches a repo-visibility
// IFC label to a tool result, for handlers that have several return paths and
// want to label each one. The returned function owns the feature-flag gate (so
// callers invoke it unconditionally) and caches the repository visibility
// lookup across calls, so a handler that returns from many branches only pays
// for one FetchRepoIsPrivate call. A failed visibility lookup is not cached, so
// a later return path can retry; on persistent failure the label is omitted
// rather than risking a misclassification.
func newRepoVisibilityIFCLabeler(
ctx context.Context,
deps ToolDependencies,
client *github.Client,
owner, repo string,
labelFn func(isPrivate bool) ifc.SecurityLabel,
) func(*mcp.CallToolResult) *mcp.CallToolResult {
var (
known bool
isPrivate bool
)
return func(r *mcp.CallToolResult) *mcp.CallToolResult {
if r == nil || r.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {
return r
}
if !known {
p, err := FetchRepoIsPrivate(ctx, client, owner, repo)
if err != nil {
return r
}
isPrivate = p
known = true
}
setIFCLabel(r, labelFn(isPrivate))
return r
}
}