172 lines
5.9 KiB
Go
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
|
|
}
|
|
}
|